feat(app): 美搭配 v2(衣橱+精选30件)/发型 v2(男女分栏+换发色修复)/美学诊断两页——多 Agent 一轮交付
- fix(tryon): 鸿蒙 http.multiFormDataList 真机发空包(400 请上传图片)→ TryonApi 手工构造 multipart,真机验证通过;换发型/换发色/换装全链路 MEIDA_TRYON 打点 - feat(hair): 60 款发型视觉分类男女(26/34)Segment 分栏;双图对比、8 色圆点、吸底应用发色 - feat(fitting): FittingRoomPage 预览+试穿黑胶囊+我的衣橱(preferences+filesDir 持久化)+精选衣橱(rawfile 30 件+manifest)+品类过滤+双槽位生成 - feat(diagnosis): 紫渐变落地页+五字段填写页(@Prop 修选择器回填)+预填跳 ReportCreatePage - chore(theme): 9 个色板 token 收口;首页入口改版
This commit is contained in:
@@ -6,6 +6,11 @@
|
||||
* 发型库三态(#8/#18):加载中 / 空态 / 失败+重新加载。
|
||||
* 结果归宿(#18,与 #17 统一):试戴/换发色成功即落 filesDir/results/hair_last.jpg
|
||||
* (换发色后覆盖为最新版本),保存优先复用该文件,杀 App 后「上次结果」卡可回看。
|
||||
*
|
||||
* v2 视觉重排(《美搭配与美学诊断-UI规范》§4,交互全部保留):
|
||||
* 顶部双图区(原图|效果图 等宽,左下角标 COLOR_BADGE_BG,点右图切换对比);
|
||||
* 性别分栏改 Segment 组件(女生/男生);8 色发色改 ColorDot 圆点;
|
||||
* 「应用发色」移至吸底主按钮(无结果时随发色卡整体隐藏,沿用现逻辑)。
|
||||
*/
|
||||
|
||||
import { router } from '@kit.ArkUI'
|
||||
@@ -18,7 +23,19 @@ import { TryonApi, Hairstyle } from '../core/TryonApi'
|
||||
import { ImageCodec } from '../core/ImageCodec'
|
||||
import { ApiError } from '../core/ApiTypes'
|
||||
import { ContextHolder } from '../core/ContextHolder'
|
||||
import { COLOR_PRIMARY } from '../core/Theme'
|
||||
import {
|
||||
COLOR_PRIMARY,
|
||||
COLOR_PAGE_BG,
|
||||
COLOR_TEXT_PRIMARY,
|
||||
COLOR_TEXT_SECONDARY,
|
||||
COLOR_TEXT_DISABLED,
|
||||
COLOR_DIVIDER,
|
||||
COLOR_IMAGE_BG,
|
||||
COLOR_BADGE_BG,
|
||||
COLOR_ERROR
|
||||
} from '../core/Theme'
|
||||
import { Segment } from '../components/f2/Segment'
|
||||
import { ColorDot } from '../components/f2/ColorDot'
|
||||
import { FacePhotoConsent, FaceConsentStore } from '../components/f2/FacePhotoConsent'
|
||||
|
||||
interface PresetColor {
|
||||
@@ -37,11 +54,6 @@ const PRESET_COLORS: PresetColor[] = [
|
||||
{ name: '蓝黑', rgb: [40, 50, 70] }
|
||||
]
|
||||
|
||||
const PAGE_BG: string = '#F5F6F8'
|
||||
const ACCENT: string = COLOR_PRIMARY
|
||||
const TEXT_PRIMARY: string = '#222222'
|
||||
const TEXT_SECONDARY: string = '#8A9099'
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct HairDesignPage {
|
||||
@@ -59,6 +71,7 @@ struct HairDesignPage {
|
||||
@State showOriginal: boolean = false
|
||||
@State colorIdx: number = 1
|
||||
@State ratio: number = 0.9
|
||||
@State genderTab: string = 'female' // 发型男女分栏(female 默认:库内女款 34 / 男款 26)
|
||||
@State showFaceConsent: boolean = false
|
||||
private consentPickCamera: boolean = false // 同意后继续的拍照/相册入口
|
||||
|
||||
@@ -97,6 +110,29 @@ struct HairDesignPage {
|
||||
}
|
||||
}
|
||||
|
||||
/** 当前 Tab 的发型(unknown 款两 Tab 都展示,服务端新增款不丢)。 */
|
||||
private visibleStyles(): Hairstyle[] {
|
||||
const out: Hairstyle[] = []
|
||||
for (const s of this.styles) {
|
||||
const g: string = TryonApi.hairstyleGender(s.id)
|
||||
if (g === this.genderTab || g === 'unknown') {
|
||||
out.push(s)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** RGB 分量转两位 hex(发色圆点展示用)。 */
|
||||
private hex2(v: number): string {
|
||||
const s: string = v.toString(16)
|
||||
return s.length < 2 ? `0${s}` : s
|
||||
}
|
||||
|
||||
/** PRESET_COLORS 的 RGB 转 #RRGGBB:仅圆点展示,发色接口 rgb 传参格式不变。 */
|
||||
private colorHex(rgb: number[]): string {
|
||||
return `#${this.hex2(rgb[0])}${this.hex2(rgb[1])}${this.hex2(rgb[2])}`
|
||||
}
|
||||
|
||||
/** 上传面部特写前须单独同意(CR-01:人脸属敏感个人信息)。 */
|
||||
private pickPhoto(fromCamera: boolean): void {
|
||||
if (!FaceConsentStore.isGranted()) {
|
||||
@@ -168,7 +204,8 @@ struct HairDesignPage {
|
||||
const pure: string = this.resultBase64.includes(',')
|
||||
? this.resultBase64.substring(this.resultBase64.indexOf(',') + 1)
|
||||
: this.resultBase64
|
||||
const buf = helper.decodeSync(pure).buffer as ArrayBuffer
|
||||
// slice() 拷贝出精确长度的副本:decodeSync 返回视图,直接取 .buffer 会带尾部脏字节
|
||||
const buf = helper.decodeSync(pure).slice().buffer as ArrayBuffer
|
||||
const rgb: string = `[${c.rgb[0]}, ${c.rgb[1]}, ${c.rgb[2]}]`
|
||||
this.resultBase64 = await TryonApi.changeHairColor(buf, rgb, this.ratio)
|
||||
this.resultPm = await ImageCodec.base64ToPixelMap(this.resultBase64)
|
||||
@@ -235,8 +272,8 @@ struct HairDesignPage {
|
||||
Column() {
|
||||
// 顶部标题栏
|
||||
Row() {
|
||||
Text('‹ 返回').fontSize(15).fontColor(TEXT_SECONDARY).onClick(() => router.back())
|
||||
Text('发型设计').fontSize(17).fontWeight(FontWeight.Medium).fontColor(TEXT_PRIMARY)
|
||||
Text('‹ 返回').fontSize(15).fontColor(COLOR_TEXT_SECONDARY).onClick(() => router.back())
|
||||
Text('发型设计').fontSize(17).fontWeight(FontWeight.Medium).fontColor(COLOR_TEXT_PRIMARY)
|
||||
Text('').width(48)
|
||||
}
|
||||
.width('100%')
|
||||
@@ -245,199 +282,247 @@ struct HairDesignPage {
|
||||
.backgroundColor(Color.White)
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 12 }) {
|
||||
// 照片区:左原图 / 右结果
|
||||
Row({ space: 12 }) {
|
||||
Column({ space: 8 }) {
|
||||
if (this.photoUri.length > 0) {
|
||||
Image(this.photoUri)
|
||||
.width('100%').height(220).objectFit(ImageFit.Cover).borderRadius(12)
|
||||
} else {
|
||||
Column({ space: 6 }) {
|
||||
Text('+').fontSize(28).fontColor(TEXT_SECONDARY)
|
||||
Text('上传面部特写').fontSize(12).fontColor(TEXT_SECONDARY)
|
||||
}
|
||||
.width('100%').height(220).justifyContent(FlexAlign.Center)
|
||||
.borderRadius(12).backgroundColor('#EDEFF3')
|
||||
.onClick(() => { this.pickPhoto(false) })
|
||||
}
|
||||
Row({ space: 8 }) {
|
||||
Text('相册').fontSize(12).fontColor(ACCENT).onClick(() => { this.pickPhoto(false) })
|
||||
Text('拍照').fontSize(12).fontColor(ACCENT).onClick(() => { this.pickPhoto(true) })
|
||||
}
|
||||
}
|
||||
.layoutWeight(1)
|
||||
|
||||
Column({ space: 8 }) {
|
||||
if (this.generating) {
|
||||
Column({ space: 8 }) {
|
||||
LoadingProgress().width(36).height(36).color(ACCENT)
|
||||
Text('生成中…').fontSize(12).fontColor(TEXT_SECONDARY)
|
||||
}
|
||||
.width('100%').height(220).justifyContent(FlexAlign.Center)
|
||||
.borderRadius(12).backgroundColor('#EDEFF3')
|
||||
} else if (this.resultPm !== undefined && !this.showOriginal) {
|
||||
Image(this.resultPm)
|
||||
.width('100%').height(220).objectFit(ImageFit.Cover).borderRadius(12)
|
||||
} else if (this.showOriginal && this.photoUri.length > 0) {
|
||||
Image(this.photoUri)
|
||||
.width('100%').height(220).objectFit(ImageFit.Cover).borderRadius(12)
|
||||
} else {
|
||||
Column() {
|
||||
Text('效果图').fontSize(12).fontColor(TEXT_SECONDARY)
|
||||
}
|
||||
.width('100%').height(220).justifyContent(FlexAlign.Center)
|
||||
.borderRadius(12).backgroundColor('#EDEFF3')
|
||||
}
|
||||
Row({ space: 8 }) {
|
||||
Text(this.showOriginal ? '看效果' : '对比')
|
||||
.fontSize(12)
|
||||
.fontColor(this.resultPm !== undefined ? ACCENT : TEXT_SECONDARY)
|
||||
.onClick(() => {
|
||||
if (this.resultPm !== undefined) {
|
||||
this.showOriginal = !this.showOriginal
|
||||
}
|
||||
})
|
||||
Text('保存')
|
||||
.fontSize(12)
|
||||
.fontColor(this.resultPm !== undefined ? ACCENT : TEXT_SECONDARY)
|
||||
.onClick(() => { this.saveResult() })
|
||||
}
|
||||
}
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
Text('请使用本人照片,上传即确认不侵犯他人肖像权')
|
||||
.fontSize(11).fontColor(TEXT_SECONDARY).width('100%')
|
||||
|
||||
if (this.statusText.length > 0) {
|
||||
Text(this.statusText).fontSize(12).fontColor(TEXT_SECONDARY).width('100%')
|
||||
}
|
||||
|
||||
// 上次发型结果回看(#18):无本次结果时展示留存副本(保存/移除)
|
||||
if (this.lastLocalPath.length > 0 && this.resultPm === undefined) {
|
||||
Column({ space: 8 }) {
|
||||
Text('上次发型结果')
|
||||
.fontSize(13).fontWeight(FontWeight.Medium).fontColor(TEXT_PRIMARY).width('100%')
|
||||
Image(fileUri.getUriFromPath(this.lastLocalPath))
|
||||
.width('100%').height(280).objectFit(ImageFit.Contain)
|
||||
.borderRadius(12).backgroundColor('#EDEFF3')
|
||||
Row({ space: 16 }) {
|
||||
Text('保存到相册').fontSize(13).fontColor(ACCENT)
|
||||
.onClick(() => { this.saveLastResult() })
|
||||
Text('知道了').fontSize(13).fontColor(TEXT_SECONDARY)
|
||||
.onClick(() => { this.lastLocalPath = '' })
|
||||
}
|
||||
.width('100%').justifyContent(FlexAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.borderRadius(12)
|
||||
.backgroundColor(Color.White)
|
||||
}
|
||||
|
||||
// 发型网格
|
||||
Text('选择发型(点选即试戴)').fontSize(14).fontWeight(FontWeight.Medium).fontColor(TEXT_PRIMARY)
|
||||
if (this.styles.length > 0) {
|
||||
Grid() {
|
||||
ForEach(this.styles, (style: Hairstyle) => {
|
||||
GridItem() {
|
||||
Column({ space: 4 }) {
|
||||
Image(TryonApi.hairstyleImageUrl(style))
|
||||
.width('100%').aspectRatio(0.72).objectFit(ImageFit.Cover)
|
||||
.borderRadius(8)
|
||||
.border(this.selectedStyleId === style.id
|
||||
? { width: 2, color: ACCENT }
|
||||
: { width: 0 })
|
||||
Text(style.name).fontSize(11).fontColor(TEXT_PRIMARY).maxLines(1)
|
||||
Column({ space: 12 }) {
|
||||
// 顶部双图区(v2):原图 | 效果图 等宽,左下角标当前显示态,点右图切换对比
|
||||
Row({ space: 12 }) {
|
||||
// 左图:原图(无照片时为上传入口占位)
|
||||
Stack({ alignContent: Alignment.BottomStart }) {
|
||||
if (this.photoUri.length > 0) {
|
||||
Image(this.photoUri)
|
||||
.width('100%').height(240).objectFit(ImageFit.Cover).borderRadius(12)
|
||||
Text('原图')
|
||||
.fontSize(11).fontColor(Color.White)
|
||||
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
|
||||
.borderRadius(12)
|
||||
.backgroundColor(COLOR_BADGE_BG)
|
||||
.margin(8)
|
||||
} else {
|
||||
Column({ space: 6 }) {
|
||||
Text('+').fontSize(28).fontColor(COLOR_TEXT_SECONDARY)
|
||||
Text('上传面部特写').fontSize(12).fontColor(COLOR_TEXT_SECONDARY)
|
||||
}
|
||||
.width('100%').height(240).justifyContent(FlexAlign.Center)
|
||||
.borderRadius(12).backgroundColor(COLOR_IMAGE_BG)
|
||||
.onClick(() => { this.pickPhoto(false) })
|
||||
}
|
||||
.onClick(() => { this.tryOn(style) })
|
||||
}, (style: Hairstyle) => style.id)
|
||||
}
|
||||
.columnsTemplate('1fr 1fr 1fr')
|
||||
.columnsGap(8)
|
||||
.rowsGap(8)
|
||||
.width('100%')
|
||||
} else if (this.listError.length > 0) {
|
||||
// 失败态(#8/#18):错误文案 + 重新加载出口
|
||||
Column({ space: 8 }) {
|
||||
Text(this.listError).fontSize(12).fontColor('#C0392B')
|
||||
Text('重新加载')
|
||||
.fontSize(13).fontColor(ACCENT)
|
||||
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
|
||||
.borderRadius(14).backgroundColor(Color.White)
|
||||
.onClick(() => { this.loadStyles() })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
|
||||
// 右图:效果图(点按在 效果图/原图 间切换对比,沿用现交互)
|
||||
Stack({ alignContent: Alignment.BottomStart }) {
|
||||
if (this.generating) {
|
||||
Column({ space: 8 }) {
|
||||
LoadingProgress().width(36).height(36).color(COLOR_PRIMARY)
|
||||
Text('生成中…').fontSize(12).fontColor(COLOR_TEXT_SECONDARY)
|
||||
}
|
||||
.width('100%').height(240).justifyContent(FlexAlign.Center)
|
||||
.borderRadius(12).backgroundColor(COLOR_IMAGE_BG)
|
||||
} else if (this.resultPm !== undefined && !this.showOriginal) {
|
||||
Image(this.resultPm)
|
||||
.width('100%').height(240).objectFit(ImageFit.Cover).borderRadius(12)
|
||||
Text('效果图')
|
||||
.fontSize(11).fontColor(Color.White)
|
||||
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
|
||||
.borderRadius(12)
|
||||
.backgroundColor(COLOR_BADGE_BG)
|
||||
.margin(8)
|
||||
} else if (this.showOriginal && this.photoUri.length > 0) {
|
||||
Image(this.photoUri)
|
||||
.width('100%').height(240).objectFit(ImageFit.Cover).borderRadius(12)
|
||||
Text('原图')
|
||||
.fontSize(11).fontColor(Color.White)
|
||||
.padding({ left: 10, right: 10, top: 4, bottom: 4 })
|
||||
.borderRadius(12)
|
||||
.backgroundColor(COLOR_BADGE_BG)
|
||||
.margin(8)
|
||||
} else {
|
||||
Column() {
|
||||
Text('效果图').fontSize(12).fontColor(COLOR_TEXT_SECONDARY)
|
||||
}
|
||||
.width('100%').height(240).justifyContent(FlexAlign.Center)
|
||||
.borderRadius(12).backgroundColor(COLOR_IMAGE_BG)
|
||||
}
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.onClick(() => {
|
||||
if (this.resultPm !== undefined && !this.generating) {
|
||||
this.showOriginal = !this.showOriginal
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.padding({ top: 8, bottom: 8 })
|
||||
} else if (this.listLoaded) {
|
||||
// 空态(#18):加载成功但无上架发型
|
||||
Column({ space: 8 }) {
|
||||
Text('发型暂未上架,稍后再试').fontSize(12).fontColor(TEXT_SECONDARY)
|
||||
Text('重新加载')
|
||||
.fontSize(13).fontColor(ACCENT)
|
||||
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
|
||||
.borderRadius(14).backgroundColor(Color.White)
|
||||
.onClick(() => { this.loadStyles() })
|
||||
|
||||
// 入口文字链(沿用):相册/拍照 + 保存(保存沿用现交互与置灰口径)
|
||||
Row({ space: 16 }) {
|
||||
Text('相册').fontSize(12).fontColor(COLOR_PRIMARY).onClick(() => { this.pickPhoto(false) })
|
||||
Text('拍照').fontSize(12).fontColor(COLOR_PRIMARY).onClick(() => { this.pickPhoto(true) })
|
||||
Text('保存')
|
||||
.fontSize(12)
|
||||
.fontColor(this.resultPm !== undefined ? COLOR_PRIMARY : COLOR_TEXT_SECONDARY)
|
||||
.onClick(() => { this.saveResult() })
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.padding({ top: 8, bottom: 8 })
|
||||
} else {
|
||||
// 加载中(#18):不再让空列表永远停在「加载中」
|
||||
Row({ space: 8 }) {
|
||||
LoadingProgress().width(18).height(18).color(ACCENT)
|
||||
Text('发型加载中…').fontSize(12).fontColor(TEXT_SECONDARY)
|
||||
|
||||
Text('请使用本人照片,上传即确认不侵犯他人肖像权')
|
||||
.fontSize(11).fontColor(COLOR_TEXT_SECONDARY).width('100%')
|
||||
|
||||
if (this.statusText.length > 0) {
|
||||
Text(this.statusText).fontSize(12).fontColor(COLOR_TEXT_SECONDARY).width('100%')
|
||||
}
|
||||
|
||||
// 上次发型结果回看(#18):无本次结果时展示留存副本(保存/移除)
|
||||
if (this.lastLocalPath.length > 0 && this.resultPm === undefined) {
|
||||
Column({ space: 8 }) {
|
||||
Text('上次发型结果')
|
||||
.fontSize(13).fontWeight(FontWeight.Medium).fontColor(COLOR_TEXT_PRIMARY).width('100%')
|
||||
Image(fileUri.getUriFromPath(this.lastLocalPath))
|
||||
.width('100%').height(280).objectFit(ImageFit.Contain)
|
||||
.borderRadius(12).backgroundColor(COLOR_IMAGE_BG)
|
||||
Row({ space: 16 }) {
|
||||
Text('保存到相册').fontSize(13).fontColor(COLOR_PRIMARY)
|
||||
.onClick(() => { this.saveLastResult() })
|
||||
Text('知道了').fontSize(13).fontColor(COLOR_TEXT_SECONDARY)
|
||||
.onClick(() => { this.lastLocalPath = '' })
|
||||
}
|
||||
.width('100%').justifyContent(FlexAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.borderRadius(12)
|
||||
.backgroundColor(Color.White)
|
||||
}
|
||||
|
||||
// 发型网格(男女分栏,三态沿用不动)
|
||||
Text('选择发型(点选即试戴)').fontSize(14).fontWeight(FontWeight.Medium).fontColor(COLOR_TEXT_PRIMARY)
|
||||
// 性别分段(v2):女生/男生,映射 genderTab;unknown 款两段都展示
|
||||
Row() {
|
||||
Segment({
|
||||
items: ['女生', '男生'],
|
||||
selected: this.genderTab === 'female' ? 0 : 1,
|
||||
onSelect: (idx: number): void => {
|
||||
this.genderTab = idx === 0 ? 'female' : 'male'
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.padding({ top: 8, bottom: 8 })
|
||||
}
|
||||
|
||||
// 换发色(有结果才可用)
|
||||
if (this.resultPm !== undefined) {
|
||||
Column({ space: 8 }) {
|
||||
Text('换个发色').fontSize(14).fontWeight(FontWeight.Medium).fontColor(TEXT_PRIMARY)
|
||||
.width('100%')
|
||||
Flex({ wrap: FlexWrap.Wrap }) {
|
||||
ForEach(PRESET_COLORS, (c: PresetColor, idx: number) => {
|
||||
Text(c.name)
|
||||
.fontSize(12)
|
||||
.fontColor(this.colorIdx === idx ? Color.White : TEXT_PRIMARY)
|
||||
.backgroundColor(this.colorIdx === idx ? ACCENT : '#EDEFF3')
|
||||
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
|
||||
.borderRadius(12)
|
||||
.margin({ right: 8, bottom: 8 })
|
||||
.onClick(() => { this.colorIdx = idx })
|
||||
})
|
||||
if (this.visibleStyles().length > 0) {
|
||||
Grid() {
|
||||
ForEach(this.visibleStyles(), (style: Hairstyle) => {
|
||||
GridItem() {
|
||||
Column({ space: 4 }) {
|
||||
Image(TryonApi.hairstyleImageUrl(style))
|
||||
.width('100%').aspectRatio(0.72).objectFit(ImageFit.Cover)
|
||||
.borderRadius(8)
|
||||
.border(this.selectedStyleId === style.id
|
||||
? { width: 2, color: COLOR_PRIMARY }
|
||||
: { width: 0 })
|
||||
Text(style.name).fontSize(11).fontColor(COLOR_TEXT_PRIMARY).maxLines(1)
|
||||
}
|
||||
}
|
||||
.onClick(() => { this.tryOn(style) })
|
||||
}, (style: Hairstyle) => style.id)
|
||||
}
|
||||
.columnsTemplate('1fr 1fr 1fr')
|
||||
.columnsGap(8)
|
||||
.rowsGap(8)
|
||||
.width('100%')
|
||||
} else if (this.listError.length > 0) {
|
||||
// 失败态(#8/#18):错误文案 + 重新加载出口
|
||||
Column({ space: 8 }) {
|
||||
Text(this.listError).fontSize(12).fontColor(COLOR_ERROR)
|
||||
Text('重新加载')
|
||||
.fontSize(13).fontColor(COLOR_PRIMARY)
|
||||
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
|
||||
.borderRadius(14).backgroundColor(Color.White)
|
||||
.onClick(() => { this.loadStyles() })
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.padding({ top: 8, bottom: 8 })
|
||||
} else if (this.listLoaded) {
|
||||
// 空态(#18):加载成功但无上架发型
|
||||
Column({ space: 8 }) {
|
||||
Text('发型暂未上架,稍后再试').fontSize(12).fontColor(COLOR_TEXT_SECONDARY)
|
||||
Text('重新加载')
|
||||
.fontSize(13).fontColor(COLOR_PRIMARY)
|
||||
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
|
||||
.borderRadius(14).backgroundColor(Color.White)
|
||||
.onClick(() => { this.loadStyles() })
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.padding({ top: 8, bottom: 8 })
|
||||
} else {
|
||||
// 加载中(#18):不再让空列表永远停在「加载中」
|
||||
Row({ space: 8 }) {
|
||||
Text('强度').fontSize(12).fontColor(TEXT_SECONDARY)
|
||||
Slider({ value: this.ratio, min: 0, max: 1, step: 0.1 })
|
||||
.layoutWeight(1)
|
||||
.onChange((v: number) => { this.ratio = v })
|
||||
Text(this.ratio.toFixed(1)).fontSize(12).fontColor(TEXT_SECONDARY).width(28)
|
||||
LoadingProgress().width(18).height(18).color(COLOR_PRIMARY)
|
||||
Text('发型加载中…').fontSize(12).fontColor(COLOR_TEXT_SECONDARY)
|
||||
}
|
||||
Button(this.generating ? '生成中…' : '应用发色')
|
||||
.width('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.padding({ top: 8, bottom: 8 })
|
||||
}
|
||||
|
||||
// 换发色卡(v2):8 色圆点+名称 + 强度滑杆;无结果整体隐藏(沿用现逻辑)
|
||||
if (this.resultPm !== undefined) {
|
||||
Column({ space: 12 }) {
|
||||
Text('换个发色').fontSize(14).fontWeight(FontWeight.Medium).fontColor(COLOR_TEXT_PRIMARY)
|
||||
.width('100%')
|
||||
Flex({ wrap: FlexWrap.Wrap }) {
|
||||
ForEach(PRESET_COLORS, (c: PresetColor, idx: number) => {
|
||||
Column() {
|
||||
ColorDot({
|
||||
colorHex: this.colorHex(c.rgb),
|
||||
label: c.name,
|
||||
selected: this.colorIdx === idx,
|
||||
onSelect: (): void => { this.colorIdx = idx }
|
||||
})
|
||||
}
|
||||
.margin({ right: 14, bottom: 12 })
|
||||
}, (c: PresetColor) => c.name)
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(ACCENT)
|
||||
.enabled(!this.generating && this.resultBase64.length > 0)
|
||||
.onClick(() => { this.changeColor() })
|
||||
Row({ space: 8 }) {
|
||||
Text('强度').fontSize(12).fontColor(COLOR_TEXT_SECONDARY)
|
||||
Slider({ value: this.ratio, min: 0, max: 1, step: 0.1 })
|
||||
.layoutWeight(1)
|
||||
.onChange((v: number) => { this.ratio = v })
|
||||
Text(this.ratio.toFixed(1)).fontSize(12).fontColor(COLOR_TEXT_SECONDARY).width(28)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.borderRadius(12)
|
||||
.backgroundColor(Color.White)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.borderRadius(12)
|
||||
.backgroundColor(Color.White)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(16)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Off)
|
||||
|
||||
// 吸底「应用发色」(v2):白底条+顶分隔线,高48圆角24主色;
|
||||
// 与发色卡同条件渲染(无结果不出现),生成中置灰并显示「生成中…」
|
||||
if (this.resultPm !== undefined) {
|
||||
Column() {
|
||||
Divider().strokeWidth(0.5).color(COLOR_DIVIDER)
|
||||
Button(this.generating ? '生成中…' : '应用发色')
|
||||
.width('100%')
|
||||
.height(48)
|
||||
.borderRadius(24)
|
||||
.fontSize(16)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(this.generating || this.resultBase64.length === 0 ? COLOR_TEXT_DISABLED : Color.White)
|
||||
.backgroundColor(this.generating || this.resultBase64.length === 0 ? COLOR_DIVIDER : COLOR_PRIMARY)
|
||||
.enabled(!this.generating && this.resultBase64.length > 0)
|
||||
.onClick(() => { this.changeColor() })
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Color.White)
|
||||
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
|
||||
}
|
||||
.width('100%')
|
||||
.padding(16)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Off)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
@@ -455,6 +540,6 @@ struct HairDesignPage {
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(PAGE_BG)
|
||||
.backgroundColor(COLOR_PAGE_BG)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user