first commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"navigation-bar": "/components/navigation-bar/navigation-bar"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { request, uploadFile } from '../../utils/request'
|
||||
import { ProcessStatus } from '../../utils/config'
|
||||
|
||||
Page({
|
||||
data: {
|
||||
state: 'idle' as 'idle' | 'processing' | 'failed',
|
||||
imagePath: '',
|
||||
clothesUuid: '',
|
||||
failReason: '',
|
||||
step: 0,
|
||||
},
|
||||
|
||||
onChoose() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['拍照', '从相册选择'],
|
||||
success: (res) => {
|
||||
const sourceType: Array<'camera' | 'album'> = res.tapIndex === 0 ? ['camera'] : ['album']
|
||||
this.chooseImage(sourceType)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
chooseImage(sourceType: Array<'camera' | 'album'>) {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType,
|
||||
success: (res) => {
|
||||
const file = res.tempFiles[0]
|
||||
this.startUpload(file.tempFilePath)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
startUpload(filePath: string) {
|
||||
this.setData({ state: 'processing', imagePath: filePath, step: 0, failReason: '' })
|
||||
|
||||
uploadFile<{ clothes_uuid: string; image_process_status: number }>(filePath)
|
||||
.then((res) => {
|
||||
this.setData({ clothesUuid: res.clothes_uuid })
|
||||
this.askNextAction()
|
||||
})
|
||||
.catch((e: any) => {
|
||||
this.setData({ state: 'idle' })
|
||||
if (e && e.code === 80000) this.showQuotaModal()
|
||||
})
|
||||
},
|
||||
|
||||
askNextAction() {
|
||||
// 后台继续轮询,仅用于更新步骤进度(不再自动跳详情)
|
||||
this.pollStatus(this.data.clothesUuid)
|
||||
// 立即弹窗让用户选择下一步,避免干等
|
||||
wx.showModal({
|
||||
title: '衣物已上传',
|
||||
content: 'AI 正在识别中,可在衣橱查看进度',
|
||||
confirmText: '继续上传',
|
||||
cancelText: '回到衣橱',
|
||||
success: (res) => {
|
||||
this.clearTimer()
|
||||
if (res.confirm) {
|
||||
this.setData({ state: 'idle', imagePath: '', clothesUuid: '', step: 0, failReason: '' })
|
||||
} else {
|
||||
wx.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => wx.switchTab({ url: '/pages/wardrobe/wardrobe' }),
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
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()
|
||||
this.setData({ state: 'failed', failReason: res.fail_reason || '识别失败' })
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
if (count >= 60) {
|
||||
this.clearTimer()
|
||||
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() {
|
||||
wx.showModal({
|
||||
title: '上传额度不足',
|
||||
content: '当前剩余 0 次,购买额度包后可继续上传',
|
||||
confirmText: '去购买',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) wx.navigateTo({ url: '/pages/mall/mall' })
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
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}` })
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
<view class="page upload-page">
|
||||
<navigation-bar title="上传衣物" />
|
||||
|
||||
<!-- 初始态 -->
|
||||
<view wx:if="{{state === 'idle'}}" class="idle">
|
||||
<view class="upload-zone" bindtap="onChoose">
|
||||
<view class="icon icon-camera upload-camera"></view>
|
||||
<view class="upload-title">拍照或从相册选择</view>
|
||||
<view class="upload-desc">支持 JPG / PNG,单张不超过 20MB</view>
|
||||
<view class="upload-desc">上传消耗 1 次上传额度</view>
|
||||
</view>
|
||||
<view class="upload-tip">AI 将自动抠图并识别 15 项属性</view>
|
||||
</view>
|
||||
|
||||
<!-- 处理中 -->
|
||||
<view wx:elif="{{state === 'processing'}}" class="processing">
|
||||
<image class="proc-img" src="{{imagePath}}" mode="aspectFit" />
|
||||
<view class="steps">
|
||||
<view class="step">
|
||||
<view class="step-dot {{step >= 0 ? 'active' : ''}}"></view>
|
||||
<text class="{{step >= 0 ? 'active-text' : ''}}">排队中</text>
|
||||
</view>
|
||||
<view class="step-line {{step >= 1 ? 'active' : ''}}"></view>
|
||||
<view class="step">
|
||||
<view class="step-dot {{step >= 1 ? 'active' : ''}}"></view>
|
||||
<text class="{{step >= 1 ? 'active-text' : ''}}">AI 抠图</text>
|
||||
</view>
|
||||
<view class="step-line {{step >= 2 ? 'active' : ''}}"></view>
|
||||
<view class="step">
|
||||
<view class="step-dot {{step >= 2 ? 'active' : ''}}"></view>
|
||||
<text class="{{step >= 2 ? 'active-text' : ''}}">智能分类</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="proc-text">AI 正在识别你的衣物…</view>
|
||||
<view class="proc-desc">通常需要 10 秒左右,请稍候</view>
|
||||
</view>
|
||||
|
||||
<!-- 失败态 -->
|
||||
<view wx:else class="failed">
|
||||
<view class="fail-icon">!</view>
|
||||
<view class="fail-title">识别失败:{{failReason}}</view>
|
||||
<view class="fail-desc">本次上传额度已自动退回</view>
|
||||
<view class="fail-btns">
|
||||
<view class="btn btn-primary" bindtap="onRetry">重新上传</view>
|
||||
<view class="btn btn-outline" bindtap="onManualEdit">手动填写信息</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,156 @@
|
||||
.upload-page {
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 初始态 */
|
||||
.upload-zone {
|
||||
margin-top: 40rpx;
|
||||
height: 520rpx;
|
||||
border: 3rpx dashed #C7C7CC;
|
||||
border-radius: 32rpx;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload-camera {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
color: #007AFF;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 30rpx;
|
||||
color: #000;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.upload-desc {
|
||||
font-size: 22rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
/* 处理中 */
|
||||
.processing {
|
||||
margin-top: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proc-img {
|
||||
width: 480rpx;
|
||||
height: 480rpx;
|
||||
border-radius: 32rpx;
|
||||
background: #F2F2F7;
|
||||
}
|
||||
|
||||
.steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 56rpx;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 22rpx;
|
||||
color: #C7C7CC;
|
||||
}
|
||||
|
||||
.step-dot {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 50%;
|
||||
background: #E5E5EA;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.step-dot.active {
|
||||
background: #007AFF;
|
||||
}
|
||||
|
||||
.active-text {
|
||||
color: #007AFF;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 100rpx;
|
||||
height: 2rpx;
|
||||
background: #E5E5EA;
|
||||
margin: 0 16rpx 34rpx;
|
||||
}
|
||||
|
||||
.step-line.active {
|
||||
background: #007AFF;
|
||||
}
|
||||
|
||||
.proc-text {
|
||||
margin-top: 56rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.proc-desc {
|
||||
font-size: 24rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
/* 失败态 */
|
||||
.failed {
|
||||
margin-top: 120rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.fail-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
background: #FF3B30;
|
||||
color: #fff;
|
||||
font-size: 72rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fail-title {
|
||||
margin-top: 40rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fail-desc {
|
||||
font-size: 24rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.fail-btns {
|
||||
width: 100%;
|
||||
margin-top: 56rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user