6.3 KiB
CRM 客户开票信息 · 粘贴快速识别 · 设计说明
Demo:
docs/demo-crm-invoice-paste-recognize.html(方案 A)
日期:2026-08-20
状态:已实现(待联调验收)
1. 背景与目标
客户管理详情弹窗右侧「开票信息」新增/编辑时,业务常从微信/邮件粘贴整段「开票资料」文本,再手工逐项填入,效率低且易错。
目标: 在新增/编辑弹窗顶部提供「粘贴识别」区,用纯前端规则解析常见开票资料文本,并将结果填入对应表单字段;用户可再微调后保存。不依赖后端、不调用大模型。
非目标:
- 不新增数据库字段(银行行号、邮编、邮寄地址等仍无独立列)
- 不接阿里云 / OCR / 大模型
- 不改开票信息保存 API 与权限
2. 已确认决策
| 项 | 结论 |
|---|---|
| 入口布局 | 方案 A:新增/编辑弹窗表单顶部「粘贴识别」区 |
| 识别方式 | 纯前端规则(标签别名 + 行解析),零成本、离线 |
| 回填策略 | 识别后覆盖对应字段(已有值一并覆盖) |
| 无表单字段的信息 | 写入 remark(银行行号、邮编、发票邮寄信息等) |
| 备注合并 | 识别产生的备注内容追加到 remark 末尾;若与已有内容重复则不重复追加 |
| 范围 | 仅 web-admin 客户详情 → InvoiceInfo.vue;以及客户新增弹窗 CustomerFormDialog.vue 底部开票信息(二级粘贴弹窗) |
3. 交互设计
组件:web-admin/src/views/crm/customer/components/InvoiceInfo.vue
在现有 el-form 顶部、.form-grid-2 字段区之前增加粘贴区:
el-dialog「添加/修改开票信息」
└─ el-form
├─ 粘贴识别面板(虚线边框 + primary-bg)
│ ├─ 标题「粘贴识别」+ 简短提示
│ ├─ el-input type=textarea(粘贴原文)
│ ├─ [清空] [识别并填入]
│ └─ 结果提示文案(成功/部分成功/失败)
└─ 原有字段栅格(单位名称、税号…)
行为:
- 打开新增/编辑弹窗时,粘贴区默认空(不强制预填样例,避免误覆盖业务数据)。
- 点击「识别并填入」→ 调用解析工具 → 覆盖已映射字段;未映射信息写入/追加
remark。 - 点击「清空」仅清空粘贴框与提示,不清空表单字段。
- 关闭弹窗时重置粘贴区与表单(沿用现有
reset)。 - 识别失败或零字段:
ElMessage.warning,不改表单。 - 部分成功:照常覆盖已识别字段,并用消息列出未识别的关键项(如缺单位名称)。
视觉对齐 Fresh Mint:主色 #10b981 / #059669 / #ecfdf5;按钮带图标(DocumentCopy / Delete / Close / Check 等既有习惯)。
4. 解析规则
新增工具:web-admin/src/utils/crm-invoice-paste-recognize.ts
参考既有模式:web-admin/src/utils/pl-paste-recognize.ts(纯函数、导出样例常量、返回 warnings)。
4.1 输入输出
interface CrmInvoicePasteResult {
companyName?: string
taxNo?: string
companyAddress?: string
legalRepresentative?: string
agent?: string
phoneFax?: string
bankName?: string
bankAccount?: string
/** 无独立字段的附加信息,供写入 remark */
remarkParts: string[]
warnings: string[]
/** 成功写入表单的字段数(不含 remarkParts) */
filledCount: number
}
4.2 标签别名 → 字段
| 字段 | 标签别名(行首,支持全/半角冒号、标签内空格) |
|---|---|
| companyName | 公司名称、单位名称、单位名、名称 |
| taxNo | 纳税人识别号、纳税识别号、统一社会信用代码、税号 |
| companyAddress | 单位地址、地址(见 4.3 邮寄段排除) |
| phoneFax | 电话/传真、电话传真、传真、电话 |
| bankName | 银行开户行、开户银行、开户行 |
| bankAccount | 银行账号、账号、帐号(去除内部空格) |
| legalRepresentative | 法定代表人、法人代表、法人 |
| agent | 代理人、委托代理人 |
「地址及电话」单独规则:整行取值后,若末尾匹配固话/手机则拆到 phoneFax,其余为 companyAddress。
4.3 段落与冲突
- 若存在「发票邮寄地址」标题,其后块内的「姓名 / 电话 / 地址」不写入
companyName/phoneFax/companyAddress,而是汇总进remarkParts(如邮寄:汪璇 / 188… / 地址…)。 - 「开票资料」等总标题行忽略。
- 银行行号 / 联行号 / 行号 →
remarkParts:银行行号:…(去空格)。 - 邮编 →
remarkParts:邮编:…。
4.4 验收样例(必须通过)
样例 1(安庆山峡): 公司名称、税号、开户行、帐号、地址、电话/传真正确回填;银行行号、邮编、邮寄信息进 remark。
样例 2(江峰生物): 单位名、税号、地址及电话拆分、开户行、带空格账号去空格、电话正确;银行行号进 remark。
单测:web-admin/src/utils/crm-invoice-paste-recognize.test.ts(与现有 vitest 风格一致,如 pl-cert-slots.test.ts)。
5. 数据流
用户粘贴文本
→ parseCrmInvoicePaste(text)
→ 覆盖 form 对应键(仅当解析值非空)
→ remark:将 remarkParts 用「;」拼接后追加到现有 remark
→ ElMessage 提示 filledCount / warnings
→ 用户点确定 → 现有 /web/crm/invoice-info/add|update(不变)
覆盖规则细节:某字段解析值为空字符串时,不清空该表单字段(只覆盖「识别到了」的字段)。
6. 风险与后续
| 风险 | 应对 |
|---|---|
| 新标签写法未覆盖 | 补别名;UI 提示可手动改 |
| 无标签散文文本 | 识别失败,手动填写 |
| 长期需行号等独立字段 | 另开变更加列;本设计不扩表 |
可选后续(本变更不做):规则失败时降级调大模型。
7. 实现清单(实现阶段)
- 新增
crm-invoice-paste-recognize.ts+ 单测(含两段业务样例) - 改
InvoiceInfo.vue:顶部粘贴区 + 识别回填 - 保留/更新 Demo:
docs/demo-crm-invoice-paste-recognize.html(与线上规则一致即可) - 更新 graphify(web-admin)
不改 backend、不改 SQL、不改 OpenSpec 基线(纯前端体验增强)。