feat: 支持退出后补告衣物处理结果

This commit is contained in:
2026-09-16 02:01:26 +08:00
parent a9f2659c42
commit 7be561500b
12 changed files with 539 additions and 22 deletions
+68 -8
View File
@@ -1,11 +1,18 @@
import { request, uploadFile, NOT_LOGIN_CODE } from '../../utils/request'
import { ProcessStatus } from '../../utils/config'
import { addPendingClothes, removePendingClothes } from '../../utils/pending-clothes'
import { navPageScroll } from '../../utils/nav'
import { logger } from '../../utils/logger'
// 日志模块名
const LOG = 'upload'
// 状态轮询:2s 一次、最多 90 次 ≈ 3 分钟,与后端单任务 3 分钟上限对齐。
// 预算耗尽只停轮询并转中性提示,绝不判定失败:任务可能仍在排队/执行中,
// 由后端恢复调度器兜底,最终结果由衣橱页核对补齐。
const POLL_INTERVAL_MS = 2000
const POLL_MAX_COUNT = 90
Page({
data: {
state: 'idle' as 'idle' | 'processing' | 'failed',
@@ -13,6 +20,10 @@ Page({
clothesUuid: '',
failReason: '',
step: 0,
// 轮询预算耗尽:仍在处理中,只做中性提示,不算失败
slowHint: false,
// 失败原因是否伴随额度退回(仅后端确认失败才为真)
quotaRefunded: false,
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
navScrolled: false,
// v2 玻璃弹层
@@ -66,11 +77,20 @@ Page({
},
startUpload(filePath: string) {
this.setData({ state: 'processing', imagePath: filePath, step: 0, failReason: '' })
this.setData({
state: 'processing',
imagePath: filePath,
step: 0,
failReason: '',
slowHint: false,
quotaRefunded: false,
})
uploadFile<{ clothes_uuid: string; image_process_status: number }>(filePath)
.then((res) => {
logger.info(LOG, '衣物上传成功, uuid=', res.clothes_uuid)
// 登记待核对:即使用户立刻退出小程序,回到衣橱时也会补齐最终处理结果
addPendingClothes(res.clothes_uuid)
this.setData({ clothesUuid: res.clothes_uuid })
this.askNextAction()
})
@@ -116,19 +136,30 @@ Page({
this.clearTimer()
if (e.detail.value === 'again') {
logger.debug(LOG, '选择继续上传,重置状态')
this.setData({ state: 'idle', imagePath: '', clothesUuid: '', step: 0, failReason: '' })
this.setData({
state: 'idle',
imagePath: '',
clothesUuid: '',
step: 0,
failReason: '',
slowHint: false,
quotaRefunded: false,
})
return
}
this.onDoneCancel()
},
pollStatus(uuid: string) {
// 先清旧定时器:onShow 续接时避免叠加成多份轮询
this.clearTimer()
let count = 0
;(this as any).pollTimer = setInterval(() => {
count++
request<{ image_process_status: number; fail_reason: string }>({
url: `/clothes/${uuid}/status`,
showError: false,
silentAuth: true,
})
.then((res) => {
const status = res.image_process_status
@@ -139,20 +170,28 @@ Page({
} else if (status === ProcessStatus.SUCCESS) {
// 处理成功不再自动跳转,由用户自行从衣橱进入详情
this.clearTimer()
removePendingClothes(uuid)
} else if (status === ProcessStatus.FAILED) {
// 仅后端确认失败才进入失败态:该路径额度已由后端自动退回
this.clearTimer()
removePendingClothes(uuid)
logger.warn(LOG, 'AI 识别失败, 原因=', res.fail_reason)
this.setData({ state: 'failed', failReason: res.fail_reason || '识别失败' })
this.setData({
state: 'failed',
failReason: res.fail_reason || '识别失败',
quotaRefunded: true,
})
}
})
.catch((e: any) => logger.warn(LOG, '状态轮询失败, code=', e && e.code))
if (count >= 60) {
if (count >= POLL_MAX_COUNT) {
// 只停轮询,不判失败:任务可能仍在队列/执行中,由后端兜底、衣橱页核对
this.clearTimer()
logger.warn(LOG, 'AI 处理超时(90s)')
this.setData({ state: 'failed', failReason: '处理超时,请重试' })
logger.warn(LOG, '状态轮询预算耗尽,转中性提示, uuid=', uuid)
this.setData({ slowHint: true })
}
}, 1500)
}, POLL_INTERVAL_MS)
},
clearTimer() {
@@ -163,6 +202,19 @@ Page({
}
},
// 离开页面只停本地轮询:后端处理不中断,最终结果由衣橱页核对补齐
onHide() {
this.clearTimer()
},
// 回到前台:仍在处理中则续接轮询,恢复进度展示
onShow() {
if (this.data.state === 'processing' && this.data.clothesUuid) {
logger.debug(LOG, 'onShow 续接状态轮询, uuid=', this.data.clothesUuid)
this.pollStatus(this.data.clothesUuid)
}
},
onUnload() {
this.clearTimer()
},
@@ -185,7 +237,15 @@ Page({
},
onRetry() {
this.setData({ state: 'idle', imagePath: '', clothesUuid: '', failReason: '' })
this.setData({
state: 'idle',
imagePath: '',
clothesUuid: '',
failReason: '',
step: 0,
slowHint: false,
quotaRefunded: false,
})
},
onManualEdit() {
+3 -2
View File
@@ -23,7 +23,8 @@
<view class="head">
<view class="eyebrow">Processing</view>
<view class="display">AI 正在识别你的衣物…</view>
<view class="up-note-left">通常需要 10 秒左右,请稍候</view>
<view wx:if="{{slowHint}}" class="up-note-left">处理时间较长,可先返回衣橱,结果会自动更新</view>
<view wx:else class="up-note-left">通常需要 10 秒左右,请稍候</view>
</view>
<view class="up-preview">
@@ -52,7 +53,7 @@
<view class="up-bang">!</view>
<view class="head">
<view class="display">识别失败</view>
<view class="up-note-left">{{failReason || '未知原因'}} · 本次上传额度已自动退回</view>
<view class="up-note-left">{{failReason || '未知原因'}}{{quotaRefunded ? ' · 本次上传额度已自动退回' : ''}}</view>
</view>
<view class="up-duo">
<view class="btn btn--ghost" bindtap="onRetry">重新上传</view>