diff --git a/web/src/pages/Setting/Ratio/PricingTagManager.jsx b/web/src/pages/Setting/Ratio/PricingTagManager.jsx
new file mode 100644
index 0000000..5ab1711
--- /dev/null
+++ b/web/src/pages/Setting/Ratio/PricingTagManager.jsx
@@ -0,0 +1,216 @@
+/*
+Copyright (C) 2025 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+import React, { useState, useEffect, useCallback, useRef } from 'react';
+import {
+ Button,
+ Table,
+ Modal,
+ Form,
+ Input,
+ Space,
+ Popconfirm,
+ Typography,
+ Tag as SemiTag,
+} from '@douyinfe/semi-ui';
+import { IconPlus, IconEdit, IconDelete } from '@douyinfe/semi-icons';
+import { useTranslation } from 'react-i18next';
+import { pricingTagApi } from '../../../helpers/api';
+import { showError, showSuccess } from '../../../helpers';
+
+const PricingTagManager = () => {
+ const { t } = useTranslation();
+ const [tags, setTags] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [modalVisible, setModalVisible] = useState(false);
+ const [editingTag, setEditingTag] = useState(null);
+ const formRef = useRef();
+
+ const loadTags = useCallback(async () => {
+ setLoading(true);
+ try {
+ const res = await pricingTagApi.getAll();
+ if (res.data.success) {
+ setTags(res.data.data);
+ }
+ } catch (e) {
+ showError(e);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ loadTags();
+ }, [loadTags]);
+
+ const handleCreate = () => {
+ setEditingTag(null);
+ setModalVisible(true);
+ };
+
+ const handleEdit = (record) => {
+ setEditingTag(record);
+ setModalVisible(true);
+ };
+
+ const handleDelete = async (id) => {
+ try {
+ const res = await pricingTagApi.delete(id);
+ if (res.data.success) {
+ showSuccess(t('删除成功'));
+ loadTags();
+ }
+ } catch (e) {
+ showError(e);
+ }
+ };
+
+ const handleSubmit = async () => {
+ try {
+ const values = await formRef.current.validate();
+ if (editingTag) {
+ const res = await pricingTagApi.update(editingTag.id, values);
+ if (res.data.success) {
+ showSuccess(t('更新成功'));
+ setModalVisible(false);
+ loadTags();
+ }
+ } else {
+ const res = await pricingTagApi.create(values);
+ if (res.data.success) {
+ showSuccess(t('创建成功'));
+ setModalVisible(false);
+ loadTags();
+ }
+ }
+ } catch (e) {
+ showError(e);
+ }
+ };
+
+ const columns = [
+ {
+ title: t('标签名称'),
+ dataIndex: 'name',
+ key: 'name',
+ render: (text, record) => (
+
+ {text}
+
+ ),
+ },
+ {
+ title: t('颜色'),
+ dataIndex: 'color',
+ key: 'color',
+ render: (color) => (
+
+ ),
+ },
+ {
+ title: t('描述'),
+ dataIndex: 'description',
+ key: 'description',
+ },
+ {
+ title: t('排序'),
+ dataIndex: 'sort_order',
+ key: 'sort_order',
+ width: 80,
+ },
+ {
+ title: t('操作'),
+ key: 'action',
+ width: 150,
+ render: (_, record) => (
+
+ } onClick={() => handleEdit(record)} />
+ handleDelete(record.id)}
+ >
+ } type="danger" />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+
+ } onClick={handleCreate}>
+ {t('添加标签')}
+
+
+
+
+
+
setModalVisible(false)}
+ onOk={handleSubmit}
+ >
+
+
+
+
+
+
+
+ );
+};
+
+export default PricingTagManager;