refactor(web): 系统设置模块页面表单一致性重构
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

- 新增 useCrudDrawer hook 封装 CRUD Drawer 通用模式(状态管理/提交/错误处理)
- 新增 useListData hook 封装非分页列表数据获取
- 11 个页面统一迁移到 DrawerForm + 共享 hooks,消除重复代码
- 错误处理统一使用 useApiRequest.execute(),移除内联 try-catch
- Modal 全部替换为 DrawerForm,保持 UI 一致性
- 净减少 ~1300 行代码(858 增 / 2136 删)
This commit is contained in:
iven
2026-05-04 11:57:38 +08:00
parent 444dc7dd8d
commit d436888ca5
13 changed files with 960 additions and 2131 deletions

View File

@@ -1,14 +1,12 @@
import { useEffect, useState, useCallback } from 'react';
import { useState } from 'react';
import {
Table,
Button,
Space,
Modal,
Form,
Input,
InputNumber,
Popconfirm,
message,
Typography,
Tag,
} from 'antd';
@@ -27,100 +25,46 @@ import {
type CreateDictionaryItemRequest,
type UpdateDictionaryItemRequest,
} from '../../api/dictionaries';
// --- Types ---
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;
// --- Component ---
export default function DictionaryManager() {
const [dictionaries, setDictionaries] = useState<Dictionary[]>([]);
const [loading, setLoading] = useState(false);
const [dictModalOpen, setDictModalOpen] = useState(false);
const [editDict, setEditDict] = useState<Dictionary | null>(null);
const [itemModalOpen, setItemModalOpen] = useState(false);
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 [dictForm] = Form.useForm();
const [itemForm] = Form.useForm();
const fetchDictionaries = useCallback(async () => {
setLoading(true);
try {
const result = await listDictionaries();
setDictionaries(Array.isArray(result) ? result : result.data ?? []);
} catch {
message.error('加载字典列表失败');
}
setLoading(false);
}, []);
useEffect(() => {
fetchDictionaries();
}, [fetchDictionaries]);
// --- Dictionary CRUD ---
const handleDictSubmit = async (values: CreateDictionaryRequest) => {
try {
if (editDict) {
await updateDictionary(editDict.id, { ...values, version: editDict.version });
message.success('字典更新成功');
} else {
await createDictionary(values);
message.success('字典创建成功');
}
closeDictModal();
fetchDictionaries();
} catch (err: unknown) {
const errorMsg =
(err as { response?: { data?: { message?: string } } })?.response?.data
?.message || '操作失败';
message.error(errorMsg);
}
};
const handleDeleteDict = async (id: string, version: number) => {
try {
await deleteDictionary(id, version);
message.success('字典已删除');
fetchDictionaries();
} catch {
message.error('删除失败');
}
};
const openEditDict = (dict: Dictionary) => {
setEditDict(dict);
dictForm.setFieldsValue({
name: dict.name,
code: dict.code,
description: dict.description,
});
setDictModalOpen(true);
};
const openCreateDict = () => {
setEditDict(null);
dictForm.resetFields();
setDictModalOpen(true);
};
const closeDictModal = () => {
setDictModalOpen(false);
setEditDict(null);
dictForm.resetFields();
};
// --- Dictionary Item CRUD ---
const openAddItem = (dictId: string) => {
setActiveDictId(dictId);
setEditItem(null);
itemForm.resetFields();
setItemModalOpen(true);
itemForm.setFieldsValue({ sort_order: 0 });
setItemDrawerOpen(true);
};
const openEditItem = (dictId: string, item: DictItem) => {
@@ -132,75 +76,56 @@ export default function DictionaryManager() {
sort_order: item.sort_order,
color: item.color,
});
setItemModalOpen(true);
setItemDrawerOpen(true);
};
const handleItemSubmit = async (values: CreateDictionaryItemRequest & { sort_order: number }) => {
if (!activeDictId) return;
try {
if (editItem) {
await updateDictionaryItem(activeDictId, editItem.id, { ...values, version: editItem.version } as UpdateDictionaryItemRequest);
message.success('字典项更新成功');
} else {
await createDictionaryItem(activeDictId, values);
message.success('字典项添加成功');
}
closeItemModal();
fetchDictionaries();
} catch (err: unknown) {
const errorMsg =
(err as { response?: { data?: { message?: string } } })?.response?.data
?.message || '操作失败';
message.error(errorMsg);
}
};
const handleDeleteItem = async (dictId: string, itemId: string, version: number) => {
try {
await deleteDictionaryItem(dictId, itemId, version);
message.success('字典项已删除');
fetchDictionaries();
} catch {
message.error('删除失败');
}
};
const closeItemModal = () => {
setItemModalOpen(false);
const closeItemDrawer = () => {
setItemDrawerOpen(false);
setActiveDictId(null);
setEditItem(null);
itemForm.resetFields();
};
// --- Columns ---
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: '说明', 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={() => openEditDict(record)}>
</Button>
<Popconfirm
title="确定删除此字典?"
onConfirm={() => handleDeleteDict(record.id, record.version)}
>
<Button size="small" danger>
</Button>
<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>
),
@@ -212,31 +137,17 @@ export default function DictionaryManager() {
{ 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: '颜色', 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>
<Button size="small" onClick={() => openEditItem(dictId, record)}></Button>
<Popconfirm title="确定删除此字典项?" onConfirm={() => handleDeleteItem(dictId, record.id, record.version)}>
<Button size="small" danger></Button>
</Popconfirm>
</Space>
),
@@ -245,17 +156,9 @@ export default function DictionaryManager() {
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={openCreateDict}>
<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>
@@ -279,68 +182,52 @@ export default function DictionaryManager() {
}}
/>
{/* Dictionary Modal */}
<Modal
title={editDict ? '编辑字典' : '新建字典'}
open={dictModalOpen}
onCancel={closeDictModal}
onOk={() => dictForm.submit()}
{/* 字典 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 form={dictForm} onFinish={handleDictSubmit} layout="vertical">
<Form.Item
name="name"
label="名称"
rules={[{ required: true, message: '请输入字典名称' }]}
>
<Input />
</Form.Item>
<Form.Item
name="code"
label="编码"
rules={[{ required: true, message: '请输入字典编码' }]}
>
<Input disabled={!!editDict} />
</Form.Item>
<Form.Item name="description" label="说明">
<Input.TextArea rows={3} />
</Form.Item>
</Form>
</Modal>
<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>
{/* Dictionary Item Modal */}
<Modal
{/* 字典项 Drawer */}
<DrawerForm
title={editItem ? '编辑字典项' : '添加字典项'}
open={itemModalOpen}
onCancel={closeItemModal}
onOk={() => itemForm.submit()}
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 form={itemForm} onFinish={handleItemSubmit} layout="vertical">
<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>
</Form>
</Modal>
<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>
);
}