274 lines
7.7 KiB
TypeScript
274 lines
7.7 KiB
TypeScript
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<Record<string, number>>((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<IAppOption>();
|
|
const config = await app.getPageConfig();
|
|
|
|
let homeData: HomeDisplayDataset;
|
|
|
|
if (config?.home) {
|
|
// Cloud config available — map to HomeDisplayDataset
|
|
const raw = config.home as Record<string, any>;
|
|
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: '',
|
|
};
|
|
},
|
|
});
|