feat: 新增控笔练习,增加首页公告
This commit is contained in:
@@ -19,7 +19,8 @@
|
|||||||
"homeContentManage/homeContentManage",
|
"homeContentManage/homeContentManage",
|
||||||
"mathIndex/mathIndex",
|
"mathIndex/mathIndex",
|
||||||
"focusIndex/focusIndex",
|
"focusIndex/focusIndex",
|
||||||
"worksheetSync/worksheetSync"
|
"worksheetSync/worksheetSync",
|
||||||
|
"unreleasedDebug/unreleasedDebug"
|
||||||
],
|
],
|
||||||
"independent": false
|
"independent": false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,34 +1,9 @@
|
|||||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||||
import { PEN_CONTROL_MODE } from './generators/penControlGenerator';
|
import { PEN_CONTROL_WORKSHEET_DEFINITIONS } from '../../config/worksheets/penControl';
|
||||||
|
|
||||||
interface PenControlWorksheetDefinition {
|
type PenControlWorksheetRow =
|
||||||
id: typeof PEN_CONTROL_MODE;
|
(typeof PEN_CONTROL_WORKSHEET_DEFINITIONS)[number];
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
ageMin: number;
|
|
||||||
ageMax: number;
|
|
||||||
difficulty: 1 | 2 | 3 | 4;
|
|
||||||
tags: string[];
|
|
||||||
sortOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PEN_CONTROL_WORKSHEET_DEFINITIONS = [
|
|
||||||
{
|
|
||||||
id: PEN_CONTROL_MODE,
|
|
||||||
icon: 'edit',
|
|
||||||
title: '控笔组合练习',
|
|
||||||
subtitle: '3–6 种图形,每种占两行田字格',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['控笔', '运笔', '田字格', '学前'],
|
|
||||||
sortOrder: 40,
|
|
||||||
},
|
|
||||||
] as const satisfies ReadonlyArray<PenControlWorksheetDefinition>;
|
|
||||||
|
|
||||||
type PenControlWorksheetRow = (typeof PEN_CONTROL_WORKSHEET_DEFINITIONS)[number];
|
|
||||||
|
|
||||||
const WORKSHEET_BY_ID = Object.fromEntries(
|
const WORKSHEET_BY_ID = Object.fromEntries(
|
||||||
PEN_CONTROL_WORKSHEET_DEFINITIONS.map((m) => [m.id, m]),
|
PEN_CONTROL_WORKSHEET_DEFINITIONS.map((m) => [m.id, m]),
|
||||||
|
|||||||
@@ -1,78 +1,44 @@
|
|||||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||||
import type { TemplateType } from '../shared/draw/drawServiceFactory';
|
import type { TemplateType } from './draw/drawServiceFactory';
|
||||||
|
import { WORD_COLORING_WORKSHEET_DEFINITIONS } from '../../config/worksheets/wordColoring';
|
||||||
|
|
||||||
/** 本地 worksheet 元数据(与云库 `worksheets` 文档 `_id` 对齐) */
|
export { WORD_COLORING_WORKSHEET_DEFINITIONS };
|
||||||
interface WordColoringWorksheetDefinition {
|
|
||||||
id: string;
|
|
||||||
templateType: TemplateType;
|
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
ageMin: number;
|
|
||||||
ageMax: number;
|
|
||||||
difficulty: 1 | 2 | 3 | 4;
|
|
||||||
tags: string[];
|
|
||||||
sortOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WORD_COLORING_WORKSHEET_DEFINITIONS = [
|
|
||||||
{
|
|
||||||
id: 'word-coloring-grid',
|
|
||||||
templateType: 'grid' as TemplateType,
|
|
||||||
icon: 'grid',
|
|
||||||
title: '网格涂色',
|
|
||||||
subtitle: '田字格涂色识字练习',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['识字', '涂色', '田字格'],
|
|
||||||
sortOrder: 30,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'word-coloring-find',
|
|
||||||
templateType: 'find' as TemplateType,
|
|
||||||
icon: 'search',
|
|
||||||
title: '找字涂色',
|
|
||||||
subtitle: '在字海中找到目标字并涂色',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['识字', '涂色', '找字'],
|
|
||||||
sortOrder: 31,
|
|
||||||
},
|
|
||||||
] as const satisfies ReadonlyArray<WordColoringWorksheetDefinition>;
|
|
||||||
|
|
||||||
type WordColoringWorksheetRow =
|
type WordColoringWorksheetRow =
|
||||||
(typeof WORD_COLORING_WORKSHEET_DEFINITIONS)[number];
|
(typeof WORD_COLORING_WORKSHEET_DEFINITIONS)[number];
|
||||||
|
|
||||||
const WORD_COLORING_WORKSHEET_BY_ID = Object.fromEntries(
|
const WORKSHEET_BY_ID = Object.fromEntries(
|
||||||
WORD_COLORING_WORKSHEET_DEFINITIONS.map((m) => [m.id, m]),
|
WORD_COLORING_WORKSHEET_DEFINITIONS.map((m) => [m.id, m]),
|
||||||
) as Record<string, WordColoringWorksheetRow>;
|
) as Record<string, WordColoringWorksheetRow>;
|
||||||
|
|
||||||
/** 页面渲染用:模式选择器列表 */
|
/** 页面渲染用:模式选择器列表 */
|
||||||
export const WORD_COLORING_MODE_OPTIONS = WORD_COLORING_WORKSHEET_DEFINITIONS;
|
export const WORD_COLORING_MODE_OPTIONS = WORD_COLORING_WORKSHEET_DEFINITIONS;
|
||||||
|
|
||||||
|
const WORD_COLORING_TEMPLATE_TYPES: Record<string, TemplateType> = {
|
||||||
|
'word-coloring-grid': 'grid',
|
||||||
|
'word-coloring-find': 'find',
|
||||||
|
};
|
||||||
|
|
||||||
/** 页面 pageInfoLookup 用 */
|
/** 页面 pageInfoLookup 用 */
|
||||||
export function getModeInfo(id: string) {
|
export function getModeInfo(id: string) {
|
||||||
const m = WORD_COLORING_WORKSHEET_BY_ID[id];
|
const m = WORKSHEET_BY_ID[id];
|
||||||
return m ? { title: m.title, desc: m.subtitle } : undefined;
|
return m ? { title: m.title, desc: m.subtitle } : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断 id 是否有效 */
|
/** 判断 id 是否有效 */
|
||||||
export function isValidMode(id: string): boolean {
|
export function isValidMode(id: string): boolean {
|
||||||
return id in WORD_COLORING_WORKSHEET_BY_ID;
|
return id in WORKSHEET_BY_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据 id 获取 templateType */
|
/** 根据 id 获取 templateType */
|
||||||
export function getTemplateType(id: string): TemplateType {
|
export function getTemplateType(id: string): TemplateType {
|
||||||
const m = WORD_COLORING_WORKSHEET_BY_ID[id];
|
return WORD_COLORING_TEMPLATE_TYPES[id] || 'grid';
|
||||||
return m?.templateType || 'grid';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发布用:从当前 mode 生成 DebugPublishMeta */
|
/** 发布用:从当前 mode 生成 DebugPublishMeta */
|
||||||
export function getPublishMetaByMode(id: string): DebugPublishMeta | null {
|
export function getPublishMetaByMode(id: string): DebugPublishMeta | null {
|
||||||
const m = WORD_COLORING_WORKSHEET_BY_ID[id];
|
const m = WORKSHEET_BY_ID[id];
|
||||||
if (!m) return null;
|
if (!m) return null;
|
||||||
return {
|
return {
|
||||||
id: m.id,
|
id: m.id,
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
// },
|
// },
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
/** 当前应用版本号;profile 页和首页公告共用 */
|
||||||
|
export const APP_VERSION = '3.0.1';
|
||||||
|
|
||||||
|
/** 当前版本的更新摘要,用于首页公告;改版本时一起改 */
|
||||||
|
export const APP_VERSION_NOTE = '新增控笔练习';
|
||||||
|
|
||||||
export const defaultPrintConfig: PrintConfig = {
|
export const defaultPrintConfig: PrintConfig = {
|
||||||
// header: 'LogoImage',
|
// header: 'LogoImage',
|
||||||
header: 'wechat',
|
header: 'wechat',
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import type { WorksheetDefinition } from './types';
|
||||||
|
|
||||||
|
export const FOCUS_WORKSHEET_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
id: 'color-shape-match',
|
||||||
|
title: '根据颜色画图形',
|
||||||
|
subtitle: '根据颜色画出对应图形',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['专注力', '颜色', '图形'],
|
||||||
|
sortOrder: 201,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'shape-symbol',
|
||||||
|
title: '图形符号配对',
|
||||||
|
subtitle: '根据图形画对应符号',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['专注力', '图形', '符号'],
|
||||||
|
sortOrder: 202,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'position-coloring',
|
||||||
|
title: '方位涂涂乐',
|
||||||
|
subtitle: '观察位置,在方格中涂色',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['专注力', '方位', '涂色'],
|
||||||
|
sortOrder: 203,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'color-pattern',
|
||||||
|
title: '颜色找规律',
|
||||||
|
subtitle: '观察颜色规律,涂色',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['专注力', '颜色', '规律'],
|
||||||
|
sortOrder: 204,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'match-connect',
|
||||||
|
title: '连连看',
|
||||||
|
subtitle: '根据物品连一连',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['专注力', '连线', '匹配'],
|
||||||
|
sortOrder: 205,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'line-recognition',
|
||||||
|
title: '线条识别',
|
||||||
|
subtitle: '认识不同线条,画对应线条',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['专注力', '线条', '识别', '控笔'],
|
||||||
|
sortOrder: 206,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'grid-reasoning',
|
||||||
|
title: '方格推理',
|
||||||
|
subtitle: '推理出合并方格并连线',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['专注力', '方格', '推理', '逻辑思维'],
|
||||||
|
sortOrder: 207,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'code-connect',
|
||||||
|
title: '译码连线',
|
||||||
|
subtitle: '按数字顺序连线',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['专注力', '译码', '连线', '逻辑思维'],
|
||||||
|
sortOrder: 208,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dot-connect',
|
||||||
|
title: '数字点连线',
|
||||||
|
subtitle: '按数字顺序连点成图',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['专注力', '数字', '连线'],
|
||||||
|
sortOrder: 209,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'grid-drawing-3x3',
|
||||||
|
title: '格子仿画 3×3',
|
||||||
|
subtitle: '简单有趣,培养专注力',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 5,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['专注力', '格子仿画', '观察'],
|
||||||
|
sortOrder: 210,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'grid-drawing-5x5',
|
||||||
|
title: '格子仿画 5×5',
|
||||||
|
subtitle: '创意挑战,提升观察力',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['专注力', '格子仿画', '观察'],
|
||||||
|
sortOrder: 211,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'grid-drawing-7x7',
|
||||||
|
title: '格子仿画 7×7',
|
||||||
|
subtitle: '大师挑战,锻炼耐心',
|
||||||
|
ageMin: 6,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['专注力', '格子仿画', '耐心'],
|
||||||
|
sortOrder: 212,
|
||||||
|
},
|
||||||
|
] as const satisfies ReadonlyArray<WorksheetDefinition>;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export type { WorksheetDefinition } from './types';
|
||||||
|
export { MATH_WORKSHEET_DEFINITIONS } from './math';
|
||||||
|
export { FOCUS_WORKSHEET_DEFINITIONS } from './focus';
|
||||||
|
export { LETTER_TRACING_WORKSHEET_DEFINITIONS } from './letterTracing';
|
||||||
|
export { PINYIN_DICTATION_WORKSHEET_DEFINITIONS } from './pinyin';
|
||||||
|
export { PEN_CONTROL_WORKSHEET_DEFINITIONS } from './penControl';
|
||||||
|
export { WORD_COLORING_WORKSHEET_DEFINITIONS } from './wordColoring';
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import type { WorksheetDefinition } from './types';
|
||||||
|
|
||||||
|
export const LETTER_TRACING_WORKSHEET_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
id: 'letter-tracing-single',
|
||||||
|
icon: 'start-a',
|
||||||
|
title: '字母默认字帖',
|
||||||
|
subtitle: '配图、例句与描红',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['字母', '描红', '看图描红'],
|
||||||
|
sortOrder: 42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'letter-tracing-upper-lower',
|
||||||
|
icon: 'draw-o',
|
||||||
|
title: '字母基础描红',
|
||||||
|
subtitle: 'Uppercase / Lowercase 总览',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['字母', '描红', '字母总览'],
|
||||||
|
sortOrder: 44,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'letter-tracing-case-pairing',
|
||||||
|
icon: 'font-size',
|
||||||
|
title: '大小写对照',
|
||||||
|
subtitle: '半组字母左大写右小写',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['字母', '描红', '大小写练习'],
|
||||||
|
sortOrder: 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'letter-tracing-two-column',
|
||||||
|
icon: 'two-columns',
|
||||||
|
title: '两列描红',
|
||||||
|
subtitle: '左 A–M、右 N–Z 配对描红',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['字母', '描红', '两列练习'],
|
||||||
|
sortOrder: 43,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'letter-tracing-half',
|
||||||
|
icon: 'square-half',
|
||||||
|
title: '13字母半表',
|
||||||
|
subtitle: '每行一个字母,13 字母半表',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['字母', '描红', '半表练习'],
|
||||||
|
sortOrder: 47,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'letter-tracing-three',
|
||||||
|
icon: 'ABC-list',
|
||||||
|
title: '三字母精练',
|
||||||
|
subtitle: '每页聚焦 3 个字母深度书写',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['字母', '描红', '三字母精练'],
|
||||||
|
sortOrder: 46,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'letter-tracing-daily-checkin',
|
||||||
|
icon: 'task-o',
|
||||||
|
title: '字母每日打卡',
|
||||||
|
subtitle: '四宫格每日字母打卡练习',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['字母', '描红', '每日打卡'],
|
||||||
|
sortOrder: 48,
|
||||||
|
},
|
||||||
|
] as const satisfies ReadonlyArray<WorksheetDefinition>;
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
import type { WorksheetDefinition } from './types';
|
||||||
|
|
||||||
|
export const MATH_WORKSHEET_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
id: 'number-find',
|
||||||
|
title: '找数字,涂一涂',
|
||||||
|
subtitle: '找出目标数字并涂色',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 5,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '数字认知', '涂色'],
|
||||||
|
sortOrder: 101,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-write',
|
||||||
|
title: '看数字,写一写',
|
||||||
|
subtitle: '按笔画顺序书写数字',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '数字书写', '描红'],
|
||||||
|
sortOrder: 102,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-coloring',
|
||||||
|
title: '按数字,涂颜色',
|
||||||
|
subtitle: '按指定数字涂色',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 5,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '数字认知', '涂色'],
|
||||||
|
sortOrder: 103,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'counting-matching',
|
||||||
|
title: '数一数,连一连',
|
||||||
|
subtitle: '连线配对数字和数量',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 5,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '计数', '连线'],
|
||||||
|
sortOrder: 104,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-object-match',
|
||||||
|
title: '数物连线',
|
||||||
|
subtitle: '连线数量相同的物体和数字',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 5,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '数物对应', '连线'],
|
||||||
|
sortOrder: 105,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-object-fill',
|
||||||
|
title: '数物填写',
|
||||||
|
subtitle: '数出数量,填写对应数字',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '数物对应', '填写'],
|
||||||
|
sortOrder: 106,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'counting-select',
|
||||||
|
title: '数一数,选一选',
|
||||||
|
subtitle: '数出数量,圈出正确答案',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 5,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '计数', '选择'],
|
||||||
|
sortOrder: 107,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'counting-fill',
|
||||||
|
title: '数一数,填一填',
|
||||||
|
subtitle: '数出物品数量,填写数字',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '计数', '填写'],
|
||||||
|
sortOrder: 108,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'compare',
|
||||||
|
title: '数一数,比大小',
|
||||||
|
subtitle: '比较数量,填入 ><=',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '比较', '数量'],
|
||||||
|
sortOrder: 109,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-sort',
|
||||||
|
title: '数字排序',
|
||||||
|
subtitle: '写出正确的数字顺序',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '数字排序', '序列'],
|
||||||
|
sortOrder: 110,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'missing-number',
|
||||||
|
title: '填上缺少的数字',
|
||||||
|
subtitle: '找出并填写缺失数字',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '数字序列', '填写'],
|
||||||
|
sortOrder: 111,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-decompose',
|
||||||
|
title: '10以内数的分与合',
|
||||||
|
subtitle: '把数字分一分,合一合',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '分与合', '10以内'],
|
||||||
|
sortOrder: 112,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-decompose-20',
|
||||||
|
title: '20以内数的分与合',
|
||||||
|
subtitle: '把数字分一分,合一合',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '分与合', '20以内'],
|
||||||
|
sortOrder: 113,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'one-digit-addition',
|
||||||
|
title: '一位数加法',
|
||||||
|
subtitle: '通过圆点学习一位数加法',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '加法', '一位数'],
|
||||||
|
sortOrder: 114,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'addition-5',
|
||||||
|
title: '5以内加法',
|
||||||
|
subtitle: '图形化展示5以内加法',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['数学', '加法', '5以内', '计算题'],
|
||||||
|
sortOrder: 115,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'addition-10',
|
||||||
|
title: '10以内加法',
|
||||||
|
subtitle: '图形化展示10以内加法',
|
||||||
|
ageMin: 4,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '加法', '10以内', '计算题'],
|
||||||
|
sortOrder: 116,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'subtraction-10',
|
||||||
|
title: '10以内减法',
|
||||||
|
subtitle: '图形化展示10以内减法',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '减法', '10以内', '计算题'],
|
||||||
|
sortOrder: 117,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'addition-subtraction-10',
|
||||||
|
title: '10以内加减法',
|
||||||
|
subtitle: '加减法混合运算',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['数学', '加减法', '10以内', '计算题'],
|
||||||
|
sortOrder: 118,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'make-ten',
|
||||||
|
title: '凑十法练习',
|
||||||
|
subtitle: '20以内进位加法',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['数学', '凑十法', '进位加法', '计算题'],
|
||||||
|
sortOrder: 119,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'break-ten',
|
||||||
|
title: '破十法练习',
|
||||||
|
subtitle: '20以内退位减法',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['数学', '破十法', '退位减法', '计算题'],
|
||||||
|
sortOrder: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'flat-ten',
|
||||||
|
title: '平十法练习',
|
||||||
|
subtitle: '20以内退位减法',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['数学', '平十法', '退位减法', '计算题'],
|
||||||
|
sortOrder: 121,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'borrow-ten',
|
||||||
|
title: '借十法练习',
|
||||||
|
subtitle: '20 以上退位减法',
|
||||||
|
ageMin: 6,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 4,
|
||||||
|
tags: ['数学', '借十法', '退位减法', '计算题'],
|
||||||
|
sortOrder: 122,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'practice-addition',
|
||||||
|
title: '加法运算',
|
||||||
|
subtitle: '10/20/50/100 以内加法',
|
||||||
|
ageMin: 6,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['数学', '口算', '加法', '计算题'],
|
||||||
|
sortOrder: 123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'practice-subtraction',
|
||||||
|
title: '减法运算',
|
||||||
|
subtitle: '10/20/50/100以内减法',
|
||||||
|
ageMin: 6,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['数学', '口算', '减法', '计算题'],
|
||||||
|
sortOrder: 124,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'practice-mixed',
|
||||||
|
title: '混合运算',
|
||||||
|
subtitle: '10/20/50/100以内加减法混合',
|
||||||
|
ageMin: 6,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['数学', '口算', '加减法', '计算题'],
|
||||||
|
sortOrder: 125,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'multiplication-table',
|
||||||
|
title: '九九乘法表',
|
||||||
|
subtitle: '学习九九乘法口诀',
|
||||||
|
ageMin: 7,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 3,
|
||||||
|
tags: ['数学', '乘法', '九九乘法表'],
|
||||||
|
sortOrder: 126,
|
||||||
|
},
|
||||||
|
] as const satisfies ReadonlyArray<WorksheetDefinition>;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { WorksheetDefinition } from './types';
|
||||||
|
|
||||||
|
export const PEN_CONTROL_WORKSHEET_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
id: 'pen-control-mix',
|
||||||
|
icon: 'edit',
|
||||||
|
title: '控笔练习',
|
||||||
|
subtitle: '多种控笔图形,运笔更轻松',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['控笔', '运笔', '田字格', '学前'],
|
||||||
|
sortOrder: 40,
|
||||||
|
},
|
||||||
|
] as const satisfies ReadonlyArray<WorksheetDefinition>;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import type { WorksheetDefinition } from './types';
|
||||||
|
|
||||||
|
export const PINYIN_DICTATION_WORKSHEET_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
id: 'pinyin-tracing',
|
||||||
|
icon: 'draw-o',
|
||||||
|
title: '拼音描红练习',
|
||||||
|
subtitle: '跟着描红学拼音字母',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['拼音', '描红', '声母', '韵母'],
|
||||||
|
sortOrder: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pinyin-dictation',
|
||||||
|
icon: 'start-a',
|
||||||
|
title: '拼音默写练习',
|
||||||
|
subtitle: '空白格子默写拼音字母',
|
||||||
|
ageMin: 6,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['拼音', '默写', '声母', '韵母'],
|
||||||
|
sortOrder: 51,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pinyin-tracing-v2',
|
||||||
|
icon: 'pen-draw',
|
||||||
|
title: '拼音描红 8 列',
|
||||||
|
subtitle: '跟写描红,声韵分块',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['拼音', '描红', '声母', '韵母'],
|
||||||
|
sortOrder: 52,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pinyin-dictation-v2',
|
||||||
|
icon: 'ABC-underline',
|
||||||
|
title: '拼音默写 8 列',
|
||||||
|
subtitle: '空白默写,声韵分块',
|
||||||
|
ageMin: 6,
|
||||||
|
ageMax: 8,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['拼音', '默写', '声母', '韵母'],
|
||||||
|
sortOrder: 53,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pinyin-daily',
|
||||||
|
icon: 'task-o',
|
||||||
|
title: '拼音每日打卡',
|
||||||
|
subtitle: '四宫格每日打卡练习',
|
||||||
|
ageMin: 5,
|
||||||
|
ageMax: 7,
|
||||||
|
difficulty: 2,
|
||||||
|
tags: ['拼音', '每日练习', '打卡'],
|
||||||
|
sortOrder: 54,
|
||||||
|
},
|
||||||
|
] as const satisfies ReadonlyArray<WorksheetDefinition>;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/** 本地 worksheet 元数据(与云库 `worksheets` 文档 `_id` 对齐) */
|
||||||
|
export interface WorksheetDefinition {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
ageMin: number;
|
||||||
|
ageMax: number;
|
||||||
|
difficulty: 1 | 2 | 3 | 4;
|
||||||
|
tags: string[];
|
||||||
|
sortOrder: number;
|
||||||
|
/** 页面渲染时使用的图标,仅部分页面有 */
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import type { WorksheetDefinition } from './types';
|
||||||
|
|
||||||
|
export const WORD_COLORING_WORKSHEET_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
id: 'word-coloring-grid',
|
||||||
|
icon: 'grid',
|
||||||
|
title: '网格涂色',
|
||||||
|
subtitle: '田字格涂色识字练习',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['识字', '涂色', '田字格'],
|
||||||
|
sortOrder: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'word-coloring-find',
|
||||||
|
icon: 'search',
|
||||||
|
title: '找字涂色',
|
||||||
|
subtitle: '在字海中找到目标字并涂色',
|
||||||
|
ageMin: 3,
|
||||||
|
ageMax: 6,
|
||||||
|
difficulty: 1,
|
||||||
|
tags: ['识字', '涂色', '找字'],
|
||||||
|
sortOrder: 31,
|
||||||
|
},
|
||||||
|
] as const satisfies ReadonlyArray<WorksheetDefinition>;
|
||||||
@@ -1,99 +1,8 @@
|
|||||||
import type { LetterTracingMode } from './generators/letter-tracing-generator';
|
|
||||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||||
|
import { LETTER_TRACING_WORKSHEET_DEFINITIONS } from '../../config/worksheets/letterTracing';
|
||||||
|
|
||||||
/** 本地 worksheet 元数据(与云库 `worksheets` 文档 `_id` 对齐) */
|
export { LETTER_TRACING_WORKSHEET_DEFINITIONS };
|
||||||
interface LetterTracingWorksheetDefinition {
|
|
||||||
id: LetterTracingMode;
|
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
ageMin: number;
|
|
||||||
ageMax: number;
|
|
||||||
difficulty: 1 | 2 | 3 | 4;
|
|
||||||
tags: string[];
|
|
||||||
sortOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LETTER_TRACING_WORKSHEET_DEFINITIONS = [
|
|
||||||
{
|
|
||||||
id: 'letter-tracing-single',
|
|
||||||
icon: 'start-a',
|
|
||||||
title: '字母默认字帖',
|
|
||||||
subtitle: '配图、例句与描红',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['字母', '描红', '看图描红'],
|
|
||||||
sortOrder: 42,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'letter-tracing-upper-lower',
|
|
||||||
icon: 'draw-o',
|
|
||||||
title: '字母基础描红',
|
|
||||||
subtitle: 'Uppercase / Lowercase 总览',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['字母', '描红', '字母总览'],
|
|
||||||
sortOrder: 44,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'letter-tracing-case-pairing',
|
|
||||||
icon: 'font-size',
|
|
||||||
title: '大小写对照',
|
|
||||||
subtitle: '半组字母左大写右小写',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['字母', '描红', '大小写练习'],
|
|
||||||
sortOrder: 45,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'letter-tracing-two-column',
|
|
||||||
icon: 'two-columns',
|
|
||||||
title: '两列描红',
|
|
||||||
subtitle: '左 A–M、右 N–Z 配对描红',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['字母', '描红', '两列练习'],
|
|
||||||
sortOrder: 43,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'letter-tracing-half',
|
|
||||||
icon: 'square-half',
|
|
||||||
title: '13字母半表',
|
|
||||||
subtitle: '每行一个字母,13 字母半表',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['字母', '描红', '半表练习'],
|
|
||||||
sortOrder: 47,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'letter-tracing-three',
|
|
||||||
icon: 'ABC-list',
|
|
||||||
title: '三字母精练',
|
|
||||||
subtitle: '每页聚焦 3 个字母深度书写',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['字母', '描红', '三字母精练'],
|
|
||||||
sortOrder: 46,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'letter-tracing-daily-checkin',
|
|
||||||
icon: 'task-o',
|
|
||||||
title: '字母每日打卡',
|
|
||||||
subtitle: '四宫格每日字母打卡练习',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['字母', '描红', '每日打卡'],
|
|
||||||
sortOrder: 48,
|
|
||||||
},
|
|
||||||
] as const satisfies ReadonlyArray<LetterTracingWorksheetDefinition>;
|
|
||||||
|
|
||||||
type LetterTracingWorksheetRow =
|
type LetterTracingWorksheetRow =
|
||||||
(typeof LETTER_TRACING_WORKSHEET_DEFINITIONS)[number];
|
(typeof LETTER_TRACING_WORKSHEET_DEFINITIONS)[number];
|
||||||
|
|||||||
@@ -1,140 +1,8 @@
|
|||||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||||
|
import { FOCUS_WORKSHEET_DEFINITIONS } from '../../config/worksheets/focus';
|
||||||
|
|
||||||
/** 本地 worksheet 元数据(与云库 `worksheets` 文档 `_id` 对齐) */
|
export { FOCUS_WORKSHEET_DEFINITIONS };
|
||||||
interface FocusWorksheetDefinition {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
ageMin: number;
|
|
||||||
ageMax: number;
|
|
||||||
difficulty: 1 | 2 | 3 | 4;
|
|
||||||
tags: string[];
|
|
||||||
sortOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FOCUS_WORKSHEET_DEFINITIONS = [
|
|
||||||
{
|
|
||||||
id: 'color-shape-match',
|
|
||||||
title: '根据颜色画图形',
|
|
||||||
subtitle: '根据颜色画出对应图形',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['专注力', '颜色', '图形'],
|
|
||||||
sortOrder: 201,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'shape-symbol',
|
|
||||||
title: '图形符号配对',
|
|
||||||
subtitle: '根据图形画对应符号',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['专注力', '图形', '符号'],
|
|
||||||
sortOrder: 202,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'position-coloring',
|
|
||||||
title: '方位涂涂乐',
|
|
||||||
subtitle: '观察位置,在方格中涂色',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['专注力', '方位', '涂色'],
|
|
||||||
sortOrder: 203,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'color-pattern',
|
|
||||||
title: '颜色找规律',
|
|
||||||
subtitle: '观察颜色规律,涂色',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['专注力', '颜色', '规律'],
|
|
||||||
sortOrder: 204,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'match-connect',
|
|
||||||
title: '连连看',
|
|
||||||
subtitle: '根据物品连一连',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['专注力', '连线', '匹配'],
|
|
||||||
sortOrder: 205,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'line-recognition',
|
|
||||||
title: '线条识别',
|
|
||||||
subtitle: '认识不同线条,画对应线条',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['专注力', '线条', '识别', '控笔'],
|
|
||||||
sortOrder: 206,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'grid-reasoning',
|
|
||||||
title: '方格推理',
|
|
||||||
subtitle: '推理出合并方格并连线',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['专注力', '方格', '推理', '逻辑思维'],
|
|
||||||
sortOrder: 207,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'code-connect',
|
|
||||||
title: '译码连线',
|
|
||||||
subtitle: '按数字顺序连线',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['专注力', '译码', '连线', '逻辑思维'],
|
|
||||||
sortOrder: 208,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'dot-connect',
|
|
||||||
title: '数字点连线',
|
|
||||||
subtitle: '按数字顺序连点成图',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['专注力', '数字', '连线'],
|
|
||||||
sortOrder: 209,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'grid-drawing-3x3',
|
|
||||||
title: '格子仿画 3×3',
|
|
||||||
subtitle: '简单有趣,培养专注力',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 5,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['专注力', '格子仿画', '观察'],
|
|
||||||
sortOrder: 210,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'grid-drawing-5x5',
|
|
||||||
title: '格子仿画 5×5',
|
|
||||||
subtitle: '创意挑战,提升观察力',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['专注力', '格子仿画', '观察'],
|
|
||||||
sortOrder: 211,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'grid-drawing-7x7',
|
|
||||||
title: '格子仿画 7×7',
|
|
||||||
subtitle: '大师挑战,锻炼耐心',
|
|
||||||
ageMin: 6,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['专注力', '格子仿画', '耐心'],
|
|
||||||
sortOrder: 212,
|
|
||||||
},
|
|
||||||
] as const satisfies ReadonlyArray<FocusWorksheetDefinition>;
|
|
||||||
|
|
||||||
type FocusWorksheetDefinitionItem =
|
type FocusWorksheetDefinitionItem =
|
||||||
(typeof FOCUS_WORKSHEET_DEFINITIONS)[number];
|
(typeof FOCUS_WORKSHEET_DEFINITIONS)[number];
|
||||||
|
|||||||
@@ -1,281 +1,8 @@
|
|||||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||||
|
import { MATH_WORKSHEET_DEFINITIONS } from '../../config/worksheets/math';
|
||||||
|
|
||||||
/** 本地 worksheet 元数据(与云库 `worksheets` 文档 `_id` 对齐,用于发布与批量同步脚本) */
|
export { MATH_WORKSHEET_DEFINITIONS };
|
||||||
interface MathWorksheetDefinition {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
ageMin: number;
|
|
||||||
ageMax: number;
|
|
||||||
difficulty: 1 | 2 | 3 | 4;
|
|
||||||
tags: string[];
|
|
||||||
sortOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MATH_WORKSHEET_DEFINITIONS = [
|
|
||||||
{
|
|
||||||
id: 'number-find',
|
|
||||||
title: '找数字,涂一涂',
|
|
||||||
subtitle: '找出目标数字并涂色',
|
|
||||||
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 5,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '数字认知', '涂色'],
|
|
||||||
sortOrder: 101,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'number-write',
|
|
||||||
title: '看数字,写一写',
|
|
||||||
subtitle: '按笔画顺序书写数字',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '数字书写', '描红'],
|
|
||||||
sortOrder: 102,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'number-coloring',
|
|
||||||
title: '按数字,涂颜色',
|
|
||||||
subtitle: '按指定数字涂色',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 5,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '数字认知', '涂色'],
|
|
||||||
sortOrder: 103,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'counting-matching',
|
|
||||||
title: '数一数,连一连',
|
|
||||||
subtitle: '连线配对数字和数量',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 5,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '计数', '连线'],
|
|
||||||
sortOrder: 104,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'number-object-match',
|
|
||||||
title: '数物连线',
|
|
||||||
subtitle: '连线数量相同的物体和数字',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 5,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '数物对应', '连线'],
|
|
||||||
sortOrder: 105,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'number-object-fill',
|
|
||||||
title: '数物填写',
|
|
||||||
subtitle: '数出数量,填写对应数字',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '数物对应', '填写'],
|
|
||||||
sortOrder: 106,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'counting-select',
|
|
||||||
title: '数一数,选一选',
|
|
||||||
subtitle: '数出数量,圈出正确答案',
|
|
||||||
ageMin: 3,
|
|
||||||
ageMax: 5,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '计数', '选择'],
|
|
||||||
sortOrder: 107,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'counting-fill',
|
|
||||||
title: '数一数,填一填',
|
|
||||||
subtitle: '数出物品数量,填写数字',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '计数', '填写'],
|
|
||||||
sortOrder: 108,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'compare',
|
|
||||||
title: '数一数,比大小',
|
|
||||||
subtitle: '比较数量,填入 ><=',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '比较', '数量'],
|
|
||||||
sortOrder: 109,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'number-sort',
|
|
||||||
title: '数字排序',
|
|
||||||
subtitle: '写出正确的数字顺序',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '数字排序', '序列'],
|
|
||||||
sortOrder: 110,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'missing-number',
|
|
||||||
title: '填上缺少的数字',
|
|
||||||
subtitle: '找出并填写缺失数字',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '数字序列', '填写'],
|
|
||||||
sortOrder: 111,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'number-decompose',
|
|
||||||
title: '10以内数的分与合',
|
|
||||||
subtitle: '把数字分一分,合一合',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '分与合', '10以内'],
|
|
||||||
sortOrder: 112,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'number-decompose-20',
|
|
||||||
title: '20以内数的分与合',
|
|
||||||
subtitle: '把数字分一分,合一合',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '分与合', '20以内'],
|
|
||||||
sortOrder: 113,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'one-digit-addition',
|
|
||||||
title: '一位数加法',
|
|
||||||
subtitle: '通过圆点学习一位数加法',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '加法', '一位数'],
|
|
||||||
sortOrder: 114,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'addition-5',
|
|
||||||
title: '5以内加法',
|
|
||||||
subtitle: '图形化展示5以内加法',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['数学', '加法', '5以内', '计算题'],
|
|
||||||
sortOrder: 115,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'addition-10',
|
|
||||||
title: '10以内加法',
|
|
||||||
subtitle: '图形化展示10以内加法',
|
|
||||||
ageMin: 4,
|
|
||||||
ageMax: 6,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '加法', '10以内', '计算题'],
|
|
||||||
sortOrder: 116,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'subtraction-10',
|
|
||||||
title: '10以内减法',
|
|
||||||
subtitle: '图形化展示10以内减法',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '减法', '10以内', '计算题'],
|
|
||||||
sortOrder: 117,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'addition-subtraction-10',
|
|
||||||
title: '10以内加减法',
|
|
||||||
subtitle: '加减法混合运算',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['数学', '加减法', '10以内', '计算题'],
|
|
||||||
sortOrder: 118,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'make-ten',
|
|
||||||
title: '凑十法练习',
|
|
||||||
subtitle: '20以内进位加法',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['数学', '凑十法', '进位加法', '计算题'],
|
|
||||||
sortOrder: 119,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'break-ten',
|
|
||||||
title: '破十法练习',
|
|
||||||
subtitle: '20以内退位减法',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['数学', '破十法', '退位减法', '计算题'],
|
|
||||||
sortOrder: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'flat-ten',
|
|
||||||
title: '平十法练习',
|
|
||||||
subtitle: '20以内退位减法',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['数学', '平十法', '退位减法', '计算题'],
|
|
||||||
sortOrder: 121,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'borrow-ten',
|
|
||||||
title: '借十法练习',
|
|
||||||
subtitle: '20 以上退位减法',
|
|
||||||
ageMin: 6,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 4,
|
|
||||||
tags: ['数学', '借十法', '退位减法', '计算题'],
|
|
||||||
sortOrder: 122,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'practice-addition',
|
|
||||||
title: '加法运算',
|
|
||||||
subtitle: '10/20/50/100 以内加法',
|
|
||||||
ageMin: 6,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['数学', '口算', '加法', '计算题'],
|
|
||||||
sortOrder: 123,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'practice-subtraction',
|
|
||||||
title: '减法运算',
|
|
||||||
subtitle: '10/20/50/100以内减法',
|
|
||||||
ageMin: 6,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['数学', '口算', '减法', '计算题'],
|
|
||||||
sortOrder: 124,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'practice-mixed',
|
|
||||||
title: '混合运算',
|
|
||||||
subtitle: '10/20/50/100以内加减法混合',
|
|
||||||
ageMin: 6,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['数学', '口算', '加减法', '计算题'],
|
|
||||||
sortOrder: 125,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'multiplication-table',
|
|
||||||
title: '九九乘法表',
|
|
||||||
subtitle: '学习九九乘法口诀',
|
|
||||||
ageMin: 7,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 3,
|
|
||||||
tags: ['数学', '乘法', '九九乘法表'],
|
|
||||||
sortOrder: 126,
|
|
||||||
},
|
|
||||||
] as const satisfies ReadonlyArray<MathWorksheetDefinition>;
|
|
||||||
|
|
||||||
type MathWorksheetDefinitionItem = (typeof MATH_WORKSHEET_DEFINITIONS)[number];
|
type MathWorksheetDefinitionItem = (typeof MATH_WORKSHEET_DEFINITIONS)[number];
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CATEGORY_LIST_WITH_ALL } from '../../core/data/categories';
|
import { CATEGORY_LIST_WITH_ALL } from '../../core/data/categories';
|
||||||
import { AGE_BANDS } from '../../core/data/difficulty';
|
import { AGE_BANDS } from '../../core/data/difficulty';
|
||||||
import { CategoryId } from '../../core/models/category';
|
import { CategoryId } from '../../core/models/category';
|
||||||
|
import { APP_VERSION, APP_VERSION_NOTE } from '../../config/config';
|
||||||
|
|
||||||
export type HomeDisplayItem = {
|
export type HomeDisplayItem = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -59,6 +60,26 @@ const HOME_AGE_BANDS: HomeDisplayDataset['ageBands'] = AGE_BANDS.map(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 首页跑马灯公告条(不随云端配置变化) */
|
||||||
|
export type HomeNotice = {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
/** 可选跳转路径,为空则点击不跳转 */
|
||||||
|
path?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HOME_NOTICES: HomeNotice[] = [
|
||||||
|
{
|
||||||
|
id: 'welcome',
|
||||||
|
text: '欢迎来到 涂鸦丫,趣味练习纸任你选',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `update-${APP_VERSION}`,
|
||||||
|
text: `${APP_VERSION} 上线:${APP_VERSION_NOTE}`,
|
||||||
|
path: '/chinesePages/penControlSheet/penControlSheet',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
/** 首页固定:每日打卡练习(不随云端配置变化) */
|
/** 首页固定:每日打卡练习(不随云端配置变化) */
|
||||||
export const HOME_DAILY_CHECKIN_ITEMS: HomeDisplayItem[] = [
|
export const HOME_DAILY_CHECKIN_ITEMS: HomeDisplayItem[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,92 @@
|
|||||||
margin-top: 48rpx;
|
margin-top: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-notice-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 24rpx @page-padding-x 0;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: fade(#ff8f1f, 10%);
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
padding-right: 16rpx;
|
||||||
|
border-right: 2rpx solid fade(#b06b1a, 30%);
|
||||||
|
height: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__icon {
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__label-text {
|
||||||
|
margin-left: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #b06b1a;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__viewport {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__track {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
white-space: nowrap;
|
||||||
|
animation: home-notice-scroll 12s linear infinite;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__group {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #7a4c12;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-notice-bar__sep {
|
||||||
|
margin: 0 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: fade(#b06b1a, 50%);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes home-notice-scroll {
|
||||||
|
0% {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translate3d(-50%, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.home-hero-placeholder {
|
.home-hero-placeholder {
|
||||||
margin: @section-gap @page-padding-x 0;
|
margin: @section-gap @page-padding-x 0;
|
||||||
height: 450rpx;
|
height: 450rpx;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from '../../utils/index';
|
} from '../../utils/index';
|
||||||
import {
|
import {
|
||||||
HOME_DAILY_CHECKIN_ITEMS,
|
HOME_DAILY_CHECKIN_ITEMS,
|
||||||
|
HOME_NOTICES,
|
||||||
createEmptyHomeDataset,
|
createEmptyHomeDataset,
|
||||||
type HomeDisplayItem,
|
type HomeDisplayItem,
|
||||||
type HomeDisplaySection,
|
type HomeDisplaySection,
|
||||||
@@ -190,6 +191,7 @@ Page({
|
|||||||
hotRecommends: SKELETON_HOT,
|
hotRecommends: SKELETON_HOT,
|
||||||
dailyCheckinItems: HOME_DAILY_CHECKIN_ITEMS.map(toItemView),
|
dailyCheckinItems: HOME_DAILY_CHECKIN_ITEMS.map(toItemView),
|
||||||
sections: [] as HomeDisplaySectionView[],
|
sections: [] as HomeDisplaySectionView[],
|
||||||
|
notices: HOME_NOTICES,
|
||||||
loading: true,
|
loading: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -295,6 +297,12 @@ Page({
|
|||||||
navigateByPath(path, title);
|
navigateByPath(path, title);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onTapNotice(e: WechatMiniprogram.TouchEvent) {
|
||||||
|
const path = e.currentTarget.dataset.path as string | undefined;
|
||||||
|
if (!path) return;
|
||||||
|
navigateByPath(path);
|
||||||
|
},
|
||||||
|
|
||||||
onTapCard(e: WechatMiniprogram.CustomEvent) {
|
onTapCard(e: WechatMiniprogram.CustomEvent) {
|
||||||
const detail = (e.detail || {}) as { title?: string; path?: string };
|
const detail = (e.detail || {}) as { title?: string; path?: string };
|
||||||
const path =
|
const path =
|
||||||
|
|||||||
@@ -22,6 +22,43 @@
|
|||||||
bind:change="onTabChange" />
|
bind:change="onTabChange" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view wx:if="{{notices.length > 0}}" class="home-notice-bar">
|
||||||
|
<view class="home-notice-bar__label">
|
||||||
|
<text class="home-notice-bar__icon">📣</text>
|
||||||
|
<text class="home-notice-bar__label-text">公告</text>
|
||||||
|
</view>
|
||||||
|
<view class="home-notice-bar__viewport">
|
||||||
|
<view class="home-notice-bar__track">
|
||||||
|
<view class="home-notice-bar__group">
|
||||||
|
<view
|
||||||
|
wx:for="{{notices}}"
|
||||||
|
wx:key="id"
|
||||||
|
class="home-notice-bar__item"
|
||||||
|
data-path="{{item.path}}"
|
||||||
|
bindtap="onTapNotice">
|
||||||
|
<text class="home-notice-bar__text"
|
||||||
|
>{{item.text}}</text
|
||||||
|
>
|
||||||
|
<text class="home-notice-bar__sep">·</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="home-notice-bar__group" aria-hidden="true">
|
||||||
|
<view
|
||||||
|
wx:for="{{notices}}"
|
||||||
|
wx:key="id"
|
||||||
|
class="home-notice-bar__item"
|
||||||
|
data-path="{{item.path}}"
|
||||||
|
bindtap="onTapNotice">
|
||||||
|
<text class="home-notice-bar__text"
|
||||||
|
>{{item.text}}</text
|
||||||
|
>
|
||||||
|
<text class="home-notice-bar__sep">·</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<swiper
|
<swiper
|
||||||
class="home-hero-swiper"
|
class="home-hero-swiper"
|
||||||
previous-margin="80rpx"
|
previous-margin="80rpx"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { disablePageShareMenu } from '../../base/pageMixin';
|
import { disablePageShareMenu } from '../../base/pageMixin';
|
||||||
|
import { APP_VERSION } from '../../config/config';
|
||||||
import { getDownloadLogCount } from '../../utils/downloadLogs';
|
import { getDownloadLogCount } from '../../utils/downloadLogs';
|
||||||
import { getFavoriteCount } from '../../utils/favorites';
|
import { getFavoriteCount } from '../../utils/favorites';
|
||||||
import {
|
import {
|
||||||
@@ -16,7 +17,7 @@ Page({
|
|||||||
isDevEnv: false,
|
isDevEnv: false,
|
||||||
printCount: 0,
|
printCount: 0,
|
||||||
favoriteCount: 0,
|
favoriteCount: 0,
|
||||||
version: '3.0.0',
|
version: APP_VERSION,
|
||||||
showProfileEditor: false,
|
showProfileEditor: false,
|
||||||
draftNickName: '',
|
draftNickName: '',
|
||||||
draftAvatarUrl: '/assets/imgs/doodle-head.png',
|
draftAvatarUrl: '/assets/imgs/doodle-head.png',
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||||
|
import { PINYIN_DICTATION_WORKSHEET_DEFINITIONS } from '../../config/worksheets/pinyin';
|
||||||
|
|
||||||
|
export { PINYIN_DICTATION_WORKSHEET_DEFINITIONS };
|
||||||
|
|
||||||
export type PinyinDictationMode =
|
export type PinyinDictationMode =
|
||||||
| 'pinyin-tracing'
|
| 'pinyin-tracing'
|
||||||
@@ -8,77 +11,6 @@ export type PinyinDictationMode =
|
|||||||
| 'pinyin-dictation-v2'
|
| 'pinyin-dictation-v2'
|
||||||
| 'pinyin-daily';
|
| 'pinyin-daily';
|
||||||
|
|
||||||
interface PinyinDictationWorksheetDefinition {
|
|
||||||
id: string; // 使用 string 以兼容动态生成的 ID
|
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
ageMin: number;
|
|
||||||
ageMax: number;
|
|
||||||
difficulty: 1 | 2 | 3 | 4;
|
|
||||||
tags: string[];
|
|
||||||
sortOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PINYIN_DICTATION_WORKSHEET_DEFINITIONS: PinyinDictationWorksheetDefinition[] =
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: 'pinyin-tracing',
|
|
||||||
icon: 'draw-o',
|
|
||||||
title: '拼音描红练习',
|
|
||||||
subtitle: '跟着描红学拼音字母',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['拼音', '描红', '声母', '韵母'],
|
|
||||||
sortOrder: 50,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pinyin-dictation',
|
|
||||||
icon: 'start-a',
|
|
||||||
title: '拼音默写练习',
|
|
||||||
subtitle: '空白格子默写拼音字母',
|
|
||||||
ageMin: 6,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['拼音', '默写', '声母', '韵母'],
|
|
||||||
sortOrder: 51,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pinyin-tracing-v2',
|
|
||||||
icon: 'pen-draw',
|
|
||||||
title: '拼音描红 8 列',
|
|
||||||
subtitle: '跟写描红,声韵分块',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 1,
|
|
||||||
tags: ['拼音', '描红', '声母', '韵母'],
|
|
||||||
sortOrder: 52,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pinyin-dictation-v2',
|
|
||||||
icon: 'ABC-underline',
|
|
||||||
title: '拼音默写 8 列',
|
|
||||||
subtitle: '空白默写,声韵分块',
|
|
||||||
ageMin: 6,
|
|
||||||
ageMax: 8,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['拼音', '默写', '声母', '韵母'],
|
|
||||||
sortOrder: 53,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pinyin-daily',
|
|
||||||
icon: 'task-o',
|
|
||||||
title: '拼音每日打卡',
|
|
||||||
subtitle: '四宫格每日打卡练习',
|
|
||||||
ageMin: 5,
|
|
||||||
ageMax: 7,
|
|
||||||
difficulty: 2,
|
|
||||||
tags: ['拼音', '每日练习', '打卡'],
|
|
||||||
sortOrder: 54,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type PinyinDictationWorksheetRow =
|
type PinyinDictationWorksheetRow =
|
||||||
(typeof PINYIN_DICTATION_WORKSHEET_DEFINITIONS)[number];
|
(typeof PINYIN_DICTATION_WORKSHEET_DEFINITIONS)[number];
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ type DebugEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DEBUG_ENTRIES: DebugEntry[] = [
|
const DEBUG_ENTRIES: DebugEntry[] = [
|
||||||
|
{
|
||||||
|
id: 'unreleased-debug',
|
||||||
|
title: '未上线页面 Debug',
|
||||||
|
subtitle: '手动配置未发布页面的标题和路由,方便手机调试。',
|
||||||
|
icon: '🧪',
|
||||||
|
path: '/supportPages/unreleasedDebug/unreleasedDebug',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'home-content',
|
id: 'home-content',
|
||||||
title: '首页内容管理',
|
title: '首页内容管理',
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export interface UnreleasedPageEntry {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UNRELEASED_PAGE_ENTRIES: UnreleasedPageEntry[] = [
|
||||||
|
// 在这里手动添加未上线页面
|
||||||
|
{
|
||||||
|
id: 'pen-control-sheet',
|
||||||
|
title: '控笔',
|
||||||
|
subtitle: '控笔练习',
|
||||||
|
path: '/chinesePages/penControlSheet/penControlSheet',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "未上线页面 Debug",
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarBackgroundColor": "#F8F0E0",
|
||||||
|
"backgroundColor": "#F8F0E0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
@import '../../style/theme.less';
|
||||||
|
|
||||||
|
page {
|
||||||
|
min-height: 100%;
|
||||||
|
background: @bg-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: @bg-header;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__hero {
|
||||||
|
padding: 48rpx 40rpx;
|
||||||
|
background: @bg-white;
|
||||||
|
border-radius: @radius-xl;
|
||||||
|
box-shadow: @shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__hero-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: @text-title;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__hero-desc {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: @text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__section {
|
||||||
|
margin-top: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__section-title {
|
||||||
|
display: block;
|
||||||
|
margin: 0 8rpx 20rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: @text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row + .debug-row {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 34rpx 36rpx;
|
||||||
|
background: @bg-white;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
border: 1rpx solid rgba(124, 118, 106, 0.12);
|
||||||
|
box-shadow: @shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row__icon-bg {
|
||||||
|
width: 84rpx;
|
||||||
|
height: 84rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: fade(@brand, 18%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row__icon {
|
||||||
|
font-size: 36rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row__content {
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row__label {
|
||||||
|
display: block;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: @text-title;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row__desc {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: @text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-row__chevron {
|
||||||
|
margin-left: 16rpx;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
color: @text-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__empty {
|
||||||
|
padding: 48rpx 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: @text-secondary;
|
||||||
|
background: @bg-white;
|
||||||
|
border-radius: @radius-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-page__notice {
|
||||||
|
margin-top: 32rpx;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
border-radius: @radius;
|
||||||
|
background: fade(#ff8f1f, 12%);
|
||||||
|
color: #b06b1a;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { UNRELEASED_PAGE_ENTRIES } from './unreleasedDebug.config';
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
entries: UNRELEASED_PAGE_ENTRIES,
|
||||||
|
isDevEnv: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
const envVersion = wx.getAccountInfoSync().miniProgram.envVersion;
|
||||||
|
const isDevEnv = envVersion === 'develop';
|
||||||
|
this.setData({ isDevEnv });
|
||||||
|
|
||||||
|
if (!isDevEnv) {
|
||||||
|
wx.showToast({ title: '仅开发版可用', icon: 'none' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onTapEntry(e: WechatMiniprogram.TouchEvent) {
|
||||||
|
if (!this.data.isDevEnv) return;
|
||||||
|
let path = e.currentTarget.dataset.path as string;
|
||||||
|
if (!path) return;
|
||||||
|
if (!path.startsWith('/')) {
|
||||||
|
path = `/${path}`;
|
||||||
|
}
|
||||||
|
wx.navigateTo({
|
||||||
|
url: path,
|
||||||
|
fail: () => {
|
||||||
|
wx.showToast({ title: '页面跳转失败', icon: 'none' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<view class="debug-page">
|
||||||
|
<view class="debug-page__hero">
|
||||||
|
<text class="debug-page__hero-title">未上线页面 Debug</text>
|
||||||
|
<text class="debug-page__hero-desc">
|
||||||
|
手动配置未发布页面的标题和路由,方便在手机上调试。
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="debug-page__section">
|
||||||
|
<text class="debug-page__section-title"
|
||||||
|
>入口列表({{entries.length}})</text
|
||||||
|
>
|
||||||
|
<view class="debug-page__rows">
|
||||||
|
<view
|
||||||
|
wx:for="{{entries}}"
|
||||||
|
wx:key="id"
|
||||||
|
class="debug-row"
|
||||||
|
data-path="{{item.path}}"
|
||||||
|
bindtap="onTapEntry">
|
||||||
|
<view class="debug-row__left">
|
||||||
|
<view class="debug-row__icon-bg">
|
||||||
|
<text class="debug-row__icon">🧪</text>
|
||||||
|
</view>
|
||||||
|
<view class="debug-row__content">
|
||||||
|
<text class="debug-row__label">{{item.title}}</text>
|
||||||
|
<text class="debug-row__desc">{{item.subtitle}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="debug-row__chevron">›</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view wx:if="{{entries.length === 0}}" class="debug-page__empty">
|
||||||
|
暂无入口,请在 unreleasedDebug.config.ts 中配置。
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view wx:if="{{!isDevEnv}}" class="debug-page__notice">
|
||||||
|
当前不是开发环境,此页仅供调试使用。
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import { MOCK_SEED_COUNTS } from './mockLikes';
|
import { MOCK_SEED_COUNTS } from './mockLikes';
|
||||||
import { MATH_WORKSHEET_DEFINITIONS } from '../../mathPages/mathDraw/mathDraw.config';
|
import {
|
||||||
import { FOCUS_WORKSHEET_DEFINITIONS } from '../../focusPages/focusDraw/focusDraw.config';
|
MATH_WORKSHEET_DEFINITIONS,
|
||||||
import { LETTER_TRACING_WORKSHEET_DEFINITIONS } from '../../englishPages/letterTracing/letterTracing.config';
|
FOCUS_WORKSHEET_DEFINITIONS,
|
||||||
import { PINYIN_DICTATION_MODE_OPTIONS } from '../../pinyinPages/pinyinDictation/pinyinDictation.config';
|
LETTER_TRACING_WORKSHEET_DEFINITIONS,
|
||||||
|
PINYIN_DICTATION_WORKSHEET_DEFINITIONS,
|
||||||
|
PEN_CONTROL_WORKSHEET_DEFINITIONS,
|
||||||
|
WORD_COLORING_WORKSHEET_DEFINITIONS,
|
||||||
|
type WorksheetDefinition,
|
||||||
|
} from '../../config/worksheets';
|
||||||
|
|
||||||
type CloudFunctionResult<T> = {
|
type CloudFunctionResult<T> = {
|
||||||
success?: boolean;
|
success?: boolean;
|
||||||
@@ -25,13 +30,6 @@ async function callCloudFunction<T>(
|
|||||||
return response.result || {};
|
return response.result || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalDef = {
|
|
||||||
id: string;
|
|
||||||
ageMin: number;
|
|
||||||
ageMax: number;
|
|
||||||
tags: readonly string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function inferGradeFromAge(ageMin: number, ageMax: number): number {
|
function inferGradeFromAge(ageMin: number, ageMax: number): number {
|
||||||
const centerAge = Math.round((ageMin + ageMax) / 2);
|
const centerAge = Math.round((ageMin + ageMax) / 2);
|
||||||
const ageGradeMap: Record<number, number> = {
|
const ageGradeMap: Record<number, number> = {
|
||||||
@@ -53,30 +51,38 @@ function inferGradeFromAge(ageMin: number, ageMax: number): number {
|
|||||||
|
|
||||||
type ConfigSource = {
|
type ConfigSource = {
|
||||||
label: string;
|
label: string;
|
||||||
data: readonly LocalDef[];
|
data: ReadonlyArray<WorksheetDefinition>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONFIG_SOURCES: ConfigSource[] = [
|
const CONFIG_SOURCES: ConfigSource[] = [
|
||||||
{
|
{
|
||||||
label: 'math',
|
label: 'math',
|
||||||
data: MATH_WORKSHEET_DEFINITIONS as unknown as LocalDef[],
|
data: MATH_WORKSHEET_DEFINITIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'focus',
|
label: 'focus',
|
||||||
data: FOCUS_WORKSHEET_DEFINITIONS as unknown as LocalDef[],
|
data: FOCUS_WORKSHEET_DEFINITIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'letterTracing',
|
label: 'letterTracing',
|
||||||
data: LETTER_TRACING_WORKSHEET_DEFINITIONS as unknown as LocalDef[],
|
data: LETTER_TRACING_WORKSHEET_DEFINITIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'pinyin',
|
label: 'pinyin',
|
||||||
data: PINYIN_DICTATION_MODE_OPTIONS as unknown as LocalDef[],
|
data: PINYIN_DICTATION_WORKSHEET_DEFINITIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'penControl',
|
||||||
|
data: PEN_CONTROL_WORKSHEET_DEFINITIONS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'wordColoring',
|
||||||
|
data: WORD_COLORING_WORKSHEET_DEFINITIONS,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function isLocalDef(value: unknown): value is LocalDef {
|
function isWorksheetDefinition(value: unknown): value is WorksheetDefinition {
|
||||||
const row = value as Partial<LocalDef>;
|
const row = value as Partial<WorksheetDefinition>;
|
||||||
return (
|
return (
|
||||||
!!row &&
|
!!row &&
|
||||||
typeof row.id === 'string' &&
|
typeof row.id === 'string' &&
|
||||||
@@ -86,22 +92,24 @@ function isLocalDef(value: unknown): value is LocalDef {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadConfigSource(source: ConfigSource): Promise<LocalDef[]> {
|
async function loadConfigSource(
|
||||||
|
source: ConfigSource,
|
||||||
|
): Promise<WorksheetDefinition[]> {
|
||||||
const rows = source.data;
|
const rows = source.data;
|
||||||
|
|
||||||
if (!Array.isArray(rows) || !rows.every(isLocalDef)) {
|
if (!Array.isArray(rows) || !rows.every(isWorksheetDefinition)) {
|
||||||
throw new Error(`${source.label} 配置格式不正确`);
|
throw new Error(`${source.label} 配置格式不正确`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...rows];
|
return [...rows];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collectAll(): Promise<LocalDef[]> {
|
async function collectAll(): Promise<WorksheetDefinition[]> {
|
||||||
const groups = await Promise.all(CONFIG_SOURCES.map(loadConfigSource));
|
const groups = await Promise.all(CONFIG_SOURCES.map(loadConfigSource));
|
||||||
return groups.flat();
|
return groups.flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertUniqueIds(rows: LocalDef[]) {
|
function assertUniqueIds(rows: WorksheetDefinition[]) {
|
||||||
const set = new Set<string>();
|
const set = new Set<string>();
|
||||||
for (const r of rows) {
|
for (const r of rows) {
|
||||||
if (set.has(r.id)) {
|
if (set.has(r.id)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user