| @@ -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 <https://www.gnu.org/licenses/>. | |||
| 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) => ( | |||
| <Space> | |||
| <SemiTag color={record.color}>{text}</SemiTag> | |||
| </Space> | |||
| ), | |||
| }, | |||
| { | |||
| title: t('颜色'), | |||
| dataIndex: 'color', | |||
| key: 'color', | |||
| render: (color) => ( | |||
| <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> | |||
| <div | |||
| style={{ | |||
| width: 20, | |||
| height: 20, | |||
| backgroundColor: color, | |||
| borderRadius: 4, | |||
| border: '1px solid #ddd', | |||
| }} | |||
| /> | |||
| <Typography.Text>{color}</Typography.Text> | |||
| </div> | |||
| ), | |||
| }, | |||
| { | |||
| title: t('描述'), | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| }, | |||
| { | |||
| title: t('排序'), | |||
| dataIndex: 'sort_order', | |||
| key: 'sort_order', | |||
| width: 80, | |||
| }, | |||
| { | |||
| title: t('操作'), | |||
| key: 'action', | |||
| width: 150, | |||
| render: (_, record) => ( | |||
| <Space> | |||
| <Button icon={<IconEdit />} onClick={() => handleEdit(record)} /> | |||
| <Popconfirm | |||
| title={t('确定删除此标签?')} | |||
| onConfirm={() => handleDelete(record.id)} | |||
| > | |||
| <Button icon={<IconDelete />} type="danger" /> | |||
| </Popconfirm> | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div> | |||
| <div style={{ marginBottom: 16 }}> | |||
| <Button icon={<IconPlus />} onClick={handleCreate}> | |||
| {t('添加标签')} | |||
| </Button> | |||
| </div> | |||
| <Table | |||
| columns={columns} | |||
| dataSource={tags} | |||
| loading={loading} | |||
| pagination={false} | |||
| rowKey="id" | |||
| /> | |||
| <Modal | |||
| title={editingTag ? t('编辑标签') : t('添加标签')} | |||
| visible={modalVisible} | |||
| onCancel={() => setModalVisible(false)} | |||
| onOk={handleSubmit} | |||
| > | |||
| <Form getFormApi={(api) => (formRef.current = api)} initValues={editingTag}> | |||
| <Form.Input | |||
| field="name" | |||
| label={t('标签名称')} | |||
| rules={[{ required: true, message: t('请输入标签名称') }]} | |||
| /> | |||
| <Form.Input | |||
| field="color" | |||
| label={t('颜色')} | |||
| placeholder="#1890ff" | |||
| /> | |||
| <Form.TextArea | |||
| field="description" | |||
| label={t('描述')} | |||
| maxLength={200} | |||
| /> | |||
| <Form.InputNumber | |||
| field="sort_order" | |||
| label={t('排序')} | |||
| defaultValue={0} | |||
| /> | |||
| </Form> | |||
| </Modal> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default PricingTagManager; | |||