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), }) }, })