feat: 开发英语启蒙的入口页

This commit is contained in:
R524809
2026-04-21 15:03:38 +08:00
parent 8c1d7e0657
commit b2f5b203f8
62 changed files with 392 additions and 259 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

@@ -13,7 +13,7 @@
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 16rpx; gap: 16rpx;
padding: 0 24rpx; padding: 0 40rpx;
box-sizing: border-box; box-sizing: border-box;
} }
+4 -4
View File
@@ -389,7 +389,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [
), ),
sheet( sheet(
{ {
id: 'letter-tracing-triple', id: 'letter-tracing-three',
title: '三字母精练', title: '三字母精练',
desc: '每页聚焦 3 个字母,大写 + 小写深度书写', desc: '每页聚焦 3 个字母,大写 + 小写深度书写',
category: 'english', category: 'english',
@@ -412,7 +412,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [
instructionText: '每页 3 个字母,逐字精练', instructionText: '每页 3 个字母,逐字精练',
}, },
generatorConfig: { generatorConfig: {
mode: 'letter-tracing-triple', mode: 'letter-tracing-three',
letterCase: 'both', letterCase: 'both',
repetitions: 5, repetitions: 5,
fadePattern: 'gradient', fadePattern: 'gradient',
@@ -422,7 +422,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [
), ),
sheet( sheet(
{ {
id: 'letter-tracing-single-line', id: 'letter-tracing-half',
title: '单字母逐行', title: '单字母逐行',
desc: '每行一个字母,13 字母半表逐字练习', desc: '每行一个字母,13 字母半表逐字练习',
category: 'english', category: 'english',
@@ -445,7 +445,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [
instructionText: '每行一个字母,逐字反复练习', instructionText: '每行一个字母,逐字反复练习',
}, },
generatorConfig: { generatorConfig: {
mode: 'letter-tracing-single-line', mode: 'letter-tracing-half',
letterCase: 'both', letterCase: 'both',
alphabetHalf: 'A-M', alphabetHalf: 'A-M',
repetitions: 5, repetitions: 5,
@@ -3,14 +3,14 @@ import type {
LetterTracingData, LetterTracingData,
TracingRow, TracingRow,
} from '../generators/letter-tracing-generator'; } from '../generators/letter-tracing-generator';
import { fontFamilyOf } from '../data/fontProfiles'; import { fontFamilyOf } from '../../shared/data/fontProfiles';
import { import {
calcLetterFontSizes, calcLetterFontSizes,
drawFourLineGrid, drawFourLineGrid,
drawLetterInFourLineGrid, drawLetterInFourLineGrid,
FOUR_LINE_GRID_H, FOUR_LINE_GRID_H,
type LetterFontSizes, type LetterFontSizes,
} from './drawTools'; } from '../../shared/draw/drawTools';
const COLOR_BLACK = '#1a1a1a'; const COLOR_BLACK = '#1a1a1a';
const COLOR_LIGHT_RED = '#FF8E8E'; const COLOR_LIGHT_RED = '#FF8E8E';
@@ -18,8 +18,8 @@ export default class LetterTracingDraw extends BaseDrawService {
'letter-tracing-upper-lower': UpperLowerDraw, 'letter-tracing-upper-lower': UpperLowerDraw,
'letter-tracing-case-pairing': CasePairingDraw, 'letter-tracing-case-pairing': CasePairingDraw,
'letter-tracing-two-column': TwoColumnDraw, 'letter-tracing-two-column': TwoColumnDraw,
'letter-tracing-single-line': SingleLineDraw, 'letter-tracing-half': SingleLineDraw,
'letter-tracing-triple': TripleDraw, 'letter-tracing-three': TripleDraw,
}[data.mode]; }[data.mode];
const delegate = new DrawClass(this.canvas, this.ctx, this.options); const delegate = new DrawClass(this.canvas, this.ctx, this.options);
@@ -4,7 +4,7 @@ import type {
LetterTracingData, LetterTracingData,
HighlightGrid, HighlightGrid,
} from '../generators/letter-tracing-generator'; } from '../generators/letter-tracing-generator';
import { fontFamilyOf } from '../data/fontProfiles'; import { fontFamilyOf } from '../../shared/data/fontProfiles';
import { import {
calcLetterFontSizes, calcLetterFontSizes,
drawFourLineGrid, drawFourLineGrid,
@@ -12,7 +12,7 @@ import {
FOUR_LINE_GRID_H, FOUR_LINE_GRID_H,
getLetterFontSize, getLetterFontSize,
type LetterFontSizes, type LetterFontSizes,
} from './drawTools'; } from '../../shared/draw/drawTools';
const LINE_INK_ALPHA = 'rgba(50, 46, 37, 1)'; const LINE_INK_ALPHA = 'rgba(50, 46, 37, 1)';
const ACCENT_RED = 'rgba(252, 46, 0, 1)'; const ACCENT_RED = 'rgba(252, 46, 0, 1)';
@@ -6,7 +6,7 @@ import type {
import { import {
fontFamilyOf, fontFamilyOf,
PRINT_CLEARLY_DASHED, PRINT_CLEARLY_DASHED,
} from '../data/fontProfiles'; } from '../../shared/data/fontProfiles';
import { import {
calcLetterFontSizes, calcLetterFontSizes,
drawFourLineGrid, drawFourLineGrid,
@@ -14,14 +14,14 @@ import {
FOUR_LINE_GRID_H, FOUR_LINE_GRID_H,
loadLetterFont, loadLetterFont,
type LetterFontSizes, type LetterFontSizes,
} from './drawTools'; } from '../../shared/draw/drawTools';
const SLOTS_PER_ROW = 6; const SLOTS_PER_ROW = 6;
const PAIR_GAP = 25; const PAIR_GAP = 25;
type SingleLineData = Extract< type SingleLineData = Extract<
LetterTracingData, LetterTracingData,
{ mode: 'letter-tracing-single-line' } { mode: 'letter-tracing-half' }
>; >;
/** /**
@@ -34,8 +34,10 @@ export default class SingleLineDraw extends BaseDrawService {
private letterSizes: LetterFontSizes = private letterSizes: LetterFontSizes =
calcLetterFontSizes(FOUR_LINE_GRID_H); calcLetterFontSizes(FOUR_LINE_GRID_H);
private dashedSizes: LetterFontSizes = private dashedSizes: LetterFontSizes = calcLetterFontSizes(
calcLetterFontSizes(FOUR_LINE_GRID_H, PRINT_CLEARLY_DASHED); FOUR_LINE_GRID_H,
PRINT_CLEARLY_DASHED,
);
async draw(data: SingleLineData) { async draw(data: SingleLineData) {
this.prepareDraw(); this.prepareDraw();
@@ -4,7 +4,10 @@ import type {
TripleSection, TripleSection,
SingleLineRow, SingleLineRow,
} from '../generators/letter-tracing-generator'; } from '../generators/letter-tracing-generator';
import { fontFamilyOf, PRINT_CLEARLY_DASHED } from '../data/fontProfiles'; import {
fontFamilyOf,
PRINT_CLEARLY_DASHED,
} from '../../shared/data/fontProfiles';
import { import {
calcLetterFontSizes, calcLetterFontSizes,
drawFourLineGrid, drawFourLineGrid,
@@ -12,7 +15,7 @@ import {
FOUR_LINE_GRID_H, FOUR_LINE_GRID_H,
loadLetterFont, loadLetterFont,
type LetterFontSizes, type LetterFontSizes,
} from './drawTools'; } from '../../shared/draw/drawTools';
// 每行可容纳的格子数(用于三字母精练模板,一行展示 6 个位置) // 每行可容纳的格子数(用于三字母精练模板,一行展示 6 个位置)
const SLOTS_PER_ROW = 6; const SLOTS_PER_ROW = 6;
@@ -23,7 +26,7 @@ const TRIPLE_ROW_GAP = 10;
// 相邻两个字母区域之间的间距(px),大于 TRIPLE_ROW_GAP // 相邻两个字母区域之间的间距(px),大于 TRIPLE_ROW_GAP
const TRIPLE_SECTION_GAP = 20; const TRIPLE_SECTION_GAP = 20;
type TripleData = Extract<LetterTracingData, { mode: 'letter-tracing-triple' }>; type TripleData = Extract<LetterTracingData, { mode: 'letter-tracing-three' }>;
/** /**
* *
@@ -3,14 +3,14 @@ import type {
LetterTracingData, LetterTracingData,
TracingRow, TracingRow,
} from '../generators/letter-tracing-generator'; } from '../generators/letter-tracing-generator';
import { fontFamilyOf } from '../data/fontProfiles'; import { fontFamilyOf } from '../../shared/data/fontProfiles';
import { import {
calcLetterFontSizes, calcLetterFontSizes,
drawFourLineGrid, drawFourLineGrid,
drawLetterInFourLineGrid, drawLetterInFourLineGrid,
FOUR_LINE_GRID_H, FOUR_LINE_GRID_H,
type LetterFontSizes, type LetterFontSizes,
} from './drawTools'; } from '../../shared/draw/drawTools';
const COLOR_BLACK = '#1a1a1a'; const COLOR_BLACK = '#1a1a1a';
const COLOR_LIGHT_RED = '#FF8E8E'; const COLOR_LIGHT_RED = '#FF8E8E';
@@ -4,7 +4,7 @@ import {
PRINT_CLEARLY_BOLD, PRINT_CLEARLY_BOLD,
PRINT_CLEARLY_DASHED, PRINT_CLEARLY_DASHED,
fontFamilyOf, fontFamilyOf,
} from '../data/fontProfiles'; } from '../../shared/data/fontProfiles';
import { import {
calcLetterFontSizes, calcLetterFontSizes,
drawFourLineGrid, drawFourLineGrid,
@@ -12,7 +12,7 @@ import {
FOUR_LINE_GRID_H, FOUR_LINE_GRID_H,
loadLetterFont, loadLetterFont,
type LetterFontSizes, type LetterFontSizes,
} from './drawTools'; } from '../../shared/draw/drawTools';
type UpperLowerData = Extract< type UpperLowerData = Extract<
LetterTracingData, LetterTracingData,
@@ -1,4 +1,4 @@
import { ALPHABET_BY_LETTER, LETTERS_UPPER } from '../data/alphabet'; import { ALPHABET_BY_LETTER, LETTERS_UPPER } from '../../shared/data/alphabet';
/** 字母描红的布局模式(与产品文档六类练习一致) */ /** 字母描红的布局模式(与产品文档六类练习一致) */
export type LetterTracingMode = export type LetterTracingMode =
@@ -11,9 +11,9 @@ export type LetterTracingMode =
/** 4 分栏对照:半组字母,左栏大写、右栏小写,每行 4 个 */ /** 4 分栏对照:半组字母,左栏大写、右栏小写,每行 4 个 */
| 'letter-tracing-two-column' | 'letter-tracing-two-column'
/** 5 三字母精练:每页聚焦 3 个字母,每字母 4 行(2 行示范 + 2 行简练) */ /** 5 三字母精练:每页聚焦 3 个字母,每字母 4 行(2 行示范 + 2 行简练) */
| 'letter-tracing-triple' | 'letter-tracing-three'
/** 6 单字母逐行:每行一个字母,13 字母半表(A–M 或 N–Z) */ /** 6 单字母逐行:每行一个字母,13 字母半表(A–M 或 N–Z) */
| 'letter-tracing-single-line'; | 'letter-tracing-half';
/** 字母大小写模式(部分模式不使用) */ /** 字母大小写模式(部分模式不使用) */
export type LetterCaseMode = 'upper' | 'lower' | 'both'; export type LetterCaseMode = 'upper' | 'lower' | 'both';
@@ -111,11 +111,11 @@ export type LetterTracingData =
rightRows: TracingRow[]; rightRows: TracingRow[];
} }
| { | {
mode: 'letter-tracing-single-line'; mode: 'letter-tracing-half';
rows: SingleLineRow[]; rows: SingleLineRow[];
} }
| { | {
mode: 'letter-tracing-triple'; mode: 'letter-tracing-three';
sections: TripleSection[]; sections: TripleSection[];
} }
| { | {
@@ -239,8 +239,20 @@ function buildSingleLineCells(L: string): (SingleLineCell | null)[] {
return [ return [
{ upper: L, lower, style: 'solid', color: _COLOR_BLACK, alpha: 1 }, { upper: L, lower, style: 'solid', color: _COLOR_BLACK, alpha: 1 },
{ upper: L, lower, style: 'solid', color: _COLOR_LIGHT_RED, alpha: 1 }, { upper: L, lower, style: 'solid', color: _COLOR_LIGHT_RED, alpha: 1 },
{ upper: L, lower, style: 'solid', color: _COLOR_LIGHT_RED, alpha: 0.5 }, {
{ upper: L, lower, style: 'dashed', color: _COLOR_LIGHT_RED, alpha: 0.5 }, upper: L,
lower,
style: 'solid',
color: _COLOR_LIGHT_RED,
alpha: 0.5,
},
{
upper: L,
lower,
style: 'dashed',
color: _COLOR_LIGHT_RED,
alpha: 0.5,
},
null, null,
null, null,
]; ];
@@ -332,7 +344,7 @@ export function generateLetterTracing(
}; };
} }
if (mode === 'letter-tracing-triple') { if (mode === 'letter-tracing-three') {
const letters = config.tripleLetters ?? ['A', 'B', 'C']; const letters = config.tripleLetters ?? ['A', 'B', 'C'];
const sections: TripleSection[] = letters.map((L) => { const sections: TripleSection[] = letters.map((L) => {
const filledRow: SingleLineRow = { const filledRow: SingleLineRow = {
@@ -348,10 +360,10 @@ export function generateLetterTracing(
rows: [filledRow, filledRow, simpleRow, simpleRow], rows: [filledRow, filledRow, simpleRow, simpleRow],
}; };
}); });
return { mode: 'letter-tracing-triple', sections }; return { mode: 'letter-tracing-three', sections };
} }
if (mode === 'letter-tracing-single-line') { if (mode === 'letter-tracing-half') {
const half = const half =
config.alphabetHalf === 'N-Z' config.alphabetHalf === 'N-Z'
? LETTERS_UPPER.slice(13) ? LETTERS_UPPER.slice(13)
@@ -362,7 +374,7 @@ export function generateLetterTracing(
cells: buildSingleLineCells(L), cells: buildSingleLineCells(L),
})); }));
return { mode: 'letter-tracing-single-line', rows }; return { mode: 'letter-tracing-half', rows };
} }
// letter-tracing-upper-lower // letter-tracing-upper-lower
@@ -1,4 +1,4 @@
import LetterTracingDraw from '../shared/draw/letterTracingDraw'; import LetterTracingDraw from './draw/letterTracingDraw';
import { createPage, type CanvasDataState } from '../../base/pageMixin'; import { createPage, type CanvasDataState } from '../../base/pageMixin';
import { defaultShareConfig } from '../../config/config'; import { defaultShareConfig } from '../../config/config';
import { getWorksheetById } from '../../core/data/worksheets'; import { getWorksheetById } from '../../core/data/worksheets';
@@ -8,7 +8,7 @@ import {
normalizeLetter, normalizeLetter,
type LetterTracingGeneratorConfig, type LetterTracingGeneratorConfig,
type LetterTracingMode, type LetterTracingMode,
} from '../shared/generators/letter-tracing-generator'; } from './generators/letter-tracing-generator';
import { LETTERS_UPPER, LETTERS_PAIRS } from '../shared/data/alphabet'; import { LETTERS_UPPER, LETTERS_PAIRS } from '../shared/data/alphabet';
import { import {
DEFAULT_LETTER_PROFILE, DEFAULT_LETTER_PROFILE,
@@ -45,13 +45,13 @@ const MODES = [
desc: '左 AM、右 NZ 配对描红', desc: '左 AM、右 NZ 配对描红',
}, },
{ {
id: 'letter-tracing-single-line', id: 'letter-tracing-half',
icon: 'square-half', icon: 'square-half',
label: '13字母半表', label: '13字母半表',
desc: '每行一个字母,13 字母半表', desc: '每行一个字母,13 字母半表',
}, },
{ {
id: 'letter-tracing-triple', id: 'letter-tracing-three',
icon: 'ABC-list', icon: 'ABC-list',
label: '三字母精练', label: '三字母精练',
desc: '每页聚焦 3 个字母深度书写', desc: '每页聚焦 3 个字母深度书写',
@@ -200,8 +200,8 @@ createPage(
: 'upper'; : 'upper';
const needsHalf = const needsHalf =
this.data.traceMode === 'letter-tracing-case-pairing' || this.data.traceMode === 'letter-tracing-case-pairing' ||
this.data.traceMode === 'letter-tracing-single-line'; this.data.traceMode === 'letter-tracing-half';
const isTriple = this.data.traceMode === 'letter-tracing-triple'; const isTriple = this.data.traceMode === 'letter-tracing-three';
return mergeLetterTracingConfig(base, { return mergeLetterTracingConfig(base, {
selectedLetter: this.data.selectedLetter, selectedLetter: this.data.selectedLetter,
letterCase, letterCase,
@@ -306,15 +306,15 @@ createPage(
const showLetterPicker = traceMode === 'letter-tracing-single'; const showLetterPicker = traceMode === 'letter-tracing-single';
const showCaseToggle = const showCaseToggle =
traceMode === 'letter-tracing-case-pairing' || traceMode === 'letter-tracing-case-pairing' ||
traceMode === 'letter-tracing-single-line'; traceMode === 'letter-tracing-half';
const showNextLetter = traceMode === 'letter-tracing-single'; const showNextLetter = traceMode === 'letter-tracing-single';
const showTripleGroupPicker = traceMode === 'letter-tracing-triple'; const showTripleGroupPicker = traceMode === 'letter-tracing-three';
const selectedLetter = options?.preserveLetter const selectedLetter = options?.preserveLetter
? (this.data.selectedLetter ?? 'A') ? (this.data.selectedLetter ?? 'A')
: normalizeLetter(merged.selectedLetter ?? ''); : normalizeLetter(merged.selectedLetter ?? '');
const letterCaseLower = const letterCaseLower =
traceMode === 'letter-tracing-case-pairing' || traceMode === 'letter-tracing-case-pairing' ||
traceMode === 'letter-tracing-single-line' traceMode === 'letter-tracing-half'
? merged.alphabetHalf === 'N-Z' ? merged.alphabetHalf === 'N-Z'
: traceMode === 'letter-tracing-single' : traceMode === 'letter-tracing-single'
? merged.letterCase === 'lower' ? merged.letterCase === 'lower'
@@ -0,0 +1,2 @@
1. 字母排序
类似 数字排序 number-sort.png 中的玩法,输出大小写字母,来排序。
+89 -55
View File
@@ -9,6 +9,8 @@ export type HomeDisplayItem = {
ageBand: string; ageBand: string;
difficulty: '入门' | '基础' | '进阶' | '挑战'; difficulty: '入门' | '基础' | '进阶' | '挑战';
icon: string; icon: string;
/** 横向滑动卡片顶部图,有则优先展示,可与 icon 二选一 */
img?: string;
badge?: string; badge?: string;
path: string; path: string;
available: boolean; available: boolean;
@@ -275,6 +277,7 @@ export const HOME_PRODUCT_TARGET_DATA: HomeDisplayDataset = {
id: 'english', id: 'english',
title: '英语启蒙', title: '英语启蒙',
subtitle: '字母学习、闪卡、描红与自然拼读', subtitle: '字母学习、闪卡、描红与自然拼读',
morePath: '/englishPages/letterTracing/letterTracing',
items: [ items: [
{ {
id: 'target-english-1', id: 'target-english-1',
@@ -686,6 +689,92 @@ export const HOME_CURRENT_CAPABILITY_DATA: HomeDisplayDataset = {
}, },
], ],
}, },
{
id: 'english',
title: '英语启蒙',
subtitle: '多种描红版式,认读与书写循序渐进',
morePath: '/englishPages/letterTracing/letterTracing',
items: [
{
id: 'current-english-1',
title: '默认字帖',
subtitle: '配图例句,逐字描红',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '',
img: '/assets/entrancePicture/english/letterTracing/letter-tracing-single.jpg',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-single',
available: true,
source: 'letter-tracing-single',
},
{
id: 'current-english-2',
title: '基础描红',
subtitle: '26 字母大小写速览',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '',
img: '/assets/entrancePicture/english/letterTracing/letter-tracing-upper-lower.jpg',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-upper-lower',
available: true,
source: 'letter-tracing-upper-lower',
},
{
id: 'current-english-3',
title: '大小写对照',
subtitle: '左大右小,逐行对照',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '',
img: '/assets/entrancePicture/english/letterTracing/letter-tracing-case-pairing.jpg',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-case-pairing',
available: true,
source: 'letter-tracing-case-pairing',
},
{
id: 'current-english-4',
title: '两列描红',
subtitle: 'Aa 配对,左右分列',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '',
img: '/assets/entrancePicture/english/letterTracing/letter-tracing-two-column.jpg',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-two-column',
available: true,
source: 'letter-tracing-two-column',
},
{
id: 'current-english-5',
title: '13字母半表',
subtitle: '每行一字,逐字专练',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '',
img: '/assets/entrancePicture/english/letterTracing/letter-tracing-half.jpg',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-half',
available: true,
source: 'letter-tracing-half',
},
{
id: 'current-english-6',
title: '三字母精练',
subtitle: '每页三字,深度书写',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '',
img: '/assets/entrancePicture/english/letterTracing/letter-tracing-three.jpg',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-three',
available: true,
source: 'letter-tracing-three',
},
],
},
{ {
id: 'craft', id: 'craft',
title: '创意手工', title: '创意手工',
@@ -718,61 +807,6 @@ export const HOME_CURRENT_CAPABILITY_DATA: HomeDisplayDataset = {
}, },
], ],
}, },
{
id: 'english',
title: '英语启蒙',
subtitle: '字母描红等已上线,其余能力持续补充',
items: [
{
id: 'current-english-1',
title: '字母学习页',
subtitle: '单字母配图与描红',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '🔠',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-single',
available: true,
source: 'letter-tracing-single',
},
{
id: 'current-english-2',
title: '26 字母描红',
subtitle: '整页四线三格练习',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '🔤',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-two-column',
available: true,
source: 'letter-tracing-two-column',
},
{
id: 'current-english-3',
title: '大小写字母对照',
subtitle: '一页认识 A-Z 与 a-z',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '🔡',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-upper-lower',
available: true,
source: 'letter-tracing-upper-lower',
},
{
id: 'current-english-4',
title: '字母描红(两列)',
subtitle: 'AM / NZ',
category: 'english',
ageBand: '4-7岁',
difficulty: '入门',
icon: '✏️',
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-case-pairing',
available: true,
source: 'letter-tracing-case-pairing',
},
],
},
], ],
}; };
+46 -19
View File
@@ -1,4 +1,5 @@
@import '../../style/theme.less'; @import '../../style/theme.less';
@import '../../style/tags.less';
@home-track-gap: 16rpx; @home-track-gap: 16rpx;
@@ -91,10 +92,12 @@
background: rgba(50, 46, 37, 0.04); background: rgba(50, 46, 37, 0.04);
} }
.home-track-row--hero { /** 首页顶部「精选」横滑行(与下方模块 track 列表的 .home-track-row 区分) */
padding-left: @page-padding-x; .home-hero-row {
padding-top: 24rpx; display: inline-flex;
padding-bottom: 24rpx; flex-direction: row;
gap: @section-gap-sm;
padding: 24rpx @page-padding-x;
} }
.home-hero-card { .home-hero-card {
@@ -190,7 +193,6 @@
.home-section-panel { .home-section-panel {
margin-top: @section-gap; margin-top: @section-gap;
padding: 0 @page-padding-x;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -198,6 +200,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 @page-padding-x;
} }
.home-section-head__title { .home-section-head__title {
@@ -220,7 +223,7 @@
margin-top: @section-gap-sm; margin-top: @section-gap-sm;
width: 100%; width: 100%;
white-space: nowrap; white-space: nowrap;
padding: 16rpx 0 20rpx; // padding: 16rpx 0 20rpx;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -228,7 +231,8 @@
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
gap: @section-gap-sm; gap: @section-gap-sm;
padding-right: @page-padding-x; // padding-right: @page-padding-x;
padding: 16rpx @page-padding-x 20rpx;
} }
.home-track-card { .home-track-card {
@@ -241,23 +245,46 @@
} }
.home-track-card__top { .home-track-card__top {
height: 200rpx; /* 与 supportPages/mathIndex .function-item-img-wrapper 一致 */
height: 270rpx;
flex-shrink: 0;
position: relative;
overflow: hidden;
display: flex; display: flex;
align-items: center; align-items: flex-start;
justify-content: center; justify-content: center;
} }
.home-track-card__cover {
width: 100%;
display: block;
}
.home-track-card__icon { .home-track-card__icon {
font-size: 72rpx; font-size: 72rpx;
line-height: 1; line-height: 1;
} }
.home-track-card__tags {
position: absolute;
right: 12rpx;
bottom: 12rpx;
display: flex;
gap: 10rpx;
}
.home-track-card__body { .home-track-card__body {
box-sizing: border-box;
width: 100%;
padding: 20rpx @page-padding-inner-x 22rpx; padding: 20rpx @page-padding-inner-x 22rpx;
background: #ffffff; background: #ffffff;
white-space: normal;
border-top: 1rpx solid #c5c5c5;
} }
.home-track-card__title { .home-track-card__title {
box-sizing: border-box;
width: 100%;
font-size: 28rpx; font-size: 28rpx;
font-weight: 600; font-weight: 600;
color: @text-title; color: @text-title;
@@ -267,16 +294,22 @@
line-clamp: 2; line-clamp: 2;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
overflow: hidden; overflow: hidden;
word-break: break-word;
} }
.home-track-card__sub { .home-track-card__sub {
box-sizing: border-box;
width: 100%;
margin-top: 8rpx; margin-top: 8rpx;
font-size: 22rpx; font-size: 22rpx;
color: @text-gray; color: @text-gray;
line-height: 1.3; line-height: 1.45;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; word-break: break-word;
white-space: nowrap;
} }
.home-age-scroll { .home-age-scroll {
@@ -336,6 +369,7 @@
.home-hot-list { .home-hot-list {
margin-top: @section-gap-sm; margin-top: @section-gap-sm;
padding: 0 @page-padding-x;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: @section-gap-stack; gap: @section-gap-stack;
@@ -427,13 +461,6 @@
display: block; display: block;
} }
.home-track-card__meta {
margin-top: 12rpx;
font-size: 22rpx;
color: @text-gray;
line-height: 1.3;
display: block;
}
.home-page .category-tabs { .home-page .category-tabs {
background: @bg-page; background: @bg-page;
+168 -142
View File
@@ -10,166 +10,192 @@
</view> </view>
<view class="home-body" style="padding-top: {{navBlockHeight}}px;"> <view class="home-body" style="padding-top: {{navBlockHeight}}px;">
<view class="home-search-wrap"> <view class="home-search-wrap">
<view class="home-search"> <view class="home-search">
<van-icon <van-icon
name="search" name="search"
size="36rpx" size="36rpx"
color="rgba(50, 46, 37, 0.45)" color="rgba(50, 46, 37, 0.45)"
class="home-search__icon" /> class="home-search__icon" />
<text class="home-search__placeholder">{{searchPlaceholder}}</text> <text class="home-search__placeholder"
</view> >{{searchPlaceholder}}</text
</view> >
<view class="home-tabs-wrap">
<category-tabs
tabs="{{tabs}}"
active-tab="{{activeTab}}"
bind:change="onTabChange" />
</view>
<scroll-view class="home-track-scroll" scroll-x enable-flex>
<view class="home-track-row home-track-row--hero">
<view
wx:for="{{featuredItems}}"
wx:key="id"
class="home-hero-card"
data-title="{{item.title}}"
data-path="{{item.path}}"
bindtap="onTapFeatured">
<view
class="home-hero-card__top"
style="background-color: {{item.topBg}}">
<text class="home-hero-card__icon">{{item.icon}}</text>
<text wx:if="{{item.badge}}" class="home-hero-card__badge"
>{{item.badge}}</text
>
</view>
<view class="home-hero-card__body">
<view class="home-hero-card__title-row">
<text class="home-hero-card__title">{{item.title}}</text>
<text class="home-hero-card__meta"
>{{item.ageBand}} · {{item.difficulty}}</text
>
</view>
<text class="home-hero-card__sub">{{item.subtitle}}</text>
</view>
</view> </view>
</view> </view>
</scroll-view>
<view class="home-section-panel"> <view class="home-tabs-wrap">
<view class="home-section-head"> <category-tabs
<text class="home-section-head__title">按年龄挑选</text> tabs="{{tabs}}"
<text class="home-section-head__more" bindtap="onTapAgeMore" active-tab="{{activeTab}}"
>查看全部</text bind:change="onTabChange" />
>
</view> </view>
<scroll-view class="home-age-scroll" scroll-x enable-flex>
<view class="home-age-row">
<view
wx:for="{{ageBands}}"
wx:key="key"
class="home-age-item"
data-key="{{item.key}}"
data-path="{{item.path}}"
bindtap="onTapAgeBand">
<view
class="home-age-item__icon-wrap {{activeAgeBand === item.key ? 'home-age-item__icon-wrap--active' : ''}}">
<text class="home-age-item__icon">{{item.icon}}</text>
</view>
<text class="home-age-item__label">{{item.label}}</text>
<text class="home-age-item__desc">{{item.desc}}</text>
</view>
</view>
</scroll-view>
</view>
<view class="home-section-panel home-section-panel--hot">
<view class="home-section-head">
<text class="home-section-head__title">热门推荐</text>
<text class="home-section-head__more" bindtap="onTapHotMore"
>查看全部</text
>
</view>
<view class="home-hot-list">
<view
wx:for="{{hotRecommends}}"
wx:key="id"
class="home-hot-card"
data-title="{{item.title}}"
data-path="{{item.path}}"
bindtap="onTapHotRecommend">
<view
class="home-hot-card__thumb"
style="background-color: {{item.topBg}}">
<text class="home-hot-card__thumb-emoji"
>{{item.icon}}</text
>
</view>
<view class="home-hot-card__content">
<text class="home-hot-card__title">{{item.title}}</text>
<text class="home-hot-card__desc">{{item.subtitle}}</text>
<view class="home-hot-card__meta">
<text class="home-hot-card__tag">{{item.ageBand}}</text>
<text class="home-hot-card__score"
>{{item.difficulty}}</text
>
</view>
</view>
<view class="home-hot-card__action"></view>
</view>
</view>
</view>
<view
wx:for="{{sections}}"
wx:key="id"
id="{{item.anchorId}}"
class="home-section-panel">
<view class="home-section-head">
<text
class="home-section-head__title home-section-head__title--module"
>{{item.title}}</text
>
<text
wx:if="{{item.morePath}}"
class="home-section-head__more"
data-title="{{item.title}}"
data-path="{{item.morePath}}"
bindtap="onTapTrackMore"
>更多</text
>
</view>
<text class="home-section-panel__sub">{{item.subtitle}}</text>
<scroll-view class="home-track-scroll" scroll-x enable-flex> <scroll-view class="home-track-scroll" scroll-x enable-flex>
<view class="home-track-row"> <view class="home-hero-row">
<view <view
wx:for="{{item.items}}" wx:for="{{featuredItems}}"
wx:key="id" wx:key="id"
class="home-track-card" class="home-hero-card"
data-title="{{item.title}}" data-title="{{item.title}}"
data-path="{{item.path}}" data-path="{{item.path}}"
bindtap="onTapTrackCard"> bindtap="onTapFeatured">
<view <view
class="home-track-card__top" class="home-hero-card__top"
style="background-color: {{item.topBg}}"> style="background-color: {{item.topBg}}">
<text class="home-track-card__icon">{{item.icon}}</text> <text class="home-hero-card__icon">{{item.icon}}</text>
<text
wx:if="{{item.badge}}"
class="home-hero-card__badge"
>{{item.badge}}</text
>
</view> </view>
<view class="home-track-card__body"> <view class="home-hero-card__body">
<text class="home-track-card__title" <view class="home-hero-card__title-row">
>{{item.title}}</text <text class="home-hero-card__title"
> >{{item.title}}</text
<text class="home-track-card__sub" >
<text class="home-hero-card__meta"
>{{item.ageBand}} · {{item.difficulty}}</text
>
</view>
<text class="home-hero-card__sub"
>{{item.subtitle}}</text >{{item.subtitle}}</text
> >
<text class="home-track-card__meta"
>{{item.ageBand}} · {{item.difficulty}}</text
>
</view> </view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
</view>
<view class="home-section-panel">
<view class="home-section-head">
<text class="home-section-head__title">按年龄挑选</text>
<text class="home-section-head__more" bindtap="onTapAgeMore"
>查看全部</text
>
</view>
<scroll-view class="home-age-scroll" scroll-x enable-flex>
<view class="home-age-row">
<view
wx:for="{{ageBands}}"
wx:key="key"
class="home-age-item"
data-key="{{item.key}}"
data-path="{{item.path}}"
bindtap="onTapAgeBand">
<view
class="home-age-item__icon-wrap {{activeAgeBand === item.key ? 'home-age-item__icon-wrap--active' : ''}}">
<text class="home-age-item__icon"
>{{item.icon}}</text
>
</view>
<text class="home-age-item__label">{{item.label}}</text>
<text class="home-age-item__desc">{{item.desc}}</text>
</view>
</view>
</scroll-view>
</view>
<view class="home-section-panel home-section-panel--hot">
<view class="home-section-head">
<text class="home-section-head__title">热门推荐</text>
<text class="home-section-head__more" bindtap="onTapHotMore"
>查看全部</text
>
</view>
<view class="home-hot-list">
<view
wx:for="{{hotRecommends}}"
wx:key="id"
class="home-hot-card"
data-title="{{item.title}}"
data-path="{{item.path}}"
bindtap="onTapHotRecommend">
<view
class="home-hot-card__thumb"
style="background-color: {{item.topBg}}">
<text class="home-hot-card__thumb-emoji"
>{{item.icon}}</text
>
</view>
<view class="home-hot-card__content">
<text class="home-hot-card__title">{{item.title}}</text>
<text class="home-hot-card__desc"
>{{item.subtitle}}</text
>
<view class="home-hot-card__meta">
<text class="home-hot-card__tag"
>{{item.ageBand}}</text
>
<text class="home-hot-card__score"
>{{item.difficulty}}</text
>
</view>
</view>
<view class="home-hot-card__action"></view>
</view>
</view>
</view>
<view
wx:for="{{sections}}"
wx:key="id"
id="{{item.anchorId}}"
class="home-section-panel">
<view class="home-section-head">
<text
class="home-section-head__title home-section-head__title--module"
>{{item.title}}</text
>
<text
wx:if="{{item.morePath}}"
class="home-section-head__more"
data-title="{{item.title}}"
data-path="{{item.morePath}}"
bindtap="onTapTrackMore"
>更多</text
>
</view>
<!-- <text class="home-section-panel__sub">{{item.subtitle}}</text> -->
<scroll-view class="home-track-scroll" scroll-x enable-flex>
<view class="home-track-row">
<view
wx:for="{{item.items}}"
wx:key="id"
class="home-track-card"
data-title="{{item.title}}"
data-path="{{item.path}}"
bindtap="onTapTrackCard">
<view
class="home-track-card__top"
style="background-color: {{item.topBg}}">
<image
wx:if="{{item.img}}"
class="home-track-card__cover"
src="{{item.img}}"
mode="widthFix" />
<text wx:else class="home-track-card__icon"
>{{item.icon}}</text
>
<view class="home-track-card__tags">
<text class="tag tag--age"
>{{item.ageBand}}</text
>
<text class="tag tag--diff"
>{{item.difficulty}}</text
>
</view>
</view>
<view class="home-track-card__body">
<text class="home-track-card__title"
>{{item.title}}</text
>
<text class="home-track-card__sub"
>{{item.subtitle}}</text
>
</view>
</view>
</view>
</scroll-view>
</view>
</view> </view>
</view> </view>
+27
View File
@@ -0,0 +1,27 @@
/**
* 通用标签 / 胶囊样式
*
* 用法:在页面 less 中 @import '../../style/tags.less'
* 然后在 wxml 里直接用 class="tag tag--age" / class="tag tag--diff"。
*/
.tag {
display: inline-block;
font-size: 20rpx;
// line-height: 1;
line-height: 20rpx;
padding: 8rpx 10rpx;
border-radius: 10rpx;
font-weight: 600;
white-space: nowrap;
}
.tag--age {
background: #ffd709;
color: #6c5a00;
}
.tag--diff {
background: #6cc24a;
color: #ffffff;
}

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB