feat: ui update
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"navigation-bar": "/components/navigation-bar/navigation-bar"
|
||||
"navigation-bar": "/components/navigation-bar/navigation-bar",
|
||||
"v2-overlay": "/components/v2-overlay/index"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { request, uploadFile, NOT_LOGIN_CODE } from '../../utils/request'
|
||||
import { ProcessStatus } from '../../utils/config'
|
||||
import { navPageScroll } from '../../utils/nav'
|
||||
import { logger } from '../../utils/logger'
|
||||
|
||||
// 日志模块名
|
||||
@@ -12,17 +13,42 @@ Page({
|
||||
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() {
|
||||
wx.showActionSheet({
|
||||
itemList: ['拍照', '从相册选择'],
|
||||
success: (res) => {
|
||||
const sourceType: Array<'camera' | 'album'> = res.tapIndex === 0 ? ['camera'] : ['album']
|
||||
this.chooseImage(sourceType)
|
||||
},
|
||||
fail: (e) => logger.debug(LOG, '取消选择图片, err=', e && e.errMsg),
|
||||
})
|
||||
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'>) {
|
||||
@@ -66,33 +92,36 @@ Page({
|
||||
// 后台继续轮询,仅用于更新步骤进度(不再自动跳详情)
|
||||
this.pollStatus(this.data.clothesUuid)
|
||||
// 立即弹窗让用户选择下一步,避免干等
|
||||
wx.showModal({
|
||||
title: '衣物已上传',
|
||||
content: 'AI 正在识别中,可在衣橱查看进度',
|
||||
confirmText: '继续上传',
|
||||
cancelText: '回到衣橱',
|
||||
success: (res) => {
|
||||
this.clearTimer()
|
||||
if (res.confirm) {
|
||||
logger.debug(LOG, '选择继续上传,重置状态')
|
||||
this.setData({ state: 'idle', imagePath: '', clothesUuid: '', step: 0, failReason: '' })
|
||||
} else {
|
||||
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),
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
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),
|
||||
})
|
||||
},
|
||||
fail: (e) => logger.warn(LOG, '上传完成弹窗失败, err=', e && e.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(() => {
|
||||
@@ -139,20 +168,19 @@ Page({
|
||||
},
|
||||
|
||||
showQuotaModal() {
|
||||
wx.showModal({
|
||||
title: '上传额度不足',
|
||||
content: '当前剩余 0 次,购买额度包后可继续上传',
|
||||
confirmText: '去购买',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.navigateTo({
|
||||
url: '/pages/mall/mall',
|
||||
fail: (e) => logger.warn(LOG, 'navigateTo 商城失败, err=', e && e.errMsg),
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (e) => logger.warn(LOG, '额度弹窗失败, err=', e && e.errMsg),
|
||||
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),
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -1,48 +1,93 @@
|
||||
<view class="page upload-page">
|
||||
<navigation-bar title="上传衣物" />
|
||||
<view class="page up-page">
|
||||
<navigation-bar title="上传衣物" scrolled="{{navScrolled}}" />
|
||||
|
||||
<!-- 初始态 -->
|
||||
<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 wx:if="{{state === 'idle'}}">
|
||||
<view class="head">
|
||||
<view class="eyebrow">Upload</view>
|
||||
<view class="display">上传衣物</view>
|
||||
</view>
|
||||
<view class="upload-tip">AI 将自动抠图并识别 15 项属性</view>
|
||||
|
||||
<view class="up-drop" hover-class="up-drop-press" bindtap="onChoose">
|
||||
<view class="icon icon-camera up-drop-ic"></view>
|
||||
<view class="up-drop-t">拍照或从相册选择</view>
|
||||
<view class="up-drop-d">支持 JPG / PNG,单张不超过 20MB</view>
|
||||
<view class="up-drop-d">上传消耗 1 次上传额度</view>
|
||||
</view>
|
||||
|
||||
<view class="up-note">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>
|
||||
<!-- 处理中:实际图片 + 三段发丝线进度轨(排队中 / AI 抠图 / 智能分类) -->
|
||||
<view wx:elif="{{state === 'processing'}}">
|
||||
<view class="head">
|
||||
<view class="eyebrow">Processing</view>
|
||||
<view class="display">AI 正在识别你的衣物…</view>
|
||||
<view class="up-note-left">通常需要 10 秒左右,请稍候</view>
|
||||
</view>
|
||||
|
||||
<view class="up-preview">
|
||||
<image wx:if="{{imagePath}}" class="pic" src="{{imagePath}}" mode="aspectFit" />
|
||||
<view wx:else class="ph icon icon-hanger"></view>
|
||||
</view>
|
||||
|
||||
<view class="up-steps">
|
||||
<view class="up-step {{step > 0 ? 'is-done' : ''}} {{step === 0 ? 'is-on' : ''}}">
|
||||
<view class="up-dot"></view>
|
||||
<text class="up-lb">排队中</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 class="up-step {{step > 1 ? 'is-done' : ''}} {{step === 1 ? 'is-on' : ''}}">
|
||||
<view class="up-dot"></view>
|
||||
<text class="up-lb">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 class="up-step {{step === 2 ? 'is-on' : ''}}">
|
||||
<view class="up-dot"></view>
|
||||
<text class="up-lb">智能分类</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 wx:else class="up-failed">
|
||||
<view class="up-bang">!</view>
|
||||
<view class="head">
|
||||
<view class="display">识别失败</view>
|
||||
<view class="up-note-left">{{failReason || '未知原因'}} · 本次上传额度已自动退回</view>
|
||||
</view>
|
||||
<view class="up-duo">
|
||||
<view class="btn btn--ghost" bindtap="onRetry">重新上传</view>
|
||||
<view class="btn btn--ink" bindtap="onManualEdit">手动填写信息</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图片来源选择 -->
|
||||
<v2-overlay
|
||||
visible="{{srcVisible}}"
|
||||
type="actionsheet"
|
||||
items="{{srcItems}}"
|
||||
bind:select="onSourceSelect"
|
||||
bind:cancel="onSourceCancel"
|
||||
/>
|
||||
|
||||
<!-- 上传完成 -->
|
||||
<v2-overlay
|
||||
visible="{{doneVisible}}"
|
||||
type="modal"
|
||||
title="衣物已上传"
|
||||
content="AI 正在识别中,可在衣橱查看进度"
|
||||
buttons="{{doneButtons}}"
|
||||
bind:confirm="onDoneConfirm"
|
||||
bind:cancel="onDoneCancel"
|
||||
/>
|
||||
|
||||
<!-- 额度不足 -->
|
||||
<v2-overlay
|
||||
visible="{{quotaVisible}}"
|
||||
type="modal"
|
||||
title="上传额度不足"
|
||||
content="当前剩余 0 次,购买额度包后可继续上传"
|
||||
buttons="{{quotaButtons}}"
|
||||
bind:confirm="onQuotaConfirm"
|
||||
bind:cancel="onQuotaCancel"
|
||||
/>
|
||||
</view>
|
||||
|
||||
@@ -1,156 +1,68 @@
|
||||
.upload-page {
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
/** 上传衣物:虚线投放框 / 处理中三步进度轨 / 失败双按钮 **/
|
||||
.up-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: calc(60rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 初始态 */
|
||||
.upload-zone {
|
||||
margin-top: 40rpx;
|
||||
height: 520rpx;
|
||||
border: 3rpx dashed #C7C7CC;
|
||||
border-radius: 32rpx;
|
||||
background: #fff;
|
||||
.up-drop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: calc(100% - var(--pad) * 2);
|
||||
margin: 56rpx var(--pad) 0;
|
||||
padding: 108rpx 48rpx;
|
||||
border: 1rpx dashed var(--hairline-2);
|
||||
border-radius: var(--r-img);
|
||||
transition: background .2s var(--ease), border-color .2s var(--ease);
|
||||
}
|
||||
|
||||
.upload-camera {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
color: #007AFF;
|
||||
margin-bottom: 24rpx;
|
||||
.up-drop-press {
|
||||
background: var(--fill);
|
||||
border-color: var(--ink-4);
|
||||
}
|
||||
|
||||
.upload-title {
|
||||
font-size: 30rpx;
|
||||
color: #000;
|
||||
.up-drop-ic {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
color: var(--ink-3);
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.up-drop-t {
|
||||
font-family: var(--serif);
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.upload-desc {
|
||||
font-size: 22rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 12rpx;
|
||||
.up-drop-d {
|
||||
margin-top: 18rpx;
|
||||
font-size: 23rpx;
|
||||
color: var(--ink-3);
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
.up-note {
|
||||
margin-top: 44rpx;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 24rpx;
|
||||
font-size: 23rpx;
|
||||
color: var(--ink-3);
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
|
||||
/* 处理中 */
|
||||
.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;
|
||||
.up-note-left {
|
||||
margin-top: 20rpx;
|
||||
font-size: 25rpx;
|
||||
color: var(--ink-2);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* 失败态 */
|
||||
.failed {
|
||||
margin-top: 120rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
.up-failed {
|
||||
padding-top: 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;
|
||||
.up-failed .up-bang {
|
||||
margin-left: var(--pad);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user