- 新增 useCrudDrawer hook 封装 CRUD Drawer 通用模式(状态管理/提交/错误处理) - 新增 useListData hook 封装非分页列表数据获取 - 11 个页面统一迁移到 DrawerForm + 共享 hooks,消除重复代码 - 错误处理统一使用 useApiRequest.execute(),移除内联 try-catch - Modal 全部替换为 DrawerForm,保持 UI 一致性 - 净减少 ~1300 行代码(858 增 / 2136 删)
234 lines
7.7 KiB
TypeScript
234 lines
7.7 KiB
TypeScript
import { useState } from 'react';
|
||
import {
|
||
Table,
|
||
Button,
|
||
Space,
|
||
Form,
|
||
Input,
|
||
InputNumber,
|
||
Popconfirm,
|
||
Typography,
|
||
Tag,
|
||
} from 'antd';
|
||
import { PlusOutlined } from '@ant-design/icons';
|
||
import {
|
||
listDictionaries,
|
||
createDictionary,
|
||
updateDictionary,
|
||
deleteDictionary,
|
||
createDictionaryItem,
|
||
updateDictionaryItem,
|
||
deleteDictionaryItem,
|
||
type DictionaryInfo,
|
||
type DictionaryItemInfo,
|
||
type CreateDictionaryRequest,
|
||
type CreateDictionaryItemRequest,
|
||
type UpdateDictionaryItemRequest,
|
||
} from '../../api/dictionaries';
|
||
import { useListData } from '../../hooks/useListData';
|
||
import { useCrudDrawer } from '../../hooks/useCrudDrawer';
|
||
import { DrawerForm } from '../../components/DrawerForm';
|
||
import { useApiRequest } from '../../hooks/useApiRequest';
|
||
|
||
type DictItem = DictionaryItemInfo;
|
||
type Dictionary = DictionaryInfo;
|
||
|
||
export default function DictionaryManager() {
|
||
const { data: dictionaries, loading, refresh } = useListData<Dictionary>(async () => {
|
||
const result = await listDictionaries();
|
||
return Array.isArray(result) ? result : result.data ?? [];
|
||
});
|
||
|
||
const { execute } = useApiRequest();
|
||
|
||
// 字典 CRUD Drawer
|
||
const dictDrawer = useCrudDrawer<Dictionary>({
|
||
getId: (r) => r.id,
|
||
onCreate: async (values) => {
|
||
await createDictionary(values as unknown as CreateDictionaryRequest);
|
||
},
|
||
onUpdate: async (id, values) => {
|
||
await updateDictionary(id, values as unknown as CreateDictionaryRequest & { version: number });
|
||
},
|
||
onSuccess: refresh,
|
||
});
|
||
|
||
// 字典项 Drawer — 因需要 activeDictId,手写状态管理
|
||
const [itemDrawerOpen, setItemDrawerOpen] = useState(false);
|
||
const [activeDictId, setActiveDictId] = useState<string | null>(null);
|
||
const [editItem, setEditItem] = useState<DictItem | null>(null);
|
||
const [itemForm] = Form.useForm();
|
||
|
||
const openAddItem = (dictId: string) => {
|
||
setActiveDictId(dictId);
|
||
setEditItem(null);
|
||
itemForm.resetFields();
|
||
itemForm.setFieldsValue({ sort_order: 0 });
|
||
setItemDrawerOpen(true);
|
||
};
|
||
|
||
const openEditItem = (dictId: string, item: DictItem) => {
|
||
setActiveDictId(dictId);
|
||
setEditItem(item);
|
||
itemForm.setFieldsValue({
|
||
label: item.label,
|
||
value: item.value,
|
||
sort_order: item.sort_order,
|
||
color: item.color,
|
||
});
|
||
setItemDrawerOpen(true);
|
||
};
|
||
|
||
const closeItemDrawer = () => {
|
||
setItemDrawerOpen(false);
|
||
setActiveDictId(null);
|
||
setEditItem(null);
|
||
itemForm.resetFields();
|
||
};
|
||
|
||
const handleItemSubmit = async (values: Record<string, unknown>) => {
|
||
if (!activeDictId) return;
|
||
const itemValues = values as unknown as CreateDictionaryItemRequest & { sort_order: number };
|
||
if (editItem) {
|
||
await execute(
|
||
() => updateDictionaryItem(activeDictId, editItem.id, { ...itemValues, version: editItem.version } as UpdateDictionaryItemRequest),
|
||
'字典项更新成功',
|
||
);
|
||
} else {
|
||
await execute(() => createDictionaryItem(activeDictId, itemValues), '字典项添加成功');
|
||
}
|
||
closeItemDrawer();
|
||
refresh();
|
||
};
|
||
|
||
const handleDeleteDict = async (id: string, version: number) => {
|
||
await execute(() => deleteDictionary(id, version), '字典已删除');
|
||
refresh();
|
||
};
|
||
|
||
const handleDeleteItem = async (dictId: string, itemId: string, version: number) => {
|
||
await execute(() => deleteDictionaryItem(dictId, itemId, version), '字典项已删除');
|
||
refresh();
|
||
};
|
||
|
||
const columns = [
|
||
{ title: '名称', dataIndex: 'name', key: 'name' },
|
||
{ title: '编码', dataIndex: 'code', key: 'code' },
|
||
{ title: '说明', dataIndex: 'description', key: 'description', ellipsis: true },
|
||
{
|
||
title: '操作',
|
||
key: 'actions',
|
||
render: (_: unknown, record: Dictionary) => (
|
||
<Space>
|
||
<Button size="small" onClick={() => openAddItem(record.id)}>添加项</Button>
|
||
<Button size="small" onClick={() => dictDrawer.openEdit(record, (r) => ({
|
||
name: r.name, code: r.code, description: r.description,
|
||
}))}>编辑</Button>
|
||
<Popconfirm title="确定删除此字典?" onConfirm={() => handleDeleteDict(record.id, record.version)}>
|
||
<Button size="small" danger>删除</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
const itemColumns = (dictId: string) => [
|
||
{ title: '标签', dataIndex: 'label', key: 'label' },
|
||
{ title: '值', dataIndex: 'value', key: 'value' },
|
||
{ title: '排序', dataIndex: 'sort_order', key: 'sort_order', width: 80 },
|
||
{
|
||
title: '颜色', dataIndex: 'color', key: 'color', width: 80,
|
||
render: (color?: string) => color ? <Tag color={color}>{color}</Tag> : '-',
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'actions',
|
||
render: (_: unknown, record: DictItem) => (
|
||
<Space>
|
||
<Button size="small" onClick={() => openEditItem(dictId, record)}>编辑</Button>
|
||
<Popconfirm title="确定删除此字典项?" onConfirm={() => handleDeleteItem(dictId, record.id, record.version)}>
|
||
<Button size="small" danger>删除</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||
<Typography.Title level={5} style={{ margin: 0 }}>数据字典管理</Typography.Title>
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={() => dictDrawer.openCreate()}>
|
||
新建字典
|
||
</Button>
|
||
</div>
|
||
|
||
<Table
|
||
columns={columns}
|
||
dataSource={dictionaries}
|
||
rowKey="id"
|
||
loading={loading}
|
||
pagination={{ pageSize: 20 }}
|
||
expandable={{
|
||
expandedRowRender: (record) => (
|
||
<Table
|
||
columns={itemColumns(record.id)}
|
||
dataSource={record.items}
|
||
rowKey="id"
|
||
size="small"
|
||
pagination={false}
|
||
/>
|
||
),
|
||
}}
|
||
/>
|
||
|
||
{/* 字典 Drawer */}
|
||
<DrawerForm
|
||
title={dictDrawer.editingRecord ? '编辑字典' : '新建字典'}
|
||
open={dictDrawer.open}
|
||
onClose={dictDrawer.close}
|
||
onSubmit={dictDrawer.handleSubmit}
|
||
initialValues={dictDrawer.initialValues}
|
||
loading={dictDrawer.loading}
|
||
width={480}
|
||
columns={1}
|
||
>
|
||
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入字典名称' }]}>
|
||
<Input />
|
||
</Form.Item>
|
||
<Form.Item name="code" label="编码" rules={[{ required: true, message: '请输入字典编码' }]}>
|
||
<Input disabled={!!dictDrawer.editingRecord} />
|
||
</Form.Item>
|
||
<Form.Item name="description" label="说明">
|
||
<Input.TextArea rows={3} />
|
||
</Form.Item>
|
||
</DrawerForm>
|
||
|
||
{/* 字典项 Drawer */}
|
||
<DrawerForm
|
||
title={editItem ? '编辑字典项' : '添加字典项'}
|
||
open={itemDrawerOpen}
|
||
onClose={closeItemDrawer}
|
||
onSubmit={handleItemSubmit}
|
||
initialValues={editItem ? { label: editItem.label, value: editItem.value, sort_order: editItem.sort_order, color: editItem.color } : { sort_order: 0 }}
|
||
loading={false}
|
||
width={480}
|
||
columns={1}
|
||
>
|
||
<Form.Item name="label" label="标签" rules={[{ required: true, message: '请输入标签' }]}>
|
||
<Input />
|
||
</Form.Item>
|
||
<Form.Item name="value" label="值" rules={[{ required: true, message: '请输入值' }]}>
|
||
<Input />
|
||
</Form.Item>
|
||
<Form.Item name="sort_order" label="排序" initialValue={0}>
|
||
<InputNumber min={0} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
<Form.Item name="color" label="颜色">
|
||
<Input placeholder="如:blue, red, green 或十六进制色值" />
|
||
</Form.Item>
|
||
</DrawerForm>
|
||
</div>
|
||
);
|
||
}
|