pms-front-react/src/pages/system/DictPage.tsx

328 lines
12 KiB
TypeScript

import React, { useState, useEffect, useCallback } from 'react';
import {
Table, Form, Input, Select, Button, Modal, message, Space, Tag, DatePicker, Popconfirm, Radio
} from 'antd';
import type { TableColumnsType } from 'antd';
import {
SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined, ExclamationCircleOutlined, SyncOutlined
} from '@ant-design/icons';
import {
listDictType, getDictType, addDictType, updateDictType, delDictType, refreshCache
} from '../../api/system/dict';
import { saveAs } from 'file-saver';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import zhCN from 'antd/es/date-picker/locale/zh_CN';
import { parseTime } from '../../utils/ruoyi';
import { Link } from 'react-router-dom';
const { RangePicker } = DatePicker;
dayjs.locale('zh-cn');
// Mock Dictionaries
const sysNormalDisableDict = [
{ value: '0', label: '正常' },
{ value: '1', label: '停用' },
];
const DictPage: React.FC = () => {
const [queryForm] = Form.useForm();
const [dictForm] = Form.useForm();
const [loading, setLoading] = useState(false);
const [typeList, setTypeList] = useState<any[]>([]);
const [total, setTotal] = useState(0);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [dateRange, setDateRange] = useState<[dayjs.Dayjs, dayjs.Dayjs] | null>(null);
const [modalVisible, setModalVisible] = useState(false);
const [modalTitle, setModalTitle] = useState('');
const [currentDict, setCurrentDict] = useState<any>({});
const [queryParams, setQueryParams] = useState({
pageNum: 1,
pageSize: 10,
dictName: undefined,
dictType: undefined,
status: undefined,
});
const getList = useCallback(async () => {
setLoading(true);
try {
const formattedQueryParams = { ...queryParams };
if (dateRange && dateRange.length === 2) {
formattedQueryParams['beginTime'] = dateRange[0].format('YYYY-MM-DD');
formattedQueryParams['endTime'] = dateRange[1].format('YYYY-MM-DD');
} else {
formattedQueryParams['beginTime'] = undefined;
formattedQueryParams['endTime'] = undefined;
}
const response = await listDictType(formattedQueryParams);
setTypeList(response.rows);
setTotal(response.total);
} catch (error) {
console.error('Failed to fetch dictionary type list:', error);
message.error('获取字典类型列表失败');
} finally {
setLoading(false);
}
}, [queryParams, dateRange]);
useEffect(() => {
getList();
}, [getList]);
const handleQuery = () => {
setQueryParams(prev => ({ ...prev, pageNum: 1 }));
};
const resetQuery = () => {
queryForm.resetFields();
setDateRange(null);
setQueryParams({
pageNum: 1,
pageSize: 10,
dictName: undefined,
dictType: undefined,
status: undefined,
});
getList();
};
const resetDictForm = () => {
dictForm.resetFields();
setCurrentDict({});
};
const handleAdd = () => {
resetDictForm();
setModalTitle('添加字典类型');
setModalVisible(true);
};
const handleUpdate = async (row: any) => {
resetDictForm();
try {
const dictId = row.dictId || selectedRowKeys[0];
const response = await getDictType(dictId);
const detail = (response && typeof response === 'object' && 'data' in response)
? response.data ?? {}
: response ?? {};
setCurrentDict(detail);
dictForm.setFieldsValue({
...detail,
});
setModalTitle('修改字典类型');
setModalVisible(true);
} catch (error) {
message.error('获取字典类型详情失败');
}
};
const handleDelete = (row?: any) => {
const dictIds = row?.dictId ? [row.dictId] : selectedRowKeys;
if (dictIds.length === 0) {
message.warning('请选择要删除的字典类型');
return;
}
Modal.confirm({
title: '确认删除',
icon: <ExclamationCircleOutlined />,
content: `是否确认删除字典编号为"${dictIds.join(',')}"的数据项?`,
onOk: async () => {
try {
await delDictType(dictIds.join(','));
message.success('删除成功');
setSelectedRowKeys([]);
getList();
} catch (error) {
message.error('删除失败');
}
},
});
};
const handleExport = async () => {
const hide = message.loading('正在导出数据...', 0);
try {
const response = await listDictType({ ...queryParams, pageNum: undefined, pageSize: undefined });
const header = ['字典编号', '字典名称', '字典类型', '状态', '备注', '创建时间'];
const data = response.rows.map((dict: any) => [
dict.dictId, dict.dictName, dict.dictType,
sysNormalDisableDict.find((d) => d.value === String(dict.status ?? ''))?.label ?? '',
dict.remark, parseTime(dict.createTime),
]);
const csvContent = [header, ...data]
.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(','))
.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, `dict_type_${dayjs().format('YYYYMMDDHHmmss')}.csv`);
hide();
message.success('导出成功');
} catch {
hide();
message.error('导出失败');
}
};
const handleRefreshCache = async () => {
try {
await refreshCache();
message.success('刷新缓存成功');
} catch (error) {
message.error('刷新缓存失败');
}
};
const submitDictForm = async (values: any) => {
try {
const dictData = { ...currentDict, ...values };
if (dictData.dictId !== undefined) {
await updateDictType(dictData);
message.success('修改成功');
} else {
await addDictType(dictData);
message.success('新增成功');
}
setModalVisible(false);
getList();
} catch (error) {
console.error('Submit dictionary type form failed:', error);
message.error('操作失败');
}
};
const columns: TableColumnsType<any> = [
{ title: '字典编号', dataIndex: 'dictId', align: 'center' },
{ title: '字典名称', dataIndex: 'dictName', align: 'center', ellipsis: true },
{
title: '字典类型', dataIndex: 'dictType', align: 'center', ellipsis: true,
render: (text: string, record: any) => (
<Link to={`/system/dict-data/index/${record.dictId}`}>{text}</Link>
),
},
{
title: '状态', dataIndex: 'status', align: 'center',
render: (text: string) => <Tag color={text === '0' ? 'green' : 'red'}>{sysNormalDisableDict.find(d => d.value === text)?.label}</Tag>,
},
{ title: '备注', dataIndex: 'remark', align: 'center', ellipsis: true },
{ title: '创建时间', dataIndex: 'createTime', align: 'center', width: 180, render: (text: string) => parseTime(text) },
{
title: '操作', key: 'operation', align: 'center', width: 180,
render: (_, record) => (
<Space size="small">
<Button type="link" icon={<EditOutlined />} onClick={() => handleUpdate(record)}></Button>
<Popconfirm
title="是否确认删除该字典类型?"
onConfirm={() => handleDelete(record)}
okText="是"
cancelText="否"
>
<Button type="link" danger icon={<DeleteOutlined />}></Button>
</Popconfirm>
</Space>
),
},
];
return (
<div className="app-container">
<Form form={queryForm} layout="inline" className="search-form" onFinish={handleQuery} initialValues={queryParams}>
<Form.Item label="字典名称" name="dictName">
<Input placeholder="请输入字典名称" allowClear onPressEnter={handleQuery} style={{ width: 240 }} />
</Form.Item>
<Form.Item label="字典类型" name="dictType">
<Input placeholder="请输入字典类型" allowClear onPressEnter={handleQuery} style={{ width: 240 }} />
</Form.Item>
<Form.Item label="状态" name="status">
<Select placeholder="字典状态" allowClear style={{ width: 240 }}>
{sysNormalDisableDict.map(dict => (
<Select.Option key={dict.value} value={dict.value}>{dict.label}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="创建时间">
<RangePicker
value={dateRange}
onChange={(dates) => setDateRange(dates)}
locale={zhCN}
format="YYYY年MM月DD日"
placeholder={['开始日期', '结束日期']}
style={{ width: 240 }}
/>
</Form.Item>
<Form.Item>
<Button type="primary" icon={<SearchOutlined />} htmlType="submit"></Button>
<Button icon={<ReloadOutlined />} onClick={resetQuery} style={{ marginLeft: 8 }}></Button>
</Form.Item>
</Form>
<Space className="mb8">
<Button type="primary" ghost icon={<PlusOutlined />} onClick={handleAdd}></Button>
<Button type="primary" ghost icon={<EditOutlined />} disabled={selectedRowKeys.length !== 1} onClick={() => handleUpdate(selectedRowKeys)}></Button>
<Button type="danger" ghost icon={<DeleteOutlined />} disabled={selectedRowKeys.length === 0} onClick={() => handleDelete()}></Button>
<Button type="warning" ghost icon={<DownloadOutlined />} onClick={handleExport}></Button>
<Button type="danger" ghost icon={<SyncOutlined />} onClick={handleRefreshCache}></Button>
</Space>
<Table
columns={columns}
dataSource={typeList}
rowKey="dictId"
loading={loading}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
}}
pagination={{
current: queryParams.pageNum,
pageSize: queryParams.pageSize,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
onChange: (page, pageSize) => {
setQueryParams(prev => ({ ...prev, pageNum: page, pageSize: pageSize }));
},
}}
/>
{/* Add/Edit Dictionary Type Modal */}
<Modal
title={modalTitle}
open={modalVisible}
onOk={dictForm.submit}
onCancel={() => setModalVisible(false)}
width={500}
forceRender
>
<Form form={dictForm} labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} onFinish={submitDictForm} initialValues={{ status: '0' }}>
<Form.Item name="dictId" hidden>
<Input />
</Form.Item>
<Form.Item label="字典名称" name="dictName" rules={[{ required: true, message: '请输入字典名称' }]}>
<Input placeholder="请输入字典名称" />
</Form.Item>
<Form.Item label="字典类型" name="dictType" rules={[{ required: true, message: '请输入字典类型' }]}>
<Input placeholder="请输入字典类型" />
</Form.Item>
<Form.Item label="状态" name="status">
<Radio.Group>
{sysNormalDisableDict.map(dict => (
<Radio key={dict.value} value={dict.value}>{dict.label}</Radio>
))}
</Radio.Group>
</Form.Item>
<Form.Item label="备注" name="remark">
<Input.TextArea placeholder="请输入内容" />
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default DictPage;