235 lines
7.5 KiB
TypeScript
235 lines
7.5 KiB
TypeScript
import { request } from '../../utils/request'
|
|
import { ClothesItem } from '../../types/models'
|
|
import { ProcessStatus } from '../../utils/config'
|
|
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' },
|
|
]
|
|
|
|
// 将后端 image_process_status 映射为前端展示步骤:0=排队中 1=AI 抠图 2=智能分类
|
|
function statusToStep(status: number): number {
|
|
if (status === ProcessStatus.QUEUED) return 0
|
|
if (status === ProcessStatus.MATTING) return 1
|
|
if (status === ProcessStatus.CLASSIFYING) return 2
|
|
return 2
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
uuid: '',
|
|
attrs: ATTRS,
|
|
detail: {} as ClothesItem,
|
|
formData: {} as ClothesItem,
|
|
imageStatus: 0,
|
|
step: 0,
|
|
failReason: '',
|
|
loading: true,
|
|
editMode: false,
|
|
notFound: false,
|
|
},
|
|
|
|
onLoad(query: Record<string, string>) {
|
|
const uuid = query.uuid || ''
|
|
logger.debug(LOG, 'onLoad, uuid=', uuid)
|
|
this.setData({ uuid })
|
|
if (!uuid) {
|
|
// uuid 缺失时不请求不轮询,否则会停在空白页且无法退出
|
|
logger.warn(LOG, 'onLoad 缺少 uuid 参数')
|
|
this.setData({ loading: false, notFound: true })
|
|
return
|
|
}
|
|
this.fetchDetail()
|
|
this.pollStatus()
|
|
},
|
|
|
|
onBackWardrobe() {
|
|
wx.switchTab({
|
|
url: '/pages/wardrobe/wardrobe',
|
|
fail: (e) => logger.warn(LOG, 'switchTab 衣橱失败, err=', e && e.errMsg),
|
|
})
|
|
},
|
|
|
|
fetchDetail(): Promise<void> {
|
|
return request<ClothesItem>({ url: `/clothes/${this.data.uuid}` })
|
|
.then((detail) => {
|
|
logger.debug(LOG, '详情加载成功, 处理状态=', detail && detail.image_process_status)
|
|
this.setData({ detail, loading: false })
|
|
})
|
|
.catch((e: any) => {
|
|
logger.warn(LOG, '详情加载失败, code=', e && e.code)
|
|
this.setData({ loading: false })
|
|
})
|
|
},
|
|
|
|
pollStatus() {
|
|
let count = 0
|
|
;(this as any).pollTimer = setInterval(() => {
|
|
count++
|
|
request<{ image_process_status: number; fail_reason?: string }>({
|
|
url: `/clothes/${this.data.uuid}/status`,
|
|
showError: false,
|
|
})
|
|
.then((res) => {
|
|
const status = res.image_process_status
|
|
if (status === ProcessStatus.SUCCESS) {
|
|
// 成功:先拉最新详情,再切换为详情视图,避免空属性闪烁
|
|
this.clearTimer()
|
|
logger.info(LOG, 'AI 处理完成')
|
|
this.fetchDetail().then(() => this.setData({ imageStatus: status, step: 2 }))
|
|
} else if (status === ProcessStatus.FAILED) {
|
|
this.clearTimer()
|
|
logger.warn(LOG, 'AI 识别失败, 原因=', res.fail_reason)
|
|
this.setData({ imageStatus: status, failReason: res.fail_reason || '识别失败' })
|
|
} else {
|
|
this.setData({ imageStatus: status, step: statusToStep(status) })
|
|
}
|
|
})
|
|
.catch((e: any) => logger.warn(LOG, '状态轮询失败, code=', e && e.code))
|
|
if (count >= 120) {
|
|
this.clearTimer()
|
|
logger.warn(LOG, '状态轮询超时(240s),停止轮询')
|
|
}
|
|
}, 2000)
|
|
},
|
|
|
|
clearTimer() {
|
|
const t = (this as any).pollTimer
|
|
if (t) {
|
|
clearInterval(t)
|
|
;(this as any).pollTimer = null
|
|
}
|
|
},
|
|
|
|
onUnload() {
|
|
this.clearTimer()
|
|
},
|
|
|
|
onToggleEdit() {
|
|
if (this.data.editMode) {
|
|
this.setData({ editMode: false })
|
|
} else {
|
|
this.setData({ editMode: true, formData: { ...this.data.detail } })
|
|
}
|
|
},
|
|
|
|
onNameInput(e: WechatMiniprogram.Input) {
|
|
this.setData({ 'formData.name': e.detail.value })
|
|
},
|
|
|
|
onRemarkInput(e: WechatMiniprogram.Input) {
|
|
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)
|
|
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 })
|
|
}
|
|
},
|
|
})
|
|
},
|
|
|
|
onSave() {
|
|
const fd = this.data.formData as any
|
|
const body: Record<string, any> = {}
|
|
const fields: Array<[string, string]> = [
|
|
['name', 'name'],
|
|
['remark', 'remark'],
|
|
['category_main', 'category_main'],
|
|
['category_sub', 'category_sub'],
|
|
['style', 'style'],
|
|
['color_main', 'color'], // 主色请求字段名是 color
|
|
['color_sub', 'color_sub'],
|
|
['thickness', 'thickness'],
|
|
['version', 'version'],
|
|
['material', 'material'],
|
|
['pattern', 'pattern'],
|
|
['sleeve_type', 'sleeve_type'],
|
|
['clothes_type', 'clothes_type'],
|
|
['neckline_type', 'neckline_type'],
|
|
['formality', 'formality'],
|
|
['occasions', 'occasions'],
|
|
['visual_impact', 'visual_impact'],
|
|
]
|
|
fields.forEach(([src, dest]) => {
|
|
if (fd[src]) body[dest] = fd[src]
|
|
})
|
|
|
|
request({ url: `/clothes/${this.data.uuid}`, method: 'PUT', data: body })
|
|
.then(() => {
|
|
logger.info(LOG, '衣物信息保存成功')
|
|
wx.showToast({ title: '已保存', icon: 'success' })
|
|
this.setData({ editMode: false })
|
|
this.fetchDetail()
|
|
})
|
|
.catch((e: any) => logger.warn(LOG, '衣物信息保存失败, code=', e && e.code))
|
|
},
|
|
|
|
onDelete() {
|
|
const canRefund = this.data.imageStatus !== ProcessStatus.FAILED
|
|
const content = canRefund ? '删除后将退回 1 次上传额度,确认删除?' : '确认删除?'
|
|
wx.showModal({
|
|
title: '删除这件衣物',
|
|
content,
|
|
confirmText: '确认删除',
|
|
confirmColor: '#FF3B30',
|
|
success: (res) => {
|
|
if (!res.confirm) return
|
|
request({ url: `/clothes/${this.data.uuid}`, method: 'DELETE' })
|
|
.then(() => {
|
|
logger.info(LOG, '衣物删除成功')
|
|
wx.showToast({ title: '已删除', icon: 'success' })
|
|
setTimeout(
|
|
() =>
|
|
wx.navigateBack({
|
|
fail: (e) => logger.warn(LOG, 'navigateBack 失败, err=', e && e.errMsg),
|
|
}),
|
|
500,
|
|
)
|
|
})
|
|
.catch((e: any) => logger.warn(LOG, '衣物删除失败, code=', e && e.code))
|
|
},
|
|
fail: (e) => logger.warn(LOG, '删除弹窗失败, err=', e && e.errMsg),
|
|
})
|
|
},
|
|
})
|