Files
zkxw/docs/superpowers/plans/2026-10-11-market-presign-mobile-parity.md
T

26 KiB
Raw Blame History

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 增加:

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:

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 改为拒绝未知字段,并限制字段长度:

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 已开放的订单状态字段:

order_status: Optional[Literal['draft', 'active', 'converted', 'void']] = Field(
    None, alias='orderStatus'
)

移动 service 的更新规则与 Web 对齐:仅 converted 禁止保存;void 允许再次编辑,但禁止转正式。

在 backend/app/services/market_presign.py 增加:

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。附件写入前调用:

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:

cd backend && source venv/bin/activate && pytest tests/test_market_presign_api.py -q

Expected: 全部 PASS。

  • Step 6: 提交后端服务层
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: 写动作白名单失败测试

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:

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() 中使用:

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') 增加:

'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,返回:

{
    '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:

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: 运行后端相关测试
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: 提交移动路由
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 断言:

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: 运行失败测试
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。导出语义化包装:

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 排除财务只读字段。

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。

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: 提交前端基础契约
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) 并断言:

assert.equal(marketListComponentKey('presign'), 'presign-list')
assert.equal(marketListComponentKey('payment'), 'generic-list')
assert.equal(marketFormComponentKey('presign'), 'presign-form')
  • Step 2: 运行失败测试
cd mobile-oa-h5 && node --test src/market/routes.test.js

Expected: FAIL,路由选择函数不存在。

  • Step 3: 创建 Web 字段完整卡片

PresignOrderCard.vue 必须显示:序号、业务日期、协议编号、母亲姓名、预产期、合同金额、到账金额、状态、创建时间;操作区使用:

<van-button v-if="can('edit')" size="small" @click.stop="$emit('edit', item)">编辑</van-button>
<van-button v-if="can('convert')" size="small" type="primary" @click.stop="$emit('convert', item)">转正式</van-button>
<van-button v-if="can('delete')" size="small" type="danger" plain @click.stop="$emit('delete', item)">删除</van-button>

所有按钮最小高度 36px,整行操作区域触控高度不小于 44px。

  • Step 4: 创建列表页

PresignOrderList.vue 使用:

<van-nav-bar title="预签约订单" left-arrow fixed placeholder @click-left="backToHost" />
<van-search v-model="keyword" placeholder="搜索母亲姓名 / 协议编号" @search="reload" @clear="reload" />
<van-list v-model:loading="loadingMore" :finished="finished" finished-text="已经到底了" @load="loadMore">
  <PresignOrderCard v-for="(item, index) in items" :key="item.id" :item="item" :index="index" />
</van-list>
<van-floating-bubble v-if="canCreate" axis="xy" icon="plus" magnetic="x" @click="openCreate" />

首次加载沿用 request-state.js;搜索重置页码,触底追加;删除和转正式用 showConfirmDialog,成功后重载第一页。

  • Step 5: 路由仅替换预签组件

在 routes.js 中按 module === 'presign' 选择专用 list/form,其余模块保持现状;保留 specialist/manager 两套路由和 meta,不合并 scope。

  • Step 6: 运行测试和构建
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: 提交列表页
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 触控约束;同时断言上传组件没有原生可见 <select>。

  • Step 2: 运行失败测试
cd mobile-oa-h5 && node --test src/market/presign/presign-components-contract.test.js

Expected: FAIL,组件文件不存在。

  • Step 3: 实现单选与多选移动组件

PresignSelectField.vue 使用只读 van-field 打开底部 van-popup。单选用 van-picker;开启 searchable 时顶部显示 van-search;多选使用 van-checkbox-group,点击“完成”一次性提交选中值,取消不修改外部值。

  • Step 4: 实现附件上传卡片

PresignAttachmentUploader.vue 使用 van-uploader :after-read="afterRead",上传触发卡固定为附件行第一项;图片 object-fit: cover,非图片显示 description 图标;支持预览、下载、删除,上传中阻止表单提交。

  • Step 5: 实现转账卡片

PresignTransferCard.vue 组合转账方式选择器、金额、时间、附件、备注、财务备注、状态 Tag;按钮完全以行级 allowedActions 决定,分别 emit remove/submit/withdraw/change/upload。

  • Step 6: 运行组件契约测试
cd mobile-oa-h5 && node --test src/market/presign/presign-components-contract.test.js

Expected: PASS。

  • Step 7: 提交表单组件
git add mobile-oa-h5/src/market/components/PresignSelectField.vue mobile-oa-h5/src/market/components/PresignAttachmentUploader.vue mobile-oa-h5/src/market/components/PresignTransferCard.vue mobile-oa-h5/src/market/presign/presign-components-contract.test.js
git commit -m "feat: add reusable Vant controls for mobile presign forms."

Task 6: 完整新增 / 编辑表单

Files:

  • Create: mobile-oa-h5/src/views/market/presign/PresignOrderForm.vue

  • Modify: mobile-oa-h5/src/market/routes.js

  • Modify: mobile-oa-h5/src/styles/market.css

  • Create: mobile-oa-h5/src/market/presign/presign-form-view.test.js

  • Step 1: 写表单字段与级联契约失败测试

断言 SFC 包含 Web 的 14 个主字段 key、loadRegionOptions、onRegionChange、loadHospitals、loadRegionManager、transfers 和固定底栏;断言没有普通 HTML <select>。

  • Step 2: 运行失败测试
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) 必须先:

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: 运行表单测试与构建
cd mobile-oa-h5 && node --test src/market/presign/*.test.js && npm run build

Expected: 测试全部 PASS,Vite build 成功。

  • Step 8: 提交表单页
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: 运行前端全部单元测试

cd mobile-oa-h5 && node --test src/**/*.test.js

Expected: 全部 PASS。

  • Step 2: 运行前端生产构建
cd mobile-oa-h5 && npm run build

Expected: Vite build 成功,无 unresolved import。

  • Step 3: 运行字段一致性检查
cd backend && source venv/bin/activate && python scripts/check_field_consistency.py

Expected: PASS;若脚本只覆盖 Web CRUD,应记录为“不适用移动专用 schema”,但不得忽略真实字段缺失。

  • Step 4: 运行后端相关测试
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: 运行后端全量测试
cd backend && source venv/bin/activate && pytest -q

Expected: 全部 PASS。

  • Step 6: 更新后端知识图谱
./scripts/update-graph.sh backend

Expected: backend/graphify-out/ 更新成功;本次不修改 web-admin,无需运行 frontend 图谱。

  • Step 7: 浏览器移动宽度验收

启动 mobile-oa-h5,以 390×844 视口检查:白色顶部栏、搜索、三张以上卡片、悬浮按钮、触底加载、地区医院级联、附件卡片、转账状态按钮和底部保存栏;保存截图供验收。

  • Step 8: 更新设计状态并提交收口

在设计文档顶部把状态改为“已实现”,追加真实测试数量、构建结果与截图位置,然后:

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 契约、组件契约、相关后端测试、全量后端、前端构建与浏览器验收全部包含。