forked from yoone/WEB
709 lines
21 KiB
TypeScript
709 lines
21 KiB
TypeScript
import * as dictApi from '@/servers/api/dict';
|
|
import { UploadOutlined } from '@ant-design/icons';
|
|
import {
|
|
ActionType,
|
|
PageContainer,
|
|
ProTable,
|
|
} from '@ant-design/pro-components';
|
|
import {
|
|
Button,
|
|
Form,
|
|
Input,
|
|
Layout,
|
|
Modal,
|
|
Space,
|
|
Table,
|
|
Upload,
|
|
message,
|
|
} from 'antd';
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
|
|
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);
|
|
|
|
// 添加字典 modal 状态
|
|
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
|
|
const [addDictName, setAddDictName] = useState('');
|
|
const [addDictTitle, setAddDictTitle] = useState('');
|
|
|
|
// 编辑字典 modal 状态
|
|
const [isEditDictModalVisible, setIsEditDictModalVisible] = useState(false);
|
|
const [editDictData, setEditDictData] = useState<any>(null);
|
|
|
|
// 右侧字典项列表的状态
|
|
const [isAddDictItemModalVisible, setIsAddDictItemModalVisible] =
|
|
useState(false);
|
|
const [isEditDictItemModalVisible, setIsEditDictItemModalVisible] =
|
|
useState(false);
|
|
const [editDictItemData, setEditDictItemData] = useState<any>(null);
|
|
const [dictItemForm] = Form.useForm();
|
|
const actionRef = useRef<ActionType>();
|
|
|
|
// 获取字典列表
|
|
const fetchDicts = async (name = '') => {
|
|
setLoadingDicts(true);
|
|
try {
|
|
const res = await dictApi.dictcontrollerGetdicts({ name });
|
|
setDicts(res);
|
|
} catch (error) {
|
|
message.error('获取字典列表失败');
|
|
} finally {
|
|
setLoadingDicts(false);
|
|
}
|
|
};
|
|
|
|
// 搜索字典
|
|
const handleSearch = (value: string) => {
|
|
fetchDicts(value);
|
|
};
|
|
|
|
// 添加字典
|
|
const handleAddDict = async () => {
|
|
const values = { name: addDictName, title: addDictTitle };
|
|
try {
|
|
await dictApi.dictcontrollerCreatedict(values);
|
|
message.success('添加成功');
|
|
setIsAddDictModalVisible(false);
|
|
setAddDictName('');
|
|
setAddDictTitle('');
|
|
fetchDicts(); // 重新获取列表
|
|
} catch (error) {
|
|
message.error('添加失败');
|
|
}
|
|
};
|
|
|
|
// 编辑字典
|
|
const handleEditDict = async () => {
|
|
if (!editDictData) return;
|
|
const values = { name: editDictData.name, title: editDictData.title };
|
|
try {
|
|
await dictApi.dictcontrollerUpdatedict({ id: editDictData.id }, values);
|
|
message.success('更新成功');
|
|
setIsEditDictModalVisible(false);
|
|
setEditDictData(null);
|
|
fetchDicts(); // 重新获取列表
|
|
} catch (error) {
|
|
message.error('更新失败');
|
|
}
|
|
};
|
|
|
|
// 删除字典
|
|
const handleDeleteDict = async (id: number) => {
|
|
try {
|
|
const result = await dictApi.dictcontrollerDeletedict({ id });
|
|
if (!result.success) {
|
|
throw new Error(result.message || '删除失败');
|
|
}
|
|
message.success('删除成功');
|
|
fetchDicts();
|
|
if (selectedDict?.id === id) {
|
|
setSelectedDict(null);
|
|
}
|
|
} catch (error: any) {
|
|
message.error(`删除失败,原因为:${error.message}`);
|
|
}
|
|
};
|
|
|
|
// 打开编辑字典 modal
|
|
const openEditDictModal = (record: any) => {
|
|
setEditDictData(record);
|
|
setIsEditDictModalVisible(true);
|
|
};
|
|
|
|
// 下载字典导入模板
|
|
const handleDownloadDictTemplate = async () => {
|
|
try {
|
|
// 使用 dictApi.dictcontrollerDownloaddicttemplate 获取字典模板
|
|
const response = await dictApi.dictcontrollerDownloaddicttemplate({
|
|
responseType: 'blob',
|
|
skipErrorHandler: true,
|
|
});
|
|
|
|
// 创建 blob 对象和下载链接
|
|
const blob = new Blob([response], {
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
});
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.setAttribute('download', 'dict_template.xlsx');
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
window.URL.revokeObjectURL(url);
|
|
} catch (error: any) {
|
|
message.error('下载字典模板失败:' + (error.message || '未知错误'));
|
|
}
|
|
};
|
|
|
|
// 添加字典项
|
|
const handleAddDictItem = () => {
|
|
dictItemForm.resetFields();
|
|
setIsAddDictItemModalVisible(true);
|
|
};
|
|
|
|
// 编辑字典项
|
|
const handleEditDictItem = (record: any) => {
|
|
setEditDictItemData(record);
|
|
dictItemForm.setFieldsValue(record);
|
|
setIsEditDictItemModalVisible(true);
|
|
};
|
|
|
|
// 删除字典项
|
|
const handleDeleteDictItem = async (id: number) => {
|
|
try {
|
|
const result = await dictApi.dictcontrollerDeletedictitem({ id });
|
|
if (!result.success) {
|
|
throw new Error(result.message || '删除失败');
|
|
}
|
|
message.success('删除成功');
|
|
|
|
// 强制刷新字典项列表
|
|
setTimeout(() => {
|
|
actionRef.current?.reload();
|
|
}, 100);
|
|
} catch (error: any) {
|
|
message.error(`删除失败,原因为:${error.message}`);
|
|
}
|
|
};
|
|
|
|
// 添加字典项表单提交
|
|
const handleAddDictItemFormSubmit = async (values: any) => {
|
|
try {
|
|
const result = await dictApi.dictcontrollerCreatedictitem({
|
|
...values,
|
|
dictId: selectedDict.id,
|
|
});
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.message || '添加失败');
|
|
}
|
|
|
|
message.success('添加成功');
|
|
setIsAddDictItemModalVisible(false);
|
|
|
|
// 强制刷新字典项列表
|
|
setTimeout(() => {
|
|
actionRef.current?.reload();
|
|
}, 100);
|
|
} catch (error: any) {
|
|
message.error(`添加失败:${error.message || '未知错误'}`);
|
|
}
|
|
};
|
|
|
|
// 编辑字典项表单提交
|
|
const handleEditDictItemFormSubmit = async (values: any) => {
|
|
if (!editDictItemData) return;
|
|
try {
|
|
const result = await dictApi.dictcontrollerUpdatedictitem(
|
|
{ id: editDictItemData.id },
|
|
values,
|
|
);
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.message || '更新失败');
|
|
}
|
|
|
|
message.success('更新成功');
|
|
setIsEditDictItemModalVisible(false);
|
|
setEditDictItemData(null);
|
|
|
|
// 强制刷新字典项列表
|
|
setTimeout(() => {
|
|
actionRef.current?.reload();
|
|
}, 100);
|
|
} catch (error: any) {
|
|
message.error(`更新失败:${error.message || '未知错误'}`);
|
|
}
|
|
};
|
|
|
|
// 导出字典项数据
|
|
const handleExportDictItems = async () => {
|
|
if (!selectedDict) {
|
|
message.warning('请先选择字典');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 获取当前字典的所有数据
|
|
const response = await dictApi.dictcontrollerGetdictitems({
|
|
dictId: selectedDict.id,
|
|
});
|
|
|
|
if (!response || response.length === 0) {
|
|
message.warning('当前字典没有数据可导出');
|
|
return;
|
|
}
|
|
|
|
// 将数据转换为CSV格式
|
|
const headers = [
|
|
'name',
|
|
'title',
|
|
'titleCN',
|
|
'value',
|
|
'sort',
|
|
'image',
|
|
'shortName',
|
|
];
|
|
const csvContent = [
|
|
headers.join(','),
|
|
...response.map((item: any) =>
|
|
headers
|
|
.map((header) => {
|
|
const value = item[header] || '';
|
|
// 如果值包含逗号或引号,需要转义
|
|
if (
|
|
typeof value === 'string' &&
|
|
(value.includes(',') || value.includes('"'))
|
|
) {
|
|
return `"${value.replace(/"/g, '""')}"`;
|
|
}
|
|
return value;
|
|
})
|
|
.join(','),
|
|
),
|
|
].join('\n');
|
|
|
|
// 创建blob并下载
|
|
const blob = new Blob(['\ufeff' + csvContent], {
|
|
// 添加BOM以支持中文
|
|
type: 'text/csv;charset=utf-8',
|
|
});
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.setAttribute('download', `${selectedDict.name}_dict_items.csv`);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
message.success(`成功导出 ${response.length} 条数据`);
|
|
} catch (error: any) {
|
|
message.error('导出字典项失败:' + (error.message || '未知错误'));
|
|
}
|
|
};
|
|
|
|
// Effects
|
|
useEffect(() => {
|
|
fetchDicts();
|
|
}, []);
|
|
|
|
// 左侧字典表格的列定义
|
|
const dictColumns = [
|
|
{ title: '字典名称', dataIndex: 'name', key: 'name' },
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
render: (_: any, record: any) => (
|
|
<Space size="small">
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
onClick={() => openEditDictModal(record)}
|
|
>
|
|
编辑
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
danger
|
|
onClick={() => handleDeleteDict(record.id)}
|
|
>
|
|
删除
|
|
</Button>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
// 右侧字典项列表的列定义
|
|
const dictItemColumns = [
|
|
{
|
|
title: '名称',
|
|
dataIndex: 'name',
|
|
key: 'name',
|
|
copyable: true,
|
|
},
|
|
{
|
|
title: '简称',
|
|
dataIndex: 'shortName',
|
|
key: 'shortName',
|
|
copyable: true,
|
|
},
|
|
{
|
|
title: '图片',
|
|
dataIndex: 'image',
|
|
key: 'image',
|
|
valueType: 'image',
|
|
width: 80,
|
|
},
|
|
{
|
|
title: '标题',
|
|
dataIndex: 'title',
|
|
key: 'title',
|
|
copyable: true,
|
|
},
|
|
{
|
|
title: '中文标题',
|
|
dataIndex: 'titleCN',
|
|
key: 'titleCN',
|
|
copyable: true,
|
|
},
|
|
{
|
|
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: '8px',
|
|
borderRight: '1px solid #f0f0f0',
|
|
}}
|
|
>
|
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|
<Input.Search
|
|
size="small"
|
|
placeholder="搜索字典"
|
|
onSearch={handleSearch}
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|
enterButton
|
|
allowClear
|
|
/>
|
|
<Space size="small">
|
|
<Button
|
|
type="primary"
|
|
onClick={() => setIsAddDictModalVisible(true)}
|
|
size="small"
|
|
>
|
|
添加字典
|
|
</Button>
|
|
<Upload
|
|
name="file"
|
|
action="/api/dict/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 size="small" icon={<UploadOutlined />}>
|
|
导入字典
|
|
</Button>
|
|
</Upload>
|
|
<Button size="small" onClick={handleDownloadDictTemplate}>
|
|
导出模板
|
|
</Button>
|
|
</Space>
|
|
<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}
|
|
/>
|
|
</Space>
|
|
</Sider>
|
|
<Content style={{ padding: '8px' }}>
|
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|
<ProTable
|
|
columns={dictItemColumns}
|
|
request={async (params) => {
|
|
// 当没有选择字典时,不发起请求
|
|
if (!selectedDict?.id) {
|
|
return {
|
|
data: [],
|
|
success: true,
|
|
};
|
|
}
|
|
const { name, title } = params;
|
|
const res = await dictApi.dictcontrollerGetdictitems({
|
|
dictId: selectedDict?.id,
|
|
name,
|
|
title,
|
|
});
|
|
|
|
// 适配新的响应格式,检查是否有 successResponse 包裹
|
|
if (res && res.success !== undefined) {
|
|
return {
|
|
data: res.data || [],
|
|
success: res.success,
|
|
total: res.data?.length || 0,
|
|
};
|
|
}
|
|
|
|
// 兼容旧的响应格式(直接返回数组)
|
|
return {
|
|
data: res || [],
|
|
success: true,
|
|
};
|
|
}}
|
|
rowKey="id"
|
|
search={{
|
|
layout: 'vertical',
|
|
}}
|
|
pagination={false}
|
|
options={{
|
|
reload: true,
|
|
density: true,
|
|
setting: true,
|
|
}}
|
|
size="small"
|
|
key={selectedDict?.id}
|
|
toolBarRender={() => [
|
|
<Button
|
|
type="primary"
|
|
onClick={handleAddDictItem}
|
|
disabled={!selectedDict}
|
|
size="small"
|
|
key="add"
|
|
>
|
|
添加字典项
|
|
</Button>,
|
|
<Upload
|
|
name="file"
|
|
action={undefined}
|
|
customRequest={async (options) => {
|
|
const { file, onSuccess, onError } = options;
|
|
try {
|
|
const result =
|
|
await dictApi.dictcontrollerImportdictitems(
|
|
{ dictId: selectedDict?.id },
|
|
[file as File],
|
|
);
|
|
onSuccess?.(result);
|
|
} catch (error) {
|
|
onError?.(error as Error);
|
|
}
|
|
}}
|
|
showUploadList={false}
|
|
disabled={!selectedDict}
|
|
onChange={(info) => {
|
|
console.log(`info`, info);
|
|
if (info.file.status === 'done') {
|
|
message.success(`${info.file.name} 文件上传成功`);
|
|
// 重新加载字典项列表
|
|
setTimeout(() => {
|
|
actionRef.current?.reload();
|
|
}, 100);
|
|
// 重新加载字典列表以更新字典项数量
|
|
fetchDicts();
|
|
} else if (info.file.status === 'error') {
|
|
message.error(`${info.file.name} 文件上传失败`);
|
|
}
|
|
}}
|
|
key="import"
|
|
>
|
|
<Button size="small" icon={<UploadOutlined />}>
|
|
导入字典项
|
|
</Button>
|
|
</Upload>,
|
|
<Button
|
|
onClick={handleExportDictItems}
|
|
disabled={!selectedDict}
|
|
size="small"
|
|
key="export"
|
|
>
|
|
导出数据
|
|
</Button>,
|
|
]}
|
|
/>
|
|
</Space>
|
|
</Content>
|
|
</Layout>
|
|
|
|
{/* 添加字典项 Modal */}
|
|
<Modal
|
|
title={`添加字典项 - ${selectedDict?.title || '未选择字典'}`}
|
|
open={isAddDictItemModalVisible}
|
|
onOk={() => dictItemForm.submit()}
|
|
onCancel={() => setIsAddDictItemModalVisible(false)}
|
|
>
|
|
<Form
|
|
form={dictItemForm}
|
|
layout="vertical"
|
|
onFinish={handleAddDictItemFormSubmit}
|
|
>
|
|
<Form.Item
|
|
label="名称"
|
|
name="name"
|
|
rules={[{ required: true, message: '请输入名称' }]}
|
|
>
|
|
<Input placeholder="名称 (e.g., zyn)" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label="标题"
|
|
name="title"
|
|
rules={[{ required: true, message: '请输入标题' }]}
|
|
>
|
|
<Input placeholder="标题 (e.g., ZYN)" />
|
|
</Form.Item>
|
|
<Form.Item label="中文标题" name="titleCN">
|
|
<Input placeholder="中文标题 (e.g., 品牌)" />
|
|
</Form.Item>
|
|
<Form.Item label="简称 (可选)" name="shortName">
|
|
<Input placeholder="简称 (可选)" />
|
|
</Form.Item>
|
|
<Form.Item label="图片 (可选)" name="image">
|
|
<Input placeholder="图片链接 (可选)" />
|
|
</Form.Item>
|
|
<Form.Item label="值 (可选)" name="value">
|
|
<Input placeholder="值 (可选)" />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
{/* 编辑字典项 Modal */}
|
|
<Modal
|
|
title="编辑字典项"
|
|
open={isEditDictItemModalVisible}
|
|
onOk={() => dictItemForm.submit()}
|
|
onCancel={() => {
|
|
setIsEditDictItemModalVisible(false);
|
|
setEditDictItemData(null);
|
|
}}
|
|
>
|
|
<Form
|
|
form={dictItemForm}
|
|
layout="vertical"
|
|
onFinish={handleEditDictItemFormSubmit}
|
|
>
|
|
<Form.Item
|
|
label="名称"
|
|
name="name"
|
|
rules={[{ required: true, message: '请输入名称' }]}
|
|
>
|
|
<Input placeholder="名称 (e.g., zyn)" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label="标题"
|
|
name="title"
|
|
rules={[{ required: true, message: '请输入标题' }]}
|
|
>
|
|
<Input placeholder="标题 (e.g., ZYN)" />
|
|
</Form.Item>
|
|
<Form.Item label="中文标题" name="titleCN">
|
|
<Input placeholder="中文标题 (e.g., 品牌)" />
|
|
</Form.Item>
|
|
<Form.Item label="简称 (可选)" name="shortName">
|
|
<Input placeholder="简称 (可选)" />
|
|
</Form.Item>
|
|
<Form.Item label="图片 (可选)" name="image">
|
|
<Input placeholder="图片链接 (可选)" />
|
|
</Form.Item>
|
|
<Form.Item label="值 (可选)" name="value">
|
|
<Input placeholder="值 (可选)" />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
{/* 添加字典 Modal */}
|
|
<Modal
|
|
title="添加新字典"
|
|
open={isAddDictModalVisible}
|
|
onOk={handleAddDict}
|
|
onCancel={() => {
|
|
setIsAddDictModalVisible(false);
|
|
setAddDictName('');
|
|
setAddDictTitle('');
|
|
}}
|
|
>
|
|
<Form layout="vertical">
|
|
<Form.Item label="字典名称">
|
|
<Input
|
|
placeholder="字典名称 (e.g., brand)"
|
|
value={addDictName}
|
|
onChange={(e) => setAddDictName(e.target.value)}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item label="字典标题">
|
|
<Input
|
|
placeholder="字典标题 (e.g., 品牌)"
|
|
value={addDictTitle}
|
|
onChange={(e) => setAddDictTitle(e.target.value)}
|
|
/>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
{/* 编辑字典 Modal */}
|
|
<Modal
|
|
title="编辑字典"
|
|
open={isEditDictModalVisible}
|
|
onOk={handleEditDict}
|
|
onCancel={() => {
|
|
setIsEditDictModalVisible(false);
|
|
setEditDictData(null);
|
|
}}
|
|
>
|
|
<Form layout="vertical">
|
|
<Form.Item label="字典名称">
|
|
<Input
|
|
placeholder="字典名称 (e.g., brand)"
|
|
value={editDictData?.name || ''}
|
|
onChange={(e) =>
|
|
setEditDictData({ ...editDictData, name: e.target.value })
|
|
}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item label="字典标题">
|
|
<Input
|
|
placeholder="字典标题 (e.g., 品牌)"
|
|
value={editDictData?.title || ''}
|
|
onChange={(e) =>
|
|
setEditDictData({ ...editDictData, title: e.target.value })
|
|
}
|
|
/>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
export default DictPage;
|