db6a51d617
一、附录 A 从"手抄表"改成"可再生成的命令"
check-api-parity.py 加 --extra,现算"代码有、契约 §4 没有"的接口 = 41 个
(旧手抄表写 83/62/21,已差近一倍,而且没人会记得回来改它)。
并按"谁去改什么"分六类 —— 只有 E 类(8 个)要客户定口径,其余是文档侧补记。
二、F-6 已修:frontend-pc 的 Mock 说明
AGENTS.md 那句改正之外,连带修了 .agents/skills/project-api/SKILL.md ——
它整篇仍以 vite-plugin-mock / mock/** 为主线,而该项目里 mock/ 目录与插件都已删除。
只改指路牌、不改路的尽头,等于没改。
三、docs/03 两处文档内部口径冲突加 ⚠️ 注(不改口径,只把冲突摆明)
§2 notifyBizType 的 5/6 与 §13.5、落库值不一致 → 实现以 §13.5 为准(催报去重按 5);
§3.6/§9.5 的 collect_file.source 字段表(入口来源)与建表注释(谁传的)冲突 →
实现以建表口径为准。两处都写明"待客户确认",见 F-30 / F-38 / F-39。
四、check-test-profile.py 加固(新增 F-98)
· 子进程输出落盘(不再用没人读的 PIPE):失败时能看见"停在哪一行",
成功时打出启动用时(基线 ~10 秒 / 72 行 / 9.5KB);
· 开跑前按 jar + profile 清残留 JVM,且保留 dev 后端(E2E 正打着它);
· 等待期间每 15 秒报一次进度,卡住时不留沉默。
教训:随机端口这个防撞设计把残留 JVM 藏起来了 —— 撞不到端口就永远发现不了,
于是表现成"时好时坏",而失败时连日志都读不到。
验证:python scripts/verify-all.py --with-e2e → 通过 8 / 失败 0 / 跳过 0
(E2E 36 脚本 / 1548 断言 / 278s;prodguard 9/9;test profile 能起)
python scripts/verify-docs.py → 22 项全部通过
163 lines
4.6 KiB
Markdown
163 lines
4.6 KiB
Markdown
---
|
||
name: project-api
|
||
description: 在本模板项目中新增或修改 API 接口与类型定义(本仓库已不用 Mock,见开头说明)。涉及 http、baseAPI、分页响应格式、响应体适配时使用。
|
||
---
|
||
|
||
# API 与 Mock
|
||
|
||
> ⚠️ **本仓库不用 Mock(2026-09-30 核实)**:`mock/` 目录与 `vite-plugin-mock` 都已删除,
|
||
> `.env.development` 里 `VITE_API_PREFIX = '/api/v1'`、`VITE_API_BASE_URL = 'http://localhost:8080'`,
|
||
> `pnpm dev` 把 `/api` 代理到真实后端 —— **后端不起着就没有数据**(这不是故障)。
|
||
>
|
||
> 所以本文档里所有关于 Mock 的部分(`mock/**`、`defineMock`、`getBaseApi` 生成 mock、
|
||
> Mock 目录约定)在本仓库**不适用**,保留它们只是因为 Gi 模板升级时要做差异比对。
|
||
> 新增接口照 `src/apis/xt/*.ts` 的写法:用 `@/utils/http`,响应体 `{ code, msg, data }`
|
||
> (适配层会补 `records`)已在 `@/utils/http` 统一处理,业务模块不要再自己解一层。
|
||
|
||
## 接口层结构
|
||
|
||
每个业务模块一个文件,例如 `src/apis/system/user.ts`:
|
||
|
||
```ts
|
||
import { getBaseApi } from '@/apis/base'
|
||
import http from '@/utils/http'
|
||
|
||
export interface ListItem {
|
||
id: string
|
||
name: string
|
||
status: Status
|
||
createTime: string
|
||
// ...
|
||
}
|
||
|
||
/** 标准 CRUD */
|
||
export const baseAPI = getBaseApi<ListItem>({ baseUrl: '/system/user' })
|
||
|
||
/** 非标准接口单独声明 */
|
||
export function getDictDataList(params: { code: string } & Pagination) {
|
||
return http.get<PageRes<DictDataItem[]>>('/system/dict/getDictDataList', params)
|
||
}
|
||
```
|
||
|
||
## getBaseApi 约定路径
|
||
|
||
| 方法 | HTTP | 路径 |
|
||
|------|------|------|
|
||
| getList | GET | `{baseUrl}/getList` |
|
||
| getDetail | GET | `{baseUrl}/getDetail` |
|
||
| add | POST | `{baseUrl}/add` |
|
||
| update | POST | `{baseUrl}/update` |
|
||
| delete | POST | `{baseUrl}/delete` |
|
||
|
||
列表参数:`{ page, size, ...filters }`
|
||
删除参数:`{ ids: string[] }`
|
||
|
||
## 响应格式
|
||
|
||
```ts
|
||
// 单条/布尔
|
||
interface ApiRes<T> {
|
||
code: number
|
||
data: T
|
||
message: string
|
||
success: boolean
|
||
}
|
||
|
||
// 分页列表 data 结构
|
||
interface PageRes<T> {
|
||
records: T
|
||
total: number
|
||
}
|
||
```
|
||
|
||
`useTable` 同时兼容 `data` 为数组或 `PageRes` 两种形态。
|
||
|
||
## http 封装
|
||
|
||
- 实例:`src/utils/http.ts`,`baseURL` = `import.meta.env.VITE_API_PREFIX`
|
||
- 开发环境 `.env.development`:`VITE_API_PREFIX = '/mock'`
|
||
- Token:请求头 `token` 字段(`getToken()`)
|
||
- 业务失败:`success === false` 时 Message/Notification 提示并 reject
|
||
- 401:跳转 `/login`
|
||
|
||
```ts
|
||
import http from '@/utils/http'
|
||
|
||
http.get<PageRes<Item[]>>('/path', { page: 1, size: 10 })
|
||
http.post<boolean>('/path', { ids: ['1'] })
|
||
```
|
||
|
||
## Mock 开发流程
|
||
|
||
### 1. 静态数据
|
||
|
||
在 `mock/_data/` 下维护 JSON/TS 数据,如 `system_dict.ts`。
|
||
|
||
### 2. 路由 Mock
|
||
|
||
在 `mock/system/`(或对应模块)新建文件:
|
||
|
||
```ts
|
||
import { defineMock } from '../_base'
|
||
import { getBaseApi, getDelayTime, resultError, resultSuccess } from '../_utils'
|
||
|
||
export default defineMock([
|
||
...getBaseApi({
|
||
baseUrl: '/system/xxx',
|
||
getListData: () => resultSuccess({ total: list.length, records: list }),
|
||
getDetailData: (query) => {
|
||
const obj = list.find((i) => i.id === query.id)
|
||
return obj ? resultSuccess(obj) : resultError(null, '未找到', 400)
|
||
}
|
||
}),
|
||
{
|
||
url: '/system/xxx/customAction',
|
||
method: 'get',
|
||
timeout: getDelayTime(),
|
||
response: ({ query }) => {
|
||
// 筛选、分页后返回
|
||
return resultSuccess({ total, records })
|
||
}
|
||
}
|
||
])
|
||
```
|
||
|
||
### 3. 工具函数
|
||
|
||
| 函数 | 用途 |
|
||
|------|------|
|
||
| `resultSuccess(data)` | `{ code:200, success:true, data, message }` |
|
||
| `resultError(data, message, code?)` | 失败响应 |
|
||
| `getDelayTime()` | 50–300ms 随机延迟 |
|
||
| `getBaseApi({ baseUrl, getListData, ... })` | 生成标准 CRUD mock |
|
||
|
||
`defineMock` 会自动给 url 加前缀 `/mock`,与 `VITE_API_PREFIX` 对应。
|
||
|
||
### 4. 注册
|
||
|
||
确保 mock 文件被 `mock/index` 或同级聚合引入(遵循现有 `mock/system/*.ts` 模式)。
|
||
|
||
## 新增模块检查清单
|
||
|
||
- [ ] `src/apis/{module}/{name}.ts`:类型 + `baseAPI` 或自定义方法
|
||
- [ ] `mock/{module}/{name}.ts`:与接口路径一致
|
||
- [ ] 列表 Mock 返回 `{ total, records }`
|
||
- [ ] 字段类型与 `Status`、`Gender` 等全局类型对齐
|
||
- [ ] 在页面通过 `useTable({ listAPI, deleteAPI })` 接入
|
||
|
||
## 自定义 listAPI 示例
|
||
|
||
```ts
|
||
useTable({
|
||
listAPI: (page) => getDictDataList({
|
||
...page,
|
||
code: dictCode.value,
|
||
name: query.name || undefined,
|
||
status: query.status === '' ? undefined : query.status
|
||
}),
|
||
immediate: false
|
||
})
|
||
```
|
||
|
||
`immediate: false` 用于依赖左侧选中项后再 `search()` 的场景。
|