|
|
@@ -0,0 +1,500 @@
|
|
|
+# 报销申请「申请附件」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 报销申请与 bx-mobile 申请页增加可选「申请附件」上传/绑定/查看,与发票夹分离,复用现有 `reimburse_apply` 附件能力。
|
|
|
+
|
|
|
+**Architecture:** 不改表结构。上传走已有 `attachmentApi`(`biz_type=reimburse_apply`);新建先 `bizId=0` 再 `bindBiz`;编辑回填 `listByBiz`。Web 用 `BxAttachmentUpload`;H5 对齐备用金 `useAttachment` + `van-uploader`。详情文案「历史附件」统一为「申请附件」。
|
|
|
+
|
|
|
+**Tech Stack:** Vue 3、Element Plus、`BxAttachmentUpload`、bx-mobile Vant、`useAttachment`、既有 BX attachment API
|
|
|
+
|
|
|
+**Spec:** `docs/superpowers/specs/2026-09-05-reimburse-apply-attachments-design.md`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 文件结构
|
|
|
+
|
|
|
+| 操作 | 文件 | 职责 |
|
|
|
+|------|------|------|
|
|
|
+| Modify | `web-admin/src/views/bx/reimburse/apply/index.vue` | 申请弹窗上传区;保存后 bind;编辑回填;详情文案 |
|
|
|
+| Modify | `web-admin/src/views/bx/reimburse/approve/index.vue` | 「历史附件」→「申请附件」 |
|
|
|
+| Modify | `web-admin/src/views/bx/view/todo/index.vue` | 同上 |
|
|
|
+| Modify | `web-admin/src/views/bx/view/all/index.vue` | 同上 |
|
|
|
+| Modify | `web-admin/src/views/bx/view/mine/index.vue` | 同上 |
|
|
|
+| Modify | `bx-mobile/src/views/reimburse/ReimburseApplyPage.vue` | 申请附件上传区 + bind/load |
|
|
|
+
|
|
|
+**不变:** 后端 schema、发票夹、`miniprogram`、付款发放/冲抵附件逻辑。
|
|
|
+
|
|
|
+**参考:**
|
|
|
+
|
|
|
+- Web 组件:`web-admin/src/components/bx/base/BxAttachmentUpload.vue`(`hasUploading` / `getFileIds`)
|
|
|
+- Web 用法:`web-admin/src/views/bx/payment/index.vue` 发放附件
|
|
|
+- H5:`bx-mobile/src/views/advance/AdvanceApplyPage.vue` + `bx-mobile/src/hooks/useAttachment.ts`
|
|
|
+- API:`attachmentApi` 已在 `web-admin/src/api/bx/index.ts` 与 `bx-mobile/src/api/index.ts`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: Web 申请弹窗 — 上传 UI 与状态
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/views/bx/reimburse/apply/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 增加 import 与状态**
|
|
|
+
|
|
|
+在 script 中补充:
|
|
|
+
|
|
|
+```ts
|
|
|
+import { attachmentApi, reimburseApi, invoiceWalletApi } from '@/api/bx'
|
|
|
+import BxAttachmentUpload from '@/components/bx/base/BxAttachmentUpload.vue'
|
|
|
+```
|
|
|
+
|
|
|
+(若 `reimburseApi` / `invoiceWalletApi` 已从 `@/api/bx` 导入,合并为一条 import,并加上 `attachmentApi`。)
|
|
|
+
|
|
|
+在现有 `selectedInvoicePicks` 附近增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+const applyAttachmentRef = ref<InstanceType<typeof BxAttachmentUpload> | null>(null)
|
|
|
+const applyAttachments = ref<BxAttachmentItem[]>([])
|
|
|
+```
|
|
|
+
|
|
|
+`BxAttachmentItem` 已从 `@/utils/bx/attachment` 导入则复用;否则一并导入。
|
|
|
+
|
|
|
+- [ ] **Step 2: 在模板插入上传区块**
|
|
|
+
|
|
|
+在「关联发票(可选)」整块(含 `selectedInvoicePicks` / `BxInvoiceSelectDialog` 触发区)之后、「备注说明」`el-form` 之前插入:
|
|
|
+
|
|
|
+```vue
|
|
|
+ <div class="form-section">
|
|
|
+ <h4>申请附件(可选)</h4>
|
|
|
+ <BxAttachmentUpload
|
|
|
+ ref="applyAttachmentRef"
|
|
|
+ v-model="applyAttachments"
|
|
|
+ biz-type="reimburse_apply"
|
|
|
+ :biz-id="editingId || 0"
|
|
|
+ hint="补充行程单、说明截图、合同等;与发票夹无关。支持图片、视频、PDF、Office、压缩包等。"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 打开新增 / 重置时清空附件**
|
|
|
+
|
|
|
+在 `openAddDialog` 与 `resetAddForm` 中增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+applyAttachments.value = []
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 打开编辑时回填附件**
|
|
|
+
|
|
|
+在 `openEditDialog` 成功拿到 `row.id` 并 `dialogs.add = true` 之后,在现有 `Promise.all([...])` 中增加加载,或在其后:
|
|
|
+
|
|
|
+```ts
|
|
|
+applyAttachments.value = await loadBxAttachments('reimburse_apply', row.id).catch(() => [])
|
|
|
+```
|
|
|
+
|
|
|
+(`loadBxAttachments` 已在本文件导入。)
|
|
|
+
|
|
|
+- [ ] **Step 5: 本地确认弹窗可打开、上传控件可见**
|
|
|
+
|
|
|
+Run: `cd web-admin && pnpm dev`(若已在跑则刷新)
|
|
|
+打开 `#/bx/reimburse/apply` → 新增 → 在发票区与备注之间应看到「申请附件(可选)」。
|
|
|
+Expected: 上传卡片可见;未保存前可不实际上传。
|
|
|
+
|
|
|
+- [ ] **Step 6: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/views/bx/reimburse/apply/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(bx): add apply-attachment upload UI on reimburse apply dialog
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 2: Web — 保存/提交时拦截上传中并 bindBiz
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/views/bx/reimburse/apply/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 增加 bind 辅助函数**
|
|
|
+
|
|
|
+放在 `saveAddForm` 附近:
|
|
|
+
|
|
|
+```ts
|
|
|
+async function bindApplyAttachments(reimburseId: string) {
|
|
|
+ const ids = (applyAttachmentRef.value?.getFileIds?.() || applyAttachments.value.map((f) => f.id)).filter(
|
|
|
+ (id) => id != null && id !== '' && !String(id).startsWith('upload-')
|
|
|
+ )
|
|
|
+ if (!ids.length) return
|
|
|
+ try {
|
|
|
+ await attachmentApi.bindBiz(ids, 'reimburse_apply', reimburseId)
|
|
|
+ } catch (e: any) {
|
|
|
+ ElMessage.warning(e?.message || '报销单已保存,但申请附件关联失败,请重新编辑后保存')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function assertApplyAttachmentsIdle(): boolean {
|
|
|
+ if (applyAttachmentRef.value?.hasUploading()) {
|
|
|
+ ElMessage.warning('附件上传中,请稍候')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 改 `submitAdd`**
|
|
|
+
|
|
|
+在 `buildAddPayload()` 之后、`loadingMap.addSubmit = true` 之前:
|
|
|
+
|
|
|
+```ts
|
|
|
+ if (!assertApplyAttachmentsIdle()) return
|
|
|
+```
|
|
|
+
|
|
|
+在 `const id = await saveAddForm(payload)` 成功且 `id` 有值后、`ElMessage.success` 之前:
|
|
|
+
|
|
|
+```ts
|
|
|
+ await bindApplyAttachments(id)
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 改 `submitAddAndApprove`**
|
|
|
+
|
|
|
+同样在 payload 校验后加 `assertApplyAttachmentsIdle()`;在 `saveAddForm` 成功得到 `id` 后、`reimburseApi.submit` 之前调用 `await bindApplyAttachments(id)`。
|
|
|
+
|
|
|
+- [ ] **Step 4: 手测绑定**
|
|
|
+
|
|
|
+1. 新增报销:填一条明细 → 上传 1 张图片 → 保存草稿
|
|
|
+2. 打开详情 →「申请附件」区应能看到文件(若文案仍是「历史附件」,Task 3 再改)
|
|
|
+3. 编辑该草稿 → 附件应回显;再传一个文件保存 → 详情应有两个
|
|
|
+
|
|
|
+Expected: 上传中点保存会 toast「附件上传中,请稍候」;无附件可正常保存。
|
|
|
+
|
|
|
+- [ ] **Step 5: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/views/bx/reimburse/apply/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(bx): bind reimburse apply attachments on save and submit
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 3: Web — 「历史附件」文案统一为「申请附件」
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/views/bx/reimburse/apply/index.vue`
|
|
|
+- Modify: `web-admin/src/views/bx/reimburse/approve/index.vue`
|
|
|
+- Modify: `web-admin/src/views/bx/view/todo/index.vue`
|
|
|
+- Modify: `web-admin/src/views/bx/view/all/index.vue`
|
|
|
+- Modify: `web-admin/src/views/bx/view/mine/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 全局替换标题文案**
|
|
|
+
|
|
|
+将上述 5 个文件中用户可见的:
|
|
|
+
|
|
|
+```html
|
|
|
+<h4 class="dlg-view-section-title">历史附件</h4>
|
|
|
+```
|
|
|
+
|
|
|
+改为:
|
|
|
+
|
|
|
+```html
|
|
|
+<h4 class="dlg-view-section-title">申请附件</h4>
|
|
|
+```
|
|
|
+
|
|
|
+仅改展示文案,不改 `loadBxAttachments('reimburse_apply', …)` 逻辑。
|
|
|
+
|
|
|
+- [ ] **Step 2: 确认无残留**
|
|
|
+
|
|
|
+Run:
|
|
|
+
|
|
|
+```bash
|
|
|
+rg '历史附件' web-admin/src/views/bx --glob '*.vue'
|
|
|
+```
|
|
|
+
|
|
|
+Expected: 无匹配(或仅注释/无关处;业务标题应为 0)。
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/views/bx/reimburse/apply/index.vue \
|
|
|
+ web-admin/src/views/bx/reimburse/approve/index.vue \
|
|
|
+ web-admin/src/views/bx/view/todo/index.vue \
|
|
|
+ web-admin/src/views/bx/view/all/index.vue \
|
|
|
+ web-admin/src/views/bx/view/mine/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+fix(bx): rename historical attachments label to apply attachments
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 4: bx-mobile 申请页 — 附件上传 UI 与 accept
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `bx-mobile/src/views/reimburse/ReimburseApplyPage.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 增加 import 与常量**
|
|
|
+
|
|
|
+在 script 中增加(路径按页面现有 `@/` 别名):
|
|
|
+
|
|
|
+```ts
|
|
|
+import { useAttachment } from '@/hooks/useAttachment'
|
|
|
+```
|
|
|
+
|
|
|
+在其它常量附近:
|
|
|
+
|
|
|
+```ts
|
|
|
+const MAX_ATTACHMENTS = 10
|
|
|
+
|
|
|
+/** 与 Web BxAttachmentUpload 对齐:图片/视频/文档/压缩包 */
|
|
|
+const attachmentAccept = [
|
|
|
+ 'image/*',
|
|
|
+ 'video/*',
|
|
|
+ 'application/pdf',
|
|
|
+ 'application/msword',
|
|
|
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
+ 'application/vnd.ms-excel',
|
|
|
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
+ 'application/vnd.ms-powerpoint',
|
|
|
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
|
+ 'text/plain',
|
|
|
+ 'text/csv',
|
|
|
+ 'application/rtf',
|
|
|
+ 'application/zip',
|
|
|
+ 'application/x-zip-compressed',
|
|
|
+ 'application/x-rar-compressed',
|
|
|
+ 'application/x-7z-compressed',
|
|
|
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
|
+ '.txt', '.csv', '.rtf', '.wps', '.et', '.dps',
|
|
|
+ '.zip', '.rar', '.7z',
|
|
|
+ '.mp4', '.webm', '.mov', '.avi',
|
|
|
+].join(',')
|
|
|
+
|
|
|
+const applyAttachment = useAttachment()
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 模板插入「申请附件」卡片**
|
|
|
+
|
|
|
+在「关联发票」区块(含 `van-popup` 发票选择器)结束之后、「报销说明」`form-card` 之前插入(结构对齐 `AdvanceApplyPage`):
|
|
|
+
|
|
|
+```vue
|
|
|
+ <!-- 申请附件(可选) -->
|
|
|
+ <div class="form-card">
|
|
|
+ <div class="form-header">
|
|
|
+ <h3>申请附件(可选)</h3>
|
|
|
+ </div>
|
|
|
+ <p class="form-tip">补充行程单、说明截图、合同等;与发票夹无关。</p>
|
|
|
+ <div class="uploader-slot-btn advance-attachment-upload">
|
|
|
+ <van-uploader
|
|
|
+ :after-read="onAfterReadApplyAttachment"
|
|
|
+ :before-read="beforeReadApplyAttachment"
|
|
|
+ :disabled="applyAttachment.uploading.value"
|
|
|
+ multiple
|
|
|
+ result-type="file"
|
|
|
+ :accept="attachmentAccept"
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="btn btn-primary btn-sm"
|
|
|
+ :class="{ 'btn-loading': applyAttachment.uploading.value }"
|
|
|
+ :disabled="applyAttachment.uploading.value"
|
|
|
+ >
|
|
|
+ {{ applyAttachment.uploading.value ? '上传中…' : '上传附件' }}
|
|
|
+ </button>
|
|
|
+ </van-uploader>
|
|
|
+ </div>
|
|
|
+ <div v-if="applyAttachment.files.value.length" class="att-grid advance-att-grid">
|
|
|
+ <div
|
|
|
+ v-for="(file, idx) in applyAttachment.files.value"
|
|
|
+ :key="file.id"
|
|
|
+ class="att-img advance-att-tile"
|
|
|
+ :class="{ 'is-uploading': file._pending }"
|
|
|
+ :title="file.fileName"
|
|
|
+ role="button"
|
|
|
+ tabindex="0"
|
|
|
+ @click="applyAttachment.open(file)"
|
|
|
+ @keydown.enter.prevent="applyAttachment.open(file)"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ v-if="applyAttachment.isImage(file.fileType) && file.previewUrl"
|
|
|
+ :src="file.previewUrl"
|
|
|
+ :alt="file.fileName"
|
|
|
+ />
|
|
|
+ <div v-else class="att-file-icon att-doc-thumb">
|
|
|
+ <span class="att-doc-thumb__ext">{{ applyAttachment.fileExt(file.fileName) }}</span>
|
|
|
+ <span class="att-doc-thumb__name">{{ file.fileName }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="file._pending" class="att-upload-mask" aria-label="上传中">
|
|
|
+ <van-loading size="22px" color="#fff" />
|
|
|
+ <span class="att-upload-mask__text">上传中</span>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ v-if="!file._pending"
|
|
|
+ type="button"
|
|
|
+ class="del"
|
|
|
+ @click.stop="applyAttachment.remove(idx)"
|
|
|
+ >
|
|
|
+ ×
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+```
|
|
|
+
|
|
|
+若页面 style 中尚无 `.advance-attachment-upload` / `.advance-att-grid` / `.advance-att-tile`,从 `AdvanceApplyPage.vue` 底部 scoped 样式原样拷贝对应规则到本文件(或复用本文件已有同名规则,勿重复冲突)。
|
|
|
+
|
|
|
+- [ ] **Step 3: before/after read 处理函数**
|
|
|
+
|
|
|
+```ts
|
|
|
+function beforeReadApplyAttachment(file: File | File[]) {
|
|
|
+ const batch = Array.isArray(file) ? file : [file]
|
|
|
+ const remain = MAX_ATTACHMENTS - applyAttachment.files.value.length
|
|
|
+ if (remain <= 0) {
|
|
|
+ showToast(`最多上传 ${MAX_ATTACHMENTS} 个附件`)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if (batch.length > remain) {
|
|
|
+ showToast(`还可上传 ${remain} 个附件,超出部分已忽略`)
|
|
|
+ return Promise.resolve(batch.slice(0, remain))
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+async function onAfterReadApplyAttachment(uploadFile: any) {
|
|
|
+ const files = Array.isArray(uploadFile) ? uploadFile : [uploadFile]
|
|
|
+ for (const f of files) {
|
|
|
+ if (!f?.file) continue
|
|
|
+ await applyAttachment.upload(f.file)
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+(`showToast` 本页应已导入。)
|
|
|
+
|
|
|
+- [ ] **Step 4: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add bx-mobile/src/views/reimburse/ReimburseApplyPage.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(bx-mobile): add optional apply-attachment uploader on reimburse apply
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 5: bx-mobile — 保存 bind 与编辑 loadList
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `bx-mobile/src/views/reimburse/ReimburseApplyPage.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 改 `onSubmit`**
|
|
|
+
|
|
|
+在 `validateReimburseApplyForm()` 通过后、`submitting.value = true` 之前:
|
|
|
+
|
|
|
+```ts
|
|
|
+ if (applyAttachment.uploading.value) {
|
|
|
+ showToast('附件上传中,请稍候')
|
|
|
+ return
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+在 `create` / `update` 成功并得到 `id` 之后、弹出「提交审批」对话框之前:
|
|
|
+
|
|
|
+```ts
|
|
|
+ try {
|
|
|
+ await applyAttachment.bindNew('reimburse_apply', id)
|
|
|
+ } catch (bindErr: any) {
|
|
|
+ showToast(bindErr?.message || '报销单已保存,但申请附件关联失败,请重新编辑后保存')
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 编辑加载与清空**
|
|
|
+
|
|
|
+在 `loadEditData` 成功解析单据后(发票 picks 处理前后均可)增加:
|
|
|
+
|
|
|
+```ts
|
|
|
+ await applyAttachment.loadList('reimburse_apply', editingId.value)
|
|
|
+```
|
|
|
+
|
|
|
+在 `loadEditData` 开头无 `id` 分支、以及 `watch(route.query.id)` 清空编辑态时:
|
|
|
+
|
|
|
+```ts
|
|
|
+ applyAttachment.clear()
|
|
|
+```
|
|
|
+
|
|
|
+(与 `selectedInvoicePicks.value = []` 同级。)
|
|
|
+
|
|
|
+- [ ] **Step 3: 手测 H5**
|
|
|
+
|
|
|
+1. 新建报销 → 上传附件 → 保存 → 进详情确认「申请附件」可见
|
|
|
+2. 编辑草稿 → 附件回显 → 删除一个再保存 → 详情同步
|
|
|
+3. 无附件可保存
|
|
|
+
|
|
|
+Expected: 与发票选择互不影响;上传中保存被拦截。
|
|
|
+
|
|
|
+- [ ] **Step 4: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add bx-mobile/src/views/reimburse/ReimburseApplyPage.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+feat(bx-mobile): bind and load reimburse apply attachments on save/edit
|
|
|
+
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 6: 联调验收(对照 Spec §6)
|
|
|
+
|
|
|
+**Files:** 无代码(或仅修联调发现的小问题)
|
|
|
+
|
|
|
+- [ ] **Step 1: 按 Spec 验收清单逐项勾选**
|
|
|
+
|
|
|
+| # | 检查项 | 结果 |
|
|
|
+|---|--------|------|
|
|
|
+| 1 | Web 上传/预览/下载/删除;与发票夹独立 | |
|
|
|
+| 2 | 无附件可保存、可提交审批 | |
|
|
|
+| 3 | 详情/审批/付款查看「申请附件」 | |
|
|
|
+| 4 | bx-mobile 上传与查看 | |
|
|
|
+| 5 | 仅 draft/rejected 可改 | |
|
|
|
+| 6 | 类型含图片/视频/文档等 | |
|
|
|
+| 7 | Web 无「历史附件」业务标题 | |
|
|
|
+
|
|
|
+- [ ] **Step 2: 若有缺陷,最小修复后追加 commit**(勿扩大范围)
|
|
|
+
|
|
|
+- [ ] **Step 3: 更新图谱(若改了代码文件)**
|
|
|
+
|
|
|
+```bash
|
|
|
+./scripts/update-graph.sh frontend
|
|
|
+# 若只动了 bx-mobile 且脚本不覆盖,按仓库惯例更新对应 graphify;无脚本则跳过
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Spec 覆盖自检
|
|
|
+
|
|
|
+| Spec 要求 | Task |
|
|
|
+|-----------|------|
|
|
|
+| Web 上传区 + 可选 | Task 1 |
|
|
|
+| 新建 bind / 编辑回填 / 上传中拦截 | Task 2 |
|
|
|
+| 「历史附件」→「申请附件」 | Task 3 |
|
|
|
+| bx-mobile 上传 UI + accept 含视频 | Task 4 |
|
|
|
+| H5 bindNew / loadList / clear | Task 5 |
|
|
|
+| 验收标准 | Task 6 |
|
|
|
+| 不做小程序/新表/发票夹改造 | 全计划未引入 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 执行说明
|
|
|
+
|
|
|
+本仓库前端几乎无组件单测;各 Task 以手测与 `rg` 检查代替 TDD 红绿循环。实现时勿改后端除非 bind/list 联调失败再最小修补。
|