# R2 收货地址 + 设计清单(feat/r2-address-design) **Goal:** 把收货地址和设计清单从本地 Storage 切换到后端 CRUD,完成字段契约、接口权限、 默认地址事务、设计数据 JSON 与状态映射,让 R3 的订单闭环可以直接消费这两组能力。 **Architecture:** 前端 `api/address.ts`、`api/design.ts` 负责 HTTP 契约,本地 `store/address.ts`、`store/design.ts` 在联调期保留为兜底;后端补齐 addresses 与 design-list 的完整 CRUD、事务与本人数据校验;`checkout` 页归 R3 所有,R2 只定义接口不修改该页面。 **Tech Stack:** Taro 3.6 + React 18 + TypeScript;NestJS 11 + Prisma/PostgreSQL。 ## 1. 本分支要开发的内容 ### 前端 `wechat_wc` | 文件 | 职责 | |---|---| | `src/utils/api/address.ts` | `fetchAddresses/createAddress/updateAddress/deleteAddress/setDefaultAddress`,负责 `region[]` 与 `province/city/district` 双向转换 | | `src/utils/api/design.ts` | `fetchDesignList/createDesign/updateDesign/deleteDesigns` | | `src/pages/address/index.tsx` | 列表、新增、编辑、删除、设为默认全部走 API;保留表单校验与区域 Picker | | `src/pages/designList/index.tsx` | 列表、状态筛选、批量删除走 API;保留 LoginGuard | | `src/types/index.ts` | AddressItem/DesignItem 与后端结构对齐,新增服务端 id、状态码、时间字段 | | `src/utils/store/address.ts`、`design.ts` | 降级为“本地缓存/离线兜底”,API 调用成功后同步刷新 | ### 后端 `wxmp_backend` | 模块 | 内容 | |---|---| | `addresses` | `GET/POST /api/addresses`、`PATCH /api/addresses/:id`、`PATCH /api/addresses/:id/default`、`DELETE /api/addresses/:id`;全部带 userId 归属校验 | | `addresses` 事务 | 设置默认时事务内先清旧默认再设新默认;删除默认地址后自动指定最新一条为默认 | | `design-list` | `GET/POST /api/design-list`、`PATCH /api/design-list/:id`、`DELETE /api/design-list/:id`、批量删除 | | `design-list` 校验 | `items` JSON 结构与大小校验、状态机转换、本人专属查询 | | `docs/api-contract-v1.md` | 地址字段映射、设计清单状态枚举、错误码 | ### 词云项目 `wordcloud` 不参与本分支,但 `designData` 中的本地贴纸图片路径问题会影响后续 R4,见风险一节。 ## 2. 状态映射(必须先定死) 后端 `DesignListStatus` 目前是 `DRAFT/SUBMITTED/PROCESSING/DONE`,前端是 `undesigned/designing/ordered`。R2 不要在两边各造一套值,建议按显示语义映射: | 前端显示 | 后端状态 | 前端状态码 | |---|---|---| | 待设计 | DRAFT | `undesigned` | | 设计中 | SUBMITTED | `designing` | | 生产中 | PROCESSING | `processing` | | 已下单 | DONE | `ordered` | “已下单”不要用独立状态表达,改为 `orderId != null` 派生;前端只显示,不提交这个状态。 状态码收口到 `docs/api-contract-v1.md`,后端校验状态迁移,前端不传自由字符串。 ## 3. 字段契约 ### 地址 后端 `province/city/district/detail` 对应前端 `region: [province, city, district] + detail`。 转换只放在 `api/address.ts`,页面和 store 不得散落第二次转换。 ### 设计数据 `DesignList.items` 是服务端 JSON,前端提交结构建议与当前 `DesignItem.designData` 对齐: ```ts interface DesignData { stickers?: StickerItem[] category?: ProductCategory imageSrc?: string imagePos?: { x: number; y: number; scale: number } } ``` 服务端只做白名单字段校验,不修改业务 JSON 内容;单条设计数据建议限制在 1MB 以内。 ## 4. 设计注意事项 ### DO - DO 地址的增删改查和默认切换全部要求登录态,401 时引导去个人中心登录。 - DO 所有按 id 操作的服务端路由同时带 `userId` 条件,404 与 403 区分清楚。 - DO 默认地址切换与删除补偿放在同一个 Prisma transaction 里。 - DO 前端继续沿用户已熟悉的表单校验,但最终校验提示以后端返回为准。 - DO 设计清单状态通过后端枚举返回,前端状态筛选基于返回的 `status` 计算。 - DO 批量删除设计条目提供单个接口或循环调用时保证部分失败可恢复;推荐后端一次批量删除。 - DO 本地 store 只在 API 失败时做降级读取,成功写回后以服务端数据为准。 ### DON'T - DON'T 把前端 `region` 数组原样 POST 给后端,后端契约是四个独立地址字段。 - DON'T 让前端直接写 `isDefault` 的两条规则,默认地址唯一性由后端事务保证。 - DON'T 在路由/service 层漏掉归属校验;这是本分支最容易出的越权洞。 - DON'T 新增前端自造状态 `ordered` 之外的字符串,状态值必须在契约文档里可枚举。 - DON'T 在本分支修改 `checkout/index.tsx`,地址选择器与下单集成留给 R3。 - DON'T 把 `wxfile://` 临时贴纸路径当作可持久化 URL 直接入库,R4 之前它只是本地预览。 - DON'T 用设计清单字段承载订单状态,`orderId` 派生展示即可。 ## 5. 补充内容 ### 验收标准 - 地址页与设计清单页在登录态下增删改查、默认切换、批量删除全部走真实后端并刷新。 - 后端所有变更接口带本人校验,越权访问返回 403,不存在资源返回 404。 - 默认地址永远唯一;删除默认地址后列表自动产生新的默认地址。 - 前端构建与后端构建通过;Swagger 涵盖地址与设计清单全部接口。 - 状态映射表在 `docs/api-contract-v1.md` 中落地,前端页面不再出现魔法字符串状态。 ### 合并与依赖 - 分支名:前端与后端均为 `feat/r2-address-design`。 - R2 依赖已有登录体系(已完成),不依赖 R1;可与 R1 并行开发。 - checkout 对地址 API 的调用在 R3 接入,R2 只需保证接口稳定即可。 ### 风险 - 设计数据中的贴纸是本地临时路径,一旦真机重启或清理缓存,旧设计预览会裂图;在 R4 上传能力完成前,文档明确这是已知限制。 - 旧版本本地 `smart_design_list_` 数据与后端数据可能并存,首次切换登录时避免双写冲突;可做一次性“导入本地清单”或直接忽略旧数据。 - 批量删除接口若后端未实现,前端循环删除会遇到部分失败,联调时先约定失败语义。 ### 测试要求 - 后端:地址 CRUD、默认地址唯一性事务、越权访问、设计清单状态迁移、超大 JSON 拒绝。 - 前端:地址新增/编辑/删除/默认切换、设计清单筛选/批量删除、断网降级提示、401 引导登录。