docs: add implementation plan for mobile-h5 dibang backend adaptation
Break the approved design into tasks covering login, db HTTP client, select-only master data, and public vehicle plates for fill pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
e1170aa855
commit
26192676a2
1 file changed
+494
@@ -0,0 +1,494 @@
|
||||
# mobile-h5 地磅移动端适配一体化后端 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将 `mobile-h5` 对接一体化 `backend`(门户 RSA 登录 + `db_*` 业务 API + CRM/PL 主数据只读选择),去掉基础资料 CRUD 与手输自动建档。
|
||||
|
||||
**Architecture:** H5 保留 Vant 页面;登录/CRM 走门户 `{code,data}` 信封;地磅业务走直接 JSON(对齐 `web-admin/src/utils/http/db.ts`)。主数据只选 CRM 客户、CRM 产品、`/api/transport/vehicle`(self+carrier)。现场页继续用出入库 id;免登场景补车辆只读公共接口。
|
||||
|
||||
**Tech Stack:** Vue 3 / Vant 4 / Vite / Axios / Pinia;`jsencrypt`;FastAPI(仅缺口补丁)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-08-03-mobile-h5-dibang-adapt-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `mobile-h5/vite.config.ts` / `.env*` | 代理到 backend;清理 master-data 斜杠特例 |
|
||||
| `mobile-h5/src/utils/loginRsa.ts` | RSA 登录体(对齐 web-admin) |
|
||||
| `mobile-h5/src/utils/http/index.ts` | 门户信封解包 + Bearer;401 处理 |
|
||||
| `mobile-h5/src/utils/http/db.ts` | 地磅直出 JSON 客户端(尾斜杠、Bearer) |
|
||||
| `mobile-h5/src/api/authApi.ts` | login / refresh |
|
||||
| `mobile-h5/src/api/{plans,inventory,weighing,dashboard}Api.ts` | id→string、`productId`、走 db 客户端 |
|
||||
| `mobile-h5/src/api/crmOptionsApi.ts` | 客户/产品 list |
|
||||
| `mobile-h5/src/api/vehicleOptionsApi.ts` | 运输车辆 list + 公共车牌 options |
|
||||
| `mobile-h5/src/components/DbCustomerPicker.vue` 等 | 只选 Picker |
|
||||
| `mobile-h5/src/views/**` | 表单接选择器;删手输建档 |
|
||||
| `mobile-h5/src/router/index.ts` | 删 master-data / user 路由 |
|
||||
| `backend/app/api/db/public_inventory.py`(或新文件) | 免登车牌 options |
|
||||
| `backend/app/main.py` | 挂载公共路由(若新文件) |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 环境与代理
|
||||
|
||||
**Files:**
|
||||
- Modify: `mobile-h5/vite.config.ts`
|
||||
- Modify: `mobile-h5/.env.example`
|
||||
- Modify: `mobile-h5/.env`(本地,勿提交密钥)
|
||||
- Modify: `mobile-h5/package.json`(增加 `jsencrypt`)
|
||||
|
||||
- [ ] **Step 1: 安装 jsencrypt**
|
||||
|
||||
```bash
|
||||
cd mobile-h5 && pnpm add jsencrypt
|
||||
# 若无 pnpm:npm install jsencrypt
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 更新 `.env.example`**
|
||||
|
||||
```env
|
||||
# 开发代理目标(Vite 用)
|
||||
VITE_API_BASE_URL=http://127.0.0.1:8000
|
||||
# 生产 API 前缀
|
||||
VITE_API_URL=/api
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 简化 `vite.config.ts` 代理**
|
||||
|
||||
保留 `/api` → `VITE_API_BASE_URL`,**不要** rewrite 去掉 `/api`。
|
||||
列表尾斜杠逻辑保留 `plans|inventory|weighing`,**删除** `master-data` / `companies|cargos|vehicles` 特例。
|
||||
|
||||
```ts
|
||||
const listResources = ['plans', 'inventory', 'weighing']
|
||||
// 删除 pathParts.length === 3 && master-data 整块
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 验证代理**
|
||||
|
||||
```bash
|
||||
cd mobile-h5 && pnpm dev
|
||||
# 另开终端,backend 已启动时:
|
||||
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5174/api/db/health
|
||||
```
|
||||
|
||||
Expected: `200`(或经代理可达的健康检查;若无 `/db/health` 则 `curl` 登录公钥接口):
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:5174/api/web/auth/login-rsa-public-key | head -c 200
|
||||
```
|
||||
|
||||
Expected: JSON 含 `publicKeyPem` 或门户信封。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add mobile-h5/package.json mobile-h5/pnpm-lock.yaml mobile-h5/vite.config.ts mobile-h5/.env.example
|
||||
git commit -m "chore(mobile-h5): point Vite proxy at unified backend and add jsencrypt"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 登录 RSA + 双 HTTP 客户端
|
||||
|
||||
**Files:**
|
||||
- Create: `mobile-h5/src/utils/loginRsa.ts`
|
||||
- Create: `mobile-h5/src/utils/http/db.ts`
|
||||
- Create: `mobile-h5/src/api/authApi.ts`
|
||||
- Modify: `mobile-h5/src/utils/http/index.ts`
|
||||
- Modify: `mobile-h5/src/views/login/index.vue`
|
||||
- Modify: `mobile-h5/src/api/usersApi.ts`(删除或停用旧 `/auth/login`)
|
||||
- Modify: `mobile-h5/src/store/modules/user.ts`(如需)
|
||||
|
||||
- [ ] **Step 1: 新增 `loginRsa.ts`**
|
||||
|
||||
对齐 `web-admin/src/utils/loginRsa.ts`:拉取 `/web/auth/login-rsa-public-key`,JSEncrypt 加密 `{ username, password, ts }`,返回 `{ encryptedLogin }`。
|
||||
公钥请求用**未加密、未强制 Bearer** 的 axios 或现有 client(注意门户信封:若返回 `{code,data}` 则取 `data.publicKeyPem`)。
|
||||
|
||||
- [ ] **Step 2: 新增 `authApi.ts`**
|
||||
|
||||
```ts
|
||||
import api from '@/utils/http' // 门户信封客户端
|
||||
import { buildWebEncryptedLoginBody } from '@/utils/loginRsa'
|
||||
|
||||
export async function fetchLogin(username: string, password: string) {
|
||||
const body = await buildWebEncryptedLoginBody(username, password)
|
||||
// POST /web/auth/login → data: { token, refreshToken, expiresIn, userInfo }
|
||||
return api.post<{
|
||||
token: string
|
||||
refreshToken: string
|
||||
expiresIn: number
|
||||
userInfo: {
|
||||
id: string
|
||||
username: string
|
||||
realName?: string
|
||||
role?: string
|
||||
}
|
||||
}>({ url: '/web/auth/login', data: body })
|
||||
}
|
||||
```
|
||||
|
||||
保证 `api.post` 对门户响应解包到 `data`(`code===200||code===0`)。
|
||||
|
||||
- [ ] **Step 3: 改造 `utils/http/index.ts`(门户客户端)**
|
||||
|
||||
- `baseURL`: 开发 `/api`,生产 `VITE_API_URL || '/api'`
|
||||
- 成功:若有 `code` 字段则解包 `data`;否则原样返回
|
||||
- 401:logout → `#/login`(hash 路由用 `router` 或 `/#/login`,勿用无 hash 的 `/login`)
|
||||
- 错误 toast 优先 `message` / `detail`
|
||||
|
||||
- [ ] **Step 4: 新增 `utils/http/db.ts`(地磅客户端)**
|
||||
|
||||
参考 `web-admin/src/utils/http/db.ts`:
|
||||
|
||||
```ts
|
||||
const DB_COLLECTION_ROOT = /^\/(plans|inventory|weighing)$/
|
||||
function normalizeDbUrl(url?: string) {
|
||||
if (!url || url.endsWith('/')) return url
|
||||
return DB_COLLECTION_ROOT.test(url) ? `${url}/` : url
|
||||
}
|
||||
```
|
||||
|
||||
- 响应直接 `res.data`(无 code 信封)
|
||||
- 请求附加 `Bearer ${accessToken}`
|
||||
- export `get/post/patch/del` 供业务 API 使用
|
||||
|
||||
- [ ] **Step 5: 改登录页**
|
||||
|
||||
`login/index.vue` 调用 `fetchLogin`:
|
||||
|
||||
```ts
|
||||
const data = await fetchLogin(formData.username, formData.password)
|
||||
userStore.setLoginStatus(true)
|
||||
userStore.setToken(data.token, data.refreshToken)
|
||||
userStore.setUserInfo({
|
||||
id: data.userInfo.id,
|
||||
username: data.userInfo.username,
|
||||
name: data.userInfo.realName || data.userInfo.username,
|
||||
role: data.userInfo.role
|
||||
})
|
||||
router.push('/dashboard')
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 手工验证登录**
|
||||
|
||||
```bash
|
||||
# backend + mobile-h5 dev 已启动
|
||||
# 浏览器打开 http://127.0.0.1:5174/#/login
|
||||
# 用门户账号(user_no)登录,应进入仪表盘
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add mobile-h5/src/utils/loginRsa.ts mobile-h5/src/utils/http/ mobile-h5/src/api/authApi.ts \
|
||||
mobile-h5/src/views/login/index.vue mobile-h5/src/api/usersApi.ts mobile-h5/src/store/modules/user.ts
|
||||
git commit -m "feat(mobile-h5): adapt portal RSA login and split db HTTP client"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 业务 API 类型与路径对齐
|
||||
|
||||
**Files:**
|
||||
- Modify: `mobile-h5/src/api/plansApi.ts`
|
||||
- Modify: `mobile-h5/src/api/inventoryApi.ts`
|
||||
- Modify: `mobile-h5/src/api/weighingApi.ts`
|
||||
- Modify: `mobile-h5/src/api/dashboardApi.ts`
|
||||
|
||||
对照权威类型:`web-admin/src/api/db/{plans,inventory,weighing}.ts`。
|
||||
|
||||
- [ ] **Step 1: 将全部业务 id 改为 `string`**
|
||||
|
||||
示例(plans):
|
||||
|
||||
```ts
|
||||
export interface Plan {
|
||||
id: string
|
||||
planNo?: string
|
||||
// ...
|
||||
productId?: string
|
||||
product: string
|
||||
purchasePlanId?: never // 无此字段
|
||||
lastWeighingSlipId?: string
|
||||
fillLinkInventoryId?: string
|
||||
// ...
|
||||
}
|
||||
|
||||
export interface PlanCreate {
|
||||
// ...
|
||||
productId: string
|
||||
plannedQuantity: number
|
||||
// 不再只传 product 名称创建
|
||||
}
|
||||
```
|
||||
|
||||
`list/get/create/update/delete/getRelated/getThirdPartyPage` 的 `id` 参数一律 `string`。
|
||||
`inventory` / `weighing` 同理:`purchasePlanId`、`inboundOutboundId` → `string`;Create 含 `productId: string`。
|
||||
|
||||
- [ ] **Step 2: 全部改用 db 客户端**
|
||||
|
||||
```ts
|
||||
import db from '@/utils/http/db'
|
||||
// list:
|
||||
return db.get({ url: '/plans', params })
|
||||
// 或 url: '/plans/' — db 客户端会 normalize
|
||||
```
|
||||
|
||||
`dashboardApi` 同样走 db 客户端:`/dashboard/inbound/by-cargo` 等。
|
||||
|
||||
- [ ] **Step 3: 冒烟请求(需已登录 token)**
|
||||
|
||||
浏览器 Network 或临时脚本:登录后 `GET /api/plans/` 应 200 且元素 `id` 为字符串。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add mobile-h5/src/api/plansApi.ts mobile-h5/src/api/inventoryApi.ts \
|
||||
mobile-h5/src/api/weighingApi.ts mobile-h5/src/api/dashboardApi.ts
|
||||
git commit -m "feat(mobile-h5): align db API types to string ids and productId"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 主数据 Options API + Picker 组件
|
||||
|
||||
**Files:**
|
||||
- Create: `mobile-h5/src/api/crmOptionsApi.ts`
|
||||
- Create: `mobile-h5/src/api/vehicleOptionsApi.ts`
|
||||
- Create: `mobile-h5/src/components/pickers/DbCustomerPicker.vue`
|
||||
- Create: `mobile-h5/src/components/pickers/DbProductPicker.vue`
|
||||
- Create: `mobile-h5/src/components/pickers/DbVehiclePicker.vue`
|
||||
- Delete 或停用: `mobile-h5/src/api/masterDataApi.ts`
|
||||
|
||||
- [ ] **Step 1: `crmOptionsApi.ts`(门户信封 client)**
|
||||
|
||||
```ts
|
||||
import api from '@/utils/http'
|
||||
|
||||
export function fetchCustomerOptions(keyword?: string) {
|
||||
return api.post<{ list?: Array<{ id: string; customerName: string }> }>({
|
||||
url: '/web/crm/customer/list',
|
||||
data: { page: 1, pageSize: 20, customerName: keyword || undefined }
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchProductOptions(keyword?: string) {
|
||||
return api.post<{ list?: Array<{ id: string; productName: string; model?: string }> }>({
|
||||
url: '/web/crm/products/list',
|
||||
data: { page: 1, pageSize: 20, productName: keyword || undefined }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
按实际后端 list 入参字段名微调(与 `web-admin` `fetchCustomerList` / `fetchProductList` 一致)。解包后取 `list` 或 `rows`。
|
||||
|
||||
- [ ] **Step 2: `vehicleOptionsApi.ts`**
|
||||
|
||||
登录态:
|
||||
|
||||
```ts
|
||||
import api from '@/utils/http' // 若 PL 走门户信封;否则用能解 PL 响应的 client
|
||||
|
||||
export function fetchVehicleOptions(q?: string) {
|
||||
return api.get<{ list?: Array<{ id: string; licensePlate: string; vehicleType: string }> }>({
|
||||
url: '/transport/vehicle',
|
||||
params: { q, page: 1, pageSize: 100 }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
响应形状对齐 `VehicleListResponse`(常用 `list`/`rows`/`items` — 以实现时实际 JSON 为准,与 `pickBxList` 同类解包)。
|
||||
|
||||
免登(Task 6 后端就绪后):
|
||||
|
||||
```ts
|
||||
export function fetchPublicVehiclePlates(params: { q?: string; inventoryId?: string; planId?: string }) {
|
||||
return api.get<Array<{ licensePlate: string; vehicleType?: string }>>({
|
||||
url: '/public/db/vehicle-plates',
|
||||
params
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 三个 Picker 组件**
|
||||
|
||||
共用模式:`van-popup` + `van-search` + 列表点击选中;**无**「手动输入」按钮。
|
||||
|
||||
- `DbCustomerPicker`:`v-model` = 客户名称字符串;emit 可选 `select(item)`
|
||||
- `DbProductPicker`:`v-model:productId` + `v-model:productName` + `v-model:specification`(规格可用 `model`)
|
||||
- `DbVehiclePicker`:`v-model` = `licensePlate`;prop `publicMode?: boolean` 决定调登录态或公共接口
|
||||
|
||||
空结果文案:`暂无匹配,请到门户维护`
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add mobile-h5/src/api/crmOptionsApi.ts mobile-h5/src/api/vehicleOptionsApi.ts \
|
||||
mobile-h5/src/components/pickers/ mobile-h5/src/api/masterDataApi.ts
|
||||
git commit -m "feat(mobile-h5): add CRM/PL option APIs and select-only pickers"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 业务表单接入选择器 + 去掉自动建档
|
||||
|
||||
**Files:**
|
||||
- Modify: `mobile-h5/src/views/plans/detail.vue`
|
||||
- Modify: `mobile-h5/src/views/inventory/detail.vue`
|
||||
- Modify: `mobile-h5/src/views/weighing/detail.vue`
|
||||
- Modify: `mobile-h5/src/views/plans/index.vue`(生成出入库 query 用 string id)
|
||||
- Modify: `mobile-h5/src/views/inventory/index.vue`
|
||||
- Modify: `mobile-h5/src/views/weighing/index.vue`
|
||||
- Modify: `mobile-h5/src/views/dashboard/index.vue`(去掉基础资料入口)
|
||||
- Modify: `mobile-h5/src/router/index.ts`(删除 master-data、user 路由)
|
||||
- Delete: `mobile-h5/src/views/master-data/**`(可选,或留文件但无路由)
|
||||
|
||||
- [ ] **Step 1: 计划详情**
|
||||
|
||||
- 往来单位 → `DbCustomerPicker` → `formData.customerName`
|
||||
- 产品 → `DbProductPicker` → `productId` + `product` + `specification`
|
||||
- 提交 `planApi.create/update` 必须带 `productId`
|
||||
- 删除对 `companyApi`/`cargoApi` 的引用
|
||||
|
||||
- [ ] **Step 2: 出入库详情**
|
||||
|
||||
- 同上客户/产品选择器
|
||||
- 车牌 → `DbVehiclePicker`(登录态)
|
||||
- **删除** `ensureVehicleExists`、`showManualVehicleInput`、手动输入 Dialog
|
||||
- `purchasePlanId` 保持 string;去掉 `Number(route.query...)`
|
||||
|
||||
- [ ] **Step 3: 地磅单详情**
|
||||
|
||||
- 供货/收货单位用客户选择器(或沿用现有双单位字段 + 客户 Picker)
|
||||
- 货物 → 产品选择器(`productId` 必填)
|
||||
- `inboundOutboundId` / `purchasePlanId`:string;加载关联时 `inventoryApi.get(id)` 勿 `Number`
|
||||
|
||||
- [ ] **Step 4: 列表页**
|
||||
|
||||
- 所有 `handleDelete(id: number)` → `string`
|
||||
- 跳转 query 一律字符串 id
|
||||
- 仪表盘 `menuList` 去掉 `/master-data`
|
||||
- 路由删除 `master-data*`;用户管理路由若存在一并删除
|
||||
|
||||
- [ ] **Step 5: 手工冒烟**
|
||||
|
||||
登录 → 新建计划(选客户+产品)→ 生成出入库(选车牌)→ 生成地磅 → 作废。
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add mobile-h5/src/views/ mobile-h5/src/router/index.ts
|
||||
git commit -m "feat(mobile-h5): wire select-only master data into business forms"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 现场免登页 + 公共车牌接口
|
||||
|
||||
**Files:**
|
||||
- Modify: `mobile-h5/src/views/inventory/fill.vue`
|
||||
- Modify: `mobile-h5/src/views/inventory/plan.vue`
|
||||
- Create or Modify: `backend/app/api/db/public_inventory.py`(或 `public_vehicles.py`)
|
||||
- Modify: `backend/app/api/__init__.py` / `backend/app/main.py`(如需新 router)
|
||||
- Create: `backend/tests/test_public_db_vehicle_plates.py`(可选轻量)
|
||||
|
||||
- [ ] **Step 1: 后端公共车牌 options**
|
||||
|
||||
在 `public_inventory` 同模块或新 router `prefix="/public/db")`:
|
||||
|
||||
```python
|
||||
@router.get("/vehicle-plates")
|
||||
def list_public_vehicle_plates(
|
||||
q: Optional[str] = Query(None),
|
||||
inventory_id: Optional[str] = Query(None, alias="inventoryId"),
|
||||
plan_id: Optional[str] = Query(None, alias="planId"),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""免登:按出入库/计划解析 tenant_no,返回 pl_vehicle 车牌(self+carrier)。"""
|
||||
# 1) 由 inventory_id 或 plan_id 查 db_inbound_outbound / db_purchase_plan 得 tenant_no
|
||||
# 2) 若都无则 400
|
||||
# 3) query PlVehicle: tenant_no, del_flag=0, optional license_plate contains q
|
||||
# 4) return [{"licensePlate": ..., "vehicleType": ...}, ...] (limit 100)
|
||||
```
|
||||
|
||||
只读,禁止 POST。挂到 `API_V1_PREFIX`。
|
||||
将路径加入 `API_ENCRYPT_EXCLUDE_PATHS`(若加密开启)。
|
||||
|
||||
- [ ] **Step 2: 单测或手工 curl**
|
||||
|
||||
```bash
|
||||
curl -s "http://127.0.0.1:8000/api/public/db/vehicle-plates?inventoryId=<已有出入库id>"
|
||||
```
|
||||
|
||||
Expected: JSON 数组含 `licensePlate`。
|
||||
|
||||
- [ ] **Step 3: fill.vue / plan.vue**
|
||||
|
||||
- 删除 `ensureVehicleExists` 与手输 UI
|
||||
- 车牌用 `DbVehiclePicker` `publicMode` + 传入当前 `inventoryId` / `planId`
|
||||
- 保存仍走现有免登 patch/create
|
||||
|
||||
- [ ] **Step 4: 二维码**
|
||||
|
||||
确认 `inventory/index` 二维码内容仍为出入库 id,打开 `#/inventory/fill/{id}`。
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/api/db/ mobile-h5/src/views/inventory/fill.vue \
|
||||
mobile-h5/src/views/inventory/plan.vue backend/tests/test_public_db_vehicle_plates.py
|
||||
git commit -m "feat: public vehicle plate options for H5 fill pages; select-only plates"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 全链路验收与收尾
|
||||
|
||||
- [ ] **Step 1: 按 spec §10 验收**
|
||||
|
||||
1. 门户账号 RSA 登录 H5
|
||||
2. 仪表盘无「基础资料」;图表有数据或空态正常
|
||||
3. 计划 → 出入库 → 地磅 → 作废
|
||||
4. 客户/产品/车辆无法手输新增
|
||||
5. 无登录打开 `#/inventory/fill/{id}`,车牌只能选
|
||||
6. Network 中业务 id 均为字符串
|
||||
|
||||
- [ ] **Step 2: 类型检查**
|
||||
|
||||
```bash
|
||||
cd mobile-h5 && pnpm exec vue-tsc --noEmit
|
||||
```
|
||||
|
||||
Expected: 无因 `number` id 引起的错误(可允许原有无关告警,但新建代码应干净)。
|
||||
|
||||
- [ ] **Step 3: 最终 commit(若有零散修复)**
|
||||
|
||||
```bash
|
||||
git add mobile-h5 backend
|
||||
git commit -m "fix(mobile-h5): polish dibang H5 adaptation after acceptance"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spec coverage checklist
|
||||
|
||||
| Spec 要求 | Task |
|
||||
|-----------|------|
|
||||
| 门户 RSA + JWT 登录 | Task 2 |
|
||||
| db API 直出 JSON + 尾斜杠 | Task 2–3 |
|
||||
| id→string、productId | Task 3、5 |
|
||||
| 删基础资料;客户/产品/车辆只选 | Task 4–5 |
|
||||
| 禁止手输自动建档(含现场) | Task 5–6 |
|
||||
| 现场继续用出入库 id | Task 6 |
|
||||
| H5 无权限 UI 门禁 | 全任务不添加角色判断 |
|
||||
| 后端仅缺口补丁(公共车牌) | Task 6 |
|
||||
| 验收清单 | Task 7 |
|
||||
|
||||
## Placeholder / consistency notes
|
||||
|
||||
- CRM list 请求体字段名以实现时 web-admin 实际调用为准(`customerName` / `productName` 等)。
|
||||
- PL 车辆列表响应字段统一用 `licensePlate`(camelCase alias)。
|
||||
- Hash 路由登出跳转统一 `#/login`。
|
||||
Reference in new issue
Block a user