import { NAV_INNER_PX } from '../../utils/navMetrics'; import { parseMiniProgramUrl } from '../../utils/index'; import { HOME_CURRENT_CAPABILITY_DATA, 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), })); } function navigateByPath(path?: string, fallbackTitle?: string) { if (!path) { wx.showToast({ title: fallbackTitle ? `${fallbackTitle} 即将上线` : '即将上线', icon: 'none', }); return; } const { path: basePath, query } = parseMiniProgramUrl(path); if (TAB_BAR_PATHS.has(basePath)) { // tabBar 页面不支持携带 query/hash,统一跳到 basePath if (Object.keys(query).length > 0) { console.warn( '[navigateByPath] tabBar 不支持携带参数,已忽略 query:', basePath, query, ); } wx.switchTab({ url: basePath }); return; } wx.navigateTo({ url: path }).catch((err) => { console.error('navigateByPath error:', err); }); } // ── 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 tabs: HomeTab[] = data.categoryTabs.map((item) => ({ id: item.id, name: item.name, })); const sections = sortSectionsByTabs(tabs, data.sections); 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, featuredItems: SKELETON_FEATURED, hotRecommends: SKELETON_HOT, sections: [] as HomeDisplaySectionView[], 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 = HOME_CURRENT_CAPABILITY_DATA; } 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 ── 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() { wx.showToast({ title: '热门内容整理中', icon: 'none' }); }, 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); }, onTapTrackCard(e: WechatMiniprogram.TouchEvent) { const path = e.currentTarget.dataset.path as string | undefined; const title = e.currentTarget.dataset.title as string | undefined; navigateByPath(path, title); }, onShareAppMessage() { return { title: '涂鸦丫 - 快来生成宝宝的专属学习卡', path: '/pages/home/home', imageUrl: 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png', }; }, onShareTimeline() { return { title: '涂鸦丫 - 快来生成宝宝的专属学习卡', query: '', }; }, });