From 323f520d7bd36c2fd73de575e0b79a511e067a75 Mon Sep 17 00:00:00 2001 From: zhengchiliu <857493511@qq.com> Date: Sun, 13 Sep 2026 03:09:01 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9Aui=20add?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/clothes-detail/clothes-detail.ts | 190 ++++++++++--- .../pages/clothes-detail/clothes-detail.wxml | 177 +++++++++++- .../pages/clothes-detail/clothes-detail.wxss | 257 ++++++++++++++++-- .../pages/outfit-detail/outfit-detail.ts | 7 +- miniprogram/pages/outfit-edit/outfit-edit.ts | 4 +- 5 files changed, 560 insertions(+), 75 deletions(-) diff --git a/miniprogram/pages/clothes-detail/clothes-detail.ts b/miniprogram/pages/clothes-detail/clothes-detail.ts index a0a324f..166e390 100644 --- a/miniprogram/pages/clothes-detail/clothes-detail.ts +++ b/miniprogram/pages/clothes-detail/clothes-detail.ts @@ -6,24 +6,90 @@ import { logger } from '../../utils/logger' // 日志模块名 const LOG = 'clothes-detail' -const ATTRS = [ - { key: 'category_main', label: '大类', type: 'text' }, - { key: 'category_sub', label: '小类', type: 'text' }, - { key: 'style', label: '风格', type: 'text' }, - { key: 'color_main', label: '主色', type: 'color' }, - { key: 'color_sub', label: '副色', type: 'text' }, - { key: 'thickness', label: '厚度', type: 'text' }, - { key: 'version', label: '版型', type: 'text' }, - { key: 'material', label: '材质', type: 'text' }, - { key: 'pattern', label: '花纹', type: 'text' }, - { key: 'sleeve_type', label: '袖型', type: 'text' }, - { key: 'clothes_type', label: '衣长', type: 'text' }, - { key: 'neckline_type', label: '领型', type: 'text' }, - { key: 'formality', label: '正式程度', type: 'score' }, - { key: 'occasions', label: '适用场合', type: 'text' }, - { key: 'visual_impact', label: '视觉冲击力', type: 'score' }, +// 属性分组定义(对齐设计稿:基础属性 / 版型与材质 / 智能量化评估) +const ATTR_GROUPS = [ + { + title: '基础属性', + attrs: [ + { key: 'category_main', label: '大类', type: 'text' }, + { key: 'category_sub', label: '小类', type: 'text' }, + { key: 'style', label: '风格', type: 'text' }, + { key: 'color_main', label: '主色', type: 'color' }, + { key: 'color_sub', label: '副色', type: 'text' }, + ], + }, + { + title: '版型与材质', + attrs: [ + { key: 'thickness', label: '厚度', type: 'text' }, + { key: 'version', label: '版型', type: 'text' }, + { key: 'material', label: '材质', type: 'text' }, + { key: 'pattern', label: '花纹', type: 'text' }, + { key: 'sleeve_type', label: '袖型', type: 'text' }, + { key: 'clothes_type', label: '衣长', type: 'text' }, + { key: 'neckline_type', label: '领型', type: 'text' }, + ], + }, + { + title: '智能量化评估', + attrs: [ + { key: 'formality', label: '正式程度', type: 'score' }, + { key: 'occasions', label: '适用场合', type: 'multi' }, + { key: 'visual_impact', label: '视觉冲击力', type: 'score' }, + ], + }, ] +// 属性快捷选项(选择面板内点选,仍支持自定义输入) +const ATTR_OPTIONS: Record = { + category_main: ['上装', '下装', '连衣裙', '外套', '鞋', '配饰'], + style: ['简约', '休闲', '通勤', '运动', '甜美', '复古'], + thickness: ['薄', '适中', '厚'], + version: ['宽松', '修身', '阔腿', '紧身', '常规合身'], + material: ['纯棉', '牛仔布', '针织', '皮质', '羊毛', '涤纶'], + pattern: ['纯色', '条纹', '格纹', '印花', '波点'], + sleeve_type: ['无袖', '短袖', '长袖'], + clothes_type: ['短款', '常规', '中长', '长款'], + neckline_type: ['圆领', 'V领', '方领', '翻领', '高领'], + occasions: ['休闲', '通勤', '约会', '运动', '聚会', '正式', '度假'], +} + +// 主色色板(名称 → 色值),供面板点选 +const COLOR_PRESETS = [ + { name: '白色', value: '#FFFFFF' }, + { name: '黑色', value: '#1C1C1E' }, + { name: '灰色', value: '#8E8E93' }, + { name: '红色', value: '#FF3B30' }, + { name: '粉色', value: '#FF2D55' }, + { name: '橙色', value: '#FF9500' }, + { name: '黄色', value: '#FFCC00' }, + { name: '绿色', value: '#34C759' }, + { name: '蓝色', value: '#007AFF' }, + { name: '紫色', value: '#5856D6' }, + { name: '棕色', value: '#A2845E' }, + { name: '米色', value: '#F5EFE0' }, +] + +interface AttrMeta { + key: string + label: string + type: string +} + +// 多值字段(适用场合)按分隔符拆分为标签 +function splitValues(v: string): string[] { + if (!v) return [] + return v.split(/[、,,/|;;\s]+/).filter((s) => !!s) +} + +function findAttr(key: string): AttrMeta | null { + for (const group of ATTR_GROUPS) { + const hit = (group.attrs as AttrMeta[]).find((a) => a.key === key) + if (hit) return hit + } + return null +} + // 将后端 image_process_status 映射为前端展示步骤:0=排队中 1=AI 抠图 2=智能分类 function statusToStep(status: number): number { if (status === ProcessStatus.QUEUED) return 0 @@ -35,7 +101,8 @@ function statusToStep(status: number): number { Page({ data: { uuid: '', - attrs: ATTRS, + attrGroups: ATTR_GROUPS, + colorPresets: COLOR_PRESETS, detail: {} as ClothesItem, formData: {} as ClothesItem, imageStatus: 0, @@ -44,6 +111,14 @@ Page({ loading: true, editMode: false, notFound: false, + // 底部属性选择面板 + sheetVisible: false, + sheetKey: '', + sheetLabel: '', + sheetType: 'text', + sheetOptions: [] as string[], + sheetValue: '', + sheetValues: [] as string[], }, onLoad(query: Record) { @@ -124,7 +199,7 @@ Page({ onToggleEdit() { if (this.data.editMode) { - this.setData({ editMode: false }) + this.setData({ editMode: false, sheetVisible: false }) } else { this.setData({ editMode: true, formData: { ...this.data.detail } }) } @@ -138,39 +213,70 @@ Page({ this.setData({ 'formData.remark': e.detail.value }) }, + // 点击属性行:打开底部选择面板 onAttrTap(e: WechatMiniprogram.TouchEvent) { if (!this.data.editMode) return const key = e.currentTarget.dataset.key as string - const attr = ATTRS.find((a) => a.key === key) + const attr = findAttr(key) if (!attr) return - const current = (this.data.formData as any)[key] || '' - - if (attr.type === 'score') { - wx.showActionSheet({ - itemList: ['1', '2', '3', '4', '5'], - success: (res) => { - this.setData({ [`formData.${key}`]: String(res.tapIndex + 1) }) - }, - }) - return - } - - wx.showModal({ - title: `编辑${attr.label}`, - editable: true, - placeholderText: `请输入${attr.label}`, - content: current, - success: (res) => { - if (res.confirm) { - this.setData({ [`formData.${key}`]: res.content }) - } - }, + const current = (((this.data.formData as any)[key] || '') + '') as string + logger.debug(LOG, '打开属性面板, key=', key, 'type=', attr.type) + this.setData({ + sheetVisible: true, + sheetKey: key, + sheetLabel: attr.label, + sheetType: attr.type, + sheetOptions: ATTR_OPTIONS[key] || [], + sheetValue: attr.type === 'multi' ? '' : current, + sheetValues: attr.type === 'multi' ? splitValues(current) : [], }) }, + onSheetClose() { + this.setData({ sheetVisible: false }) + }, + + onSheetInput(e: WechatMiniprogram.Input) { + this.setData({ sheetValue: e.detail.value }) + }, + + // 单选快捷选项 + onSheetOptionTap(e: WechatMiniprogram.TouchEvent) { + this.setData({ sheetValue: e.currentTarget.dataset.value as string }) + }, + + // 多选快捷选项(适用场合) + onSheetValueTap(e: WechatMiniprogram.TouchEvent) { + const value = e.currentTarget.dataset.value as string + const list = this.data.sheetValues.slice() + const idx = list.indexOf(value) + if (idx >= 0) list.splice(idx, 1) + else list.push(value) + this.setData({ sheetValues: list }) + }, + + // 色板点选 + onSheetColorTap(e: WechatMiniprogram.TouchEvent) { + this.setData({ sheetValue: e.currentTarget.dataset.name as string }) + }, + + // 评分点选 + onSheetScoreTap(e: WechatMiniprogram.TouchEvent) { + this.setData({ sheetValue: String(e.currentTarget.dataset.value) }) + }, + + onSheetConfirm() { + const { sheetKey, sheetType, sheetValue, sheetValues } = this.data + if (!sheetKey) return + const value = sheetType === 'multi' ? sheetValues.join('、') : (sheetValue || '').trim() + logger.info(LOG, '属性更新, key=', sheetKey, 'value=', value) + this.setData({ [`formData.${sheetKey}`]: value, sheetVisible: false }) + }, + onSave() { const fd = this.data.formData as any - const body: Record = {} + // 后端 ClothesUpdateByUUIDRequest.ClothesUUID 为必填(binding required),请求体需携带 + const body: Record = { clothes_uuid: this.data.uuid } const fields: Array<[string, string]> = [ ['name', 'name'], ['remark', 'remark'], diff --git a/miniprogram/pages/clothes-detail/clothes-detail.wxml b/miniprogram/pages/clothes-detail/clothes-detail.wxml index da030df..3c619f3 100644 --- a/miniprogram/pages/clothes-detail/clothes-detail.wxml +++ b/miniprogram/pages/clothes-detail/clothes-detail.wxml @@ -1,3 +1,45 @@ + +var COLOR_MAP = { + '白色': '#FFFFFF', + '黑色': '#1C1C1E', + '灰色': '#8E8E93', + '红色': '#FF3B30', + '粉色': '#FF2D55', + '橙色': '#FF9500', + '黄色': '#FFCC00', + '绿色': '#34C759', + '蓝色': '#007AFF', + '紫色': '#5856D6', + '棕色': '#A2845E', + '米色': '#F5EFE0' +} +function hex(v) { + if (!v) return '' + if (v.charAt(0) === '#') return v + return COLOR_MAP[v] || '' +} +function tags(v) { + if (!v) return [] + var sep = '、,,/|;; \t\n\r' + var out = [] + var cur = '' + for (var i = 0; i < v.length; i++) { + var ch = v.charAt(i) + if (sep.indexOf(ch) >= 0) { + if (cur) { + out.push(cur) + cur = '' + } + } else { + cur += ch + } + } + if (cur) out.push(cur) + return out +} +module.exports = { hex: hex, tags: tags } + + {{detail.name || '未命名衣物'}} - - + + + {{group.title}} {{item.label}} + - - + + - {{detail[item.key] || 0}}/5 + {{(editMode ? formData[item.key] : detail[item.key]) || 0}}/5 - - {{detail[item.key] || '—'}} + + + {{scene}} + + — + + + + {{(editMode ? formData[item.key] : detail[item.key]) || '—'}} + + - - + + + 备注信息 +