291 lines
8.2 KiB
TypeScript
291 lines
8.2 KiB
TypeScript
import { appSharePageMethods } from '../../base/pageMixin';
|
|
import { NAV_INNER_PX } from '../../utils/navMetrics';
|
|
import {
|
|
parseMiniProgramUrl,
|
|
stashPendingCategoryTabId,
|
|
} from '../../utils/index';
|
|
import {
|
|
HOME_DAILY_CHECKIN_ITEMS,
|
|
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<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)) {
|
|
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,
|
|
);
|
|
}
|
|
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 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[],
|
|
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 = 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);
|
|
},
|
|
|
|
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,
|
|
});
|