136 lines
4.9 KiB
TypeScript
136 lines
4.9 KiB
TypeScript
import { request } from '../utils/request'
|
|
import * as auth from '../utils/auth'
|
|
import { STORAGE_KEYS } from '../utils/config'
|
|
import { logger } from '../utils/logger'
|
|
|
|
const LOG = 'tabbar'
|
|
|
|
// 未读红点轮询最小间隔,避免频繁切 tab 时重复打接口
|
|
const SYNC_INTERVAL = 30 * 1000
|
|
let lastSyncAt = 0
|
|
|
|
Component({
|
|
data: {
|
|
selected: 0,
|
|
unread: 0,
|
|
// 弹层打开时页面调用 setHidden(true) 淡出,避免悬浮标签栏压在弹层之上
|
|
hidden: false,
|
|
list: [
|
|
{ pagePath: '/pages/wardrobe/wardrobe', text: '衣橱', icon: 'icon-hanger' },
|
|
{ pagePath: '/pages/outfit/outfit', text: '穿搭', icon: 'icon-shirt' },
|
|
{ pagePath: '/pages/horoscope/horoscope', text: '运势', icon: 'icon-sparkles' },
|
|
{ pagePath: '/pages/closet-share/closet-share', text: '分享', icon: 'icon-share' },
|
|
{ pagePath: '/pages/profile/profile', text: '我的', icon: 'icon-user' },
|
|
],
|
|
},
|
|
|
|
lifetimes: {
|
|
attached() {
|
|
// 首帧渲染流程(FLOW_INITIAL_CREATION)还没结束就 setData,渲染层会抛
|
|
// 「Expected updated data but get first rendering data」/
|
|
// 「expect FLOW_INITIAL_CREATION end but get FLOW_CREATE_NODE」。
|
|
// 因此首帧内产生的更新先暂存,等首帧流程结束后再一次性提交。
|
|
;(this as any).rendered = false
|
|
;(this as any).pending = {}
|
|
|
|
// 每个 tab 页都有独立的 tabBar 实例,直接按当前页面路由推导选中态,
|
|
// 页面侧无需再在 onShow 里回写 selected(那一步正是首帧冲突的来源)
|
|
const index = this.matchRoute()
|
|
if (index >= 0) (this as any).pending.selected = index
|
|
|
|
const cached = (wx.getStorageSync(STORAGE_KEYS.UNREAD) as number) || 0
|
|
if (cached) (this as any).pending.unread = cached
|
|
|
|
wx.nextTick(() => {
|
|
;(this as any).rendered = true
|
|
const pending = (this as any).pending
|
|
;(this as any).pending = null
|
|
if (pending && Object.keys(pending).length) this.setData(pending)
|
|
// 未登录时 syncUnread 内部只清零不发请求,不会批量 401
|
|
this.syncUnread()
|
|
})
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
// 当前页面路由在 list 中的下标,非 tab 页返回 -1
|
|
matchRoute(): number {
|
|
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
|
|
const route = pages.length ? (pages[pages.length - 1] as any).route : ''
|
|
if (!route) return -1
|
|
return this.data.list.findIndex((item) => item.pagePath === `/${route}`)
|
|
},
|
|
|
|
// 统一出口:首帧未结束时先合并暂存,避免打断渲染层首帧流程
|
|
applyData(data: Record<string, any>) {
|
|
if ((this as any).rendered) {
|
|
this.setData(data)
|
|
return
|
|
}
|
|
const pending = (this as any).pending
|
|
if (pending) Object.assign(pending, data)
|
|
else this.setData(data)
|
|
},
|
|
|
|
// 页面切换选中态(供页面调用,内部保证首帧安全)
|
|
setSelected(index: number) {
|
|
this.applyData({ selected: index })
|
|
},
|
|
|
|
// 弹层打开/关闭时淡出或恢复悬浮标签栏(供页面调用,内部保证首帧安全)
|
|
setHidden(hidden: boolean) {
|
|
const next = !!hidden
|
|
// 值未变化时不触发渲染:页面每次 onShow 都会校准一次,避免无谓的 setData
|
|
if (next === !!this.data.hidden) return
|
|
this.applyData({ hidden: next })
|
|
},
|
|
|
|
// 直接更新红点(供页面在已知数量时调用,不再触发二次请求)
|
|
setUnread(n: number) {
|
|
const unread = Number(n) || 0
|
|
wx.setStorageSync(STORAGE_KEYS.UNREAD, unread)
|
|
this.applyData({ unread })
|
|
},
|
|
|
|
// 拉取未读数:仅登录态才请求,且做节流
|
|
syncUnread(force?: boolean) {
|
|
if (!auth.isLoggedIn()) {
|
|
logger.debug(LOG, '未登录,红点置 0')
|
|
this.setUnread(0)
|
|
return
|
|
}
|
|
const now = Date.now()
|
|
if (!force && now - lastSyncAt < SYNC_INTERVAL) {
|
|
logger.debug(LOG, '红点轮询命中节流,跳过')
|
|
return
|
|
}
|
|
lastSyncAt = now
|
|
|
|
request<{ unread_count: number }>({
|
|
url: '/notification/unread',
|
|
showError: false,
|
|
silentAuth: true,
|
|
})
|
|
.then((res) => {
|
|
logger.debug(LOG, '红点同步成功, unread=', res.unread_count)
|
|
this.setUnread(res.unread_count || 0)
|
|
})
|
|
.catch((e: any) => {
|
|
// 请求失败时回滚节流时间戳,否则要白等 30s 才能重试
|
|
lastSyncAt = 0
|
|
logger.warn(LOG, '红点同步失败, code=', e && e.code)
|
|
})
|
|
},
|
|
|
|
switchTab(e: WechatMiniprogram.TouchEvent) {
|
|
const { path, index } = e.currentTarget.dataset as { path: string; index: number }
|
|
logger.debug(LOG, 'switchTab, path=', path, 'index=', index)
|
|
wx.switchTab({
|
|
url: path,
|
|
fail: (err: any) => logger.warn(LOG, 'switchTab 失败, path=', path, 'err=', err && err.errMsg),
|
|
})
|
|
this.setSelected(Number(index) || 0)
|
|
},
|
|
},
|
|
})
|