---
title: "统一码 + 上传文件：一张 jpeg 逼我重新认 API 的券码改造复盘"
date: 2026-07-25
description: "出海订单中心里，\"统一码 + 兑换方式 = 上传文件\"时券码区只该露图、不该露数字码——听起来像改两行 if，实际是在和三个层级的 orderType、两个同名 displayType 以及一整片前端推断逻辑较劲。本文复盘如何用订单接口文档 + 样例 orderDetail JSON 钉死「统一码 + 文件型」判定，把规则下沉 voucherPolicy.ts、11 条单测守住边界，以及那些本可以少走的弯路。"
canonical_url: "https://yijinlee.com/articles/article-54"
tags:
  - "AI Coding"
  - Cursor
  - React
  - orderDetail
  - displayType
  - 纯函数
  - 单元测试
  - "最小 diff"
  - 出海订单中心
topic: "AI 外骨骼"
---

METRICS: 统一码+文件型判定 · 3 纯函数 · 11 单测 · 最小 diff · 零误伤

产品需求写得很直白：出海订单中心，商品是"统一码"，兑换方式是"上传文件"——券码信息区**只展示券图**，红框里的数字码模块（文案、长串码、复制按钮、"待使用"状态）全部藏起来。

技术约束同样直白：改动要小，只动这一个场景，别的券类型一行别碰；代码得能测、能维护，别把 VoucherDetail.tsx 再堆成意大利面。

听起来像"加个 if 的事儿"。我一开始也这么以为。

### 一、开局：我在代码里"猜"了一轮

没有接口文档的时候，人很容易用 UI 现象反推后端语义。我（以及 AI 助手）早期走的就是这条路：

- `orderType === ORDER_UNIFIED` 像是统一码 ✓
- `displayType !== SHOW_TEXT` 像是"非纯数字码"→ 当成上传文件？✗
- `voucherValue` 是个 jpeg URL → 有图就是文件型？✗

全仓搜 deliveryType、redemptionType、git 历史、Jest 缓存……有效信息密度低得可怜。后端仓库当时还是空的，没法交叉验证。

**转折点**来了：你扔过来两样东西——订单详情接口文档截图（vouchers 字段枚举），再加一条样例响应 JSON。局面才从"推断"变成"对照"：

| 含义 | 字段 | 枚举别名 |
| :--- | :--- | :--- |
| 统一码 | `vouchers[].orderType` | `ORDER_UNIFIED` |
| 上传文件（文件型券） | `vouchers[].displayType` | `SHOW_FILE` |

JSON 里 `orderType: ORDER_UNIFIED`、`displayType: SHOW_FILE`、`voucherValue` 指向 jpeg——和文档严丝合缝。这次改造最关键的质量保障，就在这一张截图 + 一条 JSON 上。

💡 **教训：**"上传文件"是后端枚举语义，不是页面上"看起来像文件"就能定义的。枚举型业务规则，**先拿文档或样例响应，再写代码**；推断可以当临时假设，旁边得贴张"可能误伤 统一码+二维码"的便利贴。

### 二、改对了什么

#### 招式 1：展示规则和 UI 解耦（SRP 那种）

判定和取码逻辑抽到 `voucherPolicy.ts` 三个纯函数：

- `resolveOrderType()` — 处理字段层级差异（券级 → 子单级 → 订单级）
- `isUnifiedFileVoucher()` — 唯一的业务开关
- `getVoucherDisplayCode()` — 展示层取码

`VoucherDetail.tsx` 只负责一件事：**红框模块渲不渲染**。好处很实在：

- 11 个单测覆盖层级解析与边界（统一码+文件型 / 统一码+二维码 / 其他+文件型）
- 以后改 UI 不用再把 orderType 判断抄第三遍
- 类组件里常见且稳妥：业务规则进无副作用模块，组件做编排

```javascript
// 层级回退：?? 只对 null/undefined 生效，0 不会被误判成"缺失"
item?.orderType ?? subOrderType ?? orderData?.orderType
```

#### 招式 2：改动面卡得紧

没动 `getStatus`、退款参数 `couponCode`、图片轮播、`displayType != SHOW_SPECIAL` 过滤。非「统一码+文件型」仍走原 `renderCodeSection` 分支；统一码非文件型券仍用 `code`，其他场景仍用 `couponCode`（历史提交约定的取码规则保留）。

这就是"只改上传文件场景"该有的样子。

#### 招式 3：主动避开同名字段的坑

排查过程中特意区分了几组容易看岔的字段：

- `subOrders[0].bizType` — 子单级业务类型，含义不同于券级展示类型
- `product.redemptionType` — 商品兑换配置，不是券展示类型
- `createVoucherMethod` / `voucherCreationMode` — 发码方式，文档没定义成"上传文件展示类型"

JSON 扁平嵌套下，**同名不同义**是集成前端最常见的 bug 来源。路径写不对，测再多也白搭。

### 三、还能更好的地方

#### 前期探索成本偏高

根因很简单：需求里的"上传文件"是后端枚举，不能从前端 UI 反推；后端仓库又空的。类似任务，**文档/抓包前置**能砍掉大量无效搜索。推断不是不能用，得标明"可能误伤 统一码+二维码"这种风险。

#### 测试覆盖还有洞

`voucherPolicy.test.ts` 把判定函数盖住了，但缺：

- 组件级测试：统一码+文件型下红框 DOM 是否真的消失
- 混合券列表：第一张非文件型、后面是文件型时的展示（`renderVoucherBody` 仍按第一张 `displayType===SHOW_TEXT` 决定图片区，这是历史隐患）

Review 还点出一个产品边界：统一码+文件型在 refunding/refunded 仍渲染"退款详情"按钮，和"严格只展示图片"可能不一致——**待产品确认**，目前算半开着的门。

#### 文档可以再沉淀一行

`displayType` 枚举别名现在只散落在注释里。建议在 `voucherPolicy.ts` 或团队文档补一句"来源：订单详情接口文档"，后人改代码时少踩一次坑。

### 四、原理，不废话版

**1. 业务规则绑后端契约，不绑 UI 现象**

`voucherValue` 有 URL → 有图，不能说明"兑换方式 = 上传文件"。`displayType === SHOW_FILE` → 文档定义"文件"，才是契约。宽条件（比如 `displayType !== SHOW_TEXT`）的典型后果：统一码 + 二维码被误隐藏数字码。

**2. 空字符串驱动 UI，结构分支防空壳**

`getVoucherDisplayCode` 在统一码+文件型返回 `''`，`renderCode` / `renderCopy` 用 `if (!couponCode) return null`。数据层空值驱动不渲染，比在多处重复 `if (isUnifiedFileVoucher)` 更 DRY；`renderCodeSection` 仍对整个红框做分支，是为了避免只剩"券码"标题、下面啥也没有的空壳 DOM——数据驱动和结构分支的折中。

**3. 单测金字塔**

纯函数单测：快、稳，11 条已够判定逻辑回归。组件测试要 mock i18n / CopyToClipboard，成本高，但适合锁 UI 契约。E2E 用真实统一码+文件型订单走查。三层各干各的，别指望一层包打天下。

**4. 遗留类组件上的最小 diff**

`VoucherDetail.tsx` 还扛着轮播、Popup、CopyToClipboard、路由跳转。这次只动 `renderCodeSection` 分支，没重构 Hooks——交付风险可控。规则外置到 `voucherPolicy.ts`，已经为将来抽 `useVoucherDisplayPolicy(orderData, item)` 留了接口。

### 五、结语

| 维度 | 评价 |
| :--- | :--- |
| 需求符合度 | 核心场景（统一码+文件型 待使用）符合；退款态按钮待产品拍板 |
| 范围控制 | 判定精确，统一码+二维码等场景未误伤 |
| 工程质量 | 规则外置 + 单测较好；缺组件级与混合券回归 |
| 效率 | 文档/抓包前置可显著缩短前期搜索 |

这次改造的价值，不在于又隐藏了一个红框，而在于**用后端明确枚举替代前端推断**，再用纯函数 + 精准分支实现"只改上传文件场景"。

下次接到"枚举型展示规则"的需求，我会先把文档或一条 orderDetail JSON 甩到聊天框里，再让 AI 写第一行代码。推断可以留着当草稿，但别把它当交付标准——不然你和我都会在前仓里多绕好几圈。

## 参考文章：相关链接

- [MDN · FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)（官方文档）— multipart 上传契约，一张 jpeg 逼你重新认 API。
- [RFC 7578 · multipart/form-data](https://datatracker.ietf.org/doc/html/rfc7578)（权威引文）— 表单上传标准，统一码改造时的协议层依据。
- [Hybrid File 假上传排障](https://yijinlee.com/articles/article-2)（站内深度）— 上传语义翻车的 Hybrid 前车。
- [海外充值券 Banner：Fail-Closed 与异步竞态](https://yijinlee.com/articles/article-43)（站内深度）— 券域 Fail-Closed 策略的对照。

