199 lines
5.9 KiB
TypeScript
199 lines
5.9 KiB
TypeScript
import { request, uploadFile, NOT_LOGIN_CODE } from '../../utils/request'
|
||
import { ProcessStatus } from '../../utils/config'
|
||
import { navPageScroll } from '../../utils/nav'
|
||
import { logger } from '../../utils/logger'
|
||
|
||
// 日志模块名
|
||
const LOG = 'upload'
|
||
|
||
Page({
|
||
data: {
|
||
state: 'idle' as 'idle' | 'processing' | 'failed',
|
||
imagePath: '',
|
||
clothesUuid: '',
|
||
failReason: '',
|
||
step: 0,
|
||
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
|
||
navScrolled: false,
|
||
// v2 玻璃弹层
|
||
srcVisible: false,
|
||
srcItems: [
|
||
{ text: '拍照', value: 'camera' },
|
||
{ text: '从相册选择', value: 'album' },
|
||
],
|
||
doneVisible: false,
|
||
doneButtons: [
|
||
{ text: '回到衣橱', value: 'back' },
|
||
{ text: '继续上传', value: 'again', type: 'main' },
|
||
],
|
||
quotaVisible: false,
|
||
quotaButtons: [
|
||
{ text: '取消', value: '' },
|
||
{ text: '去购买', value: 'ok', type: 'main' },
|
||
],
|
||
},
|
||
|
||
// 页面级滚动 → 导航收缩为玻璃小标题
|
||
onPageScroll: navPageScroll,
|
||
|
||
onChoose() {
|
||
this.setData({ srcVisible: true })
|
||
},
|
||
|
||
onSourceCancel() {
|
||
this.setData({ srcVisible: false })
|
||
},
|
||
|
||
onSourceSelect(e: WechatMiniprogram.CustomEvent) {
|
||
const value = e.detail.value as string
|
||
this.setData({ srcVisible: false })
|
||
if (value !== 'camera' && value !== 'album') return
|
||
this.chooseImage([value])
|
||
},
|
||
|
||
chooseImage(sourceType: Array<'camera' | 'album'>) {
|
||
wx.chooseMedia({
|
||
count: 1,
|
||
mediaType: ['image'],
|
||
sourceType,
|
||
success: (res) => {
|
||
const file = res.tempFiles[0]
|
||
logger.debug(LOG, '已选择图片, 来源=', sourceType.join(','))
|
||
this.startUpload(file.tempFilePath)
|
||
},
|
||
fail: (e) => logger.warn(LOG, '选择图片失败, err=', e && e.errMsg),
|
||
})
|
||
},
|
||
|
||
startUpload(filePath: string) {
|
||
this.setData({ state: 'processing', imagePath: filePath, step: 0, failReason: '' })
|
||
|
||
uploadFile<{ clothes_uuid: string; image_process_status: number }>(filePath)
|
||
.then((res) => {
|
||
logger.info(LOG, '衣物上传成功, uuid=', res.clothes_uuid)
|
||
this.setData({ clothesUuid: res.clothes_uuid })
|
||
this.askNextAction()
|
||
})
|
||
.catch((e: any) => {
|
||
this.setData({ state: 'idle' })
|
||
logger.warn(LOG, '衣物上传失败, code=', e && e.code)
|
||
if (e && e.code === 80000) {
|
||
this.showQuotaModal()
|
||
return
|
||
}
|
||
// uploadFile 已统一 toast 业务错误与网络错误;未登录时请求并未发出,需补一次提示
|
||
if (e && e.code === NOT_LOGIN_CODE) {
|
||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||
}
|
||
})
|
||
},
|
||
|
||
askNextAction() {
|
||
// 后台继续轮询,仅用于更新步骤进度(不再自动跳详情)
|
||
this.pollStatus(this.data.clothesUuid)
|
||
// 立即弹窗让用户选择下一步,避免干等
|
||
this.setData({ doneVisible: true })
|
||
},
|
||
|
||
onDoneCancel() {
|
||
// 回到衣橱
|
||
this.setData({ doneVisible: false })
|
||
this.clearTimer()
|
||
wx.navigateBack({
|
||
delta: 1,
|
||
fail: (e) => {
|
||
logger.warn(LOG, 'navigateBack 失败,改跳衣橱, err=', e && e.errMsg)
|
||
wx.switchTab({
|
||
url: '/pages/wardrobe/wardrobe',
|
||
fail: (e2) => logger.warn(LOG, 'switchTab 衣橱失败, err=', e2 && e2.errMsg),
|
||
})
|
||
},
|
||
})
|
||
},
|
||
|
||
onDoneConfirm(e: WechatMiniprogram.CustomEvent) {
|
||
this.setData({ doneVisible: false })
|
||
this.clearTimer()
|
||
if (e.detail.value === 'again') {
|
||
logger.debug(LOG, '选择继续上传,重置状态')
|
||
this.setData({ state: 'idle', imagePath: '', clothesUuid: '', step: 0, failReason: '' })
|
||
return
|
||
}
|
||
this.onDoneCancel()
|
||
},
|
||
|
||
pollStatus(uuid: string) {
|
||
let count = 0
|
||
;(this as any).pollTimer = setInterval(() => {
|
||
count++
|
||
request<{ image_process_status: number; fail_reason: string }>({
|
||
url: `/clothes/${uuid}/status`,
|
||
showError: false,
|
||
})
|
||
.then((res) => {
|
||
const status = res.image_process_status
|
||
if (status === ProcessStatus.MATTING) {
|
||
this.setData({ step: 1 })
|
||
} else if (status === ProcessStatus.CLASSIFYING) {
|
||
this.setData({ step: 2 })
|
||
} else if (status === ProcessStatus.SUCCESS) {
|
||
// 处理成功不再自动跳转,由用户自行从衣橱进入详情
|
||
this.clearTimer()
|
||
} else if (status === ProcessStatus.FAILED) {
|
||
this.clearTimer()
|
||
logger.warn(LOG, 'AI 识别失败, 原因=', res.fail_reason)
|
||
this.setData({ state: 'failed', failReason: res.fail_reason || '识别失败' })
|
||
}
|
||
})
|
||
.catch((e: any) => logger.warn(LOG, '状态轮询失败, code=', e && e.code))
|
||
|
||
if (count >= 60) {
|
||
this.clearTimer()
|
||
logger.warn(LOG, 'AI 处理超时(90s)')
|
||
this.setData({ state: 'failed', failReason: '处理超时,请重试' })
|
||
}
|
||
}, 1500)
|
||
},
|
||
|
||
clearTimer() {
|
||
const t = (this as any).pollTimer
|
||
if (t) {
|
||
clearInterval(t)
|
||
;(this as any).pollTimer = null
|
||
}
|
||
},
|
||
|
||
onUnload() {
|
||
this.clearTimer()
|
||
},
|
||
|
||
showQuotaModal() {
|
||
this.setData({ quotaVisible: true })
|
||
},
|
||
|
||
onQuotaCancel() {
|
||
this.setData({ quotaVisible: false })
|
||
},
|
||
|
||
onQuotaConfirm(e: WechatMiniprogram.CustomEvent) {
|
||
this.setData({ quotaVisible: false })
|
||
if (e.detail.value !== 'ok') return
|
||
wx.navigateTo({
|
||
url: '/pages/mall/mall',
|
||
fail: (err) => logger.warn(LOG, 'navigateTo 商城失败, err=', err && err.errMsg),
|
||
})
|
||
},
|
||
|
||
onRetry() {
|
||
this.setData({ state: 'idle', imagePath: '', clothesUuid: '', failReason: '' })
|
||
},
|
||
|
||
onManualEdit() {
|
||
if (!this.data.clothesUuid) return
|
||
wx.redirectTo({
|
||
url: `/pages/clothes-detail/clothes-detail?uuid=${this.data.clothesUuid}`,
|
||
fail: (e) => logger.warn(LOG, 'redirectTo 衣物详情失败, err=', e && e.errMsg),
|
||
})
|
||
},
|
||
})
|