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

@@ -0,0 +1,74 @@
import { useState, useCallback } from 'react';
import { useApiRequest } from './useApiRequest';
export interface UseCrudDrawerOptions<T> {
getId: (record: T) => string;
onCreate: (values: Record<string, unknown>) => Promise<void>;
onUpdate: (id: string, values: Record<string, unknown> & { version: number }) => Promise<void>;
onSuccess?: () => void;
}
export interface UseCrudDrawerReturn<T> {
open: boolean;
editingRecord: T | null;
initialValues: Record<string, unknown> | undefined;
openCreate: (defaults?: Record<string, unknown>) => void;
openEdit: (record: T, fieldMap?: (record: T) => Record<string, unknown>) => void;
close: () => void;
handleSubmit: (values: Record<string, unknown>) => Promise<void>;
loading: boolean;
}
export function useCrudDrawer<T extends { version: number }>(
options: UseCrudDrawerOptions<T>,
): UseCrudDrawerReturn<T> {
const [open, setOpen] = useState(false);
const [editingRecord, setEditingRecord] = useState<T | null>(null);
const [initialValues, setInitialValues] = useState<Record<string, unknown> | undefined>(undefined);
const { execute, loading } = useApiRequest();
const openCreate = useCallback((defaults?: Record<string, unknown>) => {
setEditingRecord(null);
setInitialValues(defaults);
setOpen(true);
}, []);
const openEdit = useCallback((record: T, fieldMap?: (record: T) => Record<string, unknown>) => {
setEditingRecord(record);
setInitialValues(fieldMap ? fieldMap(record) : (record as unknown as Record<string, unknown>));
setOpen(true);
}, []);
const close = useCallback(() => {
setOpen(false);
setEditingRecord(null);
setInitialValues(undefined);
}, []);
const handleSubmit = useCallback(
async (values: Record<string, unknown>) => {
if (editingRecord) {
await execute(
() => options.onUpdate(options.getId(editingRecord), { ...values, version: (editingRecord as unknown as { version: number }).version }),
'更新成功',
);
} else {
await execute(() => options.onCreate(values), '创建成功');
}
close();
options.onSuccess?.();
},
[editingRecord, options, close, execute],
);
return {
open,
editingRecord,
initialValues,
openCreate,
openEdit,
close,
handleSubmit,
loading,
};
}

View File

@@ -0,0 +1,33 @@
import { useState, useCallback, useEffect, useRef } from 'react';
export interface UseListDataReturn<T> {
data: T[];
loading: boolean;
refresh: () => Promise<void>;
}
export function useListData<T>(fetchFn: () => Promise<T[]>, autoFetch = true): UseListDataReturn<T> {
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(false);
const fetchFnRef = useRef(fetchFn);
fetchFnRef.current = fetchFn;
const refresh = useCallback(async () => {
setLoading(true);
try {
const result = await fetchFnRef.current();
setData(result);
} catch {
setData([]);
}
setLoading(false);
}, []);
useEffect(() => {
if (autoFetch) {
refresh();
}
}, [refresh, autoFetch]);
return { data, loading, refresh };
}