Files
zt-closet-wxmini-frontend/miniprogram/pages/ai-recommend/ai-recommend.ts
T
2026-09-15 17:22:35 +08:00

172 lines
5.4 KiB
TypeScript

import { request } from '../../utils/request'
import { AiRecommendResult, ClothesItem } from '../../types/models'
import { navScroll } from '../../utils/nav'
import { logger } from '../../utils/logger'
// 日志模块名
const LOG = 'ai-recommend'
Page({
data: {
state: 'scene' as 'scene' | 'generating' | 'result' | 'empty',
showSceneSheet: true,
sceneType: 1,
scene: '',
result: null as AiRecommendResult | null,
clothes: [] as ClothesItem[],
generating: false,
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
navScrolled: false,
// v2 玻璃弹层
confirmVisible: false,
confirmButtons: [
{ text: '取消', value: '' },
{ text: '开始生成', value: 'ok', type: 'main' },
],
limitVisible: false,
limitButtons: [
{ text: '取消', value: '' },
{ text: '去购买', value: 'ok', type: 'main' },
],
},
// scroll-view 滚动 → 导航收缩为玻璃小标题
onNavScroll: navScroll,
noop() {},
selectDaily() {
this.setData({ sceneType: 1 })
},
selectCustom() {
this.setData({ sceneType: 2 })
},
onSceneInput(e: WechatMiniprogram.Input) {
this.setData({ scene: e.detail.value })
},
onSkipScene() {
this.setData({ showSceneSheet: false })
this.startGenerate(1, '')
},
onStartGenerate() {
this.setData({ showSceneSheet: false })
this.startGenerate(this.data.sceneType, this.data.scene)
},
startGenerate(sceneType: number, scene: string) {
if (this.data.generating) return
// 待生成的场景参数暂存到实例,确认后再发起
;(this as any).pendingScene = { sceneType, scene }
this.setData({ confirmVisible: true })
},
// 取消生成:回到场景选择抽屉,避免停在无任何可渲染内容的空页
onConfirmCancel() {
this.setData({ confirmVisible: false, showSceneSheet: true })
},
onConfirmGenerate(e: WechatMiniprogram.CustomEvent) {
this.setData({ confirmVisible: false })
if (e.detail.value !== 'ok') {
// 兜底:非「开始生成」按钮同样回到场景抽屉(取消按钮已走 cancel 分支)
this.setData({ showSceneSheet: true })
return
}
const pending = (this as any).pendingScene || { sceneType: 1, scene: '' }
this.doGenerate(pending.sceneType, pending.scene)
},
doGenerate(sceneType: number, scene: string) {
if (this.data.generating) return
this.setData({ state: 'generating', generating: true })
const body: Record<string, any> = { scene_type: sceneType }
if (sceneType === 2 && scene) body.scene = scene
request<AiRecommendResult>({ url: '/ai-recommend', method: 'POST', data: body, showError: false })
.then(async (res) => {
logger.info(LOG, 'AI 推荐生成成功, 单品数=', res.outfit_item && res.outfit_item.length)
const clothes = await Promise.all(
res.outfit_item.map(async (it) => {
try {
return await request<ClothesItem>({ url: `/clothes/${it.clothes_uuid}`, showError: false })
} catch (e: any) {
logger.warn(LOG, '单品详情拉取失败, uuid=', it.clothes_uuid, 'code=', e && e.code)
return { clothes_uuid: it.clothes_uuid } as ClothesItem
}
})
)
this.setData({ result: res, clothes, state: 'result', generating: false })
})
.catch((e: any) => {
this.setData({ generating: false })
const code = e && e.code
logger.warn(LOG, 'AI 推荐生成失败, code=', code)
if (code === 90400) {
this.setData({ state: 'empty' })
} else if (code === 90401 || code === 80000) {
// 90401=AI 推荐次数上限;80000=额度不足兜底,统一弹窗引导购买
this.setData({ state: 'scene', showSceneSheet: true })
this.showLimitModal()
} else if (code === 10003) {
wx.showToast({ title: '正在生成中,请稍候', icon: 'none' })
this.setData({ state: 'scene', showSceneSheet: true })
} else {
this.setData({ state: 'scene', showSceneSheet: true })
}
})
},
showLimitModal() {
this.setData({ limitVisible: true })
},
onLimitCancel() {
this.setData({ limitVisible: false })
},
onLimitConfirm(e: WechatMiniprogram.CustomEvent) {
this.setData({ limitVisible: false })
if (e.detail.value !== 'ok') return
wx.navigateTo({
url: '/pages/mall/mall',
fail: (err) => logger.warn(LOG, 'navigateTo 商城失败, err=', err && err.errMsg),
})
},
onSave() {
if (!this.data.result) return
request({
url: '/ai-recommend/save',
method: 'POST',
data: { outfit_uuid: this.data.result.outfit_uuid },
})
.then(() => {
logger.info(LOG, 'AI 穿搭保存成功')
wx.showToast({ title: '已保存到我的穿搭', icon: 'success' })
setTimeout(() => {
wx.redirectTo({
url: `/pages/outfit-detail/outfit-detail?uuid=${this.data.result!.outfit_uuid}&owner=1`,
fail: (e) => logger.warn(LOG, 'redirectTo 穿搭详情失败, err=', e && e.errMsg),
})
}, 500)
})
.catch((e: any) => logger.warn(LOG, 'AI 穿搭保存失败, code=', e && e.code))
},
onReroll() {
this.startGenerate(this.data.sceneType, this.data.scene)
},
onGoUpload() {
wx.navigateTo({
url: '/pages/upload/upload',
fail: (e) => logger.warn(LOG, 'navigateTo 上传页失败, err=', e && e.errMsg),
})
},
})