import { appSharePageMethods } from '../../base/pageMixin'; import { NAV_INNER_PX } from '../../utils/navMetrics'; import { parseMiniProgramUrl, stashPendingCategoryTabId, } from '../../utils/index'; import { HOME_DAILY_CHECKIN_ITEMS, HOME_NOTICES, createEmptyHomeDataset, type HomeDisplayItem, type HomeDisplaySection, type HomeDisplayDataset, } from './home.data'; type AgeBandKey = string; type HomeTab = { id: string; name: string; }; type HomeAgeBandView = { key: string; label: string; desc: string; path: string; icon: string; }; type HomeDisplayItemView = HomeDisplayItem & { skeleton?: boolean; }; type HomeDisplaySectionView = HomeDisplaySection & { anchorId: string; items: HomeDisplayItemView[]; }; const AGE_BAND_ICONS = ['🌱', '🚀', '🎓', '🧠', '🏆'] as const; const SECTION_ANCHOR_PREFIX = 'section-'; const TAB_BAR_PATHS = new Set([ '/pages/home/home', '/pages/category/category', '/pages/age/age', '/pages/favorites/favorites', '/pages/profile/profile', ]); function toItemView(item: HomeDisplayItem): HomeDisplayItemView { return { ...item, }; } function sortSectionsByTabs( tabs: readonly { id: string }[], sections: HomeDisplaySection[], ): HomeDisplaySectionView[] { const orderMap = tabs.reduce>((acc, tab, index) => { acc[tab.id] = index; return acc; }, {}); return [...sections] .sort((a, b) => (orderMap[a.id] ?? 999) - (orderMap[b.id] ?? 999)) .map((section) => ({ ...section, anchorId: `${SECTION_ANCHOR_PREFIX}${section.id}`, items: section.items.map(toItemView), })); } let _navigating = false; function releaseNavigateLock() { _navigating = false; } function navigateByPath(path?: string, fallbackTitle?: string) { if (_navigating) return; if (!path) { wx.showToast({ title: fallbackTitle ? `${fallbackTitle} 即将上线` : '即将上线', icon: 'none', }); return; } const { path: basePath, query } = parseMiniProgramUrl(path); if (TAB_BAR_PATHS.has(basePath)) { if ( basePath === '/pages/category/category' && query.id && String(query.id).trim() !== '' ) { stashPendingCategoryTabId(String(query.id).trim()); } else if (Object.keys(query).length > 0) { console.warn( '[navigateByPath] tabBar 不支持携带参数,已忽略 query:', basePath, query, ); } _navigating = true; wx.switchTab({ url: basePath, complete: releaseNavigateLock, fail: releaseNavigateLock, }); return; } _navigating = true; wx.navigateTo({ url: path, complete: releaseNavigateLock, fail: (err) => { console.error('navigateByPath error:', err); releaseNavigateLock(); }, }); } // ── Skeleton data ── function skeletonItem(id: string): HomeDisplayItemView { return { id, title: '\u00A0', subtitle: '\u00A0', category: 'math' as any, ageBand: '', difficulty: '基础', icon: '', path: '', available: false, skeleton: true, }; } const SKELETON_FEATURED = Array.from({ length: 3 }, (_, i) => skeletonItem(`skeleton-featured-${i}`), ); const SKELETON_HOT = Array.from({ length: 3 }, (_, i) => skeletonItem(`skeleton-hot-${i}`), ); const win = wx.getWindowInfo(); // Page-level mutable state let _homeData: HomeDisplayDataset | null = null; function applyHomeData(data: HomeDisplayDataset) { _homeData = data; const sectionsWithItems = data.sections.filter( (section) => section.items.length > 0, ); const sectionIdsWithItems = new Set( sectionsWithItems.map((section) => section.id), ); const tabs: HomeTab[] = data.categoryTabs .filter((tab) => tab.id === 'all' || sectionIdsWithItems.has(tab.id)) .map((tab) => ({ id: tab.id, name: tab.name, })); const sections = sortSectionsByTabs(tabs, sectionsWithItems); const ageBands: HomeAgeBandView[] = data.ageBands.map((item, idx) => ({ ...item, icon: AGE_BAND_ICONS[idx] || '🌟', })); return { tabs, sections, ageBands }; } Page({ data: { searchPlaceholder: '搜索你喜欢的练习册...', tabs: [] as HomeTab[], activeTab: 'all', ageBands: [] as HomeAgeBandView[], activeAgeBand: '3-4' as AgeBandKey, heroSwiperCurrent: 0, featuredItems: SKELETON_FEATURED, hotRecommends: SKELETON_HOT, dailyCheckinItems: HOME_DAILY_CHECKIN_ITEMS.map(toItemView), sections: [] as HomeDisplaySectionView[], notices: HOME_NOTICES, loading: true, }, onLoad() { void this.initData(); }, async initData() { wx.showLoading({ title: '加载中', mask: false }); const app = getApp(); const config = await app.getPageConfig(); let homeData: HomeDisplayDataset; if (config?.home) { // Cloud config available — map to HomeDisplayDataset const raw = config.home as Record; homeData = { searchPlaceholder: raw.searchPlaceholder || '搜索你喜欢的练习册...', categoryTabs: raw.categoryTabs || [], ageBands: raw.ageBands || [], featured: (raw.featured || []) as HomeDisplayItem[], hot: (raw.hot || []) as HomeDisplayItem[], sections: (raw.sections || []) as HomeDisplaySection[], }; } else { homeData = createEmptyHomeDataset(); } const { tabs, sections, ageBands } = applyHomeData(homeData); this.setData({ loading: false, searchPlaceholder: homeData.searchPlaceholder, tabs, ageBands, activeAgeBand: homeData.ageBands[0]?.key || '3-4', featuredItems: homeData.featured.map(toItemView), hotRecommends: homeData.hot.slice(0, 3).map(toItemView), sections, }); wx.hideLoading(); }, // ── Tab & Navigation ── onHeroSwiperChange(e: WechatMiniprogram.SwiperChange) { this.setData({ heroSwiperCurrent: e.detail.current }); }, onTabChange(e: WechatMiniprogram.CustomEvent) { const id = e.detail.id as string | undefined; if (!id) return; this.setData({ activeTab: id }); if (id === 'all') { wx.pageScrollTo({ scrollTop: 0, duration: 400 }); return; } wx.pageScrollTo({ selector: `#${SECTION_ANCHOR_PREFIX}${id}`, duration: 400, offsetTop: -(win.statusBarHeight + NAV_INNER_PX + 24), }); }, onTapAgeBand(e: WechatMiniprogram.TouchEvent) { const key = e.currentTarget.dataset.key as AgeBandKey | undefined; const path = e.currentTarget.dataset.path as string | undefined; if (!key) return; this.setData({ activeAgeBand: key }); navigateByPath(path, '分龄内容'); }, onTapFeatured(e: WechatMiniprogram.TouchEvent) { const path = e.currentTarget.dataset.path as string | undefined; const title = e.currentTarget.dataset.title as string | undefined; navigateByPath(path, title); }, onTapHotRecommend(e: WechatMiniprogram.TouchEvent) { const path = e.currentTarget.dataset.path as string | undefined; const title = e.currentTarget.dataset.title as string | undefined; navigateByPath(path, title); }, onTapAgeMore() { wx.switchTab({ url: '/pages/age/age' }); }, onTapHotMore() { navigateByPath('/pages/category/category?id=hot'); }, onTapTrackMore(e: WechatMiniprogram.TouchEvent) { const path = e.currentTarget.dataset.path as string | undefined; const title = e.currentTarget.dataset.title as string | undefined; navigateByPath(path, title); }, onTapNotice(e: WechatMiniprogram.TouchEvent) { const path = e.currentTarget.dataset.path as string | undefined; if (!path) return; navigateByPath(path); }, onTapCard(e: WechatMiniprogram.CustomEvent) { const detail = (e.detail || {}) as { title?: string; path?: string }; const path = detail.path || (e.currentTarget.dataset.path as string | undefined); const title = detail.title || (e.currentTarget.dataset.title as string | undefined); navigateByPath(path, title); }, ...appSharePageMethods, });