353 lines
10 KiB
TypeScript
353 lines
10 KiB
TypeScript
import { UploadOutlined } from '@ant-design/icons';
|
|
import {
|
|
ActionType,
|
|
PageContainer,
|
|
ProTable,
|
|
} from '@ant-design/pro-components';
|
|
import { request } from '@umijs/max';
|
|
import {
|
|
Button,
|
|
Form,
|
|
Input,
|
|
Layout,
|
|
Modal,
|
|
Space,
|
|
Table,
|
|
Upload,
|
|
message,
|
|
} from 'antd';
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
|
|
const { Sider, Content } = Layout;
|
|
|
|
import { attributes } from './consts';
|
|
|
|
const AttributePage: React.FC = () => {
|
|
// 左侧字典列表状态
|
|
const [dicts, setDicts] = useState<any[]>([]);
|
|
const [loadingDicts, setLoadingDicts] = useState(false);
|
|
const [searchText, setSearchText] = useState('');
|
|
const [selectedDict, setSelectedDict] = useState<any>(null);
|
|
|
|
// 右侧字典项 ProTable 的引用
|
|
const actionRef = useRef<ActionType>();
|
|
|
|
// 字典项新增/编辑模态框控制
|
|
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
|
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
|
const [dictItemForm] = Form.useForm();
|
|
|
|
const fetchDicts = async (title?: string) => {
|
|
setLoadingDicts(true);
|
|
try {
|
|
const res = await request('/dict/list', { params: { title } });
|
|
// 条件判断,过滤只保留 allowedDictNames 中的字典
|
|
const filtered = (res || []).filter((d: any) => attributes.has(d?.name));
|
|
setDicts(filtered);
|
|
} catch (error) {
|
|
message.error('获取字典列表失败');
|
|
}
|
|
setLoadingDicts(false);
|
|
};
|
|
|
|
// 组件挂载时初始化数据
|
|
useEffect(() => {
|
|
fetchDicts();
|
|
}, []);
|
|
|
|
// 搜索触发过滤
|
|
const handleSearch = (value: string) => {
|
|
fetchDicts(value);
|
|
};
|
|
|
|
// 打开添加字典项模态框
|
|
const handleAddDictItem = () => {
|
|
setEditingDictItem(null);
|
|
dictItemForm.resetFields();
|
|
setIsDictItemModalVisible(true);
|
|
};
|
|
|
|
// 打开编辑字典项模态框
|
|
const handleEditDictItem = (item: any) => {
|
|
setEditingDictItem(item);
|
|
dictItemForm.setFieldsValue(item);
|
|
setIsDictItemModalVisible(true);
|
|
};
|
|
|
|
// 字典项表单提交(新增或编辑)
|
|
const handleDictItemFormSubmit = async (values: any) => {
|
|
try {
|
|
if (editingDictItem) {
|
|
// 条件判断,存在编辑项则执行更新
|
|
await request(`/dict/item/${editingDictItem.id}`, {
|
|
method: 'PUT',
|
|
data: values,
|
|
});
|
|
message.success('更新成功');
|
|
} else {
|
|
// 否则执行新增,绑定到当前选择的字典
|
|
await request('/dict/item', {
|
|
method: 'POST',
|
|
data: { ...values, dictId: selectedDict.id },
|
|
});
|
|
message.success('添加成功');
|
|
}
|
|
setIsDictItemModalVisible(false);
|
|
actionRef.current?.reload(); // 刷新 ProTable
|
|
} catch (error) {
|
|
message.error(editingDictItem ? '更新失败' : '添加失败');
|
|
}
|
|
};
|
|
|
|
// 删除字典项
|
|
const handleDeleteDictItem = async (itemId: number) => {
|
|
try {
|
|
const res = await request(`/dict/item/${itemId}`, { method: 'DELETE' });
|
|
const isOk =
|
|
typeof res === 'boolean'
|
|
? res
|
|
: res && res.code === 0
|
|
? res.data === true || res.data === null
|
|
: false;
|
|
if (!isOk) {
|
|
message.error('删除失败');
|
|
return;
|
|
}
|
|
if (selectedDict?.id) {
|
|
const list = await request('/dict/items', {
|
|
params: {
|
|
dictId: selectedDict.id,
|
|
},
|
|
});
|
|
const exists =
|
|
Array.isArray(list) && list.some((it: any) => it.id === itemId);
|
|
if (exists) {
|
|
message.error('删除失败');
|
|
} else {
|
|
message.success('删除成功');
|
|
actionRef.current?.reload();
|
|
}
|
|
} else {
|
|
message.success('删除成功');
|
|
actionRef.current?.reload();
|
|
}
|
|
} catch (error) {
|
|
message.error('删除失败');
|
|
}
|
|
};
|
|
|
|
// 左侧字典列表列定义(紧凑样式)
|
|
const dictColumns = [
|
|
{ title: '名称', dataIndex: 'name', key: 'name' },
|
|
{ title: '标题', dataIndex: 'title', key: 'title' },
|
|
];
|
|
|
|
// 右侧字典项列表列定义(紧凑样式)
|
|
const dictItemColumns: any[] = [
|
|
{ title: '名称', dataIndex: 'name', key: 'name', copyable: true },
|
|
{ title: '标题', dataIndex: 'title', key: 'title', copyable: true },
|
|
{ title: '中文标题', dataIndex: 'titleCN', key: 'titleCN', copyable: true },
|
|
{ title: '简称', dataIndex: 'shortName', key: 'shortName', copyable: true },
|
|
{
|
|
title: '图片',
|
|
dataIndex: 'image',
|
|
key: 'image',
|
|
valueType: 'image',
|
|
width: 80,
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
valueType: 'option',
|
|
render: (_: any, record: any) => (
|
|
<Space size="small">
|
|
<Button
|
|
size="small"
|
|
type="link"
|
|
onClick={() => handleEditDictItem(record)}
|
|
>
|
|
编辑
|
|
</Button>
|
|
<Button
|
|
size="small"
|
|
type="link"
|
|
danger
|
|
onClick={() => handleDeleteDictItem(record.id)}
|
|
>
|
|
删除
|
|
</Button>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<PageContainer>
|
|
<Layout style={{ background: '#fff' }}>
|
|
<Sider
|
|
width={240}
|
|
style={{
|
|
background: '#fff',
|
|
padding: '8px',
|
|
borderRight: '1px solid #f0f0f0',
|
|
}}
|
|
>
|
|
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
|
<Input.Search
|
|
placeholder="搜索字典"
|
|
onSearch={handleSearch}
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|
enterButton
|
|
allowClear
|
|
size="small"
|
|
/>
|
|
</Space>
|
|
<div
|
|
style={{
|
|
marginTop: '8px',
|
|
overflowY: 'auto',
|
|
height: 'calc(100vh - 150px)',
|
|
}}
|
|
>
|
|
<Table
|
|
dataSource={dicts}
|
|
columns={dictColumns}
|
|
rowKey="id"
|
|
loading={loadingDicts}
|
|
size="small"
|
|
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}
|
|
/>
|
|
</div>
|
|
</Sider>
|
|
<Content style={{ padding: '8px' }}>
|
|
<ProTable
|
|
columns={dictItemColumns}
|
|
actionRef={actionRef}
|
|
request={async (params) => {
|
|
// 当没有选择字典时,不发起请求
|
|
if (!selectedDict?.id) {
|
|
return {
|
|
data: [],
|
|
success: true,
|
|
};
|
|
}
|
|
const { name, title } = params;
|
|
const res = await request('/dict/items', {
|
|
params: {
|
|
dictId: selectedDict.id,
|
|
name,
|
|
title,
|
|
},
|
|
});
|
|
return {
|
|
data: res,
|
|
success: true,
|
|
};
|
|
}}
|
|
rowKey="id"
|
|
search={{
|
|
layout: 'vertical',
|
|
}}
|
|
pagination={false}
|
|
options={false}
|
|
size="small"
|
|
key={selectedDict?.id}
|
|
headerTitle={
|
|
<Space>
|
|
<Button
|
|
type="primary"
|
|
size="small"
|
|
onClick={handleAddDictItem}
|
|
disabled={!selectedDict}
|
|
>
|
|
添加字典项
|
|
</Button>
|
|
<Upload
|
|
name="file"
|
|
action={`/dict/item/import`}
|
|
data={{ dictId: selectedDict?.id }}
|
|
showUploadList={false}
|
|
disabled={!selectedDict}
|
|
onChange={(info) => {
|
|
// 条件判断,上传状态处理
|
|
if (info.file.status === 'done') {
|
|
message.success(`${info.file.name} 文件上传成功`);
|
|
actionRef.current?.reload();
|
|
} else if (info.file.status === 'error') {
|
|
message.error(`${info.file.name} 文件上传失败`);
|
|
}
|
|
}}
|
|
>
|
|
<Button
|
|
size="small"
|
|
icon={<UploadOutlined />}
|
|
disabled={!selectedDict}
|
|
>
|
|
导入字典项
|
|
</Button>
|
|
</Upload>
|
|
</Space>
|
|
}
|
|
/>
|
|
</Content>
|
|
</Layout>
|
|
|
|
<Modal
|
|
title={editingDictItem ? '编辑字典项' : '添加字典项'}
|
|
open={isDictItemModalVisible}
|
|
onOk={() => dictItemForm.submit()}
|
|
onCancel={() => setIsDictItemModalVisible(false)}
|
|
destroyOnClose
|
|
>
|
|
<Form
|
|
form={dictItemForm}
|
|
layout="vertical"
|
|
onFinish={handleDictItemFormSubmit}
|
|
>
|
|
<Form.Item
|
|
label="名称"
|
|
name="name"
|
|
rules={[{ required: true, message: '请输入名称' }]}
|
|
>
|
|
<Input size="small" placeholder="名称 (e.g., zyn)" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label="标题"
|
|
name="title"
|
|
rules={[{ required: true, message: '请输入标题' }]}
|
|
>
|
|
<Input size="small" placeholder="标题 (e.g., ZYN)" />
|
|
</Form.Item>
|
|
<Form.Item label="中文标题" name="titleCN">
|
|
<Input size="small" placeholder="中文标题 (e.g., 品牌)" />
|
|
</Form.Item>
|
|
<Form.Item label="简称 (可选)" name="shortName">
|
|
<Input size="small" placeholder="简称 (可选)" />
|
|
</Form.Item>
|
|
<Form.Item label="图片 (可选)" name="image">
|
|
<Input size="small" placeholder="图片链接 (可选)" />
|
|
</Form.Item>
|
|
<Form.Item label="值 (可选)" name="value">
|
|
<Input size="small" placeholder="值 (可选)" />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
export default AttributePage;
|