Files
zt-closet-manage-frontend/.codebuddy/plans/goods-virtualpay-register_260b5ea9.md
T
2026-09-14 01:16:41 +08:00

126 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: goods-virtualpay-register
overview: 管理后台与管理前端支持登记/修改微信虚拟支付道具信息(product_id、product_status),发布状态仅支持未发布/已发布两态,运营无需再手动改数据库。
todos:
- id: backend-goods-register
content: 扩展管理后端:dto 增道具字段、errcode 增错误码、DAO 增按道具 ID 查询、Service 增写入与合并态校验
status: completed
- id: frontend-goods-form
content: 扩展管理前端:类型与常量增道具字段,GoodsListView 表单增校验、改价提醒与列表展示
status: completed
dependencies:
- backend-goods-register
- id: docs-and-verify
content: 更新管理后端 README 道具登记说明;执行 go build 与前端 type-check、build 验证
status: completed
dependencies:
- backend-goods-register
- frontend-goods-form
---
## 用户需求
管理后台(zt-closet-manage-backend)与管理前端(zt-closet-manage-frontend)当前缺少微信虚拟支付道具信息的填写能力:商品表已有 product_id(道具 ID)与 product_status(发布状态)两列,但管理端仅只读展示,运营在微信后台创建道具后只能人工改库。本次需补上登记能力,让商品被小程序虚拟支付下单不再依赖人工改库。
## 产品概述
商品新增/编辑表单增加「道具 ID」与「道具发布状态」两个可编辑项,运营填写并标记「已发布」后,该商品即可被小程序虚拟支付下单;列表可直观核对道具登记情况;改价时给出同步微信侧的价格风险提醒。
## 核心功能
- 表单填写:新增/编辑商品时填写道具 ID(最长 64 字)并选择发布状态(仅「未发布 / 已发布」两态)。
- 校验:发布状态选「已发布」时必须填写道具 ID;道具 ID 不得与其他商品重复。
- 列表展示:商品列表「虚拟支付」列展示发布状态标签与道具 ID,便于与微信后台核对。
- 风险提醒:编辑已发布商品且修改售价时,提交前弹确认提醒——必须同步微信后台道具价格,否则用户付款后无法发货。
- 提示文案:明确需与微信后台保持一致的三项为「道具 ID / 道具名称 / 道具价格」。
- 兼容历史数据:旧的「发布中 / 发布失败」状态在编辑表单中归一显示为「未发布」。
## 约束边界
- 仅做登记与状态标记,不调用微信接口自动创建/发布/改价道具(运营仍在微信后台手动操作)。
- 不改动小程序业务服务及下单、发货链路。
## 技术栈
- 管理后端(/root/zt-closet-manage-backend):Go + Gin + GORM,四层架构(router → service → dao → model)、注册式错误码、统一响应;无测试文件,验证以 go build / go vet 为主。
- 管理前端(/root/zt-closet-manage-frontend):Vue 3 + TypeScript + Element Plus + Vite;表单校验用 Element Plus FormRules,枚举统一维护在 src/utils/constants.ts。
- 约束参考(不改动):业务服务下单校验要求 product_id 非空且 product_status=2 才可下单(zt-closet-backend internal/service/virtualpay.go),本次改动与该约束对齐。
## 实现方案
沿用两端现有分层与表单模式:后端「新增可写字段 + 合并态校验 + 唯一性校验」,前端「新增表单字段 + 校验 + 价格同步提醒」。
### 管理后端
1. internal/dto/goods.go:GoodsCreateRequest 增加 ProductID string(binding:"omitempty,max=64")、ProductStatus int8(binding:"omitempty,oneof=0 2");GoodsUpdateRequest 增加 ProductID *string、ProductStatus *int8(同 binding,指针区分「未传」)。
2. pkg/errcode/admin_code.go 商品域新增:GoodsProductInvalid=17003「虚拟支付道具信息不合法」、GoodsProductIDExists=17004「该道具 ID 已被其他商品使用」。
3. internal/mysqldao/goods.go 新增 GoodsGetByProductID(ctx, productID):默认作用域(排除软删)按 product_id 等值查询,未找到返回 gorm.ErrRecordNotFound,复用 isNotFound 判定。
4. internal/service/goods.go:
- Create:trim 后写入 ProductID / ProductStatus;校验「ProductStatus=2 且 ProductID 为空 → GoodsProductInvalid」;ProductID 非空时查唯一性,被占用则返回 GoodsProductIDExists。
- Update:捕获已查出的现有商品,用「现有值 + 本次改动」的合并态校验(effective status=2 时 effective product_id 必填);仅当 ProductID 有传且与现有不同时做唯一性查询(排除自身);随后写入 updates map(product_id / product_status)。
5. README.md:核心功能中「只读展示」改为「可登记(未发布/已发布两态)」;商品接口表下补充运营约定(道具 ID / 名称 / 价格须与微信后台一致、改价须同步微信侧、道具 ID 唯一)。
### 管理前端
6. src/types/goods.ts:GoodsCreateRequest / GoodsUpdateRequest 增加 product_id?: string、product_status?: number。
7. src/utils/constants.ts:新增 PRODUCT_STATUS_EDITABLE = enumMap([{value:0,label:'未发布'},{value:2,label:'已发布',tagType:'success'}]),仅供表单下拉;原 PRODUCT_STATUS 四态枚举保留给列表展示,兼容历史数据。
8. src/views/goods/GoodsListView.vue:
- GoodsForm 与 form 增加 product_id / product_status;openCreate 重置为 '' / 0;openEdit 回填(product_status === 2 ? 2 : 0,历史 1/3 归一为 0)。
- 表单在「售价」后插入「道具 ID」输入框(maxlength=64)与「道具发布状态」下拉(enumOptions(PRODUCT_STATUS_EDITABLE)),各带 form-tip 提示。
- rules 增加校验:product_id 自定义 validator(发布状态为已发布时必填、≤64 字);product_status 自定义 validator(=2 且 product_id 为空报错)。
- submitForm:create / update payload 带上 product_id(trim 后)与 product_status;编辑时若原状态为 2 且售价变化,先 ElMessageBox.confirm 提醒同步微信侧价格,取消则不提交。
- 列表「虚拟支付」列宽 110→140,有 product_id 时展示状态 tag + 道具 ID 小字(超长省略)。
9. src/api/goods.ts 不改(请求类型透传,自动生效)。
## 实现要点
- 一致性关键:Update 必须用合并态校验,避免「只改其中一个字段」绕过(如单独把 product_status 改为 2 而 product_id 为空)。
- 性能:goods 为运营级小表,唯一性查询为单表等值查询,开销可忽略,无需新增索引或迁移。
- 兼容性:列表展示继续用四态枚举;表单归一化历史 1/3 值,不改存量数据。
- 爆炸半径:仅管理端商品模块与商品表单;不动下单/发货/退款链路、不动业务服务、不动前端 api 层。
- 错误处理:统一走注册式错误码,前端请求层已有统一提示,无需新增日志。
## 目录结构
```
/root/zt-closet-manage-backend/
├── internal/dto/goods.go # [MODIFY] 创建/更新请求增加 product_id、product_status 字段与 binding 校验
├── internal/service/goods.go # [MODIFY] Create/Update 写入道具字段;合并态校验(已发布必须有道具 ID);道具 ID 唯一性校验
├── internal/mysqldao/goods.go # [MODIFY] 新增 GoodsGetByProductID(排除软删),供唯一性校验
├── pkg/errcode/admin_code.go # [MODIFY] 商品域新增 17003/17004 错误码
└── README.md # [MODIFY] 商品管理说明改为可登记两态,补充道具运营约定
/root/zt-closet-manage-frontend/
├── src/types/goods.ts # [MODIFY] 创建/更新请求类型增加 product_id、product_status
├── src/utils/constants.ts # [MODIFY] 新增 PRODUCT_STATUS_EDITABLE(0/2 两态)供表单下拉
└── src/views/goods/GoodsListView.vue # [MODIFY] 表单新增道具 ID 与发布状态、校验、提交、改价提醒、列表列展示道具 ID
```
## 关键代码结构
```
// internal/dto/goods.go —— 请求新增字段
// GoodsCreateRequest
ProductID string `json:"product_id" binding:"omitempty,max=64"`
ProductStatus int8 `json:"product_status" binding:"omitempty,oneof=0 2"`
// GoodsUpdateRequest
ProductID *string `json:"product_id" binding:"omitempty,max=64"`
ProductStatus *int8 `json:"product_status" binding:"omitempty,oneof=0 2"`
```
```
// internal/service/goods.go —— Update 合并态校验思路
existing, err := s.goodsDao.GoodsGetByID(ctx, id) // 捕获返回值参与校验
// ...
effectiveProductID := existing.ProductID
if request.ProductID != nil { effectiveProductID = strings.TrimSpace(*request.ProductID) }
effectiveStatus := existing.ProductStatus
if request.ProductStatus != nil { effectiveStatus = *request.ProductStatus }
if effectiveStatus == 2 && effectiveProductID == "" {
return errcode.GoodsProductInvalid.WithDetails("发布状态为已发布时必须填写道具 ID")
}
```