first commit
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
---
|
||||
name: zt-closet-wxmini 前端实现
|
||||
overview: 基于 stitch_ai_ui 的 14 个高保真页面原型与 zt-closet-backend 的 42 个接口,在现有微信小程序骨架(TypeScript + skyline + glass-easel)上,完整实现「纸条AI衣橱」小程序:网络层、鉴权、5 个 tabBar 页与 8 个二级页,全部接通后端接口。
|
||||
design:
|
||||
styleKeywords:
|
||||
- iOS HIG
|
||||
- 清爽克制
|
||||
- 毛玻璃
|
||||
- 发丝分隔线
|
||||
- 系统蓝
|
||||
- SF Symbols 线性图标
|
||||
fontSystem:
|
||||
fontFamily: PingFang SC
|
||||
heading:
|
||||
size: 34px
|
||||
weight: 700
|
||||
subheading:
|
||||
size: 17px
|
||||
weight: 600
|
||||
body:
|
||||
size: 15px
|
||||
weight: 400
|
||||
colorSystem:
|
||||
primary:
|
||||
- "#007AFF"
|
||||
- "#5856D6"
|
||||
- "#FF2D55"
|
||||
background:
|
||||
- "#F2F2F7"
|
||||
- "#FFFFFF"
|
||||
text:
|
||||
- "#000000"
|
||||
- "#8E8E93"
|
||||
- "#C7C7CC"
|
||||
functional:
|
||||
- "#34C759"
|
||||
- "#FF9500"
|
||||
- "#FF3B30"
|
||||
- "#FFCC00"
|
||||
todos:
|
||||
- id: network-foundation
|
||||
content: 搭建 request/token 刷新与全局配置、工具函数
|
||||
status: completed
|
||||
- id: tabbar-components
|
||||
content: 实现自定义 tabBar 与空态、骨架屏、衣物卡片等公共组件
|
||||
status: completed
|
||||
dependencies:
|
||||
- network-foundation
|
||||
- id: clothes-flow
|
||||
content: 实现登录、衣橱、上传、衣物详情页
|
||||
status: completed
|
||||
dependencies:
|
||||
- tabbar-components
|
||||
- id: outfit-flow
|
||||
content: 实现穿搭、穿搭详情、创建编辑穿搭页
|
||||
status: completed
|
||||
dependencies:
|
||||
- tabbar-components
|
||||
- id: ai-horoscope
|
||||
content: 实现 AI 推荐与运势页
|
||||
status: completed
|
||||
dependencies:
|
||||
- tabbar-components
|
||||
- id: notice-profile-share
|
||||
content: 实现通知、我的、衣柜分享(列表/创建/详情)页
|
||||
status: completed
|
||||
dependencies:
|
||||
- tabbar-components
|
||||
- id: mall-order
|
||||
content: 实现商城与订单支付退款页
|
||||
status: completed
|
||||
dependencies:
|
||||
- tabbar-components
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
「纸条AI衣橱」微信小程序:用户拍照上传衣物,AI 自动抠图并识别 15 项属性,自由组合衣物创建穿搭、生成 AI 穿搭推荐(结合星座运势)、把衣橱或精选衣物分享给好友、在社区公开穿搭并互动,还可购买会员或额度包解锁更多次数。产品气质为清爽、克制、可信赖的私人衣橱助手。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 微信授权登录:`wx.login` 换 token,首次登录自动注册并发放免费额度
|
||||
- 衣橱管理:两列衣物网格、分类筛选、上传衣物(异步 AI 处理 + 状态轮询)、衣物详情查看与属性编辑、删除(含额度回退规则)
|
||||
- 穿搭管理:我的穿搭/已收藏分段、创建/编辑穿搭(1~10 件衣物多选)、穿搭详情、公开到社区(扣额度)、点赞与收藏
|
||||
- AI 穿搭推荐:场景选择、生成中状态、结果预览、保存为穿搭(含 180 秒并发锁与额度不足处理)
|
||||
- 每日运势:星座自动推算,综合/爱情/工作/财富四项评分 + 幸运色/幸运数字
|
||||
- 衣柜分享:我的分享列表、创建分享(部分/全量 + 有效期)、分享详情(属主/访客视角)
|
||||
- 通知中心:列表/详情/未读红点/单条已读
|
||||
- 额度与会员商城、订单支付(微信 JSAPI 支付)与退款申请
|
||||
- 个人资料:头像/昵称/性别/生日编辑、我的额度/订单/分享/收藏/额度明细入口
|
||||
|
||||
## 视觉规范
|
||||
|
||||
严格遵循 iOS HIG 设计语言:系统分组灰底 #F2F2F7 + 纯白卡片 + 0.5px 发丝分隔线 #E5E5EA,强调色系统蓝 #007AFF,SF Symbols 风格线性图标,底部毛玻璃 tabBar,所有页面为 750rpx 宽、左右安全边距 32rpx。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 平台:微信小程序(TypeScript),沿用现有 `skyline` 渲染器 + `glass-easel` 组件框架
|
||||
- 组件:不引入第三方组件库,纯 WXSS 手写,复用现有 `navigation-bar` 自定义导航栏
|
||||
- 网络:基于 `wx.request` 自建请求层(鉴权、统一响应解析、Token 自动刷新、错误码处理)
|
||||
- 后端:Go + Gin,统一前缀 `/api/v1`,42 个接口,接口契约以 `/root/zt-closet-backend/接口文档.md` 为准
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 整体策略
|
||||
|
||||
在现有小程序骨架(`miniprogram/`)上重写:清理模板页,新增网络层、公共组件、自定义 tabBar 与 16 个页面文件(UI 原型 14 个功能模块中,「衣柜分享」含 3 个子页面,落地为 3 个路由)。所有请求统一走 `request.ts`,页面只关心数据绑定与交互,遵循「页面 → 组件/工具 → 网络层 → 后端 API」的单向数据流。
|
||||
|
||||
### 模块划分
|
||||
|
||||
- **网络层(utils)**:`config.ts`(baseURL 占位 + 错误码表)、`request.ts`(统一请求 + 拦截逻辑)、`auth.ts`(token 存取)、`format.ts`(金额分转元、秒转时间、相对时间)
|
||||
- **公共组件(components)**:复用 `navigation-bar`;新增 `empty-state`(空态)、`skeleton`(骨架屏)、`clothes-card`(衣物卡片,含多选态)、`tag`(状态胶囊)
|
||||
- **全局壳**:`app.ts`/`app.json`/`app.wxss`(页面注册、登录态、全局样式变量)、`custom-tab-bar/`(5 tab 自定义 tabBar,base64 SVG 线性图标)
|
||||
- **页面层(16 个路由)**:5 个 tabBar 页 + 登录页 + 10 个二级页
|
||||
|
||||
### 系统架构
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Pages[页面层 16 路由]
|
||||
P1[登录]
|
||||
P2[衣橱 tab]
|
||||
P3[穿搭 tab]
|
||||
P4[运势 tab]
|
||||
P5[通知 tab]
|
||||
P6[我的 tab]
|
||||
P7[上传/衣物详情]
|
||||
P8[穿搭详情/创建编辑]
|
||||
P9[AI 推荐]
|
||||
P10[衣柜分享×3]
|
||||
P11[商城/订单]
|
||||
end
|
||||
subgraph Components[公共组件]
|
||||
C1[navigation-bar]
|
||||
C2[custom-tab-bar]
|
||||
C3[empty-state]
|
||||
C4[skeleton]
|
||||
C5[clothes-card]
|
||||
C6[tag]
|
||||
end
|
||||
subgraph Network[网络层]
|
||||
N1[request.ts 拦截/刷新]
|
||||
N2[auth.ts]
|
||||
N3[config.ts]
|
||||
end
|
||||
Pages --> Components
|
||||
Pages --> Network
|
||||
Network --> API[后端 /api/v1]
|
||||
```
|
||||
|
||||
### 关键实现要点
|
||||
|
||||
- **请求封装**:统一解析 `body.code`(业务成败只看 code,不依赖 HTTP 状态码);401 时自动调用 `POST /user/refresh-token`(同时传 refresh/access 两个 token),成功后整体替换并重试原请求,失败则清登录态重走登录;单独处理 `code===21001`(账号禁用,HTTP 200)与 `code===10003`(AI 并发锁,HTTP 429)
|
||||
- **Token 管理**:`access_token`/`refresh_token`/`expire_time` 存本地存储,所有需鉴权接口自动注入 `Authorization: Bearer`
|
||||
- **数据换算**:金额统一 `分 ÷ 100` 展示为 `¥ + 两位小数`;时间 `秒 × 1000`;相对时间格式化(刚刚/x分钟前/今天/YYYY-MM-DD)
|
||||
- **上传异步轮询**:上传返回 `clothes_uuid` 后轮询 `GET /clothes/:uuid/status`(1~2 秒,上限 60 次),终态 4 成功 / 5 失败(展示 `fail_reason`)
|
||||
- **字段陷阱**:更新衣物主色请求字段用 `color`(响应才是 `color_main`);通知 `is_read` 1=未读 2=已读;AI 推荐响应衣物列表字段名是 `outfit_item`;衣柜分享惰性过期需同时判断 `status` 与 `expire_time`
|
||||
- **性能**:列表分页 `page_size ≤ 50`,下拉刷新 + 上拉加载;图片懒加载;点赞/收藏做乐观更新 + 失败回滚(接口幂等)
|
||||
- **日志与安全**:请求失败仅记录 `code`/`msg`,不打印 token 与用户敏感信息;删除/取消/退款等不可逆操作全部二次确认
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
miniprogram/
|
||||
├── app.json # [MODIFY] 注册 16 个页面、5 个 tabBar、全局样式/渲染配置
|
||||
├── app.ts # [MODIFY] 全局登录态初始化、静默登录、全局数据
|
||||
├── app.wxss # [MODIFY] 全局样式变量(色板/圆角/间距/字阶)、通用卡片/按钮/表单样式
|
||||
├── utils/
|
||||
│ ├── config.ts # [NEW] baseURL 占位常量(HTTPS 域名待填)、错误码映射、额度/状态枚举
|
||||
│ ├── request.ts # [NEW] wx.request Promise 封装,注入鉴权头、解析 code、401 自动刷新重试、21001/10003 处理
|
||||
│ ├── auth.ts # [NEW] token 的读写清除、是否已登录判断、登出
|
||||
│ └── format.ts # [NEW] 金额/时间/相对时间格式化工具
|
||||
├── custom-tab-bar/
|
||||
│ ├── index.ts/.wxml/.wxss/.json # [NEW] 5 tab 自定义 tabBar,毛玻璃底 + 线性 SVG 图标,选中 #007AFF
|
||||
├── components/
|
||||
│ ├── navigation-bar/ # [REUSE] 复用现有自定义导航栏(返回/标题/右侧 slot)
|
||||
│ ├── empty-state/ # [NEW] 空态组件(线性插画占位 + 主文案 + 说明 + 按钮)
|
||||
│ ├── skeleton/ # [NEW] 骨架屏卡片(呼吸动画)
|
||||
│ ├── clothes-card/ # [NEW] 衣物卡片(1:1 图区、处理状态徽标、多选态描边 + 对勾)
|
||||
│ └── tag/ # [NEW] 状态胶囊标签(系统色浅底 + 深字)
|
||||
└── pages/
|
||||
├── login/ # [NEW] 微信授权登录页:插画 + 产品名 + 微信一键登录
|
||||
├── wardrobe/ # [NEW] 衣橱 tab:分类筛选 + 统计 + 两列网格 + 悬浮上传按钮
|
||||
├── upload/ # [NEW] 上传衣物:拍照/相册 + 处理中步骤条 + 失败态
|
||||
├── clothes-detail/ # [NEW] 衣物详情:大图 + 15 项属性 + 编辑态 + 删除
|
||||
├── outfit/ # [NEW] 穿搭 tab:我的穿搭/已收藏分段 + 卡片流
|
||||
├── outfit-detail/ # [NEW] 穿搭详情:拼图封面 + 点赞/收藏/公开 + 更多面板
|
||||
├── outfit-edit/ # [NEW] 创建/编辑穿搭:表单 + 衣物多选 + 保存/删除
|
||||
├── ai-recommend/ # [NEW] AI 推荐:场景选择 + 生成中 + 结果页 + 保存
|
||||
├── horoscope/ # [NEW] 运势 tab:星座大卡 + 四项评分 + 幸运色/数字
|
||||
├── notification/ # [NEW] 通知 tab:筛选 + 列表 + 未读红点
|
||||
├── notification-detail/ # [NEW] 通知详情:进入即标记已读
|
||||
├── closet-share/ # [NEW] 衣柜分享列表:我的分享 + 新建入口
|
||||
├── closet-share-create/ # [NEW] 创建分享:类型选择 + 衣物多选 + 有效期面板
|
||||
├── closet-share-detail/ # [NEW] 分享详情:属主/访客视角 + 失效态
|
||||
├── mall/ # [NEW] 额度与会员商城:会员卡 + 额度网格 + 商品列表 + 订单确认
|
||||
├── order/ # [NEW] 我的订单:状态筛选 + 支付/取消/退款
|
||||
└── profile/ # [NEW] 我的 tab:个人信息 + 功能列表 + 编辑资料
|
||||
```
|
||||
|
||||
## 实施注意事项
|
||||
|
||||
- **保留向后兼容**:不删除现有 `navigation-bar` 组件;仅删除模板页 `pages/logs` 并替换 `pages/index`
|
||||
- **渲染兼容**:保留 `skyline` 渲染器;自定义 tabBar 若在 skyline 下定位异常,回退方案为在 `app.json` 移除 `renderer`/`componentFramework` 切回 webview(单点配置,不影响页面代码)
|
||||
- **appid 对齐风险**:`project.config.json` 的 appid(`wxfc03c59fc1bb4de5`)与后端 `configs/config.yaml` 中 `WechatMini.AppID`(`wx47cffdcb7f8c57c3`)不一致,会导致 `wx.login` 换 openid 失败,联调前需由用户统一
|
||||
- **baseURL 占位**:`config.ts` 中保留 HTTPS 域名占位常量,用户填写真实域名后全局生效,无需改页面代码
|
||||
|
||||
## 设计风格
|
||||
|
||||
遵循 Apple HIG 的 iOS 原生视觉语言:清爽、克制、以内容为中心。页面背景统一 iOS 分组灰 #F2F2F7,内容承载于纯白卡片(圆角 16pt),用 0.5px 发丝分隔线区分层次,避免浓烈渐变与彩色阴影;强调色系统蓝 #007AFF,情感化点赞/收藏用系统粉 #FF2D55,辅助靛蓝 #5856D6。图标为 SF Symbols 风格线性描边,底部 tabBar 采用半透明毛玻璃 + 顶部发丝线。
|
||||
|
||||
## 布局与交互
|
||||
|
||||
- 顶部:复用自定义 navigation-bar,二级页左上角返回箭头,右上角保留微信胶囊安全区
|
||||
- 底部:自定义 tabBar 五个入口(衣橱/穿搭/运势/通知/我的),选中系统蓝、未选中系统灰
|
||||
- 内容:两列衣物网格(1:1 图区、12pt 间距);穿搭/AI 推荐为纵向卡片流 + 错落拼图封面
|
||||
- 反馈:空态居中线性插画 + 主文案 + 引导按钮;加载态骨架屏呼吸动画;轻提示毛玻璃圆角浮层;确认操作为 iOS 居中 Alert;底部动作面板为 iOS sheet(grabber + 圆角 + 取消项独立成组)
|
||||
- 列表均支持下拉刷新与上拉加载,分页每页不超过 50 条
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: 修复头像上传临时路径误判
|
||||
overview: 修复「我的」编辑头像保存不生效:微信 chooseAvatar 返回的临时路径以 http://tmp/ 开头,导致 onSaveProfile 的 startsWith('http://') 判断误判为已上传 URL 而跳过上传。改用 avatarChanged 标志位判断是否新选头像。
|
||||
todos:
|
||||
- id: fix-avatar-changed-flag
|
||||
content: 修改 profile.ts:新增 avatarChanged 标志位并替换 onSaveProfile 的头像判断逻辑
|
||||
status: completed
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
修复「我的」页面编辑资料时头像上传后保存不生效的问题,使选择的新头像能正常上传到服务端并持久保存。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 用户在编辑资料弹窗中选择新头像后,点击保存能正确触发头像上传。
|
||||
- 头像上传成功后与昵称、性别、生日一并保存,刷新后头像正确展示。
|
||||
- 未选择新头像时仍保持原有头像,不重复上传。
|
||||
|
||||
## 根因
|
||||
|
||||
`<button open-type="chooseAvatar">` 返回的 `avatarUrl` 是微信本地临时文件路径,格式以 `http://tmp/` 开头。原 `onSaveProfile` 用「非 `http://`/`https://` 开头」判断是否为临时头像,导致 `http://tmp/` 开头的临时路径被误判为已上传 URL,从而跳过 `/user/avatar/upload` 上传,头像既未上传也未保存。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 平台:微信小程序原生 TypeScript,沿用现有架构。
|
||||
- 网络层:复用 `miniprogram/utils/request.ts` 的 `request<T>` 与 `uploadFile<T>`。
|
||||
- 后端接口:复用已就绪的 `POST /user/avatar/upload`(multipart,字段名 `file`,响应 `{ avatar }`),本次无需改动。
|
||||
|
||||
## 实现方案
|
||||
|
||||
用「本次编辑是否新选了头像」的标志位 `avatarChanged` 替代 URL 前缀判断,从根本上规避微信临时路径 `http://tmp/` 开头造成的误判。
|
||||
|
||||
### 关键改动(仅 `miniprogram/pages/profile/profile.ts`)
|
||||
|
||||
1. `data` 新增 `avatarChanged: false`。
|
||||
2. `onEdit` 打开编辑弹窗时重置 `avatarChanged: false`,保证每次打开都是干净状态。
|
||||
3. `onChooseAvatar` 在写入 `editForm.avatar` 的同时置 `avatarChanged: true`。
|
||||
4. `onSaveProfile` 删除 `startsWith('http://')`/`startsWith('https://')` 判断,改为 `if (this.data.avatarChanged && avatar)` 才先调用 `uploadFile(avatar, 'file', '/user/avatar/upload')` 上传;上传成功后再 PUT `/user/profile`(body 仅含 username/sex/birth_day,不含 avatar,避免持久化带签名的临时 URL)。
|
||||
5. 保留 `avatarUploading` 防重复提交与失败复位逻辑不变。
|
||||
|
||||
### 实现注意事项
|
||||
|
||||
- 上传成功后仅依赖 `/user/avatar/upload` 后端写入 `users.avatar`,前端不把上传返回的 URL 写回 `/user/profile`,与上一轮设计保持一致。
|
||||
- `avatarChanged` 只用于「是否上传」,上传完成后无需复位为 `false`(弹窗随即关闭;下次打开由 `onEdit` 重置)。
|
||||
- 兼容性:未选头像(`avatarChanged=false`)时行为与原逻辑一致,直接保存昵称/性别/生日。
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: 修复用户头像上传
|
||||
overview: 前端「编辑资料」头像选择当前只保存微信临时文件路径,未调用后端头像上传接口,导致头像不可用。修复为:选择头像后预览,保存时先调用 POST /user/avatar/upload 上传,再更新资料。
|
||||
todos:
|
||||
- id: extend-upload-file
|
||||
content: 扩展 uploadFile 支持自定义上传 url(默认 /clothes/upload)
|
||||
status: completed
|
||||
- id: fix-avatar-save
|
||||
content: 重构 profile 保存流程:先上传头像再保存资料
|
||||
status: completed
|
||||
dependencies:
|
||||
- extend-upload-file
|
||||
- id: update-profile-ui
|
||||
content: 更新编辑弹窗上传中状态与禁用样式
|
||||
status: completed
|
||||
dependencies:
|
||||
- fix-avatar-save
|
||||
---
|
||||
|
||||
# 修复头像上传与获取链路
|
||||
|
||||
## 产品概述
|
||||
|
||||
修复「我的」页面头像上传不可用问题,并一并修复头像获取链路的两处隐患(分享者头像 URL 转换、前端缓存时效),使头像上传与展示均正确且持久生效。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 选择新头像后正常上传并持久保存,与昵称/性别/生日一并生效。
|
||||
- 分享场景分享者头像(owner_avatar)正确转换,不再生成错误 URL。
|
||||
- 前端头像展示不再因缓存过期签名 URL 而失效。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 小程序:原生 TypeScript,复用 `request` / `uploadFile`。
|
||||
- 后端:Go,复用 `pkg/upload` 与既有 service 层。
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 一、上传修复(前端)
|
||||
|
||||
- `utils/request.ts`:`uploadFile` 增加可选 `url` 参数(默认 `/clothes/upload`),保持衣物上传调用方零改动。
|
||||
- `pages/profile/profile.ts`:新增 `avatarUploading` 状态;`onSaveProfile` 判断头像为临时路径(非 `http://`/`https://` 开头)时,先调 `/user/avatar/upload`(字段名 `file`,响应 `{ avatar }`),成功后再 PUT `/user/profile`(body 仅含 username/sex/birth_day,不含 avatar)。
|
||||
- `pages/profile/profile.wxml` + `wxss`:保存按钮在上传中显示「上传中…」并禁用。
|
||||
|
||||
### 二、后端头像 URL 转换统一
|
||||
|
||||
- `pkg/upload/upload.go`:新增 `AvatarURL(raw string) string`——空串返回空;`http://`/`https://` 开头直接透传;否则 `GetCosUrl`。
|
||||
- `internal/service/user.go`:`buildUserInfoResponse` 改用 `upload.AvatarURL`。
|
||||
- `internal/service/closet.go`:详情与列表两处 owner_avatar 改用 `upload.AvatarURL`,删除无条件 `GetCosUrl`。
|
||||
|
||||
### 三、前端缓存时效修复
|
||||
|
||||
- `pages/closet-share-create/closet-share-create.ts`:`onLoad` 先读本地缓存兜底,再请求 `/user/info` 实时刷新昵称与头像并回写缓存(签名 URL 带 `t` 时间戳,缓存会过期)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
- 前端:`miniprogram/utils/request.ts`、`pages/profile/{profile.ts,profile.wxml,profile.wxss}`、`pages/closet-share-create/closet-share-create.ts`
|
||||
- 后端:`pkg/upload/upload.go`、`internal/service/user.go`、`internal/service/closet.go`
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
name: 前后端业务逻辑一致性检查报告
|
||||
overview: 全量比对后端 Go 服务(zt-closet-backend)与微信小程序前端(zt-closet-wxmini-frontend)的业务逻辑,逐模块核对接口路径、请求/响应字段、枚举值、状态机、错误码的一致性,最终产出一份差异清单报告(不改任何代码)。
|
||||
todos:
|
||||
- id: explore-backend
|
||||
content: 用 [subagent:code-explorer] 探索后端 dto/service/errcode/constants,提取权威契约清单
|
||||
status: completed
|
||||
- id: explore-frontend
|
||||
content: 用 [subagent:code-explorer] 探索前端 pages/utils/types,提取实际调用契约清单
|
||||
status: completed
|
||||
- id: compare-core
|
||||
content: 对比用户/额度/运势/衣物模块,记录差异与严重程度
|
||||
status: completed
|
||||
dependencies:
|
||||
- explore-backend
|
||||
- explore-frontend
|
||||
- id: compare-social
|
||||
content: 对比穿搭/AI推荐/衣柜分享/收藏点赞/通知模块,记录差异与严重程度
|
||||
status: completed
|
||||
dependencies:
|
||||
- explore-backend
|
||||
- explore-frontend
|
||||
- id: compare-trade
|
||||
content: 对比商品/订单/虚拟支付模块,并全表核对错误码与枚举
|
||||
status: completed
|
||||
dependencies:
|
||||
- explore-backend
|
||||
- explore-frontend
|
||||
- id: gen-report
|
||||
content: 汇总生成《前后端业务逻辑一致性检查报告》
|
||||
status: completed
|
||||
dependencies:
|
||||
- compare-core
|
||||
- compare-social
|
||||
- compare-trade
|
||||
---
|
||||
|
||||
## 用户需求
|
||||
|
||||
检查后端业务服务全量代码(`~/zt-closet-backend`),确保前端微信小程序与后端业务逻辑一致。
|
||||
|
||||
## 已确认的处理方式
|
||||
|
||||
1. **仅输出不一致报告**:以差异清单形式呈现(接口路径、字段、枚举值、状态机、错误码等),**不修改任何前后端代码**。
|
||||
2. **不核对后端《接口文档.md》**:本次只关注「前端代码」与「后端源码」的一致性,以 `/root/zt-closet-backend` 的 Go 源码为权威基准。
|
||||
|
||||
## 核心产出
|
||||
|
||||
产出一份《前后端业务逻辑一致性检查报告》,按业务模块(用户 / 额度 / 运势 / 衣物 / 穿搭 / AI 推荐 / 衣柜分享 / 收藏点赞 / 通知 / 商品 / 订单支付)分类,逐项列出:
|
||||
|
||||
- 不一致位置(前端文件:行 + 后端文件:行)
|
||||
- 差异描述
|
||||
- 严重程度(阻断 / 高风险 / 低风险 / 建议)
|
||||
- 建议修复方向
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 后端(权威基准,只读分析):Go + Gin,位于 `/root/zt-closet-backend`
|
||||
- 前端(被检查方,只读分析):微信小程序原生 + TypeScript,位于 `/root/zt-closet-wxmini-frontend/miniprogram`
|
||||
- 比对方法:静态源码契约提取 + 逐模块交叉比对,不涉及运行时
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 核心策略
|
||||
|
||||
采用「契约提取—交叉比对」方法:
|
||||
|
||||
1. **提取后端权威契约**:以 `internal/routers/router.go` 注册的 42 个接口为主线,通读 `internal/routers/api/v1/`、`internal/dto/`、`internal/service/`、`pkg/errcode/`,提取每个接口的路径、HTTP 方法、请求字段(名称/必填/类型/枚举)、响应字段(名称/类型/单位)、枚举值、状态机规则、错误码。
|
||||
2. **提取前端实际契约**:通读 17 个页面 `.ts` 与 `utils/request.ts`、`utils/config.ts`、`utils/auth.ts`、`types/models.ts`、`typings/index.d.ts`,提取实际调用的接口路径、请求体字段名、响应字段名、枚举常量定义、错误码映射表、关键业务规则处理(轮询/幂等/额度回退/惰性过期等)。
|
||||
3. **逐模块交叉比对**:后端契约 vs 前端契约,识别字段名不一致、枚举值缺失/错位、错误码遗漏、单位换算缺失、状态机处理缺失等问题。
|
||||
|
||||
### 比对维度(关键决策)
|
||||
|
||||
- 接口路径与 HTTP 方法是否一致
|
||||
- 请求字段名 / 必填性 / 类型 / 枚举取值是否一致
|
||||
- 响应字段名 / 类型 / 单位(金额分、时间秒)是否一致
|
||||
- 枚举常量值与状态机定义是否一致
|
||||
- 错误码映射表是否完整且值正确
|
||||
- 关键业务规则前端是否实现(上传轮询、支付后轮询订单、点赞乐观更新、分享惰性过期、AI 推荐额度不退还提示等)
|
||||
|
||||
### 严重程度分级
|
||||
|
||||
- **阻断**:导致功能不可用或数据错误(如字段名错误、请求路径错误、枚举值错位)
|
||||
- **高风险**:特定场景下功能出错或体验严重受损(如单位未换算、错误码遗漏导致误提示)
|
||||
- **低风险**:边缘场景或文案差异
|
||||
- **建议**:非功能性优化建议
|
||||
|
||||
## 实现要点
|
||||
|
||||
- **不修改任何代码**,唯一新增物为报告文件 `/root/zt-closet-wxmini-frontend/前后端业务逻辑一致性检查报告.md`。
|
||||
- 以后端源码为唯一权威,前端为被检查对象。
|
||||
- 重点核对项:
|
||||
- 虚拟支付链路:`/order/virtual` + `wx.requestVirtualPayment`,字段 `sign_data`/`pay_sig`/`signature`、`platform` 枚举 `android|ios|harmony|windows`、`mode` 取值,对照后端 `dto/order.go`、`service/virtualpay.go`。
|
||||
- 易错字段:主颜色请求字段 `color` vs 响应 `color_main`;通知 `is_read` 1=未读/2=已读;`/like/:targetUUID` 路径参数驼峰;AI 推荐响应字段 `outfit_item`(单数)。
|
||||
- 额度与状态机:上传/删除回退规则、公开扣额度、AI 生成失败回退、衣柜分享惰性过期。
|
||||
- 错误码全表:前端 `ERROR_MESSAGES` 与后端 `pkg/errcode` 逐项核对,确认缺失与值错误。
|
||||
- 疑似缺口:前端未调用 `/ad-reward/grant`(广告激励额度发放)入口,需确认是否属功能缺失。
|
||||
|
||||
## 目录结构
|
||||
|
||||
本次任务不修改现有代码,仅新增一份报告文件;以下为报告产出位置与需要通读的源码范围。
|
||||
|
||||
```
|
||||
/root/zt-closet-wxmini-frontend/
|
||||
└── 前后端业务逻辑一致性检查报告.md # [NEW] 唯一产出物,按业务模块分类记录差异清单
|
||||
|
||||
只读分析范围(后端):
|
||||
/root/zt-closet-backend/
|
||||
├── internal/routers/router.go # 42 个接口注册主线
|
||||
├── internal/routers/api/v1/ # 13 个 handler
|
||||
├── internal/dto/ # 14 个请求/响应 DTO
|
||||
├── internal/service/ # 18 个业务逻辑(含 virtualpay/constants)
|
||||
└── pkg/errcode/ # 10 个错误码文件
|
||||
|
||||
只读分析范围(前端):
|
||||
/root/zt-closet-wxmini-frontend/miniprogram/
|
||||
├── pages/ # 17 个页面 .ts
|
||||
├── utils/request.ts / config.ts / auth.ts # 请求封装 / 枚举与错误码 / Token
|
||||
├── types/models.ts # 前端实体类型
|
||||
└── typings/index.d.ts # 全局类型声明
|
||||
```
|
||||
|
||||
## 推荐使用的 Agent 扩展
|
||||
|
||||
### SubAgent
|
||||
|
||||
- **code-explorer**
|
||||
- 用途:后端(dto/service/errcode/constants 等 60+ 文件)与前端(17 页面 + utils + types)体量较大,使用该子代理跨目录、多文件并行探索,高效提取接口契约与调用点,避免主上下文被大量源码淹没。
|
||||
- 预期结果:产出后端权威契约清单(接口字段/枚举/错误码/状态机)与前端实际调用契约清单(路径/字段/枚举/错误码映射/业务规则处理),作为逐模块比对的输入。
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: 前端与后端一致性修复
|
||||
overview: 根据上一轮全量检查报告,修复前端代码使其与已更新的后端完全一致(广告激励功能暂不实现、通知筛选后端已配合无需改前端),覆盖虚拟支付轮询、收藏状态初始化、类型字段补齐、错误码补充等。
|
||||
todos:
|
||||
- id: fix-types-errors
|
||||
content: 补齐 models.ts 类型字段与 config.ts 70007 错误码
|
||||
status: completed
|
||||
- id: fix-collect-status
|
||||
content: outfit-detail.ts 调用收藏状态接口初始化 isCollected
|
||||
status: completed
|
||||
dependencies:
|
||||
- fix-types-errors
|
||||
- id: fix-pay-polling
|
||||
content: mall.ts 支付成功后按 order_no 轮询订单确认到账
|
||||
status: completed
|
||||
dependencies:
|
||||
- fix-types-errors
|
||||
- id: fix-order-channel
|
||||
content: order.ts 与 order.wxml 区分支付渠道并禁用虚拟订单取消
|
||||
status: completed
|
||||
dependencies:
|
||||
- fix-types-errors
|
||||
- id: fix-ai-quota-tip
|
||||
content: ai-recommend.ts 生成前弹窗提示消耗 AI 额度
|
||||
status: completed
|
||||
- id: fix-login-guide
|
||||
content: login.ts 新用户登录后引导完善生日
|
||||
status: completed
|
||||
---
|
||||
|
||||
## 用户需求
|
||||
|
||||
用户已修复后端两点:通知列表 `msg_type` 筛选、新增 `GET /collect/:targetUUID` 收藏状态查询接口。现要求前端按修复后的后端对齐,其中「广告激励额度发放」功能暂不实现,其余按上一轮一致性检查报告的结论修复前端。
|
||||
|
||||
## 核心修复项
|
||||
|
||||
1. 补齐前端类型定义与错误码:`OrderItem` 补 `pay_channel`、`GoodsItem` 补 `ai_recommend_quota`/`valid_days`、`vip_type` 注释补体验会员、错误码表补 `70007`。
|
||||
2. 收藏状态正确初始化:穿搭详情页调用新增的收藏状态查询接口,不再写死为「未收藏」。
|
||||
3. 虚拟支付成功后轮询订单确认到账:支付组件 `success` 不再直接刷新权益,而是轮询订单直到 `order_status=2`。
|
||||
4. 订单页区分支付渠道:虚拟支付订单禁用「取消订单」入口。
|
||||
5. AI 推荐生成前提示将消耗 1 次 AI 额度。
|
||||
6. 新用户登录后引导完善生日。
|
||||
|
||||
## 边界
|
||||
|
||||
- 只改前端代码,不改后端。
|
||||
- 广告激励(`/ad-reward/grant`)不实现。
|
||||
- 通知列表前端无需改动(后端已配合支持 `msg_type`)。
|
||||
- 保持现有小程序原生 + TypeScript 风格,金额单位分、时间单位秒的换算不变。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 前端:微信小程序原生 + TypeScript
|
||||
- 复用现有 `miniprogram/utils/request.ts` 的 `request<T>` 封装与 `utils/config.ts` 的枚举/错误码表
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. 类型与错误码补齐(models.ts + config.ts)
|
||||
|
||||
- `types/models.ts`
|
||||
- `OrderItem` 增补 `pay_channel: number`(注释:1=微信支付 2=虚拟支付)。
|
||||
- `GoodsItem` 增补 `ai_recommend_quota: number`(赠送 AI 推荐额度)、`valid_days: number`(加量包有效期天数)。
|
||||
- `UserInfo.vip_type` 注释由「1 非会员 2 月度会员 3 年度会员」补齐为「1 非会员 2 月度会员 3 年度会员 4 体验会员」。
|
||||
- `utils/config.ts` 的 `ERROR_MESSAGES` 增补 `70007: '虚拟支付订单不支持取消'`。
|
||||
|
||||
### 2. 收藏状态初始化(outfit-detail.ts)
|
||||
|
||||
- 在 `fetchDetail()` 中与点赞状态一起并行请求,新增调用 `request<{ is_collected: boolean; collect_count: number }>({ url: '/collect/' + this.data.uuid, showError: false })`。
|
||||
- 用响应 `is_collected` 初始化 `this.data.isCollected`;`collect_count` 可存入 `data` 备用,失败静默保持 `false`,不影响原有点赞与详情加载。
|
||||
|
||||
### 3. 支付后轮询订单确认(mall.ts)
|
||||
|
||||
- 将 `doPayment(res)` 的 `success` 回调改为调用新增的 `pollOrder(res.order_no)`。
|
||||
- `pollOrder(orderNo)` 实现:每 1.5s 调用 `request<PagedResult<OrderItem>>({ url: '/order', data: { page: 1, page_size: 50 } })`,利用后端 `Order("id DESC")` 新订单在第一页的特性,在 `list` 中按 `order_no` 匹配:
|
||||
- `order_status === 2`:停止轮询,`showToast('支付成功')` 并 `fetchQuota()` + `fetchUser()`。
|
||||
- `order_status` 为 3/4/5:停止轮询,提示对应结果。
|
||||
- `order_status === 1`:继续轮询。
|
||||
- 超过约 10 次:停止,`showToast('到账可能有延迟')` 并 `fetchQuota()` + `fetchUser()`。
|
||||
- 保留现有「先关闭订单弹窗再支付」交互;`fail` 回调保持静默处理不变。
|
||||
|
||||
### 4. 订单支付渠道区分(order.ts + order.wxml)
|
||||
|
||||
- `order.ts` 的 `fetchList` map 逻辑增补:
|
||||
- `_canCancel: it.order_status === 1 && it.pay_channel !== 2`
|
||||
- `_payChannelText: it.pay_channel === 2 ? '虚拟支付' : '微信支付'`
|
||||
- `order.wxml` 中「取消订单」按钮由直接展示改为 `wx:if="{{item._canCancel}}"` 控制,虚拟支付订单不再展示取消入口;订单信息区可选展示 `_payChannelText`。
|
||||
|
||||
### 5. AI 推荐额度提示(ai-recommend.ts)
|
||||
|
||||
- 在 `startGenerate(sceneType, scene)` 执行生成前增加 `wx.showModal` 确认,文案「本次将消耗 1 次 AI 推荐额度」,`confirm` 后才执行原有生成逻辑,`cancel` 则恢复场景选择态;`onSkipScene` 与 `onStartGenerate` 均复用该确认,避免重复弹窗。
|
||||
|
||||
### 6. 新用户生日引导(login.ts)
|
||||
|
||||
- 登录成功并保存 token/user_info 后,先按现有逻辑 `switchTab` 到衣橱页,再判断 `data.user_info.is_new_user || !data.user_info.birth_day`:满足条件时弹 `wx.showModal` 引导「完善生日可解锁今日运势」,确认后 `switchTab` 到 `pages/profile/profile`,取消则留在衣橱页。
|
||||
|
||||
## 实现要点(防回归)
|
||||
|
||||
- 所有新接口调用沿用 `request<T>` 的 `showError: false` 模式,收藏/轮询失败不打断主流程。
|
||||
- 支付轮询必须按 `order_no` 精确匹配,不能只看列表是否非空,避免误判他人订单或历史订单。
|
||||
- 订单取消禁用仅控制前端入口,后端 `70007` 仍作为兜底,双保险。
|
||||
- 不修改金额/时间换算、枚举值、请求字段名等已确认一致的部分,避免扩大改动面。
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: 小程序支付切换为虚拟支付接口
|
||||
overview: 将小程序商城支付从普通微信支付(wx.requestPayment)切换为微信虚拟支付(wx.requestVirtualPayment),对接后端已实现好的 /order/virtual 虚拟支付下单接口。
|
||||
todos:
|
||||
- id: mall-payment
|
||||
content: 改造 mall.ts:调整 OrderResult 类型、confirmPay 调用 /order/virtual 并上报 platform、doPayment 改用 wx.requestVirtualPayment
|
||||
status: completed
|
||||
- id: models-config
|
||||
content: 更新 types/models.ts 新增虚拟支付响应类型,并在 utils/config.ts 补充虚拟支付错误码文案
|
||||
status: completed
|
||||
- id: mall-wxml
|
||||
content: 将 mall.wxml 支付按钮文案由“微信支付”改为“立即支付”
|
||||
status: completed
|
||||
---
|
||||
|
||||
## 用户需求
|
||||
|
||||
后端已完成虚拟支付(微信小程序虚拟支付 / XPay)接口改造,现需将小程序前端支付流程从普通微信支付切换为虚拟支付接口。
|
||||
|
||||
## 产品概述
|
||||
|
||||
将商城(额度与会员)下单支付链路从 `wx.requestPayment` 普通微信支付迁移到 `wx.requestVirtualPayment` 虚拟支付,对接后端新增的 `POST /api/v1/order/virtual` 下单接口,覆盖 iOS/Android 全终端虚拟商品支付。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 商城下单时调用虚拟支付下单接口 `/order/virtual`,上报用户平台(android/ios/harmony/windows)
|
||||
- 使用后端返回的 `sign_data`、`pay_sig`、`signature` 调起 `wx.requestVirtualPayment`(道具直购模式)
|
||||
- 支付成功后保留原有额度与会员信息刷新逻辑
|
||||
- 补充虚拟支付相关错误码的友好提示文案
|
||||
- 调整支付按钮文案为“立即支付”
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 前端框架:微信小程序原生 + TypeScript
|
||||
- 网络请求:复用现有 `miniprogram/utils/request.ts` 的 `request<T>` 封装
|
||||
- 支付 API:`wx.requestVirtualPayment`(基础库 2.19.2+,回调风格)
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 核心改造策略
|
||||
|
||||
将 `mall.ts` 中的下单与支付两段逻辑整体替换为虚拟支付链路:
|
||||
|
||||
1. 下单:`POST /order` → `POST /order/virtual`,请求体从 `{goods_code}` 扩展为 `{goods_code, platform}`
|
||||
2. 调起支付:`wx.requestPayment` → `wx.requestVirtualPayment`,参数由 `pay_params` 改为 `sign_data/pay_sig/signature/mode`
|
||||
3. 后端返回的 `sign_data` 已经是 JSON 字符串,必须原样透传,禁止再次 `JSON.stringify`
|
||||
|
||||
### 关键决策
|
||||
|
||||
- **mode 取值**:本项目会员/额度购买属于“道具直购”,官方文档中 mode 合法值为 `short_series_goods`(道具直购)与 `short_series_coin`(代币充值),因此使用 `short_series_goods`。
|
||||
- **platform 归一化**:`wx.getDeviceInfo().platform` 可能返回 `devtools`/`mac` 等后端不接受的枚举值,需归一化为后端 `oneof=android|ios|harmony|windows` 之一;`devtools`/`mac` 等开发调试环境默认映射为 `android`(联调时按真机切换)。
|
||||
- **错误提示**:后端虚拟支付错误码(70005/70006/70008/70009/79003/71004)尚未纳入前端 `ERROR_MESSAGES`,补充文案后 `request` 封装的自动 toast 可直接生效,无需额外处理。
|
||||
- **向后兼容**:`types/models.ts` 中普通支付 `PayParams` 类型暂保留(避免影响其它引用),新增虚拟支付响应类型;`/order` 普通支付接口后端仍保留,但前端商城全量切换虚拟支付。
|
||||
|
||||
### 实现要点(防回归)
|
||||
|
||||
- `sign_data` 透传字符串,不做任何序列化/解析(签名基于服务端序列化结果,重序列化会导致 `-15016`/`-15005` 签名错误)。
|
||||
- `wx.requestVirtualPayment` 不支持 Promise 风格,使用回调;`success` 仅表示支付页拉起成功,最终到账以后端发货推送为准,前端仍按现有逻辑刷新额度与用户信息。
|
||||
- `confirmPay` 中先关闭订单弹窗再调起支付(保持现有交互),支付 `fail` 静默处理或轻提示,不重复弹订单框。
|
||||
- platform 获取失败时回退 `android`,避免请求体缺失导致 400 参数校验失败。
|
||||
|
||||
## 目录结构
|
||||
|
||||
本次为既有项目局部修改,涉及文件如下:
|
||||
|
||||
```
|
||||
miniprogram/
|
||||
├── pages/
|
||||
│ └── mall/
|
||||
│ ├── mall.ts # [MODIFY] 核心改造:OrderResult 类型、confirmPay 下单、doPayment 调起虚拟支付、platform 归一化
|
||||
│ └── mall.wxml # [MODIFY] 支付按钮文案“微信支付”→“立即支付”
|
||||
├── types/
|
||||
│ └── models.ts # [MODIFY] 新增虚拟支付下单响应类型 VirtualPayResult
|
||||
└── utils/
|
||||
└── config.ts # [MODIFY] ERROR_MESSAGES 补充虚拟支付错误码文案
|
||||
```
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
name: 穿搭列表卡片显示衣物拼图封面
|
||||
overview: 修复「穿搭」列表卡片封面空白(只有铃铛占位):后端 OutfitItemResponse 增加 processed_image 字段并返回 CDN 图片 URL;前端 outfit 列表卡片封面改为最多 5 张衣物拼图网格,无图时回退铃铛占位。
|
||||
todos:
|
||||
- id: backend-outfit-image
|
||||
content: 后端 dto/outfit.go 与 service/outfit.go 补 processed_image 组装链
|
||||
status: completed
|
||||
- id: backend-collect-image
|
||||
content: 后端 collect.go 收藏列表 items 返回带图
|
||||
status: completed
|
||||
dependencies:
|
||||
- backend-outfit-image
|
||||
- id: frontend-cover-collage
|
||||
content: 前端 models.ts 类型与 outfit 列表封面拼图渲染
|
||||
status: completed
|
||||
dependencies:
|
||||
- backend-outfit-image
|
||||
- id: verify-build-lint
|
||||
content: 执行后端 go build 与前端 lint 验证
|
||||
status: completed
|
||||
dependencies:
|
||||
- backend-outfit-image
|
||||
- backend-collect-image
|
||||
- frontend-cover-collage
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
修复「穿搭」列表页卡片封面显示空白的问题:卡片封面目前只有铃铛占位图标和件数角标,应展示该穿搭的衣物平铺拼图(与穿搭详情页的拼图封面风格一致)。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 穿搭列表卡片封面展示最多 5 件衣物的抠图平铺拼图,衣物图片缺失时仍显示铃铛占位。
|
||||
- 已收藏 tab 的穿搭卡片同样展示拼图封面。
|
||||
- 列表数据一次性从后端带图返回,不在前端逐件请求衣物详情,避免 N+1 网络开销。
|
||||
- 详情页、AI 推荐结果页等已有页面行为保持不变。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 后端:Go(沿用现有 Gin + GORM + COS 上传体系),复用 `upload.GetCosUrl` 将 COS key 转为可访问 CDN URL。
|
||||
- 前端:微信小程序原生 TypeScript + WXML/WXSS,复用 `outfit-detail` 页已有的拼图封面样式模式。
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 核心策略
|
||||
|
||||
后端在穿搭列表/详情/收藏的 `items` 响应中为每件衣物补充 `processed_image`(可访问 URL),前端列表卡片直接渲染最多 5 张拼图。选择后端带图而非前端逐件请求的理由:列表页每张卡片含多件衣物,前端 N+1 请求代价高;而后端 `buildOutfitListResponses` 本已批量查出全部衣物模型,补图片只是内存组装,无新增 SQL。
|
||||
|
||||
### 后端改动(/root/zt-closet-backend,3 个文件)
|
||||
|
||||
1. `internal/dto/outfit.go`:`OutfitItemResponse` 增加 `ProcessedImage string \`json:"processed_image"\``(与衣物详情响应字段名保持一致)。
|
||||
2. `internal/service/outfit.go`:
|
||||
|
||||
- 新增 helper `buildOutfitItemResponse(c *mysqlmodel.Clothes) dto.OutfitItemResponse`:组装 `ClothesUUID` + `upload.GetCosUrl(c.ProcessedImage)`(空值不转换),需新增 import `pkg/upload`。
|
||||
- `resolveOwnedClothes` 返回值改为 `([]int64, []dto.OutfitItemResponse, *errcode.Error)`,利用已查出的 `clothesList` 按请求顺序组装带图 items;`Create` 直接用 items 组装响应;`UpdateByUUID` 调用点以 `_` 忽略 items。
|
||||
- `itemClothesUUIDs` 改造为 `itemResponses(ctx, outfitID) ([]dto.OutfitItemResponse, error)`:按 `OutfitItem` 明细顺序、`clothesID → Clothes` 映射组装带图 items;`GetByUUID` 相应更新。
|
||||
- `buildOutfitListResponses`:`uuidByID` 改为 `clothesByID map[int64]mysqlmodel.Clothes`,按 `itemsByOutfit[o.ID]` 顺序组装 items。
|
||||
- `buildOutfitCreateResponse(o *mysqlmodel.Outfit, items []dto.OutfitItemResponse)` 签名同步调整。
|
||||
|
||||
3. `internal/service/collect.go`:`CollectService.itemClothesUUIDs` 同样改为 `itemResponses` 返回带图 items,收藏列表 `/collect` 自动带图;新增 import `pkg/upload`。
|
||||
|
||||
### 前端改动(/root/zt-closet-wxmini-frontend,3 个文件)
|
||||
|
||||
4. `miniprogram/types/models.ts`:`OutfitItem.items` 数组元素增加 `processed_image: string`。
|
||||
5. `miniprogram/pages/outfit/outfit.wxml`:`.cover` 内改为拼图网格——`wx:for="{{item.items}}" wx:key="clothes_uuid" wx:if="{{index < 5}}"`,有 `processed_image` 时渲染 `<image mode="aspectFit">`,否则铃铛占位;`items` 为空时保留居中大铃铛;`.cover-count` 件数角标保留。
|
||||
6. `miniprogram/pages/outfit/outfit.wxss`:新增 `.cover-item`、`.cover-0~4`(absolute 定位 + 轻微旋转)、`.cover-img`、占位样式,尺寸适配 280rpx 高封面(约 150×190rpx 卡片),`.cover` 增加 `overflow: hidden`,风格参照 `outfit-detail.wxss` 17-67 行。
|
||||
|
||||
### 兼容性与性能
|
||||
|
||||
- `processed_image` 为新增 JSON 字段,向后兼容;详情页 `outfit-detail.ts` 仍需 `name` 字段,保持原 `/clothes/{uuid}` 请求逻辑不变。
|
||||
- 列表组装维持现有批量查询路径(无 N+1),图片转换仅内存操作;`GetCosUrl` 生成签名 URL 为纯计算,无 IO。
|
||||
- 衣物图片缺失(抠图失败/未处理)时 `processed_image` 为空,前端自动回退铃铛占位,无异常展示。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
/root/zt-closet-backend/
|
||||
├── internal/dto/outfit.go # [MODIFY] OutfitItemResponse 增加 processed_image 字段
|
||||
└── internal/service/
|
||||
├── outfit.go # [MODIFY] helper 与响应组装链带图(Create/GetByUUID/List)
|
||||
└── collect.go # [MODIFY] 收藏列表 items 带图
|
||||
|
||||
/root/zt-closet-wxmini-frontend/miniprogram/
|
||||
├── types/models.ts # [MODIFY] OutfitItem.items 元素增加 processed_image
|
||||
└── pages/outfit/
|
||||
├── outfit.wxml # [MODIFY] 卡片封面改为最多 5 张拼图网格
|
||||
└── outfit.wxss # [MODIFY] 新增拼图封面样式,适配 280rpx 高卡片
|
||||
```
|
||||
|
||||
## 验证方式
|
||||
|
||||
- 后端:`cd /root/zt-closet-backend && go build ./...` 编译通过。
|
||||
- 前端:对 `models.ts`、`outfit.wxml`、`outfit.wxss` 执行 lint 检查无新错误。
|
||||
- 手工验证:小程序「穿搭」页两个 tab 的卡片封面应显示衣物拼图;衣物无图的旧数据回退铃铛。
|
||||
@@ -0,0 +1,172 @@
|
||||
---
|
||||
name: 通知与分享入口互换
|
||||
overview: 将底部 tabBar 的「通知」替换为「分享」,通知入口移入「我的」页面菜单;「分享」tab 页内实现「我的分享 / 分享给我的」两个分段,未读红点迁移到「我的」tab 图标上。
|
||||
design:
|
||||
styleKeywords:
|
||||
- iOS HIG
|
||||
- 清爽克制
|
||||
- 胶囊分段
|
||||
- 系统蓝
|
||||
- 线性图标
|
||||
fontSystem:
|
||||
fontFamily: PingFang SC
|
||||
heading:
|
||||
size: 34rpx
|
||||
weight: 600
|
||||
subheading:
|
||||
size: 28rpx
|
||||
weight: 500
|
||||
body:
|
||||
size: 26rpx
|
||||
weight: 400
|
||||
colorSystem:
|
||||
primary:
|
||||
- "#007AFF"
|
||||
- "#5856D6"
|
||||
- "#FF2D55"
|
||||
background:
|
||||
- "#F2F2F7"
|
||||
- "#FFFFFF"
|
||||
text:
|
||||
- "#000000"
|
||||
- "#3C3C43"
|
||||
- "#8E8E93"
|
||||
- "#C7C7CC"
|
||||
functional:
|
||||
- "#FF3B30"
|
||||
- "#34C759"
|
||||
- "#FF9500"
|
||||
todos:
|
||||
- id: update-tabbar
|
||||
content: 调整 tabBar 入口与红点(app.json + custom-tab-bar)
|
||||
status: completed
|
||||
- id: upgrade-share-page
|
||||
content: 升级分享页为 tab 页并实现「我的分享/分享给我的」双分段
|
||||
status: completed
|
||||
dependencies:
|
||||
- update-tabbar
|
||||
- id: update-profile
|
||||
content: 修改我的页通知入口并同步未读红点
|
||||
status: completed
|
||||
- id: adjust-notification
|
||||
content: 调整通知页为普通页面(返回按钮、移除 tab 同步)
|
||||
status: completed
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
对微信小程序「纸条AI衣橱」的底部导航与入口布局进行调整:将底部导航栏第 4 个入口由「通知」改为「分享」,原「我的」页面内的「我的分享」入口改为「通知」;分享页新增「我的分享 / 分享给我的」两个分段展示;未读通知红点由通知 tab 移至底部「我的」tab 图标右上角。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 底部导航栏第 4 个入口显示「分享」(分享图标),点击进入分享页。
|
||||
- 「我的」页面功能列表中新增「通知」入口(铃铛图标),点击进入通知列表页。
|
||||
- 分享页顶部提供「我的分享 / 分享给我的」分段切换:
|
||||
- 「我的分享」展示当前用户发起的衣柜分享,保留「新建分享」入口。
|
||||
- 「分享给我的」展示他人分享给当前用户的衣柜分享,卡片展示分享者头像与昵称。
|
||||
- 底部「我的」tab 图标右上角显示未读通知数红点,超过 99 显示「99+」。
|
||||
- 通知页由 tab 页改为普通二级页,支持返回上一页。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 平台:微信小程序原生 + TypeScript(沿用现有 `skyline` + `glass-easel` 架构)
|
||||
- 网络层:复用现有 `miniprogram/utils/request.ts` 的 `request<T>` 封装
|
||||
- UI:纯 WXSS 手写,复用现有 `navigation-bar`、`empty-state`、`custom-tab-bar` 组件与 `styles/icons.wxss` 图标库
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 整体策略
|
||||
|
||||
在现有「5 个 tabBar 页 + 二级页」架构上做入口互换与页面能力升级,不引入新页面、新组件、新依赖。分享页从二级页升级为 tabBar 页并新增分段切换;通知页从 tabBar 页降级为二级页。红点数据源由原通知页单向写缓存,扩展为 tabBar 组件主动拉取 `/notification/unread`。
|
||||
|
||||
### 关键决策
|
||||
|
||||
- **分享页分段采用「单一当前列表 + 切换重置」模式**:用 `activeTab`(0=我的分享,1=分享给我的)控制当前展示,切换分段时清空 `list` 并重置分页状态,再调用对应接口。避免维护两套分页状态,符合 KISS/YAGNI,切换后重新拉取的开销可接受。
|
||||
- **两个列表接口复用同一响应结构**:`GET /closet-share` 与 `GET /closet-share/received` 均返回 `PagedResult<ClosetShareItem>`;后者额外返回 `owner_name/owner_avatar`,前端 `ClosetShareItem` 补字段即可,`decorate()` 对两接口通用。
|
||||
- **红点刷新采用「缓存先行 + 异步刷新」**:tabBar 组件 `attached` 时先读 `zt_closet_unread` 立即渲染,再请求 `/notification/unread` 更新缓存与视图;通知页 `fetchUnread` 保持写缓存并调 `tabBar.refreshUnread()`,形成闭环。
|
||||
- **入口切换避免 tab 页跳转错误**:`profile` 中删除原 `onTapShare` 的 `navigateTo('/pages/closet-share/closet-share')`(tab 页不能用 navigateTo),改为 `navigateTo('/pages/notification/notification')`。
|
||||
|
||||
### 实现注意事项
|
||||
|
||||
- `closet-share` 成为 tab 页后,`onShow` 需 `syncTabBar()` 设置 `selected: 3`(对应第 4 项),与现有 wardrobe/outfit/horoscope/profile 页的 selected 同步模式一致。
|
||||
- `closet-share.wxml` 中 `navigation-bar` 增加 `back="{{false}}"`(tab 页不显示返回键),标题由「衣柜分享」调整为「分享」;`closet-share-create`、`closet-share-detail` 仍为二级页,用 `navigateTo` 跳转不受影响。
|
||||
- `notification.wxml` 去掉 `back="{{false}}"` 以恢复默认返回按钮;`notification.ts` 删除 `syncTabBar()`(避免误将 tabBar 高亮切到已不存在的第 4 项),但保留 `fetchUnread()` 以刷新「我的」tab 红点。
|
||||
- 分享给我的卡片头像使用 `image` 渲染 `owner_avatar`,为空时回退 `icon-user` 占位,与 profile 页头像处理方式一致。
|
||||
|
||||
## 架构设计
|
||||
|
||||
数据流保持「页面/组件 → request → 后端 API」的单向依赖:
|
||||
|
||||
- tabBar 组件 `attached` → 读本地缓存 `zt_closet_unread` 立即显示 → 请求 `GET /notification/unread` → 写缓存并更新红点。
|
||||
- 分享页 `onShow` → 同步 tabBar 高亮 → 按 `activeTab` 请求 `GET /closet-share` 或 `GET /closet-share/received` → 渲染列表。
|
||||
- 我的页 `onShow` → 同步 tabBar 高亮 → 拉取用户/额度信息,并调用 `tabBar.refreshUnread()` 刷新红点。
|
||||
|
||||
## 目录结构
|
||||
|
||||
本次仅修改现有文件,不新增文件:
|
||||
|
||||
```
|
||||
miniprogram/
|
||||
├── app.json # [MODIFY] tabBar.list 第4项由 notification 改为 closet-share,text 由「通知」改为「分享」
|
||||
├── custom-tab-bar/
|
||||
│ ├── index.ts # [MODIFY] 列表第4项改为分享/icon-share;refreshUnread 增加主动请求 /notification/unread
|
||||
│ └── index.wxml # [MODIFY] 红点条件由 icon-bell 改为 icon-user(我的 tab)
|
||||
├── pages/
|
||||
│ ├── closet-share/
|
||||
│ │ ├── closet-share.ts # [MODIFY] 增加 activeTab 双分段、双接口取数、分页重置、syncTabBar(selected=3)
|
||||
│ │ ├── closet-share.wxml # [MODIFY] 增加分段控件;分享给我的卡片展示 owner 信息;新建入口仅我的分享展示;back=false
|
||||
│ │ └── closet-share.wxss # [MODIFY] 增加分段样式、分享者头像昵称样式
|
||||
│ ├── profile/
|
||||
│ │ ├── profile.ts # [MODIFY] onTapShare 改为 onTapNotification(navigateTo notification);onShow 刷新 tabBar 红点
|
||||
│ │ └── profile.wxml # [MODIFY] 「我的分享」菜单行改为「通知」(icon-bell,bindtap=onTapNotification)
|
||||
│ └── notification/
|
||||
│ ├── notification.ts # [MODIFY] 删除 syncTabBar;保留 fetchUnread 刷新 tabBar 红点
|
||||
│ └── notification.wxml # [MODIFY] navigation-bar 去掉 back="{{false}}",恢复返回按钮
|
||||
└── types/
|
||||
└── models.ts # [MODIFY] ClosetShareItem 补充 owner_name、owner_avatar
|
||||
```
|
||||
|
||||
## 关键代码结构
|
||||
|
||||
补充分享列表项类型,兼容「分享给我的」返回的分享者信息:
|
||||
|
||||
```ts
|
||||
export interface ClosetShareItem {
|
||||
share_uuid: string
|
||||
share_type: number
|
||||
clothes_count: number
|
||||
expire_time: number
|
||||
status: number
|
||||
owner_name?: string // 分享者昵称(分享给我的列表返回)
|
||||
owner_avatar?: string // 分享者头像(分享给我的列表返回)
|
||||
}
|
||||
```
|
||||
|
||||
分享页核心状态:
|
||||
|
||||
```ts
|
||||
data: {
|
||||
activeTab: 0 | 1, // 0=我的分享 1=分享给我的
|
||||
list: ClosetShareItem[],
|
||||
loading: boolean,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
hasMore: boolean,
|
||||
}
|
||||
```
|
||||
|
||||
## 设计风格
|
||||
|
||||
延续现有 iOS HIG 视觉语言:清爽克制、系统分组灰底 #F2F2F7 + 纯白卡片 + 发丝分隔线,强调色系统蓝 #007AFF,线性 SF Symbols 风格图标。
|
||||
|
||||
## 分享页分段设计
|
||||
|
||||
- 顶部在导航栏下方放置胶囊式分段控件(Segmented Control):两个等宽胶囊「我的分享」「分享给我的」,选中项为系统蓝底白字,未选中项为灰底深字,切换有轻量过渡;样式与通知页筛选胶囊保持一致,保证全站视觉统一。
|
||||
- 「我的分享」列表顶部保留「+ 新建分享」描边按钮,卡片沿用现有四宫格衣物缩略图 + 件数 + 类型 + 有效期布局。
|
||||
- 「分享给我的」列表卡片在左侧缩略图基础上,右侧信息区顶部新增分享者行(小圆头像 + 昵称),帮助用户快速识别来源;无头像时回退系统灰色人形占位。
|
||||
- 两个分段的空态复用 empty-state 组件,文案分别区分「还没有分享」与「还没有收到分享」。
|
||||
|
||||
## 底部导航设计
|
||||
|
||||
- 第 4 个 tab 由铃铛图标替换为分享图标(三圆点连线),文字由「通知」改为「分享」。
|
||||
- 未读通知红点从分享 tab 移至「我的」tab 图标右上角,沿用现有红色胶囊数字样式,超过 99 显示「99+」,无未读时隐藏。
|
||||
Reference in New Issue
Block a user