feat: 支持退出后补告衣物处理结果
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user