feat: 完成分龄页开发

This commit is contained in:
R524809
2026-05-06 17:28:51 +08:00
parent 50394154f7
commit 9bdf850f20
54 changed files with 3499 additions and 1021 deletions
+118 -189
View File
@@ -1,187 +1,101 @@
import { AGE_BANDS, AgeBandKey } from '../../core/data/difficulty';
import { parseMiniProgramUrl } from '../../utils/index';
import {
AGE_ABILITIES,
AGE_TAB_SUB,
AGE_WEEK_PLANS,
type AbilityItem,
type WeekPlan,
} from './age.config';
type AbilityItem = {
icon: string;
const DEFAULT_AGE: AgeBandKey = '3-4';
const TAB_BAR_PATHS = new Set([
'/pages/home/home',
'/pages/category/category',
'/pages/age/age',
'/pages/favorites/favorites',
'/pages/profile/profile',
]);
function navigateByPath(path?: string, fallbackTitle?: string) {
const p = String(path || '').trim();
if (!p) {
wx.showToast({
title: fallbackTitle ? `${fallbackTitle} 即将上线` : '即将上线',
icon: 'none',
});
return;
}
const { path: basePath, query } = parseMiniProgramUrl(p);
if (TAB_BAR_PATHS.has(basePath)) {
if (Object.keys(query).length > 0) {
console.warn(
'[age navigateByPath] tabBar 不支持携带参数,已忽略 query:',
basePath,
query,
);
}
wx.switchTab({ url: basePath });
return;
}
wx.navigateTo({ url: p }).catch((err) => {
console.error('navigateByPath error:', err);
});
}
type RecommendedItemView = {
title: string;
desc: string;
image: string;
subtitle: string;
ageBand: string;
difficulty: string;
path: string;
worksheetId?: string;
};
const RECOMMENDED_FALLBACK: RecommendedItemView[] = [];
const DIFFICULTY_LABELS: Record<string, string> = {
1: '入门',
2: '基础',
3: '进阶',
4: '挑战',
};
type WeekExercise = {
title: string;
};
function toDifficultyLabel(value: unknown) {
const text = String(value || '').trim();
return DIFFICULTY_LABELS[text] || text;
}
type WeekPlan = {
week: number;
theme: string;
exercises: WeekExercise[];
};
const AGE_TAB_SUB: Record<AgeBandKey, string> = {
'3-4': '启蒙认知',
'4-5': '基础练习',
'5-6': '能力提升',
'6-7': '幼小衔接',
'7-8': '知识拓展',
};
/** Stitch「为你推荐」占位图(需在小程序后台配置 download 合法域名含 lh3.googleusercontent.com */
const RECOMMENDED_FROM_STITCH: { title: string; image: string }[] = [
{
title: '10以内加减法',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD-3lADhclTGODN9WtNh5v8bQPLysbllbKNkH40bAmVyE5d54PwfTBOiX9nER1hpE4rzHAfsmM_JhVnsE4EbdA914rYIb3S3e4r9QKFjUqYmj2XpE4MlYr_vLkG4oeMnvnHOqGC96nLGrPSOq-fpIddV7RSlYLDeNccBs0G2oUOy-6jIMujzTuEGk4nnxme5rpxs7rYjwcvVsqM5dlkpJthY42e4B3lSPH7ezx187WRSk--m4ZiWmAI52mMOiygzTSWoNormlvfvdM',
},
{
title: '拼音描红',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBYS4O7PyflR5q8M6oXKwj_8QHzex3OL3YvdHBncutm7a_bjlZj83-8UBpGUQvJO20V0BnAdU3iKEoLlUqwpS60CiPhVyxYaHEAqv52P5YUhJdjf0hpLir1Li4rdI_-eqGkPTasYl-5kD2r8-RjUokKg_HiW5_TF72UbxFXHQh4BefYJZfl0Oc9-aTQvkbFL0C3tagi9Qv1jRsgQldMFU4M7cHt8KkxhO1QDF7-R85ZFORJM0RiqV2l-jn0c-UscKtuWtroiX6UUKk',
},
{
title: '趣味找不同',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBSafbTyucs8JLpTgibRWwAJsHukrklTAuEnaAzax_Kr1RNCwHjqpQjta0Q-h47ClufzEmaHPgCIOlm_a2lL4gNlgBDyraCwzRU7aWCf_Fj_dM1cLGzROJmtqrdHKjQzTbCp0_Da8BvXi4a4H2X4RAYVPnLbZVmbgPXrTvpKgEI605kIMmr3n0--WojJQRsF4B_xCho80gABbIaMTHJHrs9xdEY6fZKg8ArQx7lRbJ6_EsLLB_C5a5crZ_begwruXKr3pmdU6KneC8',
},
{
title: '走迷宫大挑战',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAOZHI1Dz6ax4iFUb3QYL-EWp2euA_3kyUeCcu6V57BBCzarMwKnFzcpJxJn4X4woklWFB08Ii1w6zsk0H5eDaPdgUYmsvY0NFqvAhKN_IbZGZjZnclZlBHkLRcX64Nl767L2pg01FQu9Tp6nq2p3lAuYeEQ2fiIdXh4j-QHyikbmx4_aDlx5X5fC0fqMMxWykhwLNkDzl2Rs3ucXj7zYnUP-dNNY-aj_JvqkY4pqoXQDt9ZJcfSeL0dWes7nvD5U2C_JFhrk1bVkw',
},
{
title: '数字点连线',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuD-3lADhclTGODN9WtNh5v8bQPLysbllbKNkH40bAmVyE5d54PwfTBOiX9nER1hpE4rzHAfsmM_JhVnsE4EbdA914rYIb3S3e4r9QKFjUqYmj2XpE4MlYr_vLkG4oeMnvnHOqGC96nLGrPSOq-fpIddV7RSlYLDeNccBs0G2oUOy-6jIMujzTuEGk4nnxme5rpxs7rYjwcvVsqM5dlkpJthY42e4B3lSPH7ezx187WRSk--m4ZiWmAI52mMOiygzTSWoNormlvfvdM',
},
{
title: '方格推理高手',
image: 'https://lh3.googleusercontent.com/aida-public/AB6AXuBYS4O7PyflR5q8M6oXKwj_8QHzex3OL3YvdHBncutm7a_bjlZj83-8UBpGUQvJO20V0BnAdU3iKEoLlUqwpS60CiPhVyxYaHEAqv52P5YUhJdjf0hpLir1Li4rdI_-eqGkPTasYl-5kD2r8-RjUokKg_HiW5_TF72UbxFXHQh4BefYJZfl0Oc9-aTQvkbFL0C3tagi9Qv1jRsgQldMFU4M7cHt8KkxhO1QDF7-R85ZFORJM0RiqV2l-jn0c-UscKtuWtroiX6UUKk',
},
];
/** 各年龄段能力目标(Mock) */
const MOCK_ABILITY: Record<AgeBandKey, AbilityItem[]> = {
'3-4': [
{
icon: '🔢',
title: '数感',
desc: '认读 1-5、点数对应',
},
{
icon: '✏️',
title: '书写',
desc: '涂鸦线条、简单描红',
},
{
icon: '🧩',
title: '思维',
desc: '找相同、简单配对',
},
],
'4-5': [
{
icon: '🔢',
title: '数感',
desc: '10 以内数数、比大小',
},
{
icon: '✏️',
title: '书写',
desc: '笔画模仿、图形描边',
},
{
icon: '🧩',
title: '思维',
desc: '规律排序、图形分类',
},
],
'5-6': [
{
icon: '🔢',
title: '数感',
desc: '10以内加减法、凑十法',
},
{
icon: '✏️',
title: '书写',
desc: '练字帖、拼音入门',
},
{
icon: '🧩',
title: '思维',
desc: '方格推理、格子仿画 5×5',
},
],
'6-7': [
{
icon: '🔢',
title: '数感',
desc: '20 以内运算、应用题启蒙',
},
{
icon: '✏️',
title: '书写',
desc: '常用字书写、词语积累',
},
{
icon: '🧩',
title: '思维',
desc: '逻辑填空、空间想象',
},
],
'7-8': [
{
icon: '🔢',
title: '数感',
desc: '乘除启蒙、巧算练习',
},
{
icon: '✏️',
title: '书写',
desc: '段落抄写、古诗诵读',
},
{
icon: '🧩',
title: '思维',
desc: '数独入门、综合推理',
},
],
};
/** 4 周路线(各年龄段共用结构,内容 Mock) */
const MOCK_WEEKS: WeekPlan[] = [
{
week: 1,
theme: '数感启蒙',
exercises: [
{ title: '找数字涂一涂' },
{ title: '5 以内加法' },
{ title: '数数连一连' },
],
},
{
week: 2,
theme: '形状与连线',
exercises: [
{ title: '识别形状' },
{ title: '线条识别' },
{ title: '数字点连线' },
],
},
{
week: 3,
theme: '趣味专注',
exercises: [
{ title: '颜色找规律' },
{ title: '方格推理' },
{ title: '格子仿画' },
],
},
{
week: 4,
theme: '综合练习',
exercises: [
{ title: '10以内加减法' },
{ title: '识字卡' },
{ title: '连连看' },
],
},
];
const DEFAULT_AGE: AgeBandKey = '5-6';
async function fetchAgeRecommended(
ageKey: AgeBandKey,
): Promise<RecommendedItemView[]> {
try {
const res = await wx.cloud.callFunction({
name: 'ageRecommendedQuery',
data: { ageKey },
});
const payload = (res && (res.result as any)) || null;
if (!payload?.success) return [];
const items = (payload.data?.items || []) as any[];
return items
.map((it) => ({
title: String(it.title || ''),
image: String(it.previewImg || ''),
subtitle: String(it.subtitle || ''),
ageBand: String(it.ageBand || it.ageRange || ''),
difficulty: toDifficultyLabel(
it.difficultyLabel || it.difficulty || '',
),
path: String(it.path || ''),
worksheetId: String(it.worksheetId || ''),
}))
.filter((it) => it.title);
} catch (e) {
console.warn('[age] fetchAgeRecommended failed:', e);
return [];
}
}
Page({
data: {
@@ -190,24 +104,34 @@ Page({
rangeText: b.label.replace(/\s*岁\s*$/, ''),
subLabel: AGE_TAB_SUB[b.key],
})),
activeAgeKey: DEFAULT_AGE,
activeAgeKey: DEFAULT_AGE as AgeBandKey,
goalTitle: '',
abilityItems: [] as AbilityItem[],
weekPlans: MOCK_WEEKS,
recommendedItems: RECOMMENDED_FROM_STITCH,
weekPlans: [] as WeekPlan[],
recommendedItems: RECOMMENDED_FALLBACK,
recommendedLoading: false,
},
onLoad() {
this.applyAge(DEFAULT_AGE);
},
applyAge(key: AgeBandKey) {
async applyAge(key: AgeBandKey) {
const band = AGE_BANDS.find((b) => b.key === key);
const goalTitle = band ? `${band.label}能力目标` : '能力目标';
this.setData({
activeAgeKey: key,
goalTitle,
abilityItems: MOCK_ABILITY[key],
abilityItems: AGE_ABILITIES[key] || [],
weekPlans: AGE_WEEK_PLANS[key] || [],
recommendedLoading: true,
});
const recommended = await fetchAgeRecommended(key);
this.setData({
recommendedItems:
recommended.length > 0 ? recommended : RECOMMENDED_FALLBACK,
recommendedLoading: false,
});
},
@@ -218,18 +142,23 @@ Page({
},
onPracticeTap(e: WechatMiniprogram.TouchEvent) {
const path = e.currentTarget.dataset.path as string | undefined;
const title = e.currentTarget.dataset.title as string | undefined;
if (!title) return;
wx.showToast({ title: `即将前往:${title}`, icon: 'none' });
navigateByPath(path, title);
},
onRecommendMore() {
wx.switchTab({ url: '/pages/home/home' });
},
onRecommendTap(e: WechatMiniprogram.TouchEvent) {
const title = e.currentTarget.dataset.title as string | undefined;
if (!title) return;
wx.showToast({ title: `去看看:${title}`, icon: 'none' });
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);
},
});