feat: 完成 4 个TabBar 页面初版样式修改

This commit is contained in:
R524809
2026-04-02 17:05:55 +08:00
parent b18dbcb0d1
commit 206c44cf6d
61 changed files with 4670 additions and 996 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
{
"navigationBarTitleText": "分龄推荐",
"navigationStyle": "custom",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#F5F5F5",
"navigationBarBackgroundColor": "#FEF6E7",
"backgroundColor": "#FEF6E7",
"enablePullDownRefresh": false,
"usingComponents": {}
}
+373 -69
View File
@@ -1,108 +1,412 @@
@import '../../style/theme3.less';
@age-tab-gap: 32rpx;
@age-tab-w: 226rpx;
@age-tab-h: 156rpx;
@age-badge-current: #005e17;
/* 选中态 box-shadow + scale 外溢;内层留白 + 外层负 margin 抵消,整体上下不占更多间距 */
@age-tab-shadow-bleed-t: 36rpx;
@age-tab-shadow-bleed-b: 52rpx;
.age-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 48rpx;
background: @bg-page;
padding-bottom: calc(48rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.age-hero {
background: #fff;
padding: 40rpx 32rpx 36rpx;
margin-bottom: 24rpx;
.age-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: fade(@bg-header, 85%);
backdrop-filter: blur(40rpx);
-webkit-backdrop-filter: blur(40rpx);
box-shadow: 0 2rpx 12rpx rgba(50, 46, 37, 0.05);
}
.age-hero__title {
display: block;
font-size: 44rpx;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 12rpx;
}
.age-hero__subtitle {
display: block;
font-size: 26rpx;
line-height: 1.5;
color: #999;
}
.age-grid {
.age-nav__inner {
height: @nav-inner-height;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 24rpx;
padding: 0 24rpx;
align-items: center;
justify-content: flex-start;
padding: 0 @page-padding-x;
box-sizing: border-box;
}
.age-card {
width: calc((100% - 24rpx) / 2);
background: #fff;
border-radius: 32rpx;
.age-nav__title {
font-size: @fs-nav-title;
font-weight: @fw-nav-title;
color: @color-nav-title;
letter-spacing: @ls-nav-title;
}
.age-body {
width: 100%;
box-sizing: border-box;
}
/* 横向年龄 Tab — Stitch 为你推荐版 */
.age-tab-scroll {
width: 100%;
white-space: nowrap;
padding: @section-gap-sm 0 @section-gap-xs;
box-sizing: border-box;
margin-top: -@age-tab-shadow-bleed-t;
margin-bottom: -@age-tab-shadow-bleed-b;
}
.age-tab-scroll__inner {
padding: @age-tab-shadow-bleed-t 0 @age-tab-shadow-bleed-b;
box-sizing: border-box;
}
.age-tab-row {
display: inline-flex;
flex-direction: row;
align-items: center;
gap: @age-tab-gap;
padding: 0 @page-padding-x;
}
.age-tab {
position: relative;
width: @age-tab-w;
height: @age-tab-h;
flex-shrink: 0;
padding: 24rpx 16rpx;
border-radius: @radius-lg;
background: @bg-muted;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
border: 2rpx solid transparent;
}
.age-tab--active {
background: @brand;
border-color: @bg-white;
box-shadow: 0 16rpx 48rpx rgba(255, 215, 9, 0.38);
transform: scale(1.05);
z-index: 1;
}
.age-tab__current {
position: absolute;
top: -8rpx;
right: -8rpx;
padding: 4rpx 14rpx;
border-radius: 999rpx;
font-size: 18rpx;
font-weight: 700;
color: @text-white;
background: @age-badge-current;
line-height: 1.2;
}
.age-tab__range {
font-size: 36rpx;
font-weight: 800;
color: @text-title;
line-height: 1.2;
}
.age-tab--active .age-tab__range {
color: @text-selected-btn;
}
.age-tab__sub {
font-size: 22rpx;
color: @text-light;
line-height: 1.3;
font-weight: 600;
}
.age-tab--active .age-tab__sub {
color: fade(@text-selected-btn, 85%);
}
.age-panel {
margin: 0 @page-padding-x;
background: @bg-white;
border-radius: @radius-lg;
border: 1rpx solid fade(@bg-gray, 90%);
box-shadow: @shadow;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
}
.age-goal-panel {
padding: 36rpx @page-padding-x 40rpx;
box-sizing: border-box;
}
.age-card__art {
height: 160rpx;
.age-goal-head {
display: flex;
flex-direction: row;
align-items: center;
gap: 12rpx;
margin-bottom: @section-gap-sm;
}
.age-goal-head__icon-wrap {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: @brand-light;
display: flex;
align-items: center;
justify-content: center;
}
.age-card__emoji {
font-size: 72rpx;
.age-goal-head__icon {
font-size: 28rpx;
line-height: 1;
}
.age-card__body {
padding: 24rpx 24rpx 28rpx;
.age-goal-head__title {
font-size: @fs-section-head-title;
font-weight: 800;
color: @text-selected-btn;
letter-spacing: -0.02em;
}
.age-card__label {
display: block;
font-size: 32rpx;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 8rpx;
.age-goal-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.age-card__desc {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 12rpx;
.age-goal-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12rpx;
padding: 28rpx @page-padding-inner-x;
border-radius: @radius;
background: @bg-header;
box-sizing: border-box;
border: 1rpx solid rgba(229, 220, 201, 0.55);
}
.age-card__count {
display: block;
font-size: 22rpx;
color: #ccc;
}
.age-tip {
margin: 40rpx 24rpx 0;
background: #fff;
border-radius: 24rpx;
padding: 28rpx 24rpx;
.age-goal-item__top {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 20rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
box-sizing: border-box;
align-items: center;
gap: 12rpx;
}
.age-tip__duck {
font-size: 64rpx;
.age-goal-item__emoji {
font-size: 40rpx;
line-height: 1;
flex-shrink: 0;
}
.age-tip__text {
flex: 1;
font-size: 26rpx;
line-height: 1.55;
color: rgba(0, 0, 0, 0.65);
.age-goal-item__title {
font-size: 28rpx;
font-weight: 700;
color: @text-selected-btn;
line-height: 1.2;
}
.age-goal-item__desc {
font-size: 24rpx;
line-height: 1.5;
color: @text-secondary;
}
.age-path-head {
margin: @section-gap-lg @page-padding-x @section-gap-xs;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.age-path-head__left {
display: flex;
flex-direction: row;
align-items: center;
gap: 20rpx;
min-width: 0;
}
.age-path-head__bar {
width: 16rpx;
height: 64rpx;
border-radius: 999rpx;
background: @text-selected-btn;
flex-shrink: 0;
}
.age-path-head__title {
font-size: 40rpx;
font-weight: 800;
color: @text-title;
letter-spacing: -0.03em;
line-height: 1.2;
}
.age-path-head__badge {
flex-shrink: 0;
font-size: 20rpx;
font-weight: 700;
color: @text-secondary;
background: @bg-header;
padding: 10rpx 20rpx;
border-radius: 999rpx;
border: @border;
}
.age-week-list {
padding: 0 @page-padding-x;
display: flex;
flex-direction: column;
gap: @section-gap-lg;
}
.age-week-card {
background: @bg-white;
border-radius: @radius-lg;
overflow: hidden;
border: 1rpx solid rgba(50, 46, 37, 0.08);
box-shadow: @shadow;
}
.age-week-card__head {
padding: 28rpx @page-padding-x;
background: @bg-week-head;
display: flex;
flex-direction: row;
align-items: baseline;
gap: 16rpx;
}
.age-week-card__week {
font-size: @fs-section-head-title;
font-weight: 800;
color: @text-title;
}
.age-week-card__theme {
font-size: 26rpx;
font-weight: 600;
color: @text-gray;
}
.age-week-card__body {
padding: 0 @page-padding-x 8rpx;
}
.age-week-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 20rpx;
padding: 32rpx 0;
border-bottom: 1rpx solid #f2f2f2;
}
.age-week-row:last-child {
border-bottom: none;
}
.age-week-row__title {
flex: 1;
min-width: 0;
font-size: 28rpx;
font-weight: 700;
color: @text-title;
}
.age-week-row__btn {
flex-shrink: 0;
display: flex;
flex-direction: row;
align-items: center;
gap: 6rpx;
padding: 14rpx 28rpx;
border-radius: 999rpx;
background: @brand;
font-size: 24rpx;
font-weight: 700;
color: @text-selected-btn;
box-shadow: 0 4rpx 12rpx rgba(255, 215, 9, 0.35);
}
.age-week-row__btn-arrow {
font-size: 22rpx;
font-weight: 700;
}
/* [为你推荐] — Stitch 栅格 */
.age-rec {
margin-top: @section-gap-lg;
padding: 0 @page-padding-x @section-gap-xs;
box-sizing: border-box;
}
.age-rec__head {
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: space-between;
margin-bottom: @section-gap-xs;
}
.age-rec__title {
font-size: @fs-section-head-title;
font-weight: 800;
color: @text-title;
letter-spacing: -0.02em;
}
.age-rec__more {
font-size: 26rpx;
font-weight: 700;
color: @text-selected-btn;
padding: 8rpx 0 8rpx 24rpx;
}
.age-rec__grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
row-gap: 32rpx;
}
.age-rec__card {
width: calc(50% - 16rpx);
background: @bg-white;
border-radius: @radius-lg;
overflow: hidden;
border: 1rpx solid rgba(50, 46, 37, 0.08);
box-shadow: @shadow;
box-sizing: border-box;
}
.age-rec__img {
width: 100%;
height: 200rpx;
display: block;
background: fade(@brand, 15%);
}
.age-rec__body {
padding: 20rpx 20rpx 24rpx;
}
.age-rec__card-title {
font-size: 26rpx;
font-weight: 700;
color: @text-title;
line-height: 1.35;
}
+230 -31
View File
@@ -1,44 +1,243 @@
import { ALL_WORKSHEETS, getWorksheetsByAge } from '../../core/data/worksheets';
import { AGE_BANDS, AgeBandKey } from '../../core/data/difficulty';
import { NAV_INNER_PX } from '../../utils/navMetrics';
interface AgeGroup {
label: string;
type AbilityItem = {
icon: string;
title: string;
desc: string;
emoji: string;
minAge: number;
maxAge: number;
count: number;
gradient: string;
}
};
type WeekExercise = {
title: string;
};
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';
Page({
data: {
ageGroups: [] as AgeGroup[],
statusBarHeight: 0,
navBlockHeight: 0,
ageTabs: AGE_BANDS.map((b) => ({
key: b.key,
rangeText: b.label.replace(/\s*岁\s*$/, ''),
subLabel: AGE_TAB_SUB[b.key],
})),
activeAgeKey: DEFAULT_AGE,
goalTitle: '',
abilityItems: [] as AbilityItem[],
weekPlans: MOCK_WEEKS,
recommendedItems: RECOMMENDED_FROM_STITCH,
},
onLoad() {
const groups = [
{ label: '3-4 岁', desc: '启蒙认知', emoji: '👶', minAge: 3, maxAge: 4, gradient: 'linear-gradient(135deg, #fff9c4, #fff176)' },
{ label: '4-5 岁', desc: '基础练习', emoji: '🧒', minAge: 4, maxAge: 5, gradient: 'linear-gradient(135deg, #c8e6c9, #81c784)' },
{ label: '5-6 岁', desc: '能力提升', emoji: '👦', minAge: 5, maxAge: 6, gradient: 'linear-gradient(135deg, #bbdefb, #64b5f6)' },
{ label: '6-7 岁', desc: '幼小衔接', emoji: '👧', minAge: 6, maxAge: 7, gradient: 'linear-gradient(135deg, #e1bee7, #ba68c8)' },
{ label: '7-8 岁', desc: '巩固挑战', emoji: '🎒', minAge: 7, maxAge: 8, gradient: 'linear-gradient(135deg, #ffccbc, #ff8a65)' },
];
const ageGroups = groups.map((g) => ({
...g,
count: getWorksheetsByAge(ALL_WORKSHEETS, g.minAge).length,
}));
this.setData({ ageGroups });
const win = wx.getWindowInfo();
this.setData({
statusBarHeight: win.statusBarHeight,
navBlockHeight: win.statusBarHeight + NAV_INNER_PX,
});
this.applyAge(DEFAULT_AGE);
},
onAgeCardTap(e: WechatMiniprogram.TouchEvent) {
const minAge = Number(e.currentTarget.dataset.minAge);
const maxAge = Number(e.currentTarget.dataset.maxAge);
if (Number.isNaN(minAge) || Number.isNaN(maxAge)) return;
const title = `${minAge}-${maxAge}岁推荐`;
wx.navigateTo({
url: `/pages/category/category?minAge=${minAge}&maxAge=${maxAge}&title=${encodeURIComponent(title)}`,
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],
});
},
onAgeTabTap(e: WechatMiniprogram.TouchEvent) {
const key = e.currentTarget.dataset.key as AgeBandKey | undefined;
if (!key) return;
this.applyAge(key);
},
onPracticeTap(e: WechatMiniprogram.TouchEvent) {
const title = e.currentTarget.dataset.title as string | undefined;
if (!title) return;
wx.showToast({ title: `即将前往:${title}`, icon: 'none' });
},
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' });
},
});
+104 -25
View File
@@ -1,31 +1,110 @@
<view class="age-page">
<view class="age-hero">
<text class="age-hero__title">分龄推荐</text>
<text class="age-hero__subtitle">按年龄段挑选适合的练习单,循序渐进更省心</text>
</view>
<view class="age-grid">
<view
wx:for="{{ageGroups}}"
wx:key="label"
class="age-card"
bindtap="onAgeCardTap"
data-min-age="{{item.minAge}}"
data-max-age="{{item.maxAge}}"
>
<view class="age-card__art" style="background: {{item.gradient}}">
<text class="age-card__emoji">{{item.emoji}}</text>
</view>
<view class="age-card__body">
<text class="age-card__label">{{item.label}}</text>
<text class="age-card__desc">{{item.desc}}</text>
<text class="age-card__count">{{item.count}} 个练习</text>
</view>
<view class="age-nav" style="padding-top: {{statusBarHeight}}px;">
<view class="age-nav__inner">
<text class="age-nav__title">按年龄</text>
</view>
</view>
<view class="age-tip">
<text class="age-tip__duck">🦆</text>
<text class="age-tip__text">建议家长根据孩子当前能力微调难度,不必严格按年龄划分哦~</text>
<view class="age-body" style="padding-top: {{navBlockHeight}}px;">
<scroll-view class="age-tab-scroll" scroll-x enable-flex>
<view class="age-tab-scroll__inner">
<view class="age-tab-row">
<view
wx:for="{{ageTabs}}"
wx:key="key"
class="age-tab {{activeAgeKey === item.key ? 'age-tab--active' : ''}}"
data-key="{{item.key}}"
bindtap="onAgeTabTap">
<text class="age-tab__range">{{item.rangeText}}</text>
<text class="age-tab__sub">{{item.subLabel}}</text>
</view>
</view>
</view>
</scroll-view>
<view class="age-panel age-goal-panel">
<view class="age-goal-head">
<view class="age-goal-head__icon-wrap">
<text class="age-goal-head__icon" aria-hidden="true"
>⭐</text
>
</view>
<text class="age-goal-head__title">{{goalTitle}}</text>
</view>
<view class="age-goal-list">
<view
wx:for="{{abilityItems}}"
wx:key="title"
class="age-goal-item">
<view class="age-goal-item__top">
<text class="age-goal-item__emoji">{{item.icon}}</text>
<text class="age-goal-item__title">{{item.title}}</text>
</view>
<text class="age-goal-item__desc">{{item.desc}}</text>
</view>
</view>
</view>
<view class="age-path-head">
<view class="age-path-head__left">
<view class="age-path-head__bar" />
<text class="age-path-head__title">4 周推荐学习路线</text>
</view>
<view class="age-path-head__badge">4 周 · 每周 3 练</view>
</view>
<view class="age-week-list">
<view wx:for="{{weekPlans}}" wx:key="week" class="age-week-card">
<view class="age-week-card__head">
<text class="age-week-card__week">第 {{item.week}} 周</text>
<text class="age-week-card__theme">{{item.theme}}</text>
</view>
<view class="age-week-card__body">
<view
wx:for="{{item.exercises}}"
wx:for-item="ex"
wx:key="title"
class="age-week-row">
<text class="age-week-row__title">{{ex.title}}</text>
<view
class="age-week-row__btn"
data-title="{{ex.title}}"
catchtap="onPracticeTap">
<text>去练习</text>
<text
class="age-week-row__btn-arrow"
aria-hidden="true"
>→</text
>
</view>
</view>
</view>
</view>
</view>
<view class="age-rec">
<view class="age-rec__head">
<text class="age-rec__title">为你推荐</text>
<text class="age-rec__more" bindtap="onRecommendMore"
>更多</text
>
</view>
<view class="age-rec__grid">
<view
wx:for="{{recommendedItems}}"
wx:key="title"
class="age-rec__card"
data-title="{{item.title}}"
bindtap="onRecommendTap">
<image
class="age-rec__img"
src="{{item.image}}"
mode="aspectFill" />
<view class="age-rec__body">
<text class="age-rec__card-title">{{item.title}}</text>
</view>
</view>
</view>
</view>
</view>
</view>