一、附录 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 项全部通过
4.6 KiB
name, description
| name | description |
|---|---|
| project-api | 在本模板项目中新增或修改 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:
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[] }
响应格式
// 单条/布尔
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
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/(或对应模块)新建文件:
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 示例
useTable({
listAPI: (page) => getDictDataList({
...page,
code: dictCode.value,
name: query.name || undefined,
status: query.status === '' ? undefined : query.status
}),
immediate: false
})
immediate: false 用于依赖左侧选中项后再 search() 的场景。