forked from yoone/WEB
1
0
Fork 0
WEB/src/pages/Dict/List/index.tsx

697 lines
21 KiB
TypeScript

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';
import * as dictApi from '@/servers/api/dict';
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;