feat(fitting): 试衣间布局 v2.1——紧凑预览 300vp+三列纯图卡+全屏查看器(设计师 Agent 出稿)

- 预览区 3:4 大图(437vp/占屏57%) → 固定 300vp + Contain 不裁切(用户要求预览变小且不裁切)
- 新增 ImageViewer 全屏查看器:点预览/生成完成自动弹出;原图|效果图页签与预览角标共用 showOriginal;保存走三态口径
- 衣橱网格双列带文字行 → 三列纯图卡(104×139,品类图内左下角标),首屏可见 6 卡 vs 原 3 卡
- 试穿胶囊 52×120→44×96、品类 chips 高 32→28、角标 12→11
- 设计稿:docs/美搭配与美学诊断-UI规范.md §10(含 7 条冲突修订表与 8 条验收)
This commit is contained in:
xsl
2026-09-03 22:14:39 +08:00
parent d2c603566b
commit 1b534f5133
6 changed files with 309 additions and 69 deletions
@@ -1,7 +1,7 @@
/**
* CapsuleButton —— 「试穿」黑色竖向胶囊(《美搭配与美学诊断-UI规范》§3/§7,需求 v2 §3.2)。
*
* 形态:52×120 竖条(高约 120vp,对齐参考站),黑底白字 14(COLOR_BLACK_CAPSULE),
* 形态:44×96 竖条(v2.1 §10.4 紧凑化),黑底白字 14(COLOR_BLACK_CAPSULE),
* 右缘悬浮垂直居中;未选上衣/未传本人照/生成中置灰(§2 主按钮禁用态:
* 底 COLOR_DIVIDER、字 COLOR_TEXT_DISABLED)。label 支持换行竖排(如 '试\n穿')。
*/
@@ -22,10 +22,10 @@ export struct CapsuleButton {
.fontColor(this.isActive ? Color.White : COLOR_TEXT_DISABLED)
.textAlign(TextAlign.Center)
}
.width(52)
.height(120)
.width(44)
.height(96)
.justifyContent(FlexAlign.Center)
.borderRadius(26)
.borderRadius(22)
.backgroundColor(this.isActive ? COLOR_BLACK_CAPSULE : COLOR_DIVIDER)
.onClick(() => {
if (this.isActive) {
@@ -1,7 +1,7 @@
/**
* CategoryChip —— 品类 chip(《美搭配与美学诊断-UI规范》§3/§7)。
*
* 形态:高约 32 胶囊;选中黑底白字(COLOR_BLACK_CAPSULE,试衣间专属黑),
* 形态:高约 28 胶囊(§10.4 紧凑化);选中黑底白字(COLOR_BLACK_CAPSULE,试衣间专属黑),
* 未选白底 + COLOR_DIVIDER 描边。用于品类过滤条与「添加衣服」品类弹层。
* selected 随父层状态变化,必须 @Prop(普通成员只在创建时赋值一次,不随父刷新)。
*/
@@ -16,11 +16,11 @@ export struct CategoryChip {
build() {
Text(this.label)
.fontSize(13)
.fontSize(12)
.fontColor(this.selected ? Color.White : COLOR_TEXT_PRIMARY)
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.backgroundColor(this.selected ? COLOR_BLACK_CAPSULE : Color.White)
.borderRadius(16)
.borderRadius(14)
.border({
width: 1,
color: this.selected ? Color.Transparent : COLOR_DIVIDER
@@ -1,14 +1,11 @@
/**
* ClothCard —— 衣橱服装卡(《美搭配与美学诊断-UI规范》§3/§7)。
* ClothCard —— 衣橱服装卡(《美搭配与美学诊断-UI规范》§10.2 v2.1 三列纯图卡)。
*
* 形态:白底圆角 12;服装图 aspectRatio 3:4 Cover;名称 13 Medium 单行截断;
* 品类 11 Secondary;右上角:槽位角标(选中时,主色底)+ 删除角标(20×20 圆,黑 60%)。
* 选中态:2vp COLOR_PRIMARY 描边(点选=设入槽位,再点取消)。
* 本机文件被清时 Image onError → 「已失效,点按删除」占位(需求 §4.1 ④)。
*
* 结构说明:角标行与卡片主体是 Stack 兄弟节点(非父子),点击角标不会
* 误触卡片的选择事件;item 内容按 id 恒定(普通成员),selected/slotLabel
* 随父层选择状态变化,必须 @Prop。
* 形态:图 3:4 Cover(约 104×139),无名称/品类文字行(三列窄卡放不下,
* 品类改图内左下小角标:黑 60% 底白字 10);选中 2vp 主色描边 + 右上槽位角标;
* 我的衣橱卡保留 20×20 删除角标,内置精选(rawfilePath 非空)无删除、无失效态。
* 结构说明:角标行与卡片主体是 Stack 兄弟节点(非父子),点击角标不会误触选择;
* selected/slotLabel 随父层选择状态变化,必须 @Prop。
*/
import { fileUri } from '@kit.CoreFileKit'
@@ -17,7 +14,6 @@ import {
COLOR_BADGE_BG,
COLOR_IMAGE_BG,
COLOR_PRIMARY,
COLOR_TEXT_PRIMARY,
COLOR_TEXT_SECONDARY
} from '../../core/Theme'
@@ -47,37 +43,36 @@ export struct ClothCard {
.justifyContent(FlexAlign.Center)
.padding(8)
.backgroundColor(COLOR_IMAGE_BG)
} else if (this.rawfilePath !== '') {
Image($rawfile(this.rawfilePath))
.width('100%')
.aspectRatio(3 / 4)
.objectFit(ImageFit.Cover)
} else {
Image(fileUri.getUriFromPath(this.item.uri))
.width('100%')
.aspectRatio(3 / 4)
.objectFit(ImageFit.Cover)
.onError(() => {
this.loadFailed = true
})
Stack({ alignContent: Alignment.BottomStart }) {
if (this.rawfilePath !== '') {
Image($rawfile(this.rawfilePath))
.width('100%')
.aspectRatio(3 / 4)
.objectFit(ImageFit.Cover)
} else {
Image(fileUri.getUriFromPath(this.item.uri))
.width('100%')
.aspectRatio(3 / 4)
.objectFit(ImageFit.Cover)
.onError(() => {
this.loadFailed = true
})
}
// 左下品类角标(黑 60% 底白字 10)
Text(this.item.category)
.fontSize(10)
.fontColor(Color.White)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(8)
.backgroundColor(COLOR_BADGE_BG)
.margin(6)
}
.width('100%')
}
Column({ space: 2 }) {
Text(this.item.name)
.fontSize(13)
.fontWeight(FontWeight.Medium)
.fontColor(COLOR_TEXT_PRIMARY)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.item.category)
.fontSize(11)
.fontColor(COLOR_TEXT_SECONDARY)
}
.width('100%')
.padding({ left: 10, right: 10, top: 8, bottom: 10 })
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor(Color.White)
.backgroundColor(COLOR_IMAGE_BG)
.borderRadius(12)
.clip(true)
.border({
@@ -92,19 +87,20 @@ export struct ClothCard {
}
})
// 右上角标行(槽位角标 + 删除角标,右上 6;内置精选无删除)
// 右上角标行(槽位角标 + 删除角标;内置精选无删除)
Row({ space: 4 }) {
if (this.slotLabel !== '') {
Text(this.slotLabel)
.fontSize(11)
.fontSize(10)
.fontColor(Color.White)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.borderRadius(10)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(8)
.backgroundColor(COLOR_PRIMARY)
}
if (this.rawfilePath === '') {
Text('×')
.fontSize(14)
.fontSize(13)
.fontColor(Color.White)
.width(20)
.height(20)
@@ -116,7 +112,7 @@ export struct ClothCard {
})
}
}
.margin(6)
.margin(4)
}
.width('100%')
}
@@ -0,0 +1,120 @@
/**
* ImageViewer —— 试衣间全屏图片查看器(《美搭配与美学诊断-UI规范》§10.3)。
*
* 形态:全屏覆盖层,底 COLOR_BLACK_CAPSULE(#1A1A1A);顶部 ×关闭 + 「原图/效果图」
* 页签(仅 hasResult 时显示页签,模特态只有 ×);图 Contain 宽 100%-32 居中,
* 点图切换原图/效果——与页面预览共用父层 showOriginal(@Link 双向);底部
* 「保存到相册」主按钮(走父层既有三态口径)+「再来一次」文字链(仅结果态)。
* 关闭:点 × / 系统返回由父层 onBackPress 处理(viewerOpen 状态门闩)。
*/
import {
COLOR_BLACK_CAPSULE,
COLOR_PRIMARY
} from '../../core/Theme'
@Component
export struct ImageViewer {
@Link showOriginal: boolean // 与页面预览共用:false=效果图优先
hasResult: boolean = false
originalSrc: string = ''
resultSrc: string = ''
onSave: () => void = () => {}
onRetry: () => void = () => {}
onClose: () => void = () => {}
private currentSrc(): string {
if (this.hasResult && !this.showOriginal) {
return this.resultSrc
}
return this.originalSrc
}
build() {
Stack() {
Column() {
// 顶栏 56:× 距左 16;页签仅结果态
Stack({ alignContent: Alignment.Start }) {
Text('×')
.fontSize(24)
.fontColor(Color.White)
.width(40)
.height(40)
.textAlign(TextAlign.Center)
.onClick(() => {
this.onClose()
})
if (this.hasResult) {
Row({ space: 24 }) {
this.TabItem('原图', !this.showOriginal)
this.TabItem('效果图', this.showOriginal)
}
.justifyContent(FlexAlign.Center)
.width('100%')
}
}
.width('100%')
.height(56)
// 图:Contain 宽 100%-32 居中,点图切换
Image(this.currentSrc())
.width('100%')
.layoutWeight(1)
.objectFit(ImageFit.Contain)
.margin({ left: 16, right: 16, top: 4, bottom: 8 })
.onClick(() => {
if (this.hasResult) {
this.showOriginal = !this.showOriginal
}
})
// 底区:保存主按钮 + 再来一次(仅结果态显示两者;模特态留白)
Column({ space: 10 }) {
if (this.hasResult) {
Button('保存到相册')
.width('100%')
.height(48)
.borderRadius(24)
.fontSize(16)
.backgroundColor(COLOR_PRIMARY)
.onClick(() => {
this.onSave()
})
Text('再来一次')
.fontSize(13)
.fontColor(Color.White)
.padding({ top: 4, bottom: 8 })
.onClick(() => {
this.onRetry()
})
}
}
.width('100%')
.padding({ left: 32, right: 32, bottom: 20 })
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor(COLOR_BLACK_CAPSULE)
}
@Builder
TabItem(label: string, active: boolean) {
Column({ space: 4 }) {
Text(label)
.fontSize(13)
.fontColor(active ? Color.White : '99FFFFFF')
Row()
.width('100%')
.height(3)
.borderRadius(2)
.backgroundColor(active ? Color.White : Color.Transparent)
}
.alignItems(HorizontalAlign.Center)
.onClick(() => {
this.showOriginal = label === '原图'
})
}
}
@@ -43,6 +43,7 @@ import { FacePhotoConsent, FaceConsentStore } from '../components/f2/FacePhotoCo
import { CategoryChip } from '../components/f2/CategoryChip'
import { ClothCard } from '../components/f2/ClothCard'
import { CapsuleButton } from '../components/f2/CapsuleButton'
import { ImageViewer } from '../components/f2/ImageViewer'
/** 品类过滤 chips:全部 + 五品类(UI 规范 §8 文案)。 */
const FILTER_CHIPS: string[] = ['全部'].concat(CLOTH_CATEGORIES)
@@ -73,6 +74,7 @@ struct FittingRoomPage {
@State lastLocalPath: string = '' // 「上次结果」卡数据源
@State showOriginal: boolean = false
@State showLeaveConfirm: boolean = false // 生成中离开的挽留确认
@State viewerOpen: boolean = false // 全屏图片查看器(§10.3)
// —— 高级项 ——
@State desc: string = ''
@State advancedOpen: boolean = false
@@ -359,7 +361,8 @@ struct FittingRoomPage {
if (gen !== this.genSeq) {
return
}
this.statusText = saved ? '生成完成,点预览图可对比原图' : '结果已生成,本地暂存失败,请尽快保存到相册'
this.statusText = saved ? '生成完成,点图可查看大图' : '结果已生成,本地暂存失败,请尽快保存到相册'
this.viewerOpen = true // 生成完成自动弹全屏查看器恰一次(§10.1)
} catch (e) {
if (gen === this.genSeq) {
this.statusText = e instanceof ApiError ? `${e.message}${e.hint ? ':' + e.hint : ''}` : '生成失败,请重试'
@@ -392,6 +395,10 @@ struct FittingRoomPage {
}
onBackPress(): boolean {
if (this.viewerOpen) {
this.viewerOpen = false
return true
}
if (this.generating) {
this.showLeaveConfirm = true
return true // 拦截系统返回,交由挽留确认处理
@@ -564,27 +571,27 @@ struct FittingRoomPage {
.justifyContent(FlexAlign.Center)
.backgroundColor(COLOR_IMAGE_BG)
} else {
// 本人照/结果大图:有结果时点图切换 原图/效果 对比(沿用 v1)
Image(this.previewSrc())
// 本人照/结果大图(Contain 不裁切,§10.1):点图开全屏查看器
Image(this.showOriginal || this.resultSrc === '' ? this.humanUri : this.resultSrc)
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.objectFit(ImageFit.Contain)
.onClick(() => {
if (this.resultUrl.length > 0 && !this.generating) {
this.showOriginal = !this.showOriginal
if (!this.generating && this.humanUri.length > 0) {
this.viewerOpen = true
}
})
}
// 左上「模特/效果图」角标(黑 60% 胶囊,白字 12,距边 12)
// 左上「模特/效果图」角标(黑 60% 胶囊,白字 11,距边 10,§10.4)
if (this.humanUri.length > 0) {
Text(this.showOriginal || this.resultSrc === '' ? '模特' : '效果图')
.fontSize(12)
.fontSize(11)
.fontColor(Color.White)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.borderRadius(14)
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.borderRadius(12)
.backgroundColor(COLOR_BADGE_BG)
.position({ x: 12, y: 12 })
.position({ x: 10, y: 10 })
}
// 右缘悬浮「试穿」黑胶囊:覆盖层自身不参与命中(None),点击落到图上
@@ -637,7 +644,8 @@ struct FittingRoomPage {
}
}
.width('100%')
.aspectRatio(3 / 4) // 3:4 竖版:与生成结果(768x1024)/人像照同比例,Cover 不再裁成正方形
.height(300) // 紧凑预览(§10.1):固定 300vp,占屏约 38%
.backgroundColor(COLOR_IMAGE_BG) // Contain 灰边底
.borderRadius(16)
.clip(true)
.margin({ left: 16, right: 16, top: 8, bottom: 8 })
@@ -794,9 +802,9 @@ struct FittingRoomPage {
.width('100%')
}
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.width('100%')
}
}
@@ -834,9 +842,9 @@ struct FittingRoomPage {
}
}, (it: WardrobeItem) => this.cardKey(it) + '_b')
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.width('100%')
}
}
@@ -1172,6 +1180,25 @@ struct FittingRoomPage {
if (this.showAddSheet) {
this.AddCategorySheet()
}
// 全屏图片查看器(§10.3):与预览共用 showOriginal/resultSrc
if (this.viewerOpen) {
ImageViewer({
showOriginal: $showOriginal,
hasResult: this.resultUrl.length > 0,
originalSrc: this.humanUri,
resultSrc: this.resultSrc,
onSave: (): void => {
this.saveResult()
},
onRetry: (): void => {
this.viewerOpen = false
this.resetResult()
},
onClose: (): void => {
this.viewerOpen = false
}
})
}
}
.width('100%')
.height('100%')
+97
View File
@@ -209,3 +209,100 @@ CTA 点击 → router 跳 DiagnosisFormPage。hero 为该页专属装饰,报
3. 发型页 v2:双图+对比、分段控件、8 色圆点+名称、强度滑杆、吸底「应用发色」;旧三态与授权弹层行为不回归。
4. 落地页:紫渐变 hero、5 条困扰卡、4 步步骤条、吸底「马上体验」跳填写页。
5. 填写页:分组白卡表单、三个滚轮范围正确(100-250 / 25-300 / 年份)、必填校验+汇总条+滚动定位、隐私说明可见。
## 10. 试衣间布局 v2.1(FittingRoomPage 紧凑预览改版)
> 用户诉求:①预览区(现 3:4 大图 437vp、占屏约 57%)弄小;②我的/精选衣橱区弄大;③少滚动即可浏览选衣。
> 本节与 §3 预览区规格、§9 验收 2 冲突处**以本节为准**,其余全部沿用。逻辑屏按 360×788vp 计算。
### 10.1 五项裁决
1. **预览 vs 不裁切 = 方案 a+c+d 组合**:预览固定高 **300vp** + `ImageFit.Contain` + 底 COLOR_IMAGE_BG(人像 3:4 渲染约 225×300 居中,两侧灰边各≈51vp)。理由:试穿必须看全全身,Cover 裁切直接劣化选衣判断且用户已投诉过「被裁切」,故预览态放弃 Cover(成为 §2 全 Cover 规则的唯一例外);小图看不全→**点图开全屏查看器**(§10.3);**生成完成后自动弹出查看器一次**(genSeq 守卫:取消/离开/迟到失效不弹,用户关闭后不重复弹)。空态(未传本人照)预览仍 300vp,占位内容居中沿用。
2. **网格三列、纯图卡**:列/行距 8;卡宽≈104、图 104×139(3:4 Cover)。**去掉名称与品类两行文字**(104vp 窄卡放不下、品类已可由 chips 过滤),品类改为图内左下小角标;槽位角标与删除 × 保留右上。首屏可见 ≥2 整行=6 卡(原双列带文字约 3 卡)。
3. **生成中/结果态**:生成中覆盖层压在 300vp 预览内(黑 60%):Loading 36 + 状态文案 12(最多 2 行)+ 秒数 16 + 「取消生成」高 28;结果操作行「保存到相册/再来一次」保留在预览下方原位不动;查看器内另设「保存到相册」主按钮。
4. **胶囊/角标缩小**:「试穿」黑胶囊 **44×96**(原 52×120)、圆角 22、白字 14,右缘距预览右边 12、垂直居中,置灰条件沿用;「模特/效果图」角标字号 12→**11**、内边距 10×6、距边 10(对齐 §2 角标 11)。
5. **全屏查看器**:全屏覆盖层、底 COLOR_BLACK_CAPSULE(#1A1A1A,复用不新增色值);与页面**共用 `showOriginal` 状态**——页签、预览左上角标、图源三处同步。
### 10.2 整页线框(紧凑预览版)
```
┌──────────────────────────────────┐ 360vp
│ ‹ 返回 试衣间 │ 顶栏48 沿用
├──────────────────────────────────┤
│ ┌──────────────────────────────┐ │ 预览卡:margin16/8 高300 固定
│ │[模特]11 ┌────┐ │ │ 底#EDEFF3 圆角16 clip
│ │ ┌───────────┐ │试 │ │ │ 图 Contain 225×300 居中
│ │ │ 全身完整 │ │穿 │ │ │ 两侧灰边各≈51 不裁切
│ │ │ 不裁切 │ └────┘ │ │ 点图→全屏查看器(§10.3)
│ │ └───────────┘ 44×96 │ │ 生成中:黑60%覆盖层
│ └──────────────────────────────┘ │
│ 请先选择一件上衣… / 状态/合规提示 │ 12 条件行 ≤20vp(沿用)
├──────────────────────────────────┤
│ 我的衣橱 精选衣橱 │ Tab32 沿用(主色3vp下划线)
│ (全部)(上衣)(下装)(连衣裙)(外套)(套装)→│ chips高28 横滚 选中黑底白字
├──────────────────────────────────┤
│ ┌──────┐ ┌──────┐ ┌──────┐ │ 三列:列距8 行距8
│ │ 图3:4 │ │ 图3:4 │[×] 图3:4 │ │ 卡宽104 图104×139 Cover
│ │[上衣] │ │[下装] │ │ │ │ 左下品类角标(黑60%字10)
│ └──────┘ └──────┘ └──────┘ │ 选中2vp主色描边+右上槽位角标
│ ┌──────┐ ┌──────┐ ┌──────┐ │ 第2行 首屏完整可见
│ │ 图 │ │ + │ │ 图 │ │ 末位添加卡:+24/添加12
│ └──────┘ └──────┘ └──────┘ │ 底部「拍照」11 主色
├────── ↓ 以下滚动 ↓ ───────────────┤
│ 保存到相册 再来一次 │ 结果操作行(条件,沿用)
│ 连衣裙、外套、套装按上衣流程… │ 脚注11+高级提示词+上次结果卡
└──────────────────────────────────┘
```
垂直预算(788vp):顶栏 48 + 预览块 316(图 300+上下边距 16,占屏 40%,原 453/57%)+ 提示行 ≤20 + 滚动区 ≥404(Tab 32 + chips 36 + 网格首屏 ≥2 整行 286)。
### 10.3 全屏查看器线框
```
┌──────────────────────────────────┐ 全屏覆盖层 底#1A1A1A
│ × 原图 效果图 │ 顶56:×白24 距左16;页签白13
│ ───── │ 当前=白+3vp白下划线;未选白60%
│ │ 仅 resultUrl 非空显页签;
│ ┌────────────┐ │ 模特态仅「×」无页签
│ │ 原图/效果图 │ │ 图 Contain 宽100%-32 居中
│ │ 3:4 居中 │ │ 高=顶部56与底区之外全部
│ └────────────┘ │ 点图=切原图/效果(共用
│ │ showOriginal,关闭后预览同步)
│ [ 保存到相册 ] │ 主按钮 高48 圆角24 主色白字16
│ 再来一次 │ 白13 文字链(仅结果态显示)
└──────────────────────────────────┘
关闭:点 × / 系统返回;保存成功回填状态文案「已保存到相册」(沿用三态口径)。
```
### 10.4 尺寸与 §2/§3 冲突修订清单
| # | 原规则 | v2.1 修订 |
|---|---|---|
| 1 | §2 图片一律 ImageFit.Cover | 试衣间预览/查看器改 **Contain**(唯一例外) |
| 2 | §2 列表/网格间距 12 | 试衣间网格列/行距 **8** |
| 3 | §3 预览区高=屏 45% | 固定 **300vp**(含边距占屏 40%) |
| 4 | §3 双列 3:4 卡+名称13+品类11 | **三列纯图卡**(104×139,无文字行) |
| 5 | §3 角标白字 12、内边距 8×12、距边 12 | 字 **11**、内边距 10×6、距边 10 |
| 6 | §3「试穿」胶囊 52×120 | **44×96** 圆角 22 |
| 7 | §3 品类条 chips 高 32 | **28**(字 12、内边距 10×6、圆角 14) |
新增组件:**ImageViewer**(components/f2/ImageViewer.ets,@Component 导出;参数:imageSrc、hasResult、showOriginal 双向、onSave、onRetry、onClose)。
### 10.5 新增文案(逐字,其余沿用 §8)
| 位置 | 文案 |
|---|---|
| 试衣间·查看器页签 | 原图 / 效果图 |
| 试衣间·查看器关闭 | ×(图标,无朗读文案需求,无文字) |
| 试衣间·卡片品类角标 | 沿用品类词:上衣 / 下装 / 连衣裙 / 外套 / 套装 |
### 10.6 验收清单(可自动化判定)
1. 360×788vp 下预览块高(含上下边距)=316vp 固定;图 ImageFit.Contain、底 #EDEFF3、无裁切。
2. 无状态提示行时,衣橱网格首屏完整可见 2 行 × 3 列(≥6 卡),Tab/chips 无需滚动。
3. 网格 3 列、列/行距 8;服装卡无名称/品类文字行、左下品类角标;选中态 2vp #C2185B 描边+右上槽位角标;我的衣橱卡保留 20×20 删除。
4. 点预览图弹出全屏查看器;有结果时查看器页签与预览左上角标文案一致(同一 showOriginal 状态驱动)。
5. 生成完成自动打开查看器恰一次(取消/离开/迟到失效不弹);查看器「保存到相册」走系统保存对话框三态口径。
6. 「试穿」胶囊 44×96 右缘悬浮、未选上衣/生成中置灰不可点(沿用);生成中覆盖层四要素(Loading/文案≤2行/秒数/取消)在 300vp 内完整显示。
7. 「+添加」卡在网格末位,含相册(点卡)与拍照(底部链接)两入口(沿用)。
8. 除查看器底与胶囊复用 #1A1A1A 外无新增色值;§3/§9.2 与本节冲突处按本表 §10.4 执行。