项目2 · React 数据管理工作台
从一张静态表格出发,补齐真实管理台需要的请求状态、查询、分页、编辑和批量操作。
约 10 小时
学习重点
- 区分远程数据、查询条件和临时选择三类状态,安排各自的生命周期与归属
- 用可注入延迟和故障的 mock API 练习加载、失败、重试与空数据分支
- 把排序、分页、筛选和搜索组合成可推导的查询,并在条件变化时重置页码
- 完成详情抽屉、表单校验和批量删除确认,理解异步提交与界面反馈的配合
页面 / 接口清单
- 01数据列表页:员工记录表格、排序表头、结果统计和请求状态
- 02查询栏:关键词搜索、状态筛选、部门筛选、排序字段/方向与每页条数
- 03分页器:上一页、下一页、当前页/总页数和边界禁用
- 04详情抽屉:查看完整记录、进入编辑、保存后同步列表
- 05编辑表单:姓名、邮箱、薪资、入职日期等字段校验与异步保存
- 06批量操作:当前页多选、批量删除确认、取消和删除结果提示
- 07mock 接口:延迟、随机失败、列表查询、更新与批量删除
验收清单
0 / 8(0%)
页面启动调用 mockApi.list,成功渲染 20 条样例数据;表格列、状态文案和结果总数正确。
加载时显示明确状态;failRate=1 时出现失败提示和重试按钮,调回 0 后重试可恢复;无结果时显示空状态。
关键词、状态、部门可同时生效;排序字段和方向可切换;任一条件变化页码重置为 1,结果计数准确。
上一页/下一页在首尾禁用,页码和总页数随筛选结果变化;修改每页条数后回到第 1 页。
点击「查看」打开抽屉并展示完整字段;支持关闭、Esc 关闭和焦点落在可操作元素上,不阻塞表格浏览。
姓名必填、邮箱格式、薪资非负、入职日期必填;校验失败不提交,成功后抽屉和表格同步更新。
当前页可多选,批量操作条显示数量;点击删除先弹确认框,取消不改变数据,确认后调用接口并刷新列表。
solution 下 npm run test 全绿;mock 契约、数据模型、组件职责和 README 启动步骤与实现一致。
README(项目目录)
# React 数据管理工作台(p2-data-workbench)
一个带真实管理台骨架的数据列表:mock API 提供延迟与故障注入,页面补齐加载、失败、重试、空数据、排序、分页、条件筛选、详情抽屉、编辑校验和批量删除确认。
重点练习「远程数据、查询条件、临时选择」三类状态各归其位,而不是把所有东西塞进一个 `useState`。
## 学习目标
- 区分远程数据、查询条件和当前选择,明确各自的更新时机。
- 用可注入 `latency` / `failRate` 的 mock API 稳定复现请求状态。
- 把排序、分页、筛选、搜索收进一个查询 reducer,条件变化统一回到第 1 页。
- 完成详情抽屉、表单校验、异步保存和批量删除确认。
- 用行为测试覆盖成功、失败、重试、组合查询、编辑和批量操作。
## 环境要求
- Node.js 22.22.2+(推荐 24 LTS)
- npm 10+
## 快速开始
### 运行起点
```bash
cd starter
npm install --no-audit --no-fund
npm run dev
```
起点是一张静态表格,`src/App.jsx` 顶部用 `TODO m1`–`TODO m8` 标出了缺口。
### 运行参考实现
```bash
cd solution
npm install --no-audit --no-fund
npm run dev
```
### 运行行为测试
```bash
cd solution
npm install --no-audit --no-fund
npm run test
```
测试文件在项目根目录 `tests/`,由 `solution/vitest.config.js` 以项目根为测试根加载。测试会自己创建隔离的 mock API 实例,不会污染默认内存数据。
### 构建检查
```bash
cd starter
npm install --no-audit --no-fund
npm run build
```
`solution` 同样可执行 `npm run build`。
## 目录结构
```text
p2-data-workbench/
├── README.md
├── starter/ # 可运行起点:静态表格 + TODO
│ ├── index.html
│ ├── package.json
│ ├── vite.config.js
│ └── src/
│ ├── App.jsx
│ ├── main.jsx
│ └── styles.css
├── solution/ # 完整参考实现
│ ├── index.html
│ ├── package.json
│ ├── vite.config.js
│ ├── vitest.config.js
│ └── src/
│ ├── App.jsx
│ ├── format.js
│ ├── main.jsx
│ ├── styles.css
│ ├── api/
│ │ ├── mockApi.js
│ │ └── seedRecords.js
│ ├── state/
│ │ └── queryReducer.js
│ └── components/
│ ├── ConfirmDialog.jsx
│ ├── DataTable.jsx
│ ├── DetailDrawer.jsx
│ ├── Pagination.jsx
│ ├── RecordForm.jsx
│ ├── RequestState.jsx
│ └── Toolbar.jsx
├── tests/
│ └── app.test.jsx # Vitest + Testing Library 行为测试
├── docs/
│ ├── api.md # mockApi 契约与故障注入
│ └── data.md # 记录、查询与请求状态模型
└── fixtures/
└── records.json # 20 条示例员工记录
```
## 组件职责
| 组件 | 职责 | 为什么拆出来 |
| --- | --- | --- |
| `App` | 持有查询、请求、选择和确认状态,触发 list / update / delete | 唯一知道远程数据与查询条件的位置,避免各组件各自请求 |
| `Toolbar` | 搜索、状态/部门筛选、排序、每页条数 | 只抛查询变化,不缓存结果,也不负责重置页码 |
| `DataTable` | 当前页表格、全选/单选、查看入口 | 只渲染传入的当前页,选择状态由 Set 派生 |
| `Pagination` | 页码信息与上一页/下一页 | 只处理翻页意图,边界由按钮 disabled 和接口页码双重兜底 |
| `DetailDrawer` | 查看详情与切换到编辑 | 局部流程留在抽屉,任务数据仍以 App 的 `selectedRecord` 为准 |
| `RecordForm` | 编辑草稿与字段校验 | 校验和请求解耦,保存失败时保留用户输入 |
| `ConfirmDialog` | 批量删除确认、Esc 与焦点 | 不可逆动作集中处理,避免散落在 App JSX |
| `RequestState` | loading / error / empty 三种请求分支 | 避免 App 堆多层三元表达式,状态文案集中维护 |
| `mockApi` | 内存数据、延迟、故障注入、查询/更新/删除 | 让异步分支可重复测试,不依赖真实网络 |
## 起点缺口
| 里程碑 | 起点状态 | 需要完成 |
| --- | --- | --- |
| m1 | 静态数组 | `mockApi.list()` + 异步表格 |
| m2 | 无请求状态 | loading / error / retry / empty |
| m3 | 无查询 | 搜索、状态、部门、排序组合 |
| m4 | 假分页 | 真分页、每页条数、边界禁用 |
| m5 | 无详情 | 详情抽屉、关闭与 Esc |
| m6 | 无编辑 | 表单校验、异步保存、列表同步 |
| m7 | 无批量 | 当前页多选、删除确认 |
| m8 | 无文档 | mock 契约、数据模型、组件职责 |
## 验收方式
逐项对照 `content/projects/p2-data-workbench.json` 的 `m1`–`m8`:
1. **m1 接口与表格**:首屏先加载,再显示 20 条记录;默认按入职时间降序,第一页 5 条。
2. **m2 请求状态**:在 `solution/src/api/mockApi.js` 把默认 `failRate` 改为 1,刷新出现失败提示;改回 0 点重试恢复。搜索无结果时显示空状态。
3. **m3 组合查询**:搜索「张」+ 状态「在职」+ 部门「研发」只剩张驰;切换排序字段后页码回到 1。
4. **m4 分页**:默认 4 页,上一页/下一页在边界禁用;每页条数改为 10 后回到第 1 页且总页数变化。
5. **m5 详情抽屉**:点「查看 孙悦」打开抽屉,Esc 或遮罩可关闭,焦点落在关闭按钮。
6. **m6 编辑校验**:清空姓名、输入非法邮箱时不能提交;修正后保存,抽屉和表格同时更新。
7. **m7 批量删除**:勾选当前页两条,点「批量删除」先弹确认;取消后总数不变,确认后总数减少并提示删除数量。
8. **m8 测试与审查**:`cd solution && npm run test` 全绿;`docs/api.md`、`docs/data.md` 与实现一致。
自动化测试覆盖:加载→成功、失败→重试成功、排序/分页/筛选组合、空结果、编辑校验与保存、批量删除确认。
## 建议实现顺序
1. 先写 `mockApi` 和 `seedRecords`,确认接口返回结构。
2. 搭 `queryReducer`,把所有查询条件变化统一处理。
3. 接 `App` 的请求 effect,再补 `RequestState`。
4. 做 `DataTable`、`Pagination`、`Toolbar`。
5. 最后做 `DetailDrawer`、`RecordForm`、`ConfirmDialog`。
字段、接口参数和请求状态说明见 `docs/data.md` 与 `docs/api.md`。
数据模型
Record = { id: string, name: string, email: string, department: '产品' | '研发' | '设计' | '运营' | '财务', role: string, status: 'active' | 'on_leave' | 'inactive', salary: number, location: string, joinedAt: 'YYYY-MM-DD' }。查询状态为 { page, pageSize, sortBy, sortOrder, filters: { keyword, status, department } };接口返回 { items, total, page, pageSize, totalPages }。远程数据只保留接口响应,当前行、选中集合和编辑草稿都是界面临时状态。