207 lines
6.5 KiB
TypeScript
207 lines
6.5 KiB
TypeScript
import { request } from '../../utils/request'
|
|
import * as auth from '../../utils/auth'
|
|
import { HoroscopeData } from '../../types/models'
|
|
import { setTabBarSelected } from '../../utils/tabbar'
|
|
import { navScroll } from '../../utils/nav'
|
|
import { guestHoroscope, goLogin } from '../../utils/guest'
|
|
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: '#AEAEB2',
|
|
loading: true,
|
|
noBirth: false,
|
|
error: false,
|
|
errorText: '',
|
|
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
|
|
navScrolled: false,
|
|
// 游客态(未登录):展示本地示例运势,不发需要授权的请求
|
|
guest: false,
|
|
},
|
|
|
|
// scroll-view 滚动 → 导航收缩为玻璃小标题
|
|
onNavScroll: navScroll,
|
|
|
|
onLoad() {
|
|
// 首帧渲染前判定游客态:未登录直接用本地示例运势渲染,避免首帧 setData 冲突
|
|
if (!auth.isLoggedIn()) {
|
|
const demo = guestHoroscope()
|
|
this.setData({
|
|
guest: true,
|
|
horoscope: demo,
|
|
statDate: this.formatStatDate(demo.stat_date),
|
|
luckyColorHex: COLOR_MAP[demo.lucky_color] || '#AEAEB2',
|
|
loading: false,
|
|
})
|
|
logger.info(LOG, '未登录进入运势页,渲染游客态示例运势')
|
|
}
|
|
},
|
|
|
|
onShow() {
|
|
this.syncTabBar()
|
|
// 未登录:游客态浏览,零请求,不再强制跳登录(微信审核合规要求)
|
|
if (!auth.isLoggedIn()) {
|
|
if (!this.data.guest) {
|
|
const demo = guestHoroscope()
|
|
this.setData({
|
|
guest: true,
|
|
horoscope: demo,
|
|
statDate: this.formatStatDate(demo.stat_date),
|
|
luckyColorHex: COLOR_MAP[demo.lucky_color] || '#AEAEB2',
|
|
loading: false,
|
|
noBirth: false,
|
|
error: false,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
// 登录成功回到本页:清掉示例运势,拉取真实数据
|
|
if (this.data.guest) {
|
|
logger.info(LOG, '检测到登录态,从游客态切回真实运势')
|
|
;(this as any).firstShown = true
|
|
this.setData({ guest: false, horoscope: null, loading: true })
|
|
this.fetchHoroscope(0)
|
|
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] || '#AEAEB2',
|
|
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() {
|
|
if (this.data.guest) {
|
|
this.onGuestLogin()
|
|
return
|
|
}
|
|
this.fetchHoroscope(0)
|
|
},
|
|
|
|
// 游客态点击受限入口(设置生日 / 生成穿搭):统一进入登录页
|
|
onGuestLogin() {
|
|
logger.debug(LOG, '游客态点击受限入口,引导登录')
|
|
goLogin()
|
|
},
|
|
|
|
formatStatDate(d: number): string {
|
|
const s = String(d)
|
|
return `${s.slice(0, 4)}.${s.slice(4, 6)}.${s.slice(6, 8)}`
|
|
},
|
|
|
|
onGoProfile() {
|
|
// 设置生日需登录:游客态先引导登录,不做强制跳转
|
|
if (!auth.isLoggedIn()) {
|
|
this.onGuestLogin()
|
|
return
|
|
}
|
|
// 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() {
|
|
if (this.data.guest) {
|
|
this.onGuestLogin()
|
|
return
|
|
}
|
|
wx.navigateTo({
|
|
url: '/pages/ai-recommend/ai-recommend',
|
|
fail: (e) => logger.warn(LOG, 'navigateTo AI 推荐失败, err=', e && e.errMsg),
|
|
})
|
|
},
|
|
|
|
onUnload() {
|
|
this.clearRetryTimer()
|
|
},
|
|
})
|