Files
Alfred/docs/development/crud.md

168 lines
5.9 KiB
Markdown
Raw Permalink 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.
# CRUD 管理页面模式
管理类页面(项目管理、模型管理、供应商管理)遵循统一的 CRUD 模式。本文档描述该模式的后端和前端约定。
## 背景
三个管理页面原本各自实现了工具条、筛选、分页、搜索等逻辑,代码重复且交互不一致。统一后所有管理页面使用相同的基础设施。
## 页面结构
每个管理页面的组件结构:
```text
<Space className="app-page-flex" orientation="vertical" size="large">
<FilterToolbar /> ← 筛选 + 搜索 + 操作按钮
<EntityTable /> ← 数据表格(分页 + 排序 + 行操作)
<EntityFormModal /> ← 创建/编辑弹窗
</Space>
```
## 前端基础设施
### FilterToolbar
`src/web/shared/components/FilterToolbar.tsx` — 统一筛选工具条。
```tsx
interface FilterConfig {
key: string;
label: string;
placeholder: string;
options: Array<{ label: string; value: string }>;
value?: string; // 受控值
onChange: (value: string | undefined) => void;
}
interface SearchConfig {
placeholder: string;
keyword?: string; // 受控关键词(来自 URL
onSearch: (value: string) => void; // 点击搜索按钮
onReset: () => void; // 点击重置按钮
}
interface FilterToolbarProps {
filters?: FilterConfig[]; // 左侧筛选下拉框
search?: SearchConfig; // 搜索输入框 + 重置按钮
actions?: ReactNode; // 右侧操作区(如"新建"按钮)
}
```
**布局**:左侧 = 筛选 Select + 搜索框SearchOutlined 按钮) + 重置按钮UndoOutlined右侧 = actions 插槽。
**交互约定**
- 筛选下拉框:选中即过滤,调用 `onChange`(实时过滤)
- 搜索框:输入文本,点击搜索按钮或 Enter 触发 `onSearch`(点击搜索)
- 重置按钮:调用 `onReset` 清除全部筛选条件
### usePageSearchParams
`src/web/shared/hooks/usePageSearchParams.ts` — 将页面筛选/分页/排序状态同步到 URL 查询参数。
```tsx
const { params, setParams, resetAll } = usePageSearchParams({
defaults: { page: "1", pageSize: "20" },
});
```
- `params``Record<string, string>`,读取当前 URL 参数(含默认值)
- `setParams(patch)`:批量更新多个参数(推荐),内部使用函数式更新器 + `replace: true`
- `setParam(key, value)`:更新单个参数(**注意**:连续多次 `setParam` 会导致闭包快照覆盖,多参数更新必须使用 `setParams`
- `resetAll()`:清空所有 URL 参数
- 默认值(`defaults`)不出现在 URL 中
**核心约束**react-router 的 `setSearchParams` 函数式更新器使用 `useCallback` 闭包捕获的 `searchParams` 快照,连续调用时第二次的 `prev` 还是第一次更新前的旧值。多参数同时更新必须使用单次 `setParams({...})` 批量操作。
### useConfirmAction
`src/web/shared/hooks/useConfirmAction.ts` — 包装异步操作,提供成功/失败 toast 通知。
```tsx
const { confirmAction } = useConfirmAction();
confirmAction(() => deleteMutation.mutateAsync(id), "删除成功");
```
## 后端基础设施
### parseListParams
`src/server/helpers/list-params.ts` — 统一解析列表请求参数。
```ts
export interface ParsedListParams {
keyword?: string;
page: number;
pageSize: number;
sortBy?: string;
sortOrder?: SortOrder;
}
export function parseListParams(
url: URL,
mode: RuntimeMode,
options?: { allowedSortBy?: string[] },
): ParsedListParams | Response;
```
- 校验 page正整数、pageSize正整数最大 200
- 校验 sortBy白名单由调用方传入 `allowedSortBy`
- 校验 sortOrder仅 "asc" / "desc"
- 校验失败返回 400 Response调用方应直接返回
- 替代了旧的 `validatePagination` 中间件
### 数据访问层约定
每个实体的 DB 函数(`listProjects` / `listModels` / `listProviders`
- 通过 `paginateQuery` 工具执行分页查询
- 接受 `sortBy` / `sortOrder` 参数,各实体自带白名单(`buildOrderBy`
- 默认排序:`desc(createdAt)`
- 实体专用筛选参数在 DB 层处理(如 `status``type``capabilities`
### 路由层约定
每个实体的列表路由:
```ts
// src/server/routes/{entity}/list.ts
const parsed = parseListParams(url, mode, { allowedSortBy: [...] });
if (parsed instanceof Response) return parsed;
// 解析实体专用筛选参数(如 status、providerId、capabilities、type
// 调用 DB 函数
```
## URL 参数约定
| 参数 | 说明 | 默认值 |
| -------------- | -------------- | ------ |
| `page` | 当前页码 | 1 |
| `pageSize` | 每页条数 | 20 |
| `keyword` | 搜索关键词 | - |
| `sortBy` | 排序列 | - |
| `sortOrder` | 排序方向 | - |
| `status` | 项目状态筛选 | - |
| `type` | 供应商类型筛选 | - |
| `providerId` | 模型供应商筛选 | - |
| `capabilities` | 模型能力筛选 | - |
默认值不出现在 URL 中。
## 排序约定
- 排序列后端白名单校验,拒绝无效列名
- 所有列排序通过后端 `ORDER BY` 实现,不使用前端排序
- Table 组件的 `column.sorter` 设为 `true` 启用排序指示器
- Table 的 `onChange` 回调统一处理分页 + 排序变更
## 交互约定
| 操作 | 触发时机 | 行为 |
| -------- | -------------------- | ----------------------------- |
| 筛选下拉 | 选中选项 / 清除 | 重置到第 1 页,更新 URL |
| 搜索 | 点击搜索按钮 / Enter | 重置到第 1 页,更新 URL |
| 重置 | 点击重置按钮 | 清除所有筛选条件,回到第 1 页 |
| 分页 | 点击分页器 | 更新 URLpage+pageSize |
| 排序 | 点击列头排序 | 重置到第 1 页,更新 URL |
| 删除 | Popconfirm 确认后 | toast 通知,刷新列表 |