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

605 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
import DictItemActions from '../components/DictItemActions';
import DictItemModal from '../components/DictItemModal';
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);
// 字典项模态框状态(由 DictItemModal 组件管理)
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
const [isEditDictItem, setIsEditDictItem] = useState(false);
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
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 = () => {
setIsEditDictItem(false);
setEditingDictItemData(null);
setIsDictItemModalVisible(true);
};
// 编辑字典项
const handleEditDictItem = (record: any) => {
setIsEditDictItem(true);
setEditingDictItemData(record);
setIsDictItemModalVisible(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 handleDictItemModalOk = async (values: any) => {
try {
if (isEditDictItem && editingDictItemData) {
// 编辑字典项
const result = await dictApi.dictcontrollerUpdatedictitem(
{ id: editingDictItemData.id },
values,
);
if (!result.success) {
throw new Error(result.message || '更新失败');
}
message.success('更新成功');
} else {
// 添加字典项
const result = await dictApi.dictcontrollerCreatedictitem({
...values,
dictId: selectedDict.id,
});
if (!result.success) {
throw new Error(result.message || '添加失败');
}
message.success('添加成功');
}
setIsDictItemModalVisible(false);
// 强制刷新字典项列表
setTimeout(() => {
actionRef.current?.reload();
}, 100);
} catch (error: any) {
message.error(
`${isEditDictItem ? '更新' : '添加'}失败:${
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: 'title',
key: 'title',
copyable: true,
},
{
title: '中文标题',
dataIndex: 'titleCN',
key: 'titleCN',
copyable: true,
},
{
title: '图片',
dataIndex: 'image',
key: 'image',
valueType: 'image',
width: 80,
},
{
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={undefined}
customRequest={async (options) => {
const { file, onSuccess, onError } = options;
try {
const result = await dictApi.dictcontrollerImportdicts({}, [
file as File,
]);
onSuccess?.(result);
} catch (error) {
onError?.(error as Error);
}
}}
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={() => [
<DictItemActions
key="dictItemActions"
selectedDict={selectedDict}
actionRef={actionRef}
showExport={true}
onImport={async (file: File, dictId: number) => {
// 创建 FormData 对象
const formData = new FormData();
// 添加文件到 FormData
formData.append('file', file);
// 添加字典 ID 到 FormData
formData.append('dictId', String(dictId));
// 调用导入字典项的 API直接返回解析后的 JSON 对象
const result = await dictApi.dictcontrollerImportdictitems(
formData,
);
return result;
}}
onExport={handleExportDictItems}
onAdd={handleAddDictItem}
onRefreshDicts={fetchDicts}
/>,
]}
/>
</Space>
</Content>
</Layout>
{/* 字典项 Modal(添加或编辑) */}
<DictItemModal
visible={isDictItemModalVisible}
isEdit={isEditDictItem}
editingData={editingDictItemData}
selectedDict={selectedDict}
onCancel={() => {
setIsDictItemModalVisible(false);
setEditingDictItemData(null);
}}
onOk={handleDictItemModalOk}
/>
{/* 添加字典 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;