| name | api-contract |
| description | Use when: designing API contracts between frontend and backend based on prototype page inventory, defining request/response field structures, establishing naming conventions, and generating an api.md file per page for team alignment before coding. Triggers on: api contract, interface design, 接口约定, 前后端对齐, 字段定义, 接口设计, api.md. |
Skill: 接口约定(api-contract)
基于已评审需求、页面清单或后端 manifest 建立 wl-api-contract.json,再确定性渲染每页 api.md。本 Skill 可独立运行,不要求安装 wl-skills-design 或 wl-skills-bd。
双重作用:
- 前端 — data.ts 中
API_CONFIG 的 URL 和字段名直接基于 api.md
- 后端 — 读取同协议契约或在其项目独立建立契约,并通过严格 compare 对齐
输入优先级:已确认后端 wl-api-contract → 当前前端已确认契约 → 已评审需求。没有上游产物时不得阻断,但所有推断必须进入 openQuestions,确认前状态保持 draft。
默认使用内置 jh4j3-openapi3@1.0;自定义 profile 允许存在,但偏差必须显式。机器契约命令:
wl-skills contract init ...
wl-skills contract validate --input wl-api-contract.json --strict
wl-skills contract compare --left frontend.json --right backend.json --strict
wl-skills contract render --input wl-api-contract.json --output api.md --confirm
全局规范
URL 命名
/[服务缩写]/[资源名CamelCase]/[操作]
| 服务缩写 | 含义 | 示例 |
|---|
| pm | 生产管理 | /pm/omptMillPlanOrder/queryPage |
| mmwr | 精整作业 | /mmwr/mmwrTechFinish/queryTechList |
| mmsm | 炼钢管理 | /mmsm/mmsmRsltLadleUse/queryPage |
| sale | 销售管理 | /sale/saleOrder/queryPage |
| hrms | 人力资源 | /hrms/hrmsEmployee/queryPage |
| base | 基础数据 | /base/cmUserGroup/queryPage |
标准操作集
| 操作 | 方法 | URL 后缀 | 说明 |
|---|
| 分页列表 | POST | /queryPage | postAction(API_CONFIG.list, query) |
| 单条查询 | GET | /getById/{id} | getAction(resolveApiPath(API_CONFIG.getById, id), {}) |
| 新增 | POST | /save | postAction(API_CONFIG.save, formData) |
| 编辑 | PUT | /updateById | putAction(API_CONFIG.update, formData) |
| 删除 | DELETE | /deleteById/{id} | deleteAction(resolveApiPath(API_CONFIG.remove, id), {}) |
| 导出 | GET | /export | getAction(API_CONFIG.export, params) |
业务操作命名规范
非标准 CRUD 操作按以下约定命名,URL 后缀使用动词原形(camelCase):
| 操作 | 方法 | URL 后缀 | 请求说明 |
|---|
| 提交审批 | POST | /submit | { id } 或 { ids: [] } |
| 审批通过 | POST | /approve | { id, opinion? } |
| 审批驳回 | POST | /reject | { id, opinion } |
| 撤回 | POST | /withdraw | { id } 撤回已提交的单据 |
| 启用/禁用 | POST | /changeStatus | { id, status } |
| 转化 | POST | /convert | { id } 临时→正式(如临时客户→正式) |
| 下发 | POST | /release | { id } 计划/工单下发执行 |
| 关闭 | POST | /close | { id } 关闭订单/计划 |
| 作废 | POST | /cancel | { id } 作废单据 |
| 批量操作 | POST | /batchXxx | 如 /batchSubmit、/batchRemove |
| 子表查询 | POST | /queryXxxList | 如 /queryDetailList,主从表场景 |
命名原则:/[服务缩写]/[资源名]/[动作],动作用英文动词原形,不用中文拼音,不加 do / handle 前缀。
统一响应结构(基于真实后端契约)
⚠️ 重要:本项目后端响应外壳为 { code, message, data }(非 result),成功码为 2000(非 200)。
1. 分页查询响应
{
"code": 2000,
"message": "操作成功",
"data": {
"records": [
{
}
],
"total": 100,
"current": 1,
"size": 20,
"pages": 5,
"countId": null,
"maxLimit": null,
"orders": [],
"searchCount": true
}
}
| data 字段 | 类型 | 说明 |
|---|
records | array | 当前页数据列表 |
total | number | 总记录数 |
current | number | 当前页码 |
size | number | 每页条数 |
pages | number | 总页数 |
countId | any | MyBatis-Plus 分页计数 ID(前端忽略) |
maxLimit | any | 最大单页限制(前端忽略) |
orders | array | 排序条件回传(前端忽略,除非需回显排序) |
searchCount | boolean | 是否执行总数查询(前端忽略) |
前端 BaseTable / AbstractPageQueryHook 已自动适配:取 data.records 渲染、data.total 设分页、其他字段透传或忽略。无需在 data.ts 中处理。
2. 单条 / 非分页查询响应
{
"code": 2000,
"message": "操作成功",
"data": {
}
}
或返回数组(无需分页时):
{
"code": 2000,
"message": "操作成功",
"data": [
{
}
]
}
3. 字典查询响应
{
"code": 2000,
"message": "操作成功",
"data": [
{ "value": "01", "label": "已审批", "extra": null },
{ "value": "02", "label": "驳回", "extra": null }
]
}
4. 增删改响应
{ "code": 2000, "message": "操作成功", "data": true }
或返回主键:
{ "code": 2000, "message": "操作成功", "data": "1234567890123456789" }
5. 失败响应
{ "code": 4001, "message": "参数缺失:customerName 不能为空", "data": null }
| code 范围 | 含义 |
|---|
2000 | 成功 |
4xxx | 客户端错误(参数/权限/校验) |
5xxx | 服务端错误 |
401 / 403 | 网关层未登录 / 无权限(统一拦截) |
业务代码不需要关心 code 判断,request.ts 拦截器统一处理:非 2000 自动 Promise.reject + Toast 提示。业务代码 .then() 拿到的就是 data 字段内容。
字段命名
| 端 | 规范 | 说明 |
|---|
| 前端 | camelCase | 所有请求/响应字段名 |
| 后端 | snake_case | 数据库字段,Jackson 自动转驼峰 |
执行步骤
Step 1:接收输入
从 Phase 1 《页面清单》获取:
- 业务服务缩写(如
pm)
- 资源名(camelCase,如
omptMillPlanOrder)
- 各页面的查询字段、表格列、表单字段
Step 2:为每个页面生成 api.md
文件放在页面目录下(和 index.vue 同级),字段名与 data.ts 完全一致。
Step 3:同步生成 API_CONFIG
api.md 中的接口 URL 直接对应 data.ts 中的 API_CONFIG:
export const API_CONFIG = {
list: "/pm/omptMillPlanOrder/queryPage",
remove: "/pm/omptMillPlanOrder/deleteById/{id}",
getById: "/pm/omptMillPlanOrder/getById/{id}",
save: "/pm/omptMillPlanOrder/save",
update: "/pm/omptMillPlanOrder/updateById",
export: "/pm/omptMillPlanOrder/export",
release: "/pm/omptMillPlanOrder/release",
} as const;
export const resolveApiPath = (template: string, id: string) =>
template.replace("{id}", encodeURIComponent(id));
api.md 模板
每个页面目录下生成:
若页面使用业务字典,同时读取 .wl-skills/docs/dictionary-contract.md,在 api.md 写入机器可解析的 dict-contract,并更新 src/views/[域]/[模块]/dicts.ts。api.md 是页面片段,dicts.ts 是模块发布真值;两者由 wl-skills validate D1 确定性核对。
# 接口约定 - [页面中文名]
> 页面路径:`src/views/[域]/[模块]/[子模块]/[目录]/`
> 服务缩写:[pm / mmwr / sale / ...]
> 资源名:[camelCase 实体名]
> 状态:🟡 待后端确认
---
## API_CONFIG
```typescript
export const API_CONFIG = {
list: "/[服务缩写]/[资源名]/queryPage",
remove: "/[服务缩写]/[资源名]/deleteById/{id}",
getById: "/[服务缩写]/[资源名]/getById/{id}",
save: "/[服务缩写]/[资源名]/save",
update: "/[服务缩写]/[资源名]/updateById",
export: "/[服务缩写]/[资源名]/export",
} as const;
```
---
## 实体定义
> 字段名与 data.ts 中 queryDef/columnsDef 使用的字段名**完全一致**
| 字段名 | 类型 | 说明 | 必填 | 字典(logicValue) | 备注 |
| ------------- | ------ | -------- | ---- | ---------------- | -------------------- |
| id | string | 主键 | 自动 | - | 后端生成 |
| [field1] | string | [说明] | ✅ | - | |
| [statusField] | string | [状态] | ✅ | [dictCode] | 前端 logicValue 对应 |
| [dateField] | string | [日期] | ❌ | - | YYYY-MM-DD |
| createTime | string | 创建时间 | 自动 | - | YYYY-MM-DD HH:mm:ss |
| updateTime | string | 更新时间 | 自动 | - | |
| createBy | string | 创建人 | 自动 | - | |
---
## 接口清单
### 1. 分页查询
```
POST /[服务缩写]/[资源名]/queryPage
```
| 字段 | 类型 | 必填 | 说明 |
| ------------- | ------ | ---- | ----------------------------------- |
| current | number | ✅ | 页码(基类自动传) |
| size | number | ✅ | 每页条数(基类自动传) |
| [queryField1] | string | ❌ | [说明] |
| [queryField2] | string | ❌ | [说明],对应 logicValue: [dictCode] |
| [startDate] | string | ❌ | 开始日期 YYYY-MM-DD |
| [endDate] | string | ❌ | 结束日期 YYYY-MM-DD |
**响应 data.records:** 同实体定义;外壳 `{ code: 2000, message, data: { records, total, current, size, pages, countId, maxLimit, orders, searchCount } }`
### 2. 详情查询
```
GET /[服务缩写]/[资源名]/getById/{id}
```
**响应 data:** 单个 Entity;外壳 `{ code: 2000, message, data: {...} }`
### 3. 新增
```
POST /[服务缩写]/[资源名]/save
```
**请求:** 实体定义中必填字段(不含 id、createTime 等自动字段)
### 4. 编辑
```
PUT /[服务缩写]/[资源名]/updateById
```
**请求:** 同新增 + `id`(必填)
### 5. 删除
```
DELETE /[服务缩写]/[资源名]/deleteById/{id}
```
**请求:** 主键放在路径;批量删除必须另行声明 `batchDelete` 自定义操作,不得复用单删契约。
### 6. 导出(如需要)
```
GET /[服务缩写]/[资源名]/export?[查询参数]
```
---
## 数据字典
> 无字典时写“本页面不使用业务字典”。有字典时必须同时提供完整枚举和显式排序,不得只列 dictCode。
```dict-contract
{
"schemaVersion": 1,
"module": {
"code": "[moduleCode]",
"name": "[模块名称]"
},
"dictionaries": [
{
"code": "[dictCode]",
"name": "[字典名称]",
"order": { "field": "STR_KEY", "direction": "asc" },
"items": [
{ "value": "0", "label": "[枚举名称]" }
],
"sources": []
}
]
}
```
生成后把当前页面片段合并到模块根目录 `dicts.ts`。同 value/name/order 冲突时停止并列入待确认,不得猜测或覆盖。
---
## 联调注意
1. **响应外壳**:`{ code, message, data }`,成功 `code: 2000`(非 200,非 result)
2. 前端字段全部 camelCase,后端 JSON 序列化输出 camelCase
3. 时间字段统一 `YYYY-MM-DD HH:mm:ss`
4. 大数字 ID 后端转字符串(雪花 ID 超过 JS Number 精度)
5. 分页参数前端传 `current` / `size`(基类自动处理),后端响应 `data.records` / `data.total`
6. 枚举字段前端传 value,后端可返回 `[field]Label` 辅助展示,或前端自行通过 `logicValue` 字典翻译
7. 业务代码 `.then(res => res)` 拿到的就是 `data` 字段(拦截器已剥外壳)
8. 字典定义必须通过模块 `dicts.ts` 汇总和 D1 校验后才能进入 dict-sync
状态标记
- 🟡 待后端确认 — 刚生成
- 🟢 已确认 — 双方对齐,可编码
- 🔴 有变更 — 需双方同步