Files
zt-closet-wxmini-frontend/miniprogram/pages/horoscope/horoscope.ts
T
2026-09-13 02:44:41 +08:00

144 lines
4.4 KiB
TypeScript

import { request, redirectToLogin } from '../../utils/request'
import * as auth from '../../utils/auth'
import { HoroscopeData } from '../../types/models'
import { setTabBarSelected } from '../../utils/tabbar'
import { logger } from '../../utils/logger'
import { STORAGE_KEYS } from '../../utils/config'
// 日志模块名
const LOG = 'horoscope'
const COLOR_MAP: Record<string, string> = {
红色: '#FF3B30',
橙色: '#FF9500',
黄色: '#FFCC00',
绿色: '#34C759',
蓝色: '#007AFF',
紫色: '#5856D6',
粉色: '#FF2D55',
白色: '#FFFFFF',
黑色: '#000000',
灰色: '#8E8E93',
}
// 今日日期(YYYYMMDD),用于判断运势数据是否跨天
function todayStatDate(): number {
const d = new Date()
const pad = (n: number) => (n < 10 ? `0${n}` : String(n))
return Number(`${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}`)
}
Page({
data: {
horoscope: null as HoroscopeData | null,
statDate: '',
luckyColorHex: '#007AFF',
loading: true,
noBirth: false,
error: false,
errorText: '',
},
onShow() {
this.syncTabBar()
// 未登录直接回登录页(幂等跳转),不再发出必然 401 的请求
if (!auth.isLoggedIn()) {
redirectToLogin()
return
}
// 首帧渲染未结束就 setData 会触发「Expected updated data but get first rendering data」,
// 整个 webview 渲染失败,用户视觉上仍看到上一个页面。首次 onShow 的数据拉取延迟到下一帧
const stale =
!this.data.horoscope || Number(this.data.horoscope.stat_date) !== todayStatDate()
if (!stale) return
if (!(this as any).firstShown) {
;(this as any).firstShown = true
wx.nextTick(() => this.fetchHoroscope(0))
return
}
this.fetchHoroscope(0)
},
syncTabBar() {
setTabBarSelected(2)
},
fetchHoroscope(retry: number) {
this.clearRetryTimer()
this.setData({ loading: true, error: false, errorText: '' })
request<HoroscopeData>({ url: '/horoscope/today', showError: false })
.then((res) => {
logger.debug(LOG, '运势加载成功, statDate=', res && res.stat_date)
this.setData({
horoscope: res,
statDate: this.formatStatDate(res.stat_date),
luckyColorHex: COLOR_MAP[res.lucky_color] || '#007AFF',
loading: false,
noBirth: false,
error: false,
})
})
.catch((e: any) => {
const code = e && e.code
logger.warn(LOG, '运势加载失败, code=', code, 'retry=', retry)
const details = (e && e.details) || []
const noBirth = details.some((d: string) => d.indexOf('生日') >= 0)
if (code === 10000 && noBirth) {
this.setData({ noBirth: true, loading: false })
} else if (code === 90300 && retry < 3) {
wx.showToast({ title: '运势生成中,请稍后重试', icon: 'none' })
// 定时器挂实例并在页面卸载时清理,避免离开页面后继续请求
;(this as any).retryTimer = setTimeout(() => this.fetchHoroscope(retry + 1), 4000)
} else {
// 失败必须有可见反馈:否则会渲染一张数据为 null 的空卡
this.setData({
loading: false,
error: true,
errorText: (e && e.message) || '运势加载失败,请重试',
})
}
})
},
clearRetryTimer() {
const timer = (this as any).retryTimer
if (timer) {
clearTimeout(timer)
;(this as any).retryTimer = null
}
},
onRetry() {
this.fetchHoroscope(0)
},
formatStatDate(d: number): string {
const s = String(d)
return `${s.slice(0, 4)}.${s.slice(4, 6)}.${s.slice(6, 8)}`
},
onGoProfile() {
// switchTab 不支持 query 传参,用 storage 标记让「我的」页 onShow 自动弹出编辑弹窗
wx.setStorageSync(STORAGE_KEYS.OPEN_PROFILE_EDIT, 1)
wx.switchTab({
url: '/pages/profile/profile',
fail: (e) => {
logger.warn(LOG, 'switchTab 个人资料失败, err=', e && e.errMsg)
// 跳转失败时清除标记,避免下次进入「我的」页意外弹窗
wx.removeStorageSync(STORAGE_KEYS.OPEN_PROFILE_EDIT)
},
})
},
onGenerate() {
wx.navigateTo({
url: '/pages/ai-recommend/ai-recommend',
fail: (e) => logger.warn(LOG, 'navigateTo AI 推荐失败, err=', e && e.errMsg),
})
},
onUnload() {
this.clearRetryTimer()
},
})