feat: ui update

This commit is contained in:
2026-09-15 16:01:59 +08:00
parent 1a11816839
commit 26c392d612
89 changed files with 10377 additions and 4861 deletions
@@ -0,0 +1,206 @@
---
name: 穿搭页补全社区分段
overview: 在穿搭 tab 新增「社区」分段,浏览所有用户公开的穿搭(作者信息、拼图封面、点赞/收藏数、场景与发布时间),并新增后端社区列表接口支撑。
design:
styleKeywords:
- iOS HIG
- 清爽克制
- 发丝分隔线
- 系统蓝
- 线性图标
fontSystem:
fontFamily: PingFang SC
heading:
size: 32rpx
weight: 600
subheading:
size: 28rpx
weight: 500
body:
size: 26rpx
weight: 400
colorSystem:
primary:
- "#007AFF"
- "#5856D6"
background:
- "#F2F2F7"
- "#FFFFFF"
text:
- "#000000"
- "#3C3C43"
- "#8E8E93"
- "#C7C7CC"
functional:
- "#FF2D55"
- "#34C759"
- "#FF9500"
- "#FF3B30"
todos:
- id: backend-community-api
content: 用 [subagent:code-explorer] 确认后端批量查询与计数模式;实现 GET /outfit/community 接口(DTO/DAO/Service/路由)并更新接口文档
status: completed
- id: frontend-community-data
content: models.ts 新增社区穿搭类型;outfit.ts 实现三态分段、社区取数、相对时间与详情跳转
status: completed
dependencies:
- backend-community-api
- id: frontend-community-ui
content: outfit.wxml/wxss 实现社区分段、作者行卡片、互动数据与空态样式
status: completed
dependencies:
- frontend-community-data
- id: verify-community
content: 运行 go build 与 tsc 类型检查,核对前后端字段契约与路由注册
status: completed
dependencies:
- backend-community-api
- frontend-community-ui
---
## 产品概述
为「纸条AI衣橱」小程序补全社区功能:在「穿搭」页现有「我的穿搭 / 已收藏」两个分段基础上,新增「社区」分段,让所有用户都能浏览其他用户公开的穿搭,并进入详情点赞、收藏,形成「公开到社区 → 他人浏览互动」的完整闭环。
## 核心功能
- 穿搭页分段控件扩展为三态:「我的穿搭 / 已收藏 / 社区」,切换即加载对应列表并重置分页
- 社区分段以卡片流展示其他用户公开的穿搭(仅展示已公开且审核通过、状态正常的穿搭)
- 社区卡片内容:作者头像与昵称(卡片顶部)、拼图式衣物封面与件数、穿搭名称、场景标签、点赞数与收藏数、相对发布时间
- 点击社区卡片进入他人视角的穿搭详情页,可点赞、收藏(复用现有详情页,不新增页面)
- 社区列表支持下拉刷新、上拉加载更多、骨架屏加载态与空态提示
- 社区分段隐藏「+ 创建穿搭 / AI 推荐」入口行(浏览场景不展示管理入口)
## 视觉与交互效果
延续现有 iOS HIG 风格:分组灰底、纯白卡片、发丝分隔线、系统蓝强调色。社区卡片在现有穿搭卡片结构上顶部新增作者行(圆形头像 + 昵称 + 右侧相对时间),底部 meta 行新增点赞、收藏两组互动数据,整体与「我的穿搭」卡片流视觉统一、切换分段有平滑过渡。
## 技术栈
- 前端:微信小程序原生 + TypeScript(沿用现有 webview 渲染与纯 WXSS 手写方案,不引入组件库)
- 后端:Go + Gin + GORM/MySQL(zt-closet-backend,新增接口沿用现有分层:router → handler → service → dao)
- 复用:前端 `request` 封装、`paged` 分页工具、`format.formatRelativeTime`、`logger`、`empty-state` 组件与图标库;后端 `PageRequest/PageResponse`、`normalizePage`、批量查询与批量组装模式
## 实现方案
### 总体策略
后端新增鉴权接口 `GET /outfit/community`:分页返回「其他用户已公开(is_public=2 且 audit_status=2 且 status=1)」的穿搭列表,聚合作者信息与点赞/收藏计数后一次性下发;前端将 `pages/outfit` 页扩展为三态分段,第三段对接新接口渲染社区卡片流,点击进入现有他人视角详情页(owner=0)。全程复用既有模式,不引入新架构、不新增页面、不改动 tabBar。
### 关键决策与理由
1. **接口路径 `GET /outfit/community`**:与既有 `GET /outfit/:uuid` 在同一 Gin 路由树共存是安全的——项目内已有完全相同形态的先例(`/notification/unread` 与 `/notification/:uuid` 共存且线上运行正常),Gin 1.12 静态路由优先匹配。
2. **排除本人的公开穿搭**(SQL 加 `user_id != 当前用户`):贴合用户「查看其他用户公开的穿搭」的表述,且避免与「我的穿搭」分段内容重复;后续如需包含自己,仅需去掉一个 WHERE 条件。
3. **互动数据由后端批量聚合下发**(like_count / collect_count):现有穿搭列表在前端逐条请求 `/like/:uuid`(一页 20 条即 20 次请求),社区列表直接使用后端聚合值,杜绝该 N+1 问题。
4. **后端批量组装杜绝 N+1**:复用 `buildOutfitListResponses` 的批量模式(一次性批量查明细 + 衣物),作者信息用已有 `UserGetByIDs`,点赞/收藏计数新增 `GROUP BY target_id` 批量方法;单页固定约 7 条 SQL,与页大小无关。
5. **社区卡片复用现有拼图封面结构**(cover-item 循环)与卡片骨架,仅新增作者行与互动数据块,模板通过 `activeTab === 2` 条件渲染差异部分,避免大段重复。
6. **仅社区分段隐藏入口按钮行**,「我的穿搭 / 已收藏」保持现状,控制改动面。
### 性能与可靠性
- 后端:分页 `page/page_size`(上限 50,`normalizePage` 规整),`ORDER BY id DESC` 走主键;批量计数对空列表短路,避免 `IN ()` 查询;错误统一返回现有错误码(19001 等),沿用 slog 日志风格。
- 前端:`page_size=20`、切换分段重置并丢弃过期响应(沿用现有 reqSeq 序号防乱序机制);辅助字段(相对时间)本地计算零请求;失败不打断主流程,仅记录 code 不打印敏感信息。
- 契约一致性:`created_at` 秒级时间戳(前端 `formatRelativeTime` 直接消费);`owner_avatar` 由后端 `upload.AvatarURL` 转为可访问 URL;`processed_image` 为完整 URL。
### 实施注意事项
- 后端仓库位于 `/root/zt-closet-backend`(不在前端工作区),当前存在未提交改动(收藏/通知修复),新代码只做追加、不动无关文件。
- 后端改动需用户自行部署后,前端指向线上域名(BASE_URL)才能联调。
- 前端不改 `app.json`、不改 `outfit-detail`、不改 tabBar;`outfit.json` 已引入所需组件,无需调整。
- 复用 `isOutfitPublicVisible` 的可见性口径(公开 2 + 审核通过 2 + 状态正常 1),保证社区列表与详情页可见性判定一致。
## 目录结构
后端 `/root/zt-closet-backend/`:
```
├── internal/dto/outfit.go # [MODIFY] 新增社区列表 DTO:OutfitCommunityListRequest(内嵌 PageRequest)/ OutfitCommunityListResponse(内嵌 PageResponse)/ OutfitCommunityItem(含 outfit_uuid、outfit_name、outfit_desc、scene、source、created_at、items、owner_name、owner_avatar、like_count、collect_count)
├── internal/mysqldao/outfit.go # [MODIFY] 新增 OutfitListCommunity(ctx, excludeUserID, page, pageSize):查询 is_public=2 AND audit_status=2 AND status=1 AND user_id != 本人,Count + Order("id DESC") 分页,参照 OutfitListByUserID 写法
├── internal/mysqldao/like.go # [MODIFY] 新增 LikeCountByTargets(ctx, likeType, targetIDs):SELECT target_id, COUNT(*) ... GROUP BY target_id(仅 status=1),空入参短路
├── internal/mysqldao/collect.go # [MODIFY] 新增 CollectCountByTargets 批量计数(同上模式)
├── internal/service/outfit.go # [MODIFY] 新增 CommunityList:normalizePage → 查列表 → 批量组装(复用 OutfitItemListByOutfitIDs + ClothesGetByIDs + UserGetByIDs + upload.AvatarURL + 批量计数),空列表直接返回空数组
├── internal/routers/api/v1/outfit.go # [MODIFY] 新增 Community handler(GetUserID → ShouldBindQuery → svc → ToResponse,与现有 List 写法一致)
├── internal/routers/router.go # [MODIFY] 鉴权组注册 auth.GET("/outfit/community", outfitHandler.Community)
└── 接口文档.md # [MODIFY] 新增社区列表接口章节;修正 FAQ「没有社区广场接口」的过时说明
```
前端 `/root/zt-closet-wxmini-frontend/`:
```
└── miniprogram/
├── types/models.ts # [MODIFY] 新增 CommunityOutfitItem 类型(与后端响应字段一一对应)
└── pages/outfit/
├── outfit.ts # [MODIFY] activeTab 扩展 0/1/2;fetchList 按分段选 /outfit、/collect、/outfit/community;社区项装饰(直接取 like_count/collect_count,计算相对时间 _time);onOutfitTap 社区项 owner=0;空态文案分支;日志沿用 logger
├── outfit.wxml # [MODIFY] 分段控件增加「社区」;entry-row 在社区分段隐藏;卡片新增作者行(社区分段)与收藏数、相对时间;状态标签仅非社区分段展示;空态文案分支
└── outfit.wxss # [MODIFY] 新增作者行、圆形头像(含 icon-user 占位)、昵称、时间、互动数据样式(延续现有 rpx 与色板)
```
## 关键代码结构
后端响应契约(新增到 `internal/dto/outfit.go`):
```
type OutfitCommunityListResponse struct {
PageResponse
List []OutfitCommunityItem `json:"list"`
}
type OutfitCommunityItem struct {
OutfitUUID string `json:"outfit_uuid"`
OutfitName string `json:"outfit_name"`
OutfitDesc string `json:"outfit_desc"`
Scene string `json:"scene"`
Source int8 `json:"source"`
CreatedAt int64 `json:"created_at"`
Items []OutfitItemResponse `json:"items"`
OwnerName string `json:"owner_name"`
OwnerAvatar string `json:"owner_avatar"`
LikeCount int64 `json:"like_count"`
CollectCount int64 `json:"collect_count"`
}
```
前端类型(新增到 `types/models.ts`):
```ts
export interface CommunityOutfitItem {
outfit_uuid: string
outfit_name: string
outfit_desc: string
scene: string
source: number
created_at: number
items: { clothes_uuid: string; processed_image: string }[]
owner_name: string
owner_avatar: string
like_count: number
collect_count: number
}
```
## 设计风格
延续项目既有 iOS HIG 视觉语言:分组灰底 #F2F2F7、纯白卡片(圆角 32rpx)、0.5px 发丝分隔线、系统蓝 #007AFF 强调色、SF Symbols 风格线性图标,清爽克制、以内容为中心,社区分段与现有分段无缝衔接。
## 分段控件
胶囊分段由两项扩展为三项「我的穿搭 / 已收藏 / 社区」,三项等宽;选中项为白色滑块 + 系统蓝加粗文字,未选中为 #3C3C43;切换带轻量过渡,样式沿用现有 .segment 规格(高 64rpx、圆角 16rpx)。
## 社区卡片
- 作者行(新增):左侧 56rpx 圆形头像(无头像回退浅灰底 icon-user 占位),昵称 26rpx 深色单行省略,右侧相对时间 22rpx 灰字
- 封面区:复用现有拼图 cover-item 错落摆放(最多 5 件)+ 右上角件数胶囊
- 信息区:穿搭名 32rpx 加粗单行省略,描述 26rpx 灰字最多两行
- 互动区(meta 行):左侧场景标签(tag-gray)与件数,右侧点赞(icon-heart)与收藏(icon-star)计数,图标 36rpx 灰字 22rpx,间距与现有卡片一致
## 状态设计
加载态复用骨架屏(sk-card 呼吸动画);社区空态复用 empty-state(title「还没有公开的穿搭」,desc「把喜欢的穿搭公开到社区,让大家看到你的搭配」,无按钮);列表底部「没有更多了」提示沿用现有样式;卡片按压态用现有 active 透明度反馈。
## Agent Extensions
### SubAgent
- **code-explorer**
- Purpose: 后端仓库(117 个 Go 文件)实现社区接口前,跨文件梳理 like/collect 数据模型、批量计数与批量组装模式、DTO/Handler 既有写法,确保新代码完全复用现有模式
- Expected outcome: 产出可复用的批量计数 SQL 模式与接口实现要点清单(表名、字段、既有工具函数位置),降低返工与不一致风险