From 8ff78d13a7ad9789f5f2b22678cd13ed7385d573 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Fri, 19 Jun 2026 11:12:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog/order-v3):=20=E8=A1=A5=E5=85=A8?= =?UTF-8?q?=E5=87=BA=E8=A1=8C=E4=BF=A1=E6=81=AF=E5=BC=B9=E7=AA=97=E4=B8=89?= =?UTF-8?q?=E5=A4=84=E5=89=8D=E7=AB=AF=E7=BC=BA=E9=99=B7(=E5=9B=9E?= =?UTF-8?q?=E6=98=BE=E4=B8=8D=E5=85=A8/=E7=B4=A7=E6=80=A5=E8=81=94?= =?UTF-8?q?=E7=B3=BB=E4=BA=BA=E8=AF=AF=E5=BF=85=E5=A1=AB/=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=90=8E=E4=B8=8D=E5=85=B3=E9=97=AD)+=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=B8=A2=E5=A4=B1=E9=81=BF=E5=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- ...全出行信息弹窗三处缺陷-前端待修-管理后台.md | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 changelogs-v2/2026-06/19_补全出行信息弹窗三处缺陷-前端待修-管理后台.md diff --git a/changelogs-v2/2026-06/19_补全出行信息弹窗三处缺陷-前端待修-管理后台.md b/changelogs-v2/2026-06/19_补全出行信息弹窗三处缺陷-前端待修-管理后台.md new file mode 100644 index 0000000..8a5d238 --- /dev/null +++ b/changelogs-v2/2026-06/19_补全出行信息弹窗三处缺陷-前端待修-管理后台.md @@ -0,0 +1,177 @@ +# 「补全出行信息」弹窗三处缺陷(回显不全 / 紧急联系人误必填 / 保存后不关闭) — 前端待修 — 管理后台 + +> 变更类型:🐛 前端缺陷(**无后端代码变更、零 DDL**;后端接口与数据经测试服实测均正常,本文档给出对接修复方式 + 数据丢失避坑) +> 端类型:管理后台(订单详情页 → 「补全出行信息」弹窗) +> 日期:2026-06-19 +> 服务:hl-order-service-v3(出行人 / 订单详情接口,均已上线且行为正常) +> 责任端:前端 hl-ui(mmg) +> 实测样本:测试服网关 9443 + 真实 admin token,订单 `HL20260618165816942`(orderId=`2067532338723504130`,定制中 / 待补全信息) + +--- + +## ⚠️ 关键说明 + +这三个问题**全部在前端侧**。后端经测试服逐项实测均正常: + +- 后端**有**该出行人的完整明文数据,且已提供**明文回显接口**(订单详情 overview,#3509); +- 保存接口对该场景返回 **HTTP 200 `success=true`**(不是保存失败); +- 紧急联系人后端**不要求必填**(VO 无 `@NotBlank`,Service 无校验),空值提交返回 200。 + +> 🔴 **最高优先级警告(修复问题一时必读 §4)**:补全弹窗里被清空显示的「证件号 / 手机号」,若在保存时以**空字符串 `""`** 回传,后端会把数据库里**已有的真实证件号 / 手机号抹掉**(merge 语义:`null` 保留原值、`""` 覆盖为空)。修回显的同时必须改对保存回传,否则会造成线上数据丢失。 + +--- + +## 1. 问题一:回显信息不全(证件号 / 手机号 / 备注 显示为空,状态显示「待填」实为「已完成」) + +**现象**:打开弹窗,出行人「王完善」的证件号码、手机号、客户备注均为空,出行人标签显示「待填」。 + +**实测对比(同一订单,后端实际数据 vs 弹窗显示)**: + +| 字段 | 后端实际值(订单详情接口明文返回) | 弹窗显示 | +|---|---|---| +| 证件号 | `152522198605311079`(明文) | **空** | +| 手机号 | `18547062756`(明文) | **空** | +| 客户备注 | `测试测试 哈哈哈哈` | **空** | +| profileStatus | `COMPLETED`(已完成) | **待填** | +| 紧急联系人 | name=`1` / phone=`18547056215` | 正常显示 ✓ | + +**根因**:弹窗的回显数据**没取自明文源**。后端有两个出行人查询口径: + +1. `GET /v3/admin/order/{id}/traveler/list` —— **脱敏版**(#2894 PII 应急修复),证件号 / 手机号返回的是 `idCardMasked` / `phoneMasked`(形如 `152***********1079`、`185****2756`)。这种带星号的脱敏值**无法放进可编辑输入框**,前端只能留空 → 表现为「证件号 / 手机号 空」。 +2. `GET /v3/admin/order/{id}`(订单详情 overview)→ `overview.customerInfo.travelers[]` —— **明文版**(`TravelerPlainVO`,#3509,admin 端业务例外),证件号字段名 `idCard`、手机号 `phone` 均为明文。 + +补全弹窗是从**订单详情页**打开的,订单详情数据前端**已经加载过**(含明文出行人 + 紧急联系人 + 客户备注)。弹窗应直接用这份已加载的明文数据回填,而不是再调脱敏的 `/traveler/list`。 + +**修复方式**:弹窗回显改用订单详情明文源: + +| 弹窗字段 | 取自订单详情(`GET /v3/admin/order/{id}` → `data.overview`) | +|---|---| +| 出行人 姓名/性别/生日/证件类型 | `customerInfo.travelers[].name / gender / birthday / idType` | +| 出行人 **证件号** | `customerInfo.travelers[].idCard`(明文) | +| 出行人 **手机号** | `customerInfo.travelers[].phone`(明文) | +| 出行人 状态标签 | `customerInfo.travelers[].profileStatus`(直接读,`COMPLETED`=已完成,别再用「输入框是否为空」反推,否则永远显示待填) | +| 紧急联系人 姓名/电话 | `customerInfo.emergencyContactName / emergencyContactPhone` | +| 客户备注 | `remarkInfo.customerRemark` | + +```bash +# 回显数据源:订单详情 overview(明文) +curl -k "https://api.test.1814.love:9443/v3/admin/order/2067532338723504130" \ + -H "Authorization: Bearer " +``` + +实测返回(节选,已上线): + +```json +{ + "code": 200, + "data": { + "overview": { + "customerInfo": { + "emergencyContactName": "1", + "emergencyContactPhone": "18547056215", + "travelers": [ + { + "id": "2067803630752174082", + "name": "王完善", "gender": "1", "birthday": "1986-05-31", + "idType": "ID_CARD", "idCard": "152522198605311079", "phone": "18547062756", + "profileStatus": "COMPLETED", "nationality": "中国", "race": "汉族" + } + ] + }, + "remarkInfo": { "customerRemark": "测试测试 哈哈哈哈" } + } + } +} +``` + +--- + +## 2. 问题二:紧急联系人被前端设为必填(后端并不要求) + +**现象**:不填紧急联系人无法保存(前端拦截)。 + +**实测**:保存接口传**空紧急联系人**(`emergencyContactName=""`、`emergencyContactPhone=""`)→ **HTTP 200,`success=true`**,保存成功。后端 `TravelerInfoSaveReqVO` 的 `emergencyContactName` / `emergencyContactPhone` 无 `@NotBlank`,Service 层也无必填校验。 + +**修复方式**:前端去掉紧急联系人两个字段的「必填」校验。弹窗副标题「需填写……紧急联系人信息」也应同步改为非强制措辞(如「如有请填写紧急联系人」)。 + +> 注:紧急联系人手机号**若填了**,后端会校验 11 位手机号格式(不合法返 `581113`);不填则跳过。即「非必填,但填了要合法」。 + +--- + +## 3. 问题三:保存成功后弹窗不关闭 + +**现象**:点「保存并完成」后弹窗一直不消失。 + +**实测**:保存接口 `PUT /v3/admin/order/{id}/traveler-info` 对该场景返回 **HTTP 200**: + +```json +{ "code": 200, "success": true, + "data": { "createdCount": 0, "updatedCount": 1, "deletedCount": 0, + "completedCount": 1, "pendingCount": 0, "allCompleted": true } } +``` + +保存是**成功的**,后端无报错。弹窗不关闭是前端**没有在成功响应后关闭弹窗 / 刷新页面**。 + +**修复方式**: + +1. 保存成功(`code===200 && success===true`)后:关闭弹窗 + 刷新订单详情(出行人区、流程状态)。 +2. ⚠️ 判成功要看**业务字段** `code===200 && success===true`,**不能只看 HTTP 状态码**。原因:保存接口带幂等保护 `@Idempotent`,**同订单 3 秒内重复提交**会被拦截,此时 **HTTP 仍是 200 但 `success=false`、`code!=200`**(实测复现:连续两次提交,第二次 `success=false`)。前端若只看 HTTP 200 就当成功,会把「被幂等拦截、其实没保存」误判为成功;若据此关弹窗也属误关。正确做法:仅 `success===true` 关弹窗,`success===false` 弹出后端 `message` 提示用户。 + +--- + +## 4. ⚠️ 数据丢失陷阱(修问题一时必须同步处理) + +保存接口的 merge 语义(`mergeFromEditItem`):**某字段传 `null`(或不传该 key)= 保留数据库原值;传任何非 null 值(含空字符串 `""`)= 覆盖**。 + +结合问题一的回显修复,存在数据丢失风险链: + +- 出行人「王完善」DB 里证件号 = `152522198605311079`、手机号 = `18547062756`(已完成); +- 若弹窗回显仍把证件号 / 手机号显示为空,用户没动这两个框,保存时前端把 `idNo:""`、`phone:""` 回传; +- 后端 `"" != null` → **把真实证件号 / 手机号覆盖成空** → 该出行人从 `COMPLETED` 退回 `PENDING`,**证件号 / 手机号永久丢失**。 + +**前端务必遵守**: + +1. **优先**:按 §1 用明文源回填证件号 / 手机号,用户未修改时**原样回传明文值**(保存接口字段名是 `idNo` / `phone`,注意回显源字段名是 `idCard` / `phone`,证件号需做 `idCard → idNo` 映射)。 +2. **退路**:某字段确实没有可回填的值、或不想回传,就传 `null` / 不带该 key(保留原值),**绝不能传空字符串 `""`**。 +3. 仅当用户**主动清空**某字段且业务确实要清空时,才传 `""`。 + +--- + +## 5. 接口契约速查 + +**回显**:`GET /v3/admin/order/{id}` → `data.overview.customerInfo`(明文出行人 + 紧急联系人)+ `data.overview.remarkInfo.customerRemark`。 + +**保存**:`PUT /v3/admin/order/{id}/traveler-info` + +| 字段 | 必填 | 说明 | +|---|---|---| +| travelers[] | 是(≤30,非空) | 全量同步:`id=null` 新增 / `id` 非空 更新 / DB 有而本次没提交的 → 删除 | +| travelers[].id | 更新必填 | 回显时务必带上,否则会被当新增 + 旧行被删 | +| travelers[].birthday | **是**(`@NotNull`) | 出生日期,后端按年龄派生出行人类型 | +| travelers[].name / gender / idType | 否 | 缺则该出行人停留 PENDING | +| travelers[].**idNo** | 否 | 证件号**明文**回传(回显源字段叫 `idCard`,注意映射);空串会覆盖,见 §4 | +| travelers[].**phone** | 否 | 手机号明文回传;空串会覆盖,见 §4 | +| travelers[].nationality / race | 否 | 不传后端默认「中国」/「汉族」;**传空串 `""` 会被拒**(`581101`) | +| emergencyContactName / emergencyContactPhone | **否**(见 §2) | 订单级紧急联系人;电话填了要合法 11 位 | +| customerRemark | 否 | 客户备注 | + +**幂等**:同订单 3 秒窗口重复提交被拒(HTTP 200 但 `success=false`),前端按 §3 判 `success`。 + +--- + +## 6. 实测确认(测试服 9443 + 真实 admin token,订单 2067532338723504130) + +- `GET /v3/admin/order/{id}`:明文证件号 `152522198605311079`、手机号 `18547062756`、备注 `测试测试 哈哈哈哈`、profileStatus `COMPLETED` 均正常返回 ✓ +- `GET /v3/admin/order/{id}/traveler/list`:证件号为脱敏 `152***********1079`、手机号 `185****2756`(确认前端用错了脱敏源会留空)✓ +- `PUT /v3/admin/order/{id}/traveler-info`(保留明文值):HTTP 200 `success=true`、`allCompleted=true` ✓ +- `PUT`(空紧急联系人):HTTP 200 `success=true`(确认紧急联系人非必填)✓ +- 连续两次 `PUT`:第二次 `success=false`(确认 3 秒幂等窗口,前端需判 `success`)✓ +- 测试结束已将订单数据恢复原状(紧急联系人 `1`/`18547056215`、证件号、profileStatus 均还原)✓ + +--- + +## 备注 + +- 本文为前端缺陷通知 + 现有接口对接说明,**无后端代码变更、零 DDL、无新增 / 改动接口**。 +- 不建 Gitea 工单(前端 bug 走 changelog 通知前端同事)。 +- 后端明文回显能力(`TravelerPlainVO`)为 #3509 已上线特性,admin 端业务例外、需 admin 权限,前端可放心使用。