Files
hms/apps/web/src/pages/settings/DictionaryManager.tsx
iven d436888ca5
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
refactor(web): 系统设置模块页面表单一致性重构
- 新增 useCrudDrawer hook 封装 CRUD Drawer 通用模式(状态管理/提交/错误处理)
- 新增 useListData hook 封装非分页列表数据获取
- 11 个页面统一迁移到 DrawerForm + 共享 hooks,消除重复代码
- 错误处理统一使用 useApiRequest.execute(),移除内联 try-catch
- Modal 全部替换为 DrawerForm,保持 UI 一致性
- 净减少 ~1300 行代码(858 增 / 2136 删)
2026-05-04 11:57:38 +08:00

234 lines
7.7 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 { 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>
);
}