feat: 添加产品工具, 重构产品 #31
35
.umirc.ts
35
.umirc.ts
|
|
@ -43,18 +43,31 @@ export default defineConfig({
|
|||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: '站点管理',
|
||||
path: '/site',
|
||||
access: 'canSeeSite',
|
||||
routes: [
|
||||
{
|
||||
name: '站点列表',
|
||||
path: '/site/list',
|
||||
component: './Site/List',
|
||||
},
|
||||
],
|
||||
},
|
||||
name: '字典管理',
|
||||
path: '/dict',
|
||||
access: 'canSeeDict',
|
||||
routes: [
|
||||
{
|
||||
name: '字典列表',
|
||||
path: '/dict/list',
|
||||
component: './Dict/List',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '站点管理',
|
||||
path: '/site',
|
||||
access: 'canSeeSite',
|
||||
routes: [
|
||||
{
|
||||
name: '站点列表',
|
||||
path: '/site/list',
|
||||
component: './Site/List',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '商品管理',
|
||||
path: '/product',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export default (initialState: any) => {
|
|||
const canSeeLogistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('logistics') ?? false);
|
||||
const canSeeStatistics = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('statistics') ?? false);
|
||||
const canSeeSite = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('site') ?? false);
|
||||
const canSeeDict = (isSuper || isAdmin) || (initialState?.user?.permissions?.includes('dict') ?? false);
|
||||
|
||||
return {
|
||||
canSeeOrganiza,
|
||||
|
|
@ -20,5 +21,6 @@ export default (initialState: any) => {
|
|||
canSeeLogistics,
|
||||
canSeeStatistics,
|
||||
canSeeSite,
|
||||
canSeeDict,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,350 @@
|
|||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Input, Button, Table, Layout, Space, Modal, message, Form, Upload } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { request } from '@umijs/max';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
const DictPage: React.FC = () => {
|
||||
// 左侧字典列表的状态
|
||||
const [dicts, setDicts] = useState<any[]>([]);
|
||||
const [loadingDicts, setLoadingDicts] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [selectedDict, setSelectedDict] = useState<any>(null);
|
||||
|
||||
// 右侧字典项列表的状态
|
||||
const [dictItems, setDictItems] = useState<any[]>([]);
|
||||
const [loadingDictItems, setLoadingDictItems] = useState(false);
|
||||
|
||||
// 控制添加字典模态框的显示
|
||||
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
|
||||
const [newDictName, setNewDictName] = useState('');
|
||||
const [newDictTitle, setNewDictTitle] = useState('');
|
||||
const [editingDict, setEditingDict] = useState<any>(null);
|
||||
|
||||
// 控制字典项模态框的显示
|
||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
||||
const [dictItemForm, setDictItemForm] = useState({ name: '', title: '', value: '' });
|
||||
|
||||
// 获取字典列表
|
||||
const fetchDicts = async (title?: string) => {
|
||||
setLoadingDicts(true);
|
||||
try {
|
||||
const res = await request('/api/dicts', { params: { title } });
|
||||
if (res) {
|
||||
setDicts(res);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('获取字典列表失败');
|
||||
}
|
||||
setLoadingDicts(false);
|
||||
};
|
||||
|
||||
// 获取字典项列表
|
||||
const fetchDictItems = async (dictId?: number) => {
|
||||
setLoadingDictItems(true);
|
||||
try {
|
||||
const res = await request('/api/dict-items', { params: { dictId } });
|
||||
if (res) {
|
||||
setDictItems(res);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('获取字典项列表失败');
|
||||
}
|
||||
setLoadingDictItems(false);
|
||||
};
|
||||
|
||||
// 组件加载时获取数据
|
||||
useEffect(() => {
|
||||
fetchDicts();
|
||||
fetchDictItems();
|
||||
}, []);
|
||||
|
||||
// 当选择的字典变化时,重新获取字典项
|
||||
useEffect(() => {
|
||||
fetchDictItems(selectedDict?.id);
|
||||
}, [selectedDict]);
|
||||
|
||||
// 处理字典搜索
|
||||
const handleSearch = (value: string) => {
|
||||
fetchDicts(value);
|
||||
};
|
||||
|
||||
// 处理添加字典
|
||||
const handleAddDict = async () => {
|
||||
if (!newDictName || !newDictTitle) {
|
||||
message.warning('请输入字典名称和标题');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (editingDict) {
|
||||
await request(`/api/dicts/${editingDict.id}`, {
|
||||
method: 'PUT',
|
||||
data: { name: newDictName, title: newDictTitle },
|
||||
});
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
await request('/api/dicts', {
|
||||
method: 'POST',
|
||||
data: { name: newDictName, title: newDictTitle },
|
||||
});
|
||||
message.success('添加成功');
|
||||
}
|
||||
setIsAddDictModalVisible(false);
|
||||
setNewDictName('');
|
||||
setNewDictTitle('');
|
||||
setEditingDict(null);
|
||||
fetchDicts(); // 重新获取字典列表
|
||||
} catch (error) {
|
||||
message.error(editingDict ? '更新失败' : '添加失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditDict = (dict: any) => {
|
||||
setEditingDict(dict);
|
||||
setNewDictName(dict.name);
|
||||
setNewDictTitle(dict.title);
|
||||
setIsAddDictModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开添加字典项模态框
|
||||
const handleAddDictItem = () => {
|
||||
setEditingDictItem(null);
|
||||
setDictItemForm({ name: '', title: '', value: '' });
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开编辑字典项模态框
|
||||
const handleEditDictItem = (item: any) => {
|
||||
setEditingDictItem(item);
|
||||
setDictItemForm(item);
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 处理字典项表单提交(添加/编辑)
|
||||
const handleDictItemFormSubmit = async () => {
|
||||
if (!dictItemForm.name || !dictItemForm.title) {
|
||||
message.warning('请输入名称和标题');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (editingDictItem) {
|
||||
// 编辑
|
||||
await request(`/api/dict-items/${editingDictItem.id}`, {
|
||||
method: 'PUT',
|
||||
data: dictItemForm,
|
||||
});
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
// 添加
|
||||
await request('/api/dict-items', {
|
||||
method: 'POST',
|
||||
data: { ...dictItemForm, dictId: selectedDict.id },
|
||||
});
|
||||
message.success('添加成功');
|
||||
}
|
||||
setIsDictItemModalVisible(false);
|
||||
fetchDictItems(selectedDict.id);
|
||||
} catch (error) {
|
||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理删除字典项
|
||||
const handleDeleteDictItem = async (itemId: number) => {
|
||||
try {
|
||||
await request(`/api/dict-items/${itemId}`, { method: 'DELETE' });
|
||||
message.success('删除成功');
|
||||
fetchDictItems(selectedDict.id);
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理删除字典
|
||||
const handleDeleteDict = async (dictId: number) => {
|
||||
try {
|
||||
await request(`/api/dicts/${dictId}`, { method: 'DELETE' });
|
||||
message.success('删除成功');
|
||||
fetchDicts(); // 重新获取字典列表
|
||||
// 如果删除的是当前选中的字典,则清空右侧列表
|
||||
if (selectedDict?.id === dictId) {
|
||||
setSelectedDict(null);
|
||||
setDictItems([]);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 左侧字典列表的列定义
|
||||
const dictColumns = [
|
||||
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: any) => (
|
||||
<Space>
|
||||
<Button type="link" onClick={(e) => { e.stopPropagation(); handleEditDict(record); }}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button type="link" danger onClick={(e) => { e.stopPropagation(); handleDeleteDict(record.id); }}>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// 右侧字典项列表的列定义
|
||||
const dictItemColumns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: any) => (
|
||||
<Space size="middle">
|
||||
<Button type="link" onClick={() => handleEditDictItem(record)}>编辑</Button>
|
||||
<Button type="link" danger onClick={() => handleDeleteDictItem(record.id)}>删除</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Layout style={{ background: '#fff' }}>
|
||||
<Sider width={300} style={{ background: '#fff', padding: '16px', borderRight: '1px solid #f0f0f0' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Input.Search placeholder="搜索字典" onSearch={handleSearch} onChange={e => setSearchText(e.target.value)} enterButton allowClear />
|
||||
<Button type="primary" onClick={() => setIsAddDictModalVisible(true)} block>
|
||||
添加字典
|
||||
</Button>
|
||||
<Space>
|
||||
<Upload
|
||||
name="file"
|
||||
action="/api/dicts/import"
|
||||
showUploadList={false}
|
||||
onChange={info => {
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功`);
|
||||
fetchDicts();
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>导入字典</Button>
|
||||
</Upload>
|
||||
<Button onClick={() => window.open('/api/dicts/template')}>下载模板</Button>
|
||||
</Space>
|
||||
<Table
|
||||
dataSource={dicts}
|
||||
columns={dictColumns}
|
||||
rowKey="id"
|
||||
loading={loadingDicts}
|
||||
onRow={record => ({
|
||||
onClick: () => {
|
||||
// 如果点击的是当前已选中的行,则取消选择
|
||||
if (selectedDict?.id === record.id) {
|
||||
setSelectedDict(null);
|
||||
} else {
|
||||
setSelectedDict(record);
|
||||
}
|
||||
},
|
||||
})}
|
||||
rowClassName={record => (selectedDict?.id === record.id ? 'ant-table-row-selected' : '')}
|
||||
pagination={false}
|
||||
/>
|
||||
</Space>
|
||||
</Sider>
|
||||
<Content style={{ padding: '16px' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Button type="primary" onClick={handleAddDictItem} disabled={!selectedDict}>
|
||||
添加字典项
|
||||
</Button>
|
||||
<Space>
|
||||
<Upload
|
||||
name="file"
|
||||
action={`/api/dict-items/import`}
|
||||
data={{ dictId: selectedDict?.id }}
|
||||
showUploadList={false}
|
||||
disabled={!selectedDict}
|
||||
onChange={info => {
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功`);
|
||||
fetchDictItems(selectedDict.id);
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button icon={<UploadOutlined />} disabled={!selectedDict}>导入字典项</Button>
|
||||
</Upload>
|
||||
<Button onClick={() => window.open('/api/dict-items/template')} disabled={!selectedDict}>下载模板</Button>
|
||||
</Space>
|
||||
<Table dataSource={dictItems} columns={dictItemColumns} rowKey="id" loading={loadingDictItems} />
|
||||
</Space>
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
<Modal
|
||||
title={editingDictItem ? '编辑字典项' : '添加字典项'}
|
||||
visible={isDictItemModalVisible}
|
||||
onOk={handleDictItemFormSubmit}
|
||||
onCancel={() => setIsDictItemModalVisible(false)}
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="名称">
|
||||
<Input placeholder="名称 (e.g., zyn)" value={dictItemForm.name} onChange={e => setDictItemForm({ ...dictItemForm, name: e.target.value })} />
|
||||
</Form.Item>
|
||||
<Form.Item label="标题">
|
||||
<Input placeholder="标题 (e.g., ZYN)" value={dictItemForm.title} onChange={e => setDictItemForm({ ...dictItemForm, title: e.target.value })} />
|
||||
</Form.Item>
|
||||
<Form.Item label="值 (可选)">
|
||||
<Input placeholder="值 (可选)" value={dictItemForm.value} onChange={e => setDictItemForm({ ...dictItemForm, value: e.target.value })} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={editingDict ? '编辑字典' : '添加新字典'}
|
||||
visible={isAddDictModalVisible}
|
||||
onOk={handleAddDict}
|
||||
onCancel={() => setIsAddDictModalVisible(false)}
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="字典名称">
|
||||
<Input placeholder="字典名称 (e.g., brand)" value={newDictName} onChange={e => setNewDictName(e.target.value)} />
|
||||
</Form.Item>
|
||||
<Form.Item label="字典标题">
|
||||
<Input placeholder="字典标题 (e.g., 品牌)" value={newDictTitle} onChange={e => setNewDictTitle(e.target.value)} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DictPage;
|
||||
|
|
@ -35,9 +35,8 @@ const List: React.FC = () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
title: '标识',
|
||||
dataIndex: 'unique_key',
|
||||
hideInSearch: true,
|
||||
title: '显示名称',
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
|
|
|
|||
Loading…
Reference in New Issue