# 科研捐赠协议单 · 一键导入增强(单样本 / 多样本) > 日期:2026-07-02 > 状态:已确认 / 已实现 > 关联模块:`web-admin` 捐赠协议单新增弹窗(`DonationFormDialog.vue`) > 关联常量:`SAMPLE_TYPE_PERINATAL_HSC`、`SAMPLE_TYPE_UC_MSC`(`sample-production-reception-fields.ts`) --- ## 1. 背景与目标 ### 1.1 业务诉求 科研捐赠协议单新增时,左侧「一键导入」支持粘贴客户提供的标准文本,解析后自动填充右侧表单。客户文本存在两种典型场景: 1. **单样本**:如围产期造血干细胞捐献,仅一种样本类型。 2. **多样本**:如同一产妇同时采集「围产期造血干细胞」与「脐带间充质干细胞」,文本中含多条不同样本的采集时间字段。 当前实现仅支持**首行标题推断一个样本类型**、**固定快递运输**、**胎次仅识别阿拉伯数字**、**时间格式不支持「点」**,无法正确导入多样本数据。 ### 1.2 目标 1. 根据**采集时间字段名**推断应创建的样本类型与行数(1 行或 N 行)。 2. 正确映射运输方式:快递 → `kdys`,顺风车等 → `ryxd`(人员携带)。 3. 胎次支持中文数字(如「二胎」→ `2`)。 4. 中文时间支持 `18点05分` 格式(与 `12时17分` 等价)。 5. 保持「仅填充空白字段、不覆盖已有值」策略。 6. **不改后端表结构**;协议级 `sampleCollectTime` 取各样本采集时间中的**最早值**(已确认:选项 A)。 ### 1.3 非目标(本期不做) - 样本表增加独立 `sample_collect_time` 字段(方案 B,后续若产品要求再立项)。 - 配置化规则引擎(JSON/YAML 运营可改规则)。 - 细胞存储协议单一键导入(本模块无此功能)。 - 一键导入 UI 布局变更(仍为新增弹窗左侧粘贴区 + 右侧表单)。 --- ## 2. 客户文本样例 ### 2.1 单样本(捐献) ``` 围产期干细胞(捐献) 产妇姓名:李会 新生儿性别:男 新生儿体重:3900g 新生儿评分: 9 分娩时间:2026年6月22日12时17分 造血干细胞采集时间:2026年6月22日12时18分 母血采集时间:未采集 分娩方式:顺产 胎次:3 采集医护:王天琪 采集医院:利辛县人民医院 运输方式:顺丰快递 销售人员:赵文静 采集医院:利辛县人民医院 ``` **期望结果**:1 条样本(围产期造血干细胞);运输 `kdys` + 顺丰;胎次 `3`。 ### 2.2 多样本(科研脐带) ``` 围产期干细胞(科研脐带) 产妇姓名:陆玲 新生儿性别:男孩 新生儿体重:3700g 分娩时间:2026年3月14日 18点05分 造血干细胞样本采集时间:2026年3月14日 18点06分 脐带采集时间:2026年3月14日 18点08分 母血采集时间:2026年3月14日 19点40分 分娩方式:顺产 胎次:二胎 运输方式:顺风车 销售人员:张慧茹 采集医院:涡阳县中医院 ``` **期望结果**:2 条样本(围产期造血干细胞、脐带间充质干细胞);协议级 `sampleCollectTime` = `2026-03-14 18:06`(最早);运输 `ryxd`,不填快递公司;胎次 `2`。 --- ## 3. 业务规则 ### 3.1 样本类型推断(优先级) | 优先级 | 规则 | 说明 | |--------|------|------| | 1 | **采集时间字段** | 存在且值可解析 → 创建对应样本行(可多条) | | 2 | **首行标题** | 无采集时间字段时,回退现有 `matchCellSampleType(header)` 逻辑 | **采集时间字段 → 样本类型映射**: | 字段名(含别名) | 目标类型名 | typeKey | |------------------|------------|---------| | `造血干细胞样本采集时间`、`造血干细胞采集时间`、`样本采集时间` | 围产期造血干细胞 | `hsc` | | `脐带采集时间` | 脐带间充质干细胞 | `uc_msc` | - 同一 typeKey 在文本中多次出现:以**首次有效时间**为准。 - 字段值为空或「未采集」等占位:不创建该样本行。 - 多样本场景下,**首行标题不参与**样本类型决定(避免「围产期干细胞(科研脐带)」歧义)。 ### 3.2 协议级样本采集时间(已确认) - 存储位置:`formData.sampleCollectTime` → `biz_research_donation_collection.sample_collect_time`。 - **多样本**:取所有已识别样本采集时间中的**最早时间**(minute 精度 `YYYY-MM-DD HH:mm`)。 - **单样本**:该样本采集时间写入协议级字段(与现逻辑一致)。 - 若协议级字段已有值:不覆盖(空白才填)。 ### 3.3 运输方式映射 客户文本字段仍为 `运输方式`;解析结果区分 **模式** 与 **原始标签**: | 客户文本特征 | `transportType` | 后续处理 | |--------------|-----------------|----------| | 含「快递」「顺丰」「圆通」「中通」「韵达」「EMS」等 | `kdys` | 匹配 `expressCompanyId`(现有 `matchExpressCompany`) | | 含「顺风车」「顺丰车」(后者为笔误)、「自行」「人员携带」「自带」「自送」等 | `ryxd` | **不**填 `expressCompanyId`;可选后续人工填携带人 | | 无法识别 | — | warning,不填运输 | - 多样本时:各样本行复制**同一套**运输配置(客户数据中通常一致)。 - 仅当样本行 `transport.transportType` 为空时写入。 ### 3.4 胎次 | 输入示例 | 解析结果 | |----------|----------| | `3` | `3` | | `二胎`、`三胎` | `2`、`3` | | `第二胎` | `2` | | 无法解析 | warning,不填 | 实现:`parseBirthOrder(text)` — 先 `extractNumericPart`;若无阿拉伯数字,再解析中文数字(一~十及「N胎」模式)。 ### 3.5 其他字段(保持 / 微调) | 字段 | 规则 | |------|------| | 新生儿性别 | 扩展识别 `男孩`→`1`、`女孩`→`0` | | 母血采集时间 | 「未采集」等 → 清空(已有) | | 采集医院重复行 | 首次有效值为准(已有) | | 母亲 / 医院 / 销售 | 远程匹配(已有) | | 样本编码 | 每行样本 `assignDonationSampleCodeIfMissing`(已有) | ### 3.6 填充策略(不变) - **仅填空**:目标字段为空才写入。 - **样本行**:若 `formData.samples` 已有同类型样本且 `sampleTypeId` 非空,不重复添加;若为空行可复用。 - 导入完成后:`ElMessage.success` 列出已填充项;`ElMessage.warning` 列出未匹配项。 --- ## 4. 技术设计 ### 4.1 解析层结构变更 **文件**:`web-admin/src/utils/donation-standard-import-parse.ts` 扩展 `DonationStandardImportParsed`(或新增子类型): ```typescript export type DonationSampleImportHint = { typeKey: 'hsc' | 'uc_msc' typeName: string collectTime?: string // 已解析为标准 datetime-minute } export type DonationStandardImportParsed = { sampleTypeHeader: string sampleHints: DonationSampleImportHint[] // 新增:0..N transportModeText?: string // 原始运输方式文本 transportType?: 'kdys' | 'ryxd' // 新增:解析后的系统码 // ... 现有 common 字段保持不变 } ``` **新增 / 导出函数**: | 函数 | 职责 | |------|------| | `parseBirthOrder(text)` | 阿拉伯 + 中文胎次 | | `resolveTransportType(text)` | 运输文本 → `kdys` \| `ryxd` \| null | | `matchSampleTypeByExactName(name, types)` | 按 `typeName` 精确匹配(优先于 segment 评分) | **采集时间字段识别**:在 `KEY_ALIASES` 之外,对 key 做 pattern 匹配(支持「造血干细胞**样本**采集时间」等变体),命中后写入 `sampleHints` 而非单一 `sampleCollectTime`。 **回退**:`sampleHints.length === 0` 时,保留现有 `sampleCollectTime` + 首行 `matchCellSampleType` 行为。 ### 4.2 时间解析增强 **文件**:`web-admin/src/utils/chinese-datetime-parse.ts` - 正则扩展:`[时::点]` 均视为小时分隔符。 - 例:`2026年3月14日 18点06分` → `2026-03-14 18:06`。 **可选新文件**:`web-admin/src/utils/chinese-numeral-parse.ts` - `parseChineseNumeral(text: string): number | null` — 支持「二」「二胎」「三胎」等;供 `parseBirthOrder` 调用。 ### 4.3 填充层变更 **文件**:`web-admin/src/views/research-donation/list/DonationFormDialog.vue` — `handleStandardImport()` ``` 1. parseDonationStandardImportText(text) 2. 并行:母亲 / 医院 / 销售 / 字典预加载(现有) 3. common 字段填空(性别、体重、评分、分娩时间、母血、分娩方式、胎次、医护…) 4. 样本行: if sampleHints.length > 0: for each hint: matchSampleTypeByExactName(hint.typeName) 获取或创建对应样本行 assignDonationSampleCodeIfMissing sampleCollectTime = min(hint.collectTime) // 最早 else: 现有单样本逻辑(首行 + 一行样本) 5. 运输: if parsed.transportType === 'kdys': 匹配快递公司 if parsed.transportType === 'ryxd': transportType=ryxd,跳过 expressCompanyId 6. hydrateSampleLookupOptions + 消息反馈 ``` ### 4.4 数据模型(无变更) | 实体 | 字段 | 说明 | |------|------|------| | `biz_research_donation_collection` | `sample_collect_time` | 协议级;多样本取最早采集时间 | | `biz_research_donation_sample` | — | 无 per-sample 采集时间;本期不加 | | `biz_research_donation_sample_transport` | `transport_type` | `kdys` / `ryxd` | --- ## 5. 测试 **新建**:`web-admin/src/utils/donation-standard-import-parse.test.ts`(或项目现有 vitest 路径) | 用例 | 断言 | |------|------| | 单样本完整文本(§2.1) | 1 个 hint(hsc);transportType=kdys;birthOrder=3 | | 多样本完整文本(§2.2) | 2 个 hint(hsc+uc_msc);最早时间 18:06;transportType=ryxd;birthOrder=2 | | 胎次「二胎」「3」 | 2、3 | | 时间「18点06分」「12时18分」 | 等价解析 | | 仅首行无采集时间 | sampleHints 空,回退首行匹配 | | 运输「顺风车」「顺丰快递」 | ryxd / kdys | **手工验收**(`/#/market/research-donation/audit` 或 list 页新增): 1. 粘贴 §2.1 → 1 样本、顺丰、胎次 3。 2. 粘贴 §2.2 → 2 样本、人员携带、协议采集时间 18:06、胎次 2。 3. 先手动填部分字段再导入 → 已填字段不被覆盖。 --- ## 6. 改动文件清单 | 文件 | 改动类型 | |------|----------| | `web-admin/src/utils/donation-standard-import-parse.ts` | 扩展解析:多样本 hints、运输、胎次 | | `web-admin/src/utils/chinese-datetime-parse.ts` | 支持「点」 | | `web-admin/src/utils/chinese-numeral-parse.ts` | 新建(可选,也可内联于 parse 文件) | | `web-admin/src/views/research-donation/list/DonationFormDialog.vue` | `handleStandardImport` 多样本 + 运输 | | `web-admin/src/utils/donation-standard-import-parse.test.ts` | 新建单元测试 | **不改**:`backend/`、`docs/sql/`、OpenSpec(除非评审要求补充 ui-conventions 条目)。 --- ## 7. 风险与限制 1. **协议级采集时间**:多样本各时间不同,列表/采集信息页仅展示一个时间;业务接受「最早时间」代表协议(已确认)。 2. **运输携带人**:`ryxd` 模式下客户文本通常不含携带人姓名;导入后可能需人工补全。 3. **未知样本类型**:若未来出现第三种采集时间字段,需扩展 `SAMPLE_COLLECT_TIME_RULES`;本期仅 hsc / uc_msc。 --- ## 8. 验收标准 - [ ] §2.1 单样本文本一键导入后,样本 Tab 有 1 行「围产期造血干细胞」,运输为快递运输 + 顺丰。 - [ ] §2.2 多样本文本一键导入后,样本 Tab 有 2 行(造血 + 脐带),运输为人员携带,协议采集时间为 18:06。 - [ ] 胎次「二胎」解析为 2;「3」解析为 3。 - [ ] `18点06分` 时间正确解析。 - [ ] 已有字段不被覆盖;单元测试通过。 --- ## 9. 决策记录 | 日期 | 决策 | 理由 | |------|------|------| | 2026-07-02 | 方案 A:纯前端增强,不改表 | 最小改动、与现架构一致 | | 2026-07-02 | 协议级 `sampleCollectTime` 取**最早**采集时间 | 用户确认选项 A | | 2026-07-02 | 多样本以采集时间字段为准,首行标题仅作回退 | 避免标题歧义 |