Files
shanxia2.0/docs/superpowers/specs/2026-08-09-bx-mobile-reimburse-invoice-attach-picker-design.md
T
zlwuandCursor 2f87223236 docs: add bx-mobile reimburse invoice attachment picker design
Align mobile invoice selection with web-admin at attachment granularity;
include interactive HTML demo for the 3-column picker UX.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 19:38:40 +08:00

5.0 KiB
Raw Blame History

bx-mobile 报销申请:发票选择对齐 Web(附件粒度)

日期:2026-08-09
状态:已确认待实现
范围:bx-mobile 报销申请页;不动后端契约、不动 web-admin
视觉 Demo:mobile-ui/shanxia-mp-bx-reimburse-invoice-picker-demo.html

1. 背景与问题

报销申请「从发票夹选择」在两端粒度不一致:

端 选择粒度 提交
web-admin 附件(invoiceId + attachmentId) invoiceLinks 带附件
bx-mobile(现状) 发票档案(仅 invoiceId) invoiceLinks 仅发票

领域模型:一次上传创建 1 条发票(金额入账)+ N 个附件文件。Web 已按附件勾选;移动端需对齐。

后端 ReimburseService.normalize_invoice_links / sync_reimburse_invoices 已支持可选 attachment_id;详情 get_linked_invoices_brief 在缺 attachment_id 时回退该发票第一个附件。

2. 目标

  1. 移动端选择器展示附件(缩略图网格),多附件可分别勾选。
  2. 保存/提交携带 attachmentId。
  3. 编辑回显与 Web 一致:有关联附件 ID 则用该 ID;否则用详情接口已回退的首附件。
  4. 余额扣减规则不变(按报销合计 vs 发票钱包余额)。

3. 非目标

  • 不改后端 API / 表结构。
  • 不改 web-admin。
  • 不抽公共组件(当前仅申请页需要)。
  • 不改审批页、列表页;详情页若已有关联展示则保持,本变更不强制改造。

4. 方案选择

采用:页面内改造 ReimburseApplyPage.vue(方案 1)

  • 对齐 Demo A:底部 sheet + 按月分组 + 3 列附件缩略图。
  • 状态模型从 selectedInvoiceIds: string[] 改为 selectedInvoicePicks(附件级)。
  • 预览加载复用发票夹页已有模式(attachmentApi.preview)。

不采用:抽公共组件(过重);仅改 payload 保留列表行(与 UI 决策 A 冲突)。

5. 数据流

refreshWallet:
  invoiceApi.list → walletInvoices
  flatten attachments → attachItems[]
    key = `${invoiceId}:${attachmentId}`
    amount = 发票金额(整票,非附件拆分)

选择器:
  draftKeys ⊆ attachItems.keys → confirm → selectedPicks

提交:
  invoiceLinks = selectedPicks.map({ invoiceId, attachmentId })
  invoiceIds = unique(invoiceLinks.invoiceId)

编辑加载:
  data.invoices[] → 每条取 attachmentId(或 attachment_id)
  若无附件信息:用发票列表中该发票 attachments[0]
  恢复 selectedPicks(key / 文件名 / 预览)

换申请人:清空 selectedPicks,并刷新钱包与发票列表(与现逻辑一致)。

6. UI 规格(对齐 Demo)

参考:mobile-ui/shanxia-mp-bx-reimburse-invoice-picker-demo.html。

6.1 表单区「关联发票」

  • 保留余额提示文案。
  • 按钮:从发票夹选择。
  • 已选:横向滚动附件卡片(缩略图 / PDF 占位、文件名、整票金额、移除)。
  • 提示补充:选择对象为附件文件(可沿用 Demo 文案或等价简短说明)。

6.2 选择器 Sheet

  • 标题:选择发票附件。
  • 按 createTime 月份分组,月内新→旧。
  • 3 列网格卡片:序号、选中勾、预览、文件名、整票金额。
  • 无附件的发票不出现。
  • 底部:确定(已选 N)。

6.3 Token

使用 bx-mobile Fresh Mint(--primary: #00c98d 等),不混用小程序三墙色。

7. 实现要点(文件)

文件 变更
bx-mobile/src/views/reimburse/ReimburseApplyPage.vue 选择状态、flatten、picker UI、payload、编辑回显、样式
mobile-ui/shanxia-mp-bx-reimburse-invoice-picker-demo.html 已落地的视觉/交互参考(实现对照用)

可选复用(优先内联,避免过度抽象):

  • InvoiceWalletMinePage.vue 中 normalizeInvoiceFiles / preview kick 的写法可抄到申请页私有函数。

8. 边界与错误

场景 行为
发票无任何附件 不进入选择器列表
旧关联仅有 invoiceId 详情 brief 已带首附件 attachmentId;前端按附件级恢复;若仍缺则本地用该票 attachments[0]
预览失败 显示文档/占位,不阻断勾选
余额不足 现有提交拦截不变
同一发票勾多附件 允许;invoiceIds 去重,invoiceLinks 多条

9. 验收

  1. 打开 /reimburse/apply,选择器为 3 列附件网格且按月分组。
  2. 同一发票多附件可只勾部分;保存后详情/再编辑仍为对应附件。
  3. 网络请求 body 中 invoiceLinks 含 attachmentId。
  4. 仅有历史 invoiceId 关联的单据再编辑时,回显为首附件(与 Web 一致)。
  5. 余额不足仍无法提交;换申请人清空已选。

10. 测试建议

  • 手工:单附件发票、多附件发票、取消勾选、编辑回显、代申请换人。
  • 无需新增后端测试(契约已存在)。
  • 可选:对 flatten / payload 纯函数若抽出则可单测;本方案内联则手工即可。