diff --git a/docs/superpowers/plans/2026-10-11-market-presign-mobile-parity.md b/docs/superpowers/plans/2026-10-11-market-presign-mobile-parity.md new file mode 100644 index 00000000..17234ffe --- /dev/null +++ b/docs/superpowers/plans/2026-10-11-market-presign-mobile-parity.md @@ -0,0 +1,637 @@ +# Market Presign Mobile Parity 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:** 将市场专员、地区经理的预签约订单完整对齐 Web 端字段和业务操作,并以 Vant 4、工作流移动端风格实现白色顶部栏、卡片列表、触底分页及移动表单。 + +**Architecture:** 预签约订单使用专用列表和专用表单组件,复用现有市场路由上下文、鉴权、HTTP、错误处理与现场 scope;后端在既有 `/mini/market/{specialist|manager}` 固定路由下补齐删除订单和意向金转账操作。复杂表单状态拆到纯 JavaScript 模块中先做单元测试,Vue 组件只负责 Vant 展示与事件编排。 + +**Tech Stack:** Python 3.9、FastAPI、SQLAlchemy、Pydantic v2、pytest、Vue 3、Vant 4、Vite、Node `node:test` + +--- + +## 文件结构 + +### 后端 + +- Modify: `backend/app/schemas/market_mobile.py` — 收紧预签订单/转账 DTO,并提供移动转账请求体。 +- Modify: `backend/app/services/market_presign.py` — scope 内订单删除、转账增改删、提交、撤回。 +- Modify: `backend/app/services/market_support.py` — 预签/转账动作白名单与表单选项。 +- Modify: `backend/app/api/market_mobile.py` — 暴露移动端删除与转账路由。 +- Modify: `backend/tests/test_market_presign_api.py` — 服务层权限、状态机、附件与软删除测试。 +- Modify: `backend/tests/test_market_support_api.py` — 动作白名单和选项返回测试。 + +### `mobile-oa-h5` + +- Modify: `mobile-oa-h5/src/api/market.js` — 预签删除、转账增改删、提交、撤回 API。 +- Modify: `mobile-oa-h5/src/market/action-policy.js` — `delete` 与预签转账动作白名单。 +- Modify: `mobile-oa-h5/src/market/api-contract.test.js` — URL 与 HTTP 方法契约。 +- Modify: `mobile-oa-h5/src/market/module-actions.test.js` — 前后端动作交集测试。 +- Create: `mobile-oa-h5/src/market/presign/presign-presenter.js` — 列表字段、状态、金额、日期格式化。 +- Create: `mobile-oa-h5/src/market/presign/presign-presenter.test.js` — 展示逻辑测试。 +- Create: `mobile-oa-h5/src/market/presign/presign-form-state.js` — 表单默认值、级联清理、转账锁定和 payload。 +- Create: `mobile-oa-h5/src/market/presign/presign-form-state.test.js` — 表单纯逻辑测试。 +- Create: `mobile-oa-h5/src/market/components/PresignOrderCard.vue` — Web 字段完整卡片及操作按钮。 +- Create: `mobile-oa-h5/src/market/components/PresignSelectField.vue` — Vant Field + Popup/Picker 单选。 +- Create: `mobile-oa-h5/src/market/components/PresignAttachmentUploader.vue` — 上传触发卡与附件卡片。 +- Create: `mobile-oa-h5/src/market/components/PresignTransferCard.vue` — 意向金转账卡片及状态操作。 +- Create: `mobile-oa-h5/src/views/market/presign/PresignOrderList.vue` — 白色顶部栏、搜索、触底分页和悬浮按钮。 +- Create: `mobile-oa-h5/src/views/market/presign/PresignOrderForm.vue` — Web 字段完整新增/编辑页。 +- Modify: `mobile-oa-h5/src/market/routes.js` — 仅预签约订单切到专用列表/表单。 +- Modify: `mobile-oa-h5/src/styles/market.css` — 预签专用移动样式,品牌主色 `#0a9f6d`。 + +## Task 1: 后端预签操作状态机与 scope 校验 + +**Files:** +- Modify: `backend/app/schemas/market_mobile.py` +- Modify: `backend/app/services/market_presign.py` +- Modify: `backend/tests/test_market_presign_api.py` + +- [ ] **Step 1: 写订单删除和转账越权的失败测试** + +在 `backend/tests/test_market_presign_api.py` 增加: + +```python +def test_specialist_can_only_delete_own_non_converted_presign(): + from app.services.market_presign import delete_presign_order + + db = _session() + db.add_all([ + _order('own', sales_user_id='u1'), + _order('other', sales_user_id='u2'), + ]) + db.commit() + + delete_presign_order(db, order_id='own', ctx=_ctx('specialist'), user_info=_user()) + assert db.query(BizPresignOrder).filter_by(id='own').one().del_flag == 1 + with pytest.raises(HTTPException): + delete_presign_order(db, order_id='other', ctx=_ctx('specialist'), user_info=_user()) + + +def test_presign_transfer_rejects_other_scope_order(): + from app.schemas.market_mobile import MarketPresignTransferBody + from app.services.market_presign import create_presign_transfer + + db = _session() + db.add(_order('other', sales_user_id='u2')) + db.commit() + + with pytest.raises(HTTPException): + create_presign_transfer( + db, + order_id='other', + body=MarketPresignTransferBody(transferAmount=100, transferType='wxzz'), + ctx=_ctx('specialist'), + user_info=_user(), + ) +``` + +- [ ] **Step 2: 运行失败测试** + +Run: + +```bash +cd backend && source venv/bin/activate && pytest tests/test_market_presign_api.py -k 'delete_own or transfer_rejects' -q +``` + +Expected: FAIL,提示 `delete_presign_order` / `create_presign_transfer` 尚不存在。 + +- [ ] **Step 3: 收紧 DTO 并实现订单删除和 scoped transfer 查询** + +在 `backend/app/schemas/market_mobile.py` 将预签 DTO 改为拒绝未知字段,并限制字段长度: + +```python +class MarketPresignTransferBody(BaseModel): + model_config = ConfigDict(extra='forbid', populate_by_name=True) + + transfer_amount: Optional[MarketPaymentAmount] = Field(None, alias='transferAmount') + transfer_time: Optional[str] = Field(None, alias='transferTime') + transfer_type: Optional[str] = Field(None, max_length=16, alias='transferType') + transfer_attachments: Optional[str] = Field(None, max_length=4000, alias='transferAttachments') + remark: Optional[str] = Field(None, max_length=512) +``` + +`MarketPresignOrderBody` 同时加入 Web 已开放的订单状态字段: + +```python +order_status: Optional[Literal['draft', 'active', 'converted', 'void']] = Field( + None, alias='orderStatus' +) +``` + +移动 service 的更新规则与 Web 对齐:仅 `converted` 禁止保存;`void` 允许再次编辑,但禁止转正式。 + +在 `backend/app/services/market_presign.py` 增加: + +```python +def delete_presign_order(db, *, order_id, ctx, user_info): + row = assert_presign_access(db, order_id, ctx, user_info=user_info) + if (row.order_status or '').strip() == 'converted': + raise ValueError('已转正式的预签约订单不可删除') + presign_crud.soft_delete_presign_order( + db, order_id=row.id, tenant_no=ctx.tenant_no, user_id=ctx.user_id + ) + + +def _scoped_transfer(db, *, transfer_id, ctx, user_info): + transfer = ( + db.query(BizPresignTransfer) + .filter( + BizPresignTransfer.id == transfer_id, + BizPresignTransfer.tenant_no == ctx.tenant_no, + BizPresignTransfer.del_flag == 0, + ) + .first() + ) + if transfer is None: + raise ValueError('意向金转账不存在') + order = assert_presign_access(db, transfer.presign_order_id, ctx, user_info=user_info) + return transfer, order +``` + +- [ ] **Step 4: 实现转账增改删、提交和撤回** + +继续在 `market_presign.py` 增加 `create_presign_transfer`、`update_presign_transfer`、`delete_presign_transfer`、`submit_presign_transfer`、`withdraw_presign_transfer`;每个函数先调用 `assert_presign_access` 或 `_scoped_transfer`,只允许订单状态 `''/draft/active`。附件写入前调用: + +```python +fields['transfer_attachments'] = validate_attachment_refs( + db, + fields.get('transfer_attachments'), + module='presign', + ctx=ctx, + creating=creating, + business_id=order.id, +) +``` + +删除转账必须满足 `pay_status == 'dfj'`,以 `del_flag=1`、`update_by`、`update_time` 软删除;提交调用 `presign_crud.submit_receipt_finance`,撤回调用 `presign_crud.withdraw_receipt`。 + +- [ ] **Step 5: 跑预签服务测试** + +Run: + +```bash +cd backend && source venv/bin/activate && pytest tests/test_market_presign_api.py -q +``` + +Expected: 全部 PASS。 + +- [ ] **Step 6: 提交后端服务层** + +```bash +git add backend/app/schemas/market_mobile.py backend/app/services/market_presign.py backend/tests/test_market_presign_api.py +git commit -m "feat: enforce scoped presign deletion and transfer workflows on mobile." +``` + +## Task 2: 后端移动路由、动作白名单与选项 + +**Files:** +- Modify: `backend/app/api/market_mobile.py` +- Modify: `backend/app/services/market_support.py` +- Modify: `backend/tests/test_market_support_api.py` + +- [ ] **Step 1: 写动作白名单失败测试** + +```python +def test_presign_actions_include_delete_and_nested_transfer_actions(): + active = decorate_allowed_actions( + 'presign', + { + 'orderStatus': 'active', + 'transfers': [ + {'id': 't1', 'payStatus': 'dfj'}, + {'id': 't2', 'payStatus': 'jfqrz'}, + {'id': 't3', 'payStatus': 'yjf'}, + ], + }, + scope_kind='specialist', + detail=True, + ) + assert active['allowedActions'] == ['edit', 'convert', 'delete'] + assert [row['allowedActions'] for row in active['transfers']] == [ + ['edit', 'delete', 'submit'], ['withdraw'], [] + ] +``` + +同一测试还需断言:`void -> ['edit', 'delete']`,`converted -> ['edit']`;已转正式的“编辑”只用于打开只读表单,不允许提交保存。 + +- [ ] **Step 2: 运行并确认失败** + +Run: + +```bash +cd backend && source venv/bin/activate && pytest tests/test_market_support_api.py -k presign_actions -q +``` + +Expected: FAIL,当前 `presign` 没有 `delete`,转账行没有 `allowedActions`。 + +- [ ] **Step 3: 补齐动作装饰与表单选项** + +在 `allowed_actions()` 中使用: + +```python +if module == 'presign': + if state == 'converted': + return ['edit'] + if state == 'void': + return ['edit', 'delete'] + return ['edit', 'convert', 'delete'] +if module == 'presign-transfer': + if state == 'dfj': + return ['edit', 'delete', 'submit'] + if state == 'jfqrz': + return ['withdraw'] + return [] +``` + +`decorate_allowed_actions('presign', ..., detail=True)` 必须逐笔装饰 `transfers`。`get_form_options(module='presign')` 增加: + +```python +'payModes': [ + {'label': '趸交', 'value': 'dj'}, + {'label': '分期', 'value': 'fq'}, +], +'orderStatuses': [ + {'label': '草稿', 'value': 'draft'}, + {'label': '进行中', 'value': 'active'}, + {'label': '已转正式', 'value': 'converted'}, + {'label': '已作废', 'value': 'void'}, +], +'transferTypes': _dict_options(db, ctx.tenant_no, 'zzfs'), +``` + +地区人员选项不能继续只返回 `SysUser`;查询需同时带出 `BizCollectionRegionStaff.is_leader`,返回: + +```python +{ + 'label': user.nick_name, + 'value': user.id, + 'isLeader': bool(staff.is_leader), +} +``` + +所选地区存在多个绑定时,负责人排在普通人员之前。上传校验需把预签模块单文件上限对齐 Web 的 20MiB:`save_market_attachment()` 调用 `_validate_upload(..., max_bytes=20 * 1024 * 1024)`;其他市场模块继续保持现有 10MiB,避免扩大无关范围。 + +- [ ] **Step 4: 注册移动路由** + +在 `_register_presign_routes()` 中增加以下固定路径,均复用当前 `permission_guard`: + +```python +DELETE {prefix}/{order_id} +POST {prefix}/{order_id}/transfers +PUT /mini/market/{scope_kind}/presign-transfers/{transfer_id} +DELETE /mini/market/{scope_kind}/presign-transfers/{transfer_id} +POST /mini/market/{scope_kind}/presign-transfers/{transfer_id}/submit +POST /mini/market/{scope_kind}/presign-transfers/{transfer_id}/withdraw +``` + +每个路由只调用 Task 1 的 service 函数,并把 `PermissionError` 转为 403、`ValueError` 转为 400。修正该函数中重复的 `data = market_presign.create_presign_order(` 行。 + +- [ ] **Step 5: 运行后端相关测试** + +```bash +cd backend && source venv/bin/activate && pytest tests/test_market_presign_api.py tests/test_market_support_api.py tests/test_market_mobile_permissions.py -q +``` + +Expected: 全部 PASS。 + +- [ ] **Step 6: 提交移动路由** + +```bash +git add backend/app/api/market_mobile.py backend/app/services/market_support.py backend/tests/test_market_support_api.py +git commit -m "feat: expose complete scoped presign actions to mobile clients." +``` + +## Task 3: 前端 API 与纯业务状态 + +**Files:** +- Modify: `mobile-oa-h5/src/api/market.js` +- Modify: `mobile-oa-h5/src/market/action-policy.js` +- Modify: `mobile-oa-h5/src/market/api-contract.test.js` +- Modify: `mobile-oa-h5/src/market/module-actions.test.js` +- Create: `mobile-oa-h5/src/market/presign/presign-presenter.js` +- Create: `mobile-oa-h5/src/market/presign/presign-presenter.test.js` +- Create: `mobile-oa-h5/src/market/presign/presign-form-state.js` +- Create: `mobile-oa-h5/src/market/presign/presign-form-state.test.js` + +- [ ] **Step 1: 写 API 和动作契约失败测试** + +在 `api-contract.test.js` 断言: + +```javascript +const presign = { marketRole: 'specialist', module: 'presign' } +assert.equal(buildMarketActionPath(presign, 'delete', 'o 1'), '/mini/market/specialist/presign-orders/o%201') +assert.equal(buildMarketActionPath(presign, 'create-transfer', 'o1'), '/mini/market/specialist/presign-orders/o1/transfers') +assert.equal(buildMarketActionPath(presign, 'submit-transfer', 't1'), '/mini/market/specialist/presign-transfers/t1/submit') +assert.equal(marketActionMethod('delete'), 'DELETE') +assert.equal(marketActionMethod('edit-presign-transfer'), 'PUT') +``` + +在 `module-actions.test.js` 断言预签白名单包含 `delete/create-transfer/edit-presign-transfer/delete-transfer/submit-transfer/withdraw-transfer`,并拒绝后端未授权动作。 + +- [ ] **Step 2: 运行失败测试** + +```bash +cd mobile-oa-h5 && node --test src/market/api-contract.test.js src/market/module-actions.test.js +``` + +Expected: FAIL,动作和 URL 映射尚未实现。 + +- [ ] **Step 3: 实现 API 映射和请求函数** + +`buildMarketActionPath()` 按 Task 2 的固定端点映射;`marketActionMethod()` 返回 DELETE/PUT/POST。导出语义化包装: + +```javascript +export const deletePresignOrder = (context, id, options) => + performMarketAction(context, 'delete', { id }, options) +export const createPresignTransfer = (context, orderId, data, options) => + performMarketAction(context, 'create-transfer', { id: orderId, data }, options) +export const updatePresignTransfer = (context, transferId, data, options) => + performMarketAction(context, 'edit-presign-transfer', { id: transferId, data }, options) +``` + +- [ ] **Step 4: 写展示与表单纯逻辑测试** + +覆盖:8 位日期转 `YYYY-MM-DD`、金额、Web 状态文案、跨页序号、地区变化清空医院、默认表单、转账状态锁定、payload 排除财务只读字段。 + +```javascript +assert.equal(formatPresignDate('20261011'), '2026-10-11') +assert.equal(formatPresignMoney(28800), '¥28,800.00') +assert.equal(presignCardIndex(2, 10, 3), '13') +assert.deepEqual(changePresignRegion({ regionId: 'r1', hospitalId: 'h1' }, 'r2'), { + regionId: 'r2', hospitalId: '' +}) +assert.equal(isPresignTransferLocked({ payStatus: 'jfqrz' }), true) +``` + +- [ ] **Step 5: 实现纯函数并运行测试** + +`presign-presenter.js` 只负责格式化;`presign-form-state.js` 导出 `createPresignForm`、`changePresignRegion`、`isPresignLocked`、`isPresignTransferLocked`、`buildPresignOrderPayload`、`buildPresignTransferPayload`。 + +```bash +cd mobile-oa-h5 && node --test src/market/presign/*.test.js src/market/api-contract.test.js src/market/module-actions.test.js +``` + +Expected: 全部 PASS。 + +- [ ] **Step 6: 提交前端基础契约** + +```bash +git add mobile-oa-h5/src/api/market.js mobile-oa-h5/src/market/action-policy.js mobile-oa-h5/src/market/*.test.js mobile-oa-h5/src/market/presign +git commit -m "feat: add tested mobile presign contracts and form state." +``` + +## Task 4: 专用卡片列表与滚动分页 + +**Files:** +- Create: `mobile-oa-h5/src/market/components/PresignOrderCard.vue` +- Create: `mobile-oa-h5/src/views/market/presign/PresignOrderList.vue` +- Modify: `mobile-oa-h5/src/market/routes.js` +- Modify: `mobile-oa-h5/src/styles/market.css` +- Modify: `mobile-oa-h5/src/market/routes.test.js` + +- [ ] **Step 1: 写预签专用路由失败测试** + +导出 `marketListComponentKey(module)` / `marketFormComponentKey(module)` 并断言: + +```javascript +assert.equal(marketListComponentKey('presign'), 'presign-list') +assert.equal(marketListComponentKey('payment'), 'generic-list') +assert.equal(marketFormComponentKey('presign'), 'presign-form') +``` + +- [ ] **Step 2: 运行失败测试** + +```bash +cd mobile-oa-h5 && node --test src/market/routes.test.js +``` + +Expected: FAIL,路由选择函数不存在。 + +- [ ] **Step 3: 创建 Web 字段完整卡片** + +`PresignOrderCard.vue` 必须显示:序号、业务日期、协议编号、母亲姓名、预产期、合同金额、到账金额、状态、创建时间;操作区使用: + +```vue +编辑 +转正式 +删除 +``` + +所有按钮最小高度 36px,整行操作区域触控高度不小于 44px。 + +- [ ] **Step 4: 创建列表页** + +`PresignOrderList.vue` 使用: + +```vue + + + + + + +``` + +首次加载沿用 `request-state.js`;搜索重置页码,触底追加;删除和转正式用 `showConfirmDialog`,成功后重载第一页。 + +- [ ] **Step 5: 路由仅替换预签组件** + +在 `routes.js` 中按 `module === 'presign'` 选择专用 list/form,其余模块保持现状;保留 specialist/manager 两套路由和 meta,不合并 scope。 + +- [ ] **Step 6: 运行测试和构建** + +```bash +cd mobile-oa-h5 && node --test src/market/routes.test.js src/market/request-state.test.js && npm run build +``` + +Expected: 测试全部 PASS,Vite build 成功。 + +- [ ] **Step 7: 提交列表页** + +```bash +git add mobile-oa-h5/src/market/components/PresignOrderCard.vue mobile-oa-h5/src/views/market/presign/PresignOrderList.vue mobile-oa-h5/src/market/routes.js mobile-oa-h5/src/market/routes.test.js mobile-oa-h5/src/styles/market.css +git commit -m "feat: replace mobile presign list with scroll-loaded parity cards." +``` + +## Task 5: Vant 选择器、附件与转账卡片 + +**Files:** +- Create: `mobile-oa-h5/src/market/components/PresignSelectField.vue` +- Create: `mobile-oa-h5/src/market/components/PresignAttachmentUploader.vue` +- Create: `mobile-oa-h5/src/market/components/PresignTransferCard.vue` +- Create: `mobile-oa-h5/src/market/presign/presign-components-contract.test.js` + +- [ ] **Step 1: 写组件契约失败测试** + +读取三个 SFC 源文件并断言它们分别包含 `van-popup/van-picker`、`van-uploader`、`allowedActions` 与 44px 触控约束;同时断言上传组件没有原生可见 ``。 + +- [ ] **Step 2: 运行失败测试** + +```bash +cd mobile-oa-h5 && node --test src/market/presign/presign-form-view.test.js +``` + +Expected: FAIL,专用表单尚不存在。 + +- [ ] **Step 3: 实现首次加载和编辑回填** + +并行加载基础 form-options 与详情;编辑时按“回填地区 → 用 `regionId` 再取医院/地区人员 → 回填医院”的顺序。新增默认业务日期为当天、采集专员为当前用户、缴费方式 `dj`、订单状态 `active`。 + +- [ ] **Step 4: 实现地区医院联动** + +`onRegionChange(regionId)` 必须先: + +```javascript +form.value = changePresignRegion(form.value, regionId) +await loadRegionOptions(regionId) +const leader = formOptions.value.staff?.find(item => item.isLeader) || formOptions.value.staff?.[0] +form.value.regionManagerUserId = leader?.value || '' +``` + +请求失败时医院保持空值并 Toast,不恢复旧地区的医院。 + +- [ ] **Step 5: 实现保存与转账操作** + +保存前校验母亲姓名和至少一个样本类型;新订单把未保存转账放进 `transfers` 一次提交;编辑订单先保存主单,再顺序创建或更新 `dfj` 转账。提交、撤回和删除转账均弹确认框,成功后重拉详情。 + +- [ ] **Step 6: 实现离开保护与上传并发保护** + +复用 `canLeaveMarketForm`;`dirty || uploadPending > 0` 时离开需确认;上传未结束时禁用确定按钮;组件卸载时终止所有 AbortController。 + +- [ ] **Step 7: 运行表单测试与构建** + +```bash +cd mobile-oa-h5 && node --test src/market/presign/*.test.js && npm run build +``` + +Expected: 测试全部 PASS,Vite build 成功。 + +- [ ] **Step 8: 提交表单页** + +```bash +git add mobile-oa-h5/src/views/market/presign/PresignOrderForm.vue mobile-oa-h5/src/market/routes.js mobile-oa-h5/src/styles/market.css mobile-oa-h5/src/market/presign/presign-form-view.test.js +git commit -m "feat: deliver Web-parity presign editing with mobile interactions." +``` + +## Task 7: 综合验证、知识图谱与文档收口 + +**Files:** +- Modify: `backend/graphify-out/*` — 由脚本生成。 +- Modify: `docs/superpowers/specs/2026-10-11-market-presign-mobile-parity-design.md` — 状态改为已实现并记录验证结果。 + +- [ ] **Step 1: 运行前端全部单元测试** + +```bash +cd mobile-oa-h5 && node --test src/**/*.test.js +``` + +Expected: 全部 PASS。 + +- [ ] **Step 2: 运行前端生产构建** + +```bash +cd mobile-oa-h5 && npm run build +``` + +Expected: Vite build 成功,无 unresolved import。 + +- [ ] **Step 3: 运行字段一致性检查** + +```bash +cd backend && source venv/bin/activate && python scripts/check_field_consistency.py +``` + +Expected: PASS;若脚本只覆盖 Web CRUD,应记录为“不适用移动专用 schema”,但不得忽略真实字段缺失。 + +- [ ] **Step 4: 运行后端相关测试** + +```bash +cd backend && source venv/bin/activate && pytest tests/test_market_presign_api.py tests/test_market_support_api.py tests/test_market_mobile_permissions.py -q +``` + +Expected: 全部 PASS。 + +- [ ] **Step 5: 运行后端全量测试** + +```bash +cd backend && source venv/bin/activate && pytest -q +``` + +Expected: 全部 PASS。 + +- [ ] **Step 6: 更新后端知识图谱** + +```bash +./scripts/update-graph.sh backend +``` + +Expected: `backend/graphify-out/` 更新成功;本次不修改 `web-admin`,无需运行 frontend 图谱。 + +- [ ] **Step 7: 浏览器移动宽度验收** + +启动 `mobile-oa-h5`,以 390×844 视口检查:白色顶部栏、搜索、三张以上卡片、悬浮按钮、触底加载、地区医院级联、附件卡片、转账状态按钮和底部保存栏;保存截图供验收。 + +- [ ] **Step 8: 更新设计状态并提交收口** + +在设计文档顶部把状态改为“已实现”,追加真实测试数量、构建结果与截图位置,然后: + +```bash +git add backend/graphify-out docs/superpowers/specs/2026-10-11-market-presign-mobile-parity-design.md +git commit -m "docs: record verified mobile presign parity implementation." +``` + +## 自检结果 + +- 规格覆盖:白色顶部栏、仅关键词、悬浮新增、Web 列表字段与操作、动态分页、完整表单、地区医院级联、数据库选项、附件卡、转账状态机、双角色 scope 均有对应任务。 +- 非目标控制:未安排其他市场模块改造,未修改 Web 管理端,未把业务表单下沉到 `mobile-h5`。 +- 类型一致:前端统一使用 `delete/create-transfer/edit-presign-transfer/delete-transfer/submit-transfer/withdraw-transfer`;后端对应固定端点与 `allowedActions`。 +- 验证闭环:纯函数、API 契约、组件契约、相关后端测试、全量后端、前端构建与浏览器验收全部包含。 + diff --git a/docs/superpowers/specs/2026-10-11-market-presign-mobile-parity-design.md b/docs/superpowers/specs/2026-10-11-market-presign-mobile-parity-design.md index 3741fa20..1e66f3a4 100644 --- a/docs/superpowers/specs/2026-10-11-market-presign-mobile-parity-design.md +++ b/docs/superpowers/specs/2026-10-11-market-presign-mobile-parity-design.md @@ -101,7 +101,7 @@ 卡片底部操作与 Web 一致: -- `编辑`:草稿、进行中等后端允许状态显示。 +- `编辑`:与 Web 一致始终显示;已转正式订单进入只读表单,不允许保存。 - `转正式`:非已转正式、非已作废且后端返回 `convert` 时显示。 - `删除`:非已转正式且后端返回 `delete` 时显示。