209 lines
7.5 KiB
TypeScript
209 lines
7.5 KiB
TypeScript
import { request, redirectToLogin } from '../../utils/request'
|
|
import * as auth from '../../utils/auth'
|
|
import { PagedResult, ClothesItem, QuotaItem } from '../../types/models'
|
|
import { QuotaType } from '../../utils/config'
|
|
import { normalizePaged, mergePaged, hasMorePaged } from '../../utils/paged'
|
|
import { clothesStatusFlag } from '../../utils/clothes-status'
|
|
import { resolvePendingClothes } from '../../utils/pending-clothes'
|
|
import { setTabBarSelected } from '../../utils/tabbar'
|
|
import { navScroll } from '../../utils/nav'
|
|
import { logger } from '../../utils/logger'
|
|
|
|
// 日志模块名
|
|
const LOG = 'wardrobe'
|
|
|
|
const CATEGORIES = [
|
|
{ name: '全部', value: '' },
|
|
{ name: '上装', value: '上装' },
|
|
{ name: '下装', value: '下装' },
|
|
{ name: '连衣裙', value: '连衣裙' },
|
|
{ name: '外套', value: '外套' },
|
|
{ name: '鞋', value: '鞋' },
|
|
{ name: '配饰', value: '配饰' },
|
|
]
|
|
|
|
Page({
|
|
data: {
|
|
categories: CATEGORIES,
|
|
activeCategory: 0,
|
|
list: [] as ClothesItem[],
|
|
total: 0,
|
|
uploadRemain: 0,
|
|
loading: true,
|
|
refreshing: false,
|
|
loadingMore: false, // 上拉加载独立闸门,避免与首次 loading 互相干扰
|
|
page: 1,
|
|
pageSize: 20,
|
|
hasMore: true,
|
|
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
|
|
navScrolled: false,
|
|
// 「上传后退出小程序」的失败结果回归告知(v2 玻璃弹窗)
|
|
failVisible: false,
|
|
failText: '',
|
|
failUuid: '',
|
|
failButtons: [
|
|
{ text: '知道了', value: '' },
|
|
{ text: '查看衣物', value: 'ok', type: 'main' },
|
|
],
|
|
},
|
|
|
|
// scroll-view 滚动 → 导航收缩为玻璃小标题
|
|
onNavScroll: navScroll,
|
|
|
|
onShow() {
|
|
this.syncTabBar()
|
|
// 未登录直接回登录页(幂等跳转),不再发出必然 401 的请求
|
|
if (!auth.isLoggedIn()) {
|
|
redirectToLogin()
|
|
return
|
|
}
|
|
// 核对「上传后退出小程序」的衣物最终处理结果(pending 为空时零请求)
|
|
this.resolvePending()
|
|
// 首帧渲染未结束就 setData 会触发「Expected updated data but get first rendering data」,
|
|
// 整个 webview 渲染失败,用户视觉上仍看到上一个页面(从 login switchTab 过来时表现为
|
|
// "卡在登录页")。把首次 onShow 的刷新延迟到下一帧,规避首帧冲突。
|
|
if (!(this as any).firstShown) {
|
|
;(this as any).firstShown = true
|
|
wx.nextTick(() => this.refresh())
|
|
return
|
|
}
|
|
logger.debug(LOG, 'onShow 触发刷新')
|
|
this.refresh()
|
|
},
|
|
|
|
syncTabBar() {
|
|
setTabBarSelected(0)
|
|
},
|
|
|
|
refresh() {
|
|
this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 })
|
|
this.fetchList(1)
|
|
this.fetchQuota()
|
|
},
|
|
|
|
onRefresh() {
|
|
this.setData({ refreshing: true })
|
|
Promise.all([this.fetchList(1), this.fetchQuota()]).finally(() => {
|
|
this.setData({ refreshing: false })
|
|
})
|
|
},
|
|
|
|
onReachBottom() {
|
|
if (!this.data.hasMore || this.data.loading || this.data.loadingMore) return
|
|
this.fetchList(this.data.page + 1)
|
|
},
|
|
|
|
fetchList(page: number): Promise<void> {
|
|
const cat = CATEGORIES[this.data.activeCategory].value
|
|
const params: Record<string, any> = { page, page_size: this.data.pageSize }
|
|
if (cat) params.category_main = cat
|
|
|
|
// 请求序号:快速切分类/下拉刷新时旧响应可能后到,序号不匹配的响应直接丢弃
|
|
const seq = ((this as any).reqSeq || 0) + 1
|
|
;(this as any).reqSeq = seq
|
|
if (page > 1) this.setData({ loadingMore: true })
|
|
|
|
return request<PagedResult<ClothesItem>>({ url: '/clothes', data: params })
|
|
.then((res) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
const { list: raw, total } = normalizePaged<ClothesItem>(res)
|
|
logger.debug(LOG, '列表加载成功, 分类=', cat || '全部', 'page=', page, '条数=', raw && raw.length)
|
|
// 状态标识收敛到 clothes-status:处理中(玻璃)/ 处理失败(实心)/ 无
|
|
const list = raw.map((it) => {
|
|
const flag = clothesStatusFlag(it)
|
|
return { ...it, _statusText: flag.text, _statusType: flag.type }
|
|
})
|
|
const merged = mergePaged(this.data.list, list, page === 1)
|
|
this.setData({
|
|
list: merged,
|
|
total,
|
|
hasMore: hasMorePaged(merged, total, this.data.pageSize),
|
|
loading: false,
|
|
loadingMore: false,
|
|
page,
|
|
})
|
|
})
|
|
.catch((e: any) => {
|
|
if ((this as any).reqSeq !== seq) return
|
|
logger.warn(LOG, '列表加载失败, page=', page, 'code=', e && e.code)
|
|
this.setData({ loading: false, loadingMore: false })
|
|
})
|
|
},
|
|
|
|
fetchQuota(): Promise<void> {
|
|
return request<{ quotas: QuotaItem[] }>({ url: '/quota/info', showError: false, silentAuth: true })
|
|
.then((res) => {
|
|
const upload = res.quotas.find((q) => q.quota_type === QuotaType.UPLOAD)
|
|
if (upload) this.setData({ uploadRemain: upload.remain })
|
|
logger.debug(LOG, '额度加载成功, 上传剩余=', upload ? upload.remain : 0)
|
|
})
|
|
.catch((e: any) => logger.warn(LOG, '额度加载失败(静默降级), code=', e && e.code))
|
|
},
|
|
|
|
// 核对待处理衣物:成功静默、失败弹窗告知;有终结态时重刷列表对齐状态
|
|
resolvePending() {
|
|
if ((this as any).resolvingPending) return
|
|
;(this as any).resolvingPending = true
|
|
resolvePendingClothes()
|
|
.then((res) => {
|
|
if (!res.finished.length && !res.failed.length) return
|
|
logger.info(LOG, '待核对衣物已终结, 成功=', res.finished.length, '失败=', res.failed.length)
|
|
if (res.failed.length) this.showFailModal(res.failed)
|
|
this.refresh()
|
|
})
|
|
.catch((e: any) => logger.warn(LOG, '待核对衣物解析异常, code=', e && e.code))
|
|
.finally(() => {
|
|
;(this as any).resolvingPending = false
|
|
})
|
|
},
|
|
|
|
showFailModal(failed: Array<{ uuid: string; reason: string }>) {
|
|
const first = (failed[0] || ({} as { uuid: string; reason: string })) as {
|
|
uuid: string
|
|
reason: string
|
|
}
|
|
const content =
|
|
failed.length === 1
|
|
? `1 件衣物识别失败:${first.reason},额度已自动退回`
|
|
: `${failed.length} 件衣物识别失败,额度已自动退回,可进入详情查看原因`
|
|
this.setData({ failVisible: true, failText: content, failUuid: first.uuid || '' })
|
|
},
|
|
|
|
onFailCancel() {
|
|
this.setData({ failVisible: false })
|
|
},
|
|
|
|
onFailConfirm(e: WechatMiniprogram.CustomEvent) {
|
|
const uuid = this.data.failUuid
|
|
this.setData({ failVisible: false })
|
|
if (e.detail.value !== 'ok' || !uuid) return
|
|
wx.navigateTo({
|
|
url: `/pages/clothes-detail/clothes-detail?uuid=${uuid}`,
|
|
fail: (err) => logger.warn(LOG, 'navigateTo 衣物详情失败, err=', err && err.errMsg),
|
|
})
|
|
},
|
|
|
|
onCategoryTap(e: WechatMiniprogram.TouchEvent) {
|
|
const index = Number(e.currentTarget.dataset.index)
|
|
if (index === this.data.activeCategory) return
|
|
this.setData({ activeCategory: index, list: [], loading: true })
|
|
this.refresh()
|
|
},
|
|
|
|
onClothesTap(e: WechatMiniprogram.CustomEvent) {
|
|
const item = e.detail.item as ClothesItem
|
|
logger.debug(LOG, '点击衣物, uuid=', item && item.clothes_uuid)
|
|
wx.navigateTo({
|
|
url: `/pages/clothes-detail/clothes-detail?uuid=${item.clothes_uuid}`,
|
|
fail: (err) => logger.warn(LOG, 'navigateTo 衣物详情失败, err=', err && err.errMsg),
|
|
})
|
|
},
|
|
|
|
onUpload() {
|
|
wx.navigateTo({
|
|
url: '/pages/upload/upload',
|
|
fail: (e) => logger.warn(LOG, 'navigateTo 上传页失败, err=', e && e.errMsg),
|
|
})
|
|
},
|
|
})
|