From 1b534f513310f4418d985512f2fa1983e7f3e1c8 Mon Sep 17 00:00:00 2001 From: xiangsilian Date: Thu, 3 Sep 2026 22:14:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(fitting):=20=E8=AF=95=E8=A1=A3=E9=97=B4?= =?UTF-8?q?=E5=B8=83=E5=B1=80=20v2.1=E2=80=94=E2=80=94=E7=B4=A7=E5=87=91?= =?UTF-8?q?=E9=A2=84=E8=A7=88=20300vp+=E4=B8=89=E5=88=97=E7=BA=AF=E5=9B=BE?= =?UTF-8?q?=E5=8D=A1+=E5=85=A8=E5=B1=8F=E6=9F=A5=E7=9C=8B=E5=99=A8?= =?UTF-8?q?=EF=BC=88=E8=AE=BE=E8=AE=A1=E5=B8=88=20Agent=20=E5=87=BA?= =?UTF-8?q?=E7=A8=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 预览区 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 条验收) --- .../main/ets/components/f2/CapsuleButton.ets | 8 +- .../main/ets/components/f2/CategoryChip.ets | 8 +- .../src/main/ets/components/f2/ClothCard.ets | 82 ++++++------ .../main/ets/components/f2/ImageViewer.ets | 120 ++++++++++++++++++ .../src/main/ets/pages/FittingRoomPage.ets | 63 ++++++--- docs/美搭配与美学诊断-UI规范.md | 97 ++++++++++++++ 6 files changed, 309 insertions(+), 69 deletions(-) create mode 100644 app/entry/src/main/ets/components/f2/ImageViewer.ets diff --git a/app/entry/src/main/ets/components/f2/CapsuleButton.ets b/app/entry/src/main/ets/components/f2/CapsuleButton.ets index e898df7..f4ca994 100644 --- a/app/entry/src/main/ets/components/f2/CapsuleButton.ets +++ b/app/entry/src/main/ets/components/f2/CapsuleButton.ets @@ -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) { diff --git a/app/entry/src/main/ets/components/f2/CategoryChip.ets b/app/entry/src/main/ets/components/f2/CategoryChip.ets index 13aa619..11941a7 100644 --- a/app/entry/src/main/ets/components/f2/CategoryChip.ets +++ b/app/entry/src/main/ets/components/f2/CategoryChip.ets @@ -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 diff --git a/app/entry/src/main/ets/components/f2/ClothCard.ets b/app/entry/src/main/ets/components/f2/ClothCard.ets index ecdf40a..2fceb9e 100644 --- a/app/entry/src/main/ets/components/f2/ClothCard.ets +++ b/app/entry/src/main/ets/components/f2/ClothCard.ets @@ -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%') } diff --git a/app/entry/src/main/ets/components/f2/ImageViewer.ets b/app/entry/src/main/ets/components/f2/ImageViewer.ets new file mode 100644 index 0000000..cc2072d --- /dev/null +++ b/app/entry/src/main/ets/components/f2/ImageViewer.ets @@ -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 === '原图' + }) + } +} diff --git a/app/entry/src/main/ets/pages/FittingRoomPage.ets b/app/entry/src/main/ets/pages/FittingRoomPage.ets index 54b7da0..85e3106 100644 --- a/app/entry/src/main/ets/pages/FittingRoomPage.ets +++ b/app/entry/src/main/ets/pages/FittingRoomPage.ets @@ -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%') diff --git a/docs/美搭配与美学诊断-UI规范.md b/docs/美搭配与美学诊断-UI规范.md index fe5a57c..34ea667 100644 --- a/docs/美搭配与美学诊断-UI规范.md +++ b/docs/美搭配与美学诊断-UI规范.md @@ -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 执行。