diff --git a/docs/生成方案/字母描红设计方案.md b/docs/生成方案/字母描红设计方案.md index 9228f9f..78b8965 100644 --- a/docs/生成方案/字母描红设计方案.md +++ b/docs/生成方案/字母描红设计方案.md @@ -12,7 +12,7 @@ 覆盖四种练习形态: -1. **单字母学习页**:选定一个字母,含书写示意、阅读句(如 *L is for Lion*)、配图,下方四线三格描红行。 +1. **单字母学习页**:选定一个字母,含书写示意、阅读句(如 _L is for Lion_)、配图,下方四线三格描红行。 2. **整页 26 字母**:26 个字母各占一行(或等价满版排列),四线三格内描红。 3. **上大小写下小写**:上半区展示大写 A–Z,下半区展示小写 a–z。 4. **两列 26 字母**:一页内左列 A–M、右列 N–Z(或同构小写),每行含示范与描红格。 @@ -25,14 +25,12 @@ 从产品视角是四种题型;从引擎视角可收敛为**同一排版模板 + 同一数据生成器**,通过 `generatorConfig.mode` 区分。 - -| 用户可见类型 | 排版本质 | 模板 `template` | 生成器 `generator` | -| --------- | ------------------ | ----------------- | ---------------- | -| 单字母学习页 | 教学区 + 描红区(混合型纵向分区) | `tracing-writing` | `letter-tracing` | -| 26 字母整页描红 | 纯描红满版(多行) | `tracing-writing` | `letter-tracing` | -| 上大写 / 下小写 | 纯描红双区块(展示 + 轻描红) | `tracing-writing` | `letter-tracing` | -| 两列 26 字母 | 纯描红双栏 | `tracing-writing` | `letter-tracing` | - +| 用户可见类型 | 排版本质 | 模板 `template` | 生成器 `generator` | +| --------------- | --------------------------------- | ----------------- | ------------------ | +| 单字母学习页 | 教学区 + 描红区(混合型纵向分区) | `tracing-writing` | `letter-tracing` | +| 26 字母整页描红 | 纯描红满版(多行) | `tracing-writing` | `letter-tracing` | +| 上大写 / 下小写 | 纯描红双区块(展示 + 轻描红) | `tracing-writing` | `letter-tracing` | +| 两列 26 字母 | 纯描红双栏 | `tracing-writing` | `letter-tracing` | **结论**:一个 `TracingWritingRenderer`(扩展四线三格能力)+ 一个 `LetterTracingGenerator`,用 `mode` 参数驱动四种版面,无需四种独立页面类型(除非产品坚持拆入口卡片)。 @@ -42,31 +40,31 @@ ### 3.1 `single-letter`(单字母学习页) -- **上部(约 40%~45% 内容高)**:教学区 - - 配图(与字母绑定的词汇插图,内置 26 组映射 + 资源 key / CDN URL) - - 大写 + 小写展示 - - 阅读句:`*X* is for *Word`* - - 书写顺序示意(可用简化箭头/步骤编号,首版可弱化) -- **下部(约 50%)**:描红区 - - 至少两行四线三格:一行大写、一行小写(或按配置多行) - - 每行:示范格(实线/深色)+ 若干渐淡虚线格 + 可选空白格 +- **上部(约 40%~45% 内容高)**:教学区 + - 配图(与字母绑定的词汇插图,内置 26 组映射 + 资源 key / CDN URL) + - 大写 + 小写展示 + - 阅读句:`*X* is for *Word`\* + - 书写顺序示意(可用简化箭头/步骤编号,首版可弱化) +- **下部(约 50%)**:描红区 + - 至少两行四线三格:一行大写、一行小写(或按配置多行) + - 每行:示范格(实线/深色)+ 若干渐淡虚线格 + 可选空白格 ### 3.2 `full-alphabet`(26 字母整页) -- 26 行(大写或二选一为小写),每行对应一个字母。 -- 每行:四线三格横排多个单元格——首格示范,后续为渐淡描红或空白。 +- 26 行(大写或二选一为小写),每行对应一个字母。 +- 每行:四线三格横排多个单元格——首格示范,后续为渐淡描红或空白。 - 行高由可用高度 / 26 自动换算,保证打印可读(需与 `fontSize`、DPI 导出策略一致)。 ### 3.3 `upper-lower-split`(上大写、下小写) -- 上区块:标题如「大写字母」+ 两行(或紧凑网格)排完 A–Z,每字母一格四线三格,偏**展示**(描红可弱:首格实线 + 空白)。 -- 分隔线。 +- 上区块:标题如「大写字母」+ 两行(或紧凑网格)排完 A–Z,每字母一格四线三格,偏**展示**(描红可弱:首格实线 + 空白)。 +- 分隔线。 - 下区块:标题如「小写字母」+ 同理排完 a–z。 ### 3.4 `two-column`(两列) -- 左列 A–M(或 a–m),右列 N–Z(或 n–z)。 -- 每行结构与 `full-alphabet` 类似:示范 + 多格描红。 +- 左列 A–M(或 a–m),右列 N–Z(或 n–z)。 +- 每行结构与 `full-alphabet` 类似:示范 + 多格描红。 - 中间留白或细分隔线,避免幼儿视觉混淆。 --- @@ -75,9 +73,9 @@ 用于英文字母书写指导(区别于汉字田字格): -- **顶线**:大写字母上沿参考。 -- **中虚线**:小写无升部字母的上沿(x-height 概念)。 -- **基线**:字母落笔主参考线。 +- **顶线**:大写字母上沿参考。 +- **中虚线**:小写无升部字母的上沿(x-height 概念)。 +- **基线**:字母落笔主参考线。 - **底线**:降部(如 g、p、y)下沿参考。 大写主体落在顶线—基线之间;小写主体多在中虚线—基线之间,降部延伸至底线。渲染器需实现 `drawFourLineGrid(cellRect)`,并与 `fontSize`、基线对齐算法统一。 @@ -96,99 +94,97 @@ ┌─────────────────────────────────────────┐ -│ ← 返回 英语字母描红 ❤️ │ +│ ← 返回 英语字母描红 ❤️ │ ├─────────────────────────────────────────┤ -│ │ +│ │ -│ ┌─────────────────────────────────┐ │ +│ ┌─────────────────────────────────┐ │ -│ │ │ │ +│ │ │ │ -│ │ A4 打印效果预览区 │ │ +│ │ A4 打印效果预览区 │ │ -│ │ (Canvas 实时渲染) │ │ +│ │ (Canvas 实时渲染) │ │ -│ │ │ │ +│ │ │ │ -│ └─────────────────────────────────┘ │ +│ └─────────────────────────────────┘ │ -│ │ +│ │ -│ ⚙️ 参数设置 │ +│ ⚙️ 参数设置 │ -│ ┌─────────────────────────────────┐ │ +│ ┌─────────────────────────────────┐ │ -│ │ │ │ +│ │ │ │ -│ │ 练习类型: │ │ +│ │ 练习类型: │ │ -│ │ ┌──────┐┌──────┐┌──────┐ │ │ +│ │ ┌──────┐┌──────┐┌──────┐ │ │ -│ │ │ 单字母 ││ 整页 ││大小写│ │ │ ← 核心切换 +│ │ │ 单字母 ││ 整页 ││大小写│ │ │ ← 核心切换 -│ │ │ 学习页 ││ 描红 ││对照 │ │ │ +│ │ │ 学习页 ││ 描红 ││对照 │ │ │ -│ │ └──────┘└──────┘└──────┘ │ │ +│ │ └──────┘└──────┘└──────┘ │ │ -│ │ ┌──────┐ │ │ +│ │ ┌──────┐ │ │ -│ │ │ 两列 │ │ │ +│ │ │ 两列 │ │ │ -│ │ │ 描红 │ │ │ +│ │ │ 描红 │ │ │ -│ │ └──────┘ │ │ +│ │ └──────┘ │ │ -│ │ │ │ +│ │ │ │ -│ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ +│ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ -│ │ │ │ +│ │ │ │ -│ │ 大小写:[大写 ▼] │ │ ← mode 2/4 时显示 +│ │ 大小写:[大写 ▼] │ │ ← mode 2/4 时显示 -│ │ │ │ +│ │ │ │ -│ │ 选择字母:A [选中] B [选中]... │ │ ← mode 1 时显示 +│ │ 选择字母:A [选中] B [选中]... │ │ ← mode 1 时显示 -│ │ │ │ +│ │ │ │ -│ │ 每行描红格数:[5 ▼] │ │ ← mode 1/2/4 时显示 +│ │ 每行描红格数:[5 ▼] │ │ ← mode 1/2/4 时显示 -│ │ │ │ +│ │ │ │ -│ └─────────────────────────────────┘ │ +│ └─────────────────────────────────┘ │ -│ │ +│ │ -│ ┌──────────┐ ┌──────────────┐ │ +│ ┌──────────┐ ┌──────────────┐ │ -│ │ 🔄 换一批 │ │ 📥 保存到相册 │ │ +│ │ 🔄 换一批 │ │ 📥 保存到相册 │ │ -│ └──────────┘ └──────────────┘ │ +│ └──────────┘ └──────────────┘ │ -│ │ +│ │ └─────────────────────────────────────────┘ ### 5.2 参数项与 Mode 的显隐 - -| 配置项 | `single-letter` | `full-alphabet` | `upper-lower-split` | `two-column` | -| ---------- | --------------- | --------------- | ------------------- | ------------ | -| 练习类型(mode) | 显示 | 显示 | 显示 | 显示 | -| 选择字母 | 显示(单选) | 隐藏 | 隐藏 | 隐藏 | -| 大小写 | 隐藏(默认双写) | 显示 | 隐藏(固定双区) | 显示 | -| 每行描红格数 | 显示 | 显示 | 隐藏或固定为 1 | 显示 | -| 换一批 | 可切换下一字母 | 可不展示或禁用 | 可不展示 | 可不展示 | - +| 配置项 | `single-letter` | `full-alphabet` | `upper-lower-split` | `two-column` | +| ---------------- | ---------------- | --------------- | ------------------- | ------------ | +| 练习类型(mode) | 显示 | 显示 | 显示 | 显示 | +| 选择字母 | 显示(单选) | 隐藏 | 隐藏 | 隐藏 | +| 大小写 | 隐藏(默认双写) | 显示 | 隐藏(固定双区) | 显示 | +| 每行描红格数 | 显示 | 显示 | 隐藏或固定为 1 | 显示 | +| 换一批 | 可切换下一字母 | 可不展示或禁用 | 可不展示 | 可不展示 | 参数面板由 `WorksheetConfig.userConfigurable` 描述;前端根据当前 `mode` **过滤**不适用字段(或在 JSON 层拆成 4 条配置,每条只含本 mode 的 `userConfigurable`)。 ### 5.3 入口策略 -- **推荐**:发现页 / 英语分类下 **4 张卡片**,对应 4 个 `worksheet id`,均进入**同一通用 worksheet 页面**,仅默认 `generatorConfig` 不同。 +- **推荐**:发现页 / 英语分类下 **4 张卡片**,对应 4 个 `worksheet id`,均进入**同一通用 worksheet 页面**,仅默认 `generatorConfig` 不同。 - **可选**:单页内提供「练习类型」Segment,切换 `mode` 并合并默认参数后重绘(与多卡片二选一或并存)。 --- @@ -221,15 +217,15 @@ interface LetterTracingGeneratorConfig { 生成器产出**与平台无关**的数据,供 `TracingWritingRenderer` 消费: -- `single-letter`:`{ teaching: { upper, lower, word, sentence, imageRef }, rows: TracingRow[] }` -- `full-alphabet` / `two-column`:`{ columns?: 1 | 2, rows: TracingRow[] }` +- `single-letter`:`{ teaching: { upper, lower, word, sentence, imageRef }, rows: TracingRow[] }` +- `full-alphabet` / `two-column`:`{ columns?: 1 | 2, rows: TracingRow[] }` - `upper-lower-split`:`{ upperBlock: TracingRow[] | GlyphCell[], lowerBlock: ... }` `TracingRow` 建议包含:`displayChar`、`cells: { char, opacity, isGuide }[]`、`gridType: 'four-line'`。 ### 6.3 词汇与配图数据 -- 维护 `Record<'A'..'Z', { word: string; imageKey: string }>`(或云端下发)。 +- 维护 `Record<'A'..'Z', { word: string; imageKey: string }>`(或云端下发)。 - 图片走内置分包资源或云存储 URL,由 [产品设计文档](../产品设计文档.md) §7.3 中「云素材」规则约束:**插图是设计资产,可 CDN;整页描红仍客户端拼版**。 --- @@ -238,23 +234,21 @@ interface LetterTracingGeneratorConfig { ### 7.1 职责 -- 沿用现有 `BaseTemplateRenderer` 流程:背景、页眉、说明文案、内容区、页脚。 -- 根据 `data.mode` 分支调用 `drawSingleLetterPage` / `drawFullAlphabetPage` / `drawUpperLowerSplitPage` / `drawTwoColumnPage`。 +- 沿用现有 `BaseTemplateRenderer` 流程:背景、页眉、说明文案、内容区、页脚。 +- 根据 `data.mode` 分支调用 `drawSingleLetterPage` / `drawFullAlphabetPage` / `drawUpperLowerSplitPage` / `drawTwoColumnPage`。 - 统一使用 **A4 逻辑坐标**(与技术文档 §7.1 Canvas 管线一致)。 ### 7.2 与汉字描红的差异 - -| 维度 | 汉字练字(`character-tracing`) | 字母描红(`letter-tracing`) | -| --- | ------------------------- | ---------------------- | -| 格子 | 田字格 | 四线三格 | -| 字形 | SVG 笔画路径 | 优先 `fillText` + 字体轮廓 | -| 版面 | 多字多格排布 | 四种 mode 分区逻辑 | - +| 维度 | 汉字练字(`character-tracing`) | 字母描红(`letter-tracing`) | +| ---- | ------------------------------- | ---------------------------- | +| 格子 | 田字格 | 四线三格 | +| 字形 | SVG 笔画路径 | 优先 `fillText` + 字体轮廓 | +| 版面 | 多字多格排布 | 四种 mode 分区逻辑 | ### 7.3 参考现有实现 -- `service/wordDrawService.ts`:布局分区、渐淡格思路可对齐。 +- `service/wordDrawService.ts`:布局分区、渐淡格思路可对齐。 - `mathPages/shared/service/numberWriteDraw.ts`:四线三格线型可参考。 --- @@ -330,7 +324,7 @@ interface LetterTracingGeneratorConfig { ```json { - "id": "letter-tracing-two-column", + "id": "letter-tracing-case-pairing", "title": "字母描红(两列)", "category": "english", "subcategory": "letter-tracing", @@ -354,24 +348,19 @@ interface LetterTracingGeneratorConfig { ## 九、实现落地清单(工程侧) - -| 项 | 说明 | -| ------------------------ | -------------------------------------------------------------------------- | -| `LetterTracingGenerator` | `core/generators/` 或英语分包 `shared/generators/`,注册到 `generatorRegistry` | -| `TracingWritingRenderer` | 扩展四线三格 + 四种 `draw*Page` | -| 字母词汇与配图映射 | `core/data/alphabet.ts` 或云端配置 | -| 通用 worksheet 页 | `pages/worksheet/worksheet`(或过渡期 `englishPages` 专用页再迁) | +| 项 | 说明 | +| ------------------------ | ---------------------------------------------------------------------------------------- | +| `LetterTracingGenerator` | `core/generators/` 或英语分包 `shared/generators/`,注册到 `generatorRegistry` | +| `TracingWritingRenderer` | 扩展四线三格 + 四种 `draw*Page` | +| 字母词汇与配图映射 | `core/data/alphabet.ts` 或云端配置 | +| 通用 worksheet 页 | `pages/worksheet/worksheet`(或过渡期 `englishPages` 专用页再迁) | | `ALL_WORKSHEETS` | 增加 4 条 `WorksheetDefinition`,`category` 对齐英语,`legacyPage` 可指向 worksheet 路由 | -| 分包 | 按需新增 `englishPages`,避免主包膨胀 | - +| 分包 | 按需新增 `englishPages`,避免主包膨胀 | --- ## 十、修订记录 - -| 日期 | 版本 | 说明 | -| ---------- | ---- | ---- | +| 日期 | 版本 | 说明 | +| ---------- | ---- | -------- | | 2026-04-03 | v1.0 | 初稿入库 | - - diff --git a/miniprogram/core/data/worksheets.ts b/miniprogram/core/data/worksheets.ts index 09e0205..08d4241 100644 --- a/miniprogram/core/data/worksheets.ts +++ b/miniprogram/core/data/worksheets.ts @@ -280,7 +280,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ instructionText: '看一看,读一读,再描一描', }, generatorConfig: { - mode: 'picture-tracing', + mode: 'letter-tracing-single', letterCase: 'upper', selectedLetter: 'A', repetitions: 5, @@ -290,9 +290,9 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ ), sheet( { - id: 'letter-tracing-full', - title: '分栏对照', - desc: '半组字母左大写右小写,每行四个', + id: 'letter-tracing-two-column', + title: '两列练习', + desc: '左 A–M、右 N–Z,每组包含大小写', category: 'english', page: 'letterTracing', subpackage: 'englishPages', @@ -301,7 +301,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ ageRange: [4, 7], difficulty: 'beginner', previewBg: '#e8f4ff', - previewText: 'A→Z', + previewText: 'A|N', }, { subcategory: 'letter-tracing', @@ -310,12 +310,12 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ sortOrder: 43, layoutConfig: { ...L.trace, - instructionText: '左栏大写、右栏小写,逐行对应描红', + columns: 2, + instructionText: '左 A–M、右 N–Z,大小写配对描红', }, generatorConfig: { - mode: 'column-compare', + mode: 'letter-tracing-two-column', letterCase: 'both', - alphabetHalf: 'A-M', repetitions: 4, fadePattern: 'gradient', }, @@ -347,7 +347,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ instructionText: '认读全部大写与小写字母', }, generatorConfig: { - mode: 'overview', + mode: 'letter-tracing-upper-lower', letterCase: 'both', repetitions: 1, fadePattern: 'first-only', @@ -356,9 +356,9 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ ), sheet( { - id: 'letter-tracing-two-column', - title: '大小写配对', - desc: '左 A–M、右 N–Z,每行四组大小写', + id: 'letter-tracing-case-pairing', + title: '大小写练习', + desc: '半组字母左大写右小写,每行四个', category: 'english', page: 'letterTracing', subpackage: 'englishPages', @@ -367,7 +367,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ ageRange: [4, 7], difficulty: 'beginner', previewBg: '#e8f4ff', - previewText: 'A|N', + previewText: 'A→Z', }, { subcategory: 'letter-tracing', @@ -376,12 +376,12 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ sortOrder: 45, layoutConfig: { ...L.trace, - columns: 2, - instructionText: '沿虚线描写字母', + instructionText: '左栏大写、右栏小写,逐行对应描红', }, generatorConfig: { - mode: 'case-pairing', + mode: 'letter-tracing-case-pairing', letterCase: 'both', + alphabetHalf: 'A-M', repetitions: 4, fadePattern: 'gradient', }, @@ -412,7 +412,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ instructionText: '每页 3 个字母,逐字精练', }, generatorConfig: { - mode: 'triple-focus', + mode: 'letter-tracing-triple', letterCase: 'both', repetitions: 5, fadePattern: 'gradient', @@ -445,7 +445,7 @@ export const ALL_WORKSHEETS: WorksheetDefinition[] = [ instructionText: '每行一个字母,逐字反复练习', }, generatorConfig: { - mode: 'single-line', + mode: 'letter-tracing-single-line', letterCase: 'both', alphabetHalf: 'A-M', repetitions: 5, diff --git a/miniprogram/core/draw/baseDraw.ts b/miniprogram/core/draw/baseDraw.ts index 7fbff1e..98cff08 100644 --- a/miniprogram/core/draw/baseDraw.ts +++ b/miniprogram/core/draw/baseDraw.ts @@ -13,7 +13,7 @@ import drawHeader from './drawHeader'; * - 分割线绘制 * - 打印配置管理 */ -const LINE_COLOR = 'rgba(50, 46, 37, 0.4)'; +const LINE_COLOR = '#BCBAB2'; // 打印友好的深灰线色,避免渐变色 const LINE_WIDTH = 3; export class BaseDrawService { diff --git a/miniprogram/core/draw/drawHeader.ts b/miniprogram/core/draw/drawHeader.ts index 0d6f01e..d33963c 100644 --- a/miniprogram/core/draw/drawHeader.ts +++ b/miniprogram/core/draw/drawHeader.ts @@ -23,7 +23,7 @@ const META_FONT = '14px "Microsoft Yahei"'; const META_FONT_PX = 12; const META_UNDERLINE_GAP = 3; const TITLE_COLOR = '#322E25'; -const LINE_COLOR = 'rgba(50, 46, 37, 0.4)'; +const LINE_COLOR = '#BCBAB2'; const LINE_WIDTH = 3; const META_COLOR = '#7C766A'; const META_ROW_MARGIN_X = 130; diff --git a/miniprogram/englishPages/letterTracing/letterTracing.ts b/miniprogram/englishPages/letterTracing/letterTracing.ts index 1f36cbd..dad1c1a 100644 --- a/miniprogram/englishPages/letterTracing/letterTracing.ts +++ b/miniprogram/englishPages/letterTracing/letterTracing.ts @@ -30,14 +30,14 @@ const MODES = [ desc: 'Uppercase / Lowercase 总览', }, { - id: 'letter-tracing-two-column', - label: '大小写配对', - desc: '左 A–M、右 N–Z 配对描红', + id: 'letter-tracing-case-pairing', + label: '大小写练习', + desc: '半组字母左大写右小写', }, { - id: 'letter-tracing-full', - label: '分栏对照', - desc: '半组字母左大写右小写', + id: 'letter-tracing-two-column', + label: '两列练习', + desc: '左 A–M、右 N–Z 配对描红', }, { id: 'letter-tracing-triple', @@ -89,7 +89,7 @@ createPage( hasContent: false, showShareDialog: false, worksheetId: 'letter-tracing-single', - traceMode: 'picture-tracing', + traceMode: 'letter-tracing-single', selectedLetter: 'A', letterCaseLower: false, showLetterPicker: true, @@ -166,14 +166,14 @@ createPage( const base = (def?.generatorConfig ?? {}) as Partial; const letterCase = - this.data.traceMode === 'picture-tracing' + this.data.traceMode === 'letter-tracing-single' ? (base.letterCase ?? 'upper') : this.data.letterCaseLower ? 'lower' : 'upper'; const needsHalf = - this.data.traceMode === 'column-compare' || - this.data.traceMode === 'single-line'; + this.data.traceMode === 'letter-tracing-case-pairing' || + this.data.traceMode === 'letter-tracing-single-line'; return mergeLetterTracingConfig(base, { selectedLetter: this.data.selectedLetter, letterCase, @@ -254,17 +254,20 @@ createPage( const traceMode = merged.mode; const showLetterPicker = - traceMode === 'picture-tracing' || traceMode === 'triple-focus'; + traceMode === 'letter-tracing-single' || + traceMode === 'letter-tracing-triple'; const showCaseToggle = - traceMode === 'column-compare' || traceMode === 'single-line'; - const showNextLetter = traceMode === 'picture-tracing'; + traceMode === 'letter-tracing-case-pairing' || + traceMode === 'letter-tracing-single-line'; + const showNextLetter = traceMode === 'letter-tracing-single'; const selectedLetter = options?.preserveLetter ? (this.data.selectedLetter ?? 'A') : normalizeLetter(merged.selectedLetter ?? ''); const letterCaseLower = - traceMode === 'column-compare' || traceMode === 'single-line' + traceMode === 'letter-tracing-case-pairing' || + traceMode === 'letter-tracing-single-line' ? merged.alphabetHalf === 'N-Z' - : traceMode === 'picture-tracing' + : traceMode === 'letter-tracing-single' ? merged.letterCase === 'lower' : false; diff --git a/miniprogram/englishPages/letterTracing/字母描红产品文档.md b/miniprogram/englishPages/letterTracing/字母描红产品文档.md index 91e6c2e..560bb6e 100644 --- a/miniprogram/englishPages/letterTracing/字母描红产品文档.md +++ b/miniprogram/englishPages/letterTracing/字母描红产品文档.md @@ -24,18 +24,18 @@ 所有字母以浅红色描红字体呈现,可打印后用铅笔覆写练习。 -### 3. 大小写配对(case-pairing)—— 左右分栏 · 大小写成对练习 - -> 以 Aa 为一组进行配对描红,强化大小写对应关系。 - -页面分左右两栏(左栏 A–M,右栏 N–Z),每行展示 4 组大小写配对字母。同一行的字母互相对应,每组内包含示范字母(实色)和渐淡描红格,引导从临摹到独立书写。 - -### 4. 分栏对照(column-compare)—— 半表字母 · 左大写右小写对照 +### 3. 分栏对照(case-pairing)—— 半表字母 · 左大写右小写对照 > 半组字母(A–M 或 N–Z)逐行展开,左大写右小写一一对照。 四线三格横跨整页宽度,每行分为左右两半:左半栏写大写字母,右半栏写对应的小写字母。第一个字母为黑色示范,后续为浅红色描红。可切换 A–M / N–Z 两个半表。 +### 4. 两列练习(two-column)—— 左右分栏 · 大小写成对练习 + +> 以 Aa 为一组进行配对描红,强化大小写对应关系。 + +页面分左右两栏(左栏 A–M,右栏 N–Z),每行展示 4 组大小写配对字母。同一行的字母互相对应,每组内包含示范字母(实色)和渐淡描红格,引导从临摹到独立书写。每行第一组字母是黑色,后续字母是浅红色,每个颜色渐淡,两栏之间间距 18 + ### 5. 三字母精练(triple-focus)—— 每页 3 个字母 · 深度书写 > 每页聚焦 3 个字母,兼顾专注度和练习量,适合进阶阶段。 diff --git a/miniprogram/englishPages/shared/draw/casePairingDraw.ts b/miniprogram/englishPages/shared/draw/casePairingDraw.ts index 5cb4d05..9323416 100644 --- a/miniprogram/englishPages/shared/draw/casePairingDraw.ts +++ b/miniprogram/englishPages/shared/draw/casePairingDraw.ts @@ -3,121 +3,128 @@ import type { LetterTracingData, TracingRow, } from '../generators/letter-tracing-generator'; -import { drawFourLineGrid } from './drawTools'; +import { fontFamilyOf } from '../data/fontProfiles'; +import { + calcLetterFontSizes, + drawFourLineGrid, + drawLetterInFourLineGrid, + FOUR_LINE_GRID_H, + type LetterFontSizes, +} from './drawTools'; +const COLOR_BLACK = '#1a1a1a'; +const COLOR_LIGHT_RED = '#FF8E8E'; -const LETTER_FACE = 'Helvetica, Arial, "Segoe UI", Roboto, sans-serif'; +/** 左右大写/小写两栏之间的间距(相对行宽 + 下限,避免过窄屏过小) */ +const COLUMN_GUTTER_RATIO = 0.02; +const COLUMN_GUTTER_MIN = 14; -type CasePairingData = Extract; +/** + * 单栏内字母排版区域占栏宽比例(<1 则同栏字母更紧凑、字间距更小) + */ +const INTRA_COLUMN_LETTER_BAND_RATIO = 1; +type CasePairingData = Extract< + LetterTracingData, + { mode: 'letter-tracing-case-pairing' } +>; + +/** + * 分栏对照绘制: + * 四线三格横跨整页宽度,字母分左右两侧书写—— + * 左栏大写、右栏小写(各 repetitions 个),中间留间距;同栏内字母略收紧。 + * 每侧第一个字母黑色,后续浅红色。 + */ export default class CasePairingDraw extends BaseDrawService { + private letterSizes: LetterFontSizes = + calcLetterFontSizes(FOUR_LINE_GRID_H); + async draw(data: CasePairingData) { this.prepareDraw(); await this.drawHeaderAndDivider(); - this.drawContent(data.leftVisualRows, data.rightVisualRows); + this.drawContent(data.leftRows, data.rightRows); this.drawPrintFooter(); } - private drawContent( - leftVisualRows: TracingRow[][], - rightVisualRows: TracingRow[][], - ) { + private drawContent(leftRows: TracingRow[], rightRows: TracingRow[]) { const M = 24; - const y0 = this.currentY + 6; - const bottom = this.canvasHeight - M; - const gapCol = 18; - const colW = (this.canvasWidth - M * 2 - gapCol) / 2; - const maxVisual = Math.max( - leftVisualRows.length, - rightVisualRows.length, + const lineW = this.canvasWidth - M * 2; + const maxR = Math.max(leftRows.length, rightRows.length); + + const footerReserve = 56; + const availableH = + this.canvasHeight - this.currentY - footerReserve - 12; + const rowGap = Math.max( + 4, + (availableH - maxR * FOUR_LINE_GRID_H) / (maxR + 1), ); - const rowSlotH = Math.max(40, (bottom - y0) / maxVisual - 6); - const drawSide = ( - blocks: TracingRow[][], - startX: number, - width: number, - ) => { - let y = y0; - for (const visualRow of blocks) { - const nSeg = visualRow.length; - const segGap = 6; - const segW = (width - (nSeg - 1) * segGap) / nSeg; - for (let s = 0; s < nSeg; s++) { - this.drawTracingRow( - visualRow[s], - startX + s * (segW + segGap), - y, - rowSlotH, - segW, - ); - } - y += rowSlotH + 8; + const y0 = this.currentY + rowGap; + + const columnGutter = Math.max( + COLUMN_GUTTER_MIN, + Math.round(lineW * COLUMN_GUTTER_RATIO), + ); + const halfContentW = (lineW - columnGutter) / 2; + const leftX = M; + const rightX = M + halfContentW + columnGutter; + + for (let i = 0; i < maxR; i++) { + const y = y0 + i * (FOUR_LINE_GRID_H + rowGap); + + drawFourLineGrid(this.ctx, M, y, lineW, FOUR_LINE_GRID_H); + + if (i < leftRows.length) { + this.drawLettersInHalf( + leftRows[i], + leftX, + y, + halfContentW, + INTRA_COLUMN_LETTER_BAND_RATIO, + ); } - }; - - drawSide(leftVisualRows, M, colW); - drawSide(rightVisualRows, M + colW + gapCol, colW); + if (i < rightRows.length) { + this.drawLettersInHalf( + rightRows[i], + rightX, + y, + halfContentW, + INTRA_COLUMN_LETTER_BAND_RATIO, + ); + } + } } - private drawTracingRow( + /** + * 在四线三格的半侧区域内绘制字母。 + * 第一个字母黑色,后续字母浅红色。 + */ + private drawLettersInHalf( row: TracingRow, - startX: number, + x: number, y: number, - rowH: number, - totalWidth: number, + columnW: number, + letterBandRatio: number, ) { - const gap = 4; const n = row.cells.length; - const cellW = n > 0 ? (totalWidth - gap * (n - 1)) / n : totalWidth; + if (n === 0) return; + + const bandW = columnW * letterBandRatio; + const inset = (columnW - bandW) / 2; + const slotW = bandW / n; for (let i = 0; i < n; i++) { - const cx = startX + i * (cellW + gap); - drawFourLineGrid(this.ctx, cx, y, cellW, rowH); const cell = row.cells[i]; - this.drawCharInCell( + const color = i === 0 ? COLOR_BLACK : COLOR_LIGHT_RED; + const cx = x + inset + slotW * i + slotW / 2; + + drawLetterInFourLineGrid( + this.ctx, cell.char, cx, y, - cellW, - rowH, - cell.opacity, - cell.isGuide, - cell.pairChar, + FOUR_LINE_GRID_H, + this.letterSizes, + { fontFamily: fontFamilyOf(this.letterSizes._profile), color }, ); } } - - private drawCharInCell( - ch: string, - x: number, - y: number, - w: number, - h: number, - opacity: number, - bold: boolean, - pairChar?: string, - ) { - const ctx = this.ctx; - if (opacity <= 0) return; - ctx.save(); - ctx.globalAlpha = opacity; - ctx.fillStyle = bold ? '#1a1a1a' : '#4a4a4a'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'alphabetic'; - const baselineY = y + h * 0.72; - - if (pairChar) { - const fs = Math.min(h * 0.5, w * 0.22); - ctx.font = `${bold ? 'bold ' : ''}${fs}px ${LETTER_FACE}`; - const gap = fs * 0.2; - const totalW = fs * 2 + gap; - const x0 = x + w / 2 - totalW / 2; - ctx.fillText(ch, x0 + fs * 0.5, baselineY); - ctx.fillText(pairChar, x0 + fs * 1.5 + gap, baselineY); - } else { - const fontSize = Math.min(h * 0.72, w * 0.85); - ctx.font = `${bold ? 'bold ' : ''}${fontSize}px ${LETTER_FACE}`; - ctx.fillText(ch, x + w / 2, baselineY); - } - ctx.restore(); - } } diff --git a/miniprogram/englishPages/shared/draw/columnCompareDraw.ts b/miniprogram/englishPages/shared/draw/columnCompareDraw.ts deleted file mode 100644 index b27d8b1..0000000 --- a/miniprogram/englishPages/shared/draw/columnCompareDraw.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { BaseDrawService } from '../../../core/draw/baseDraw'; -import type { - LetterTracingData, - TracingRow, -} from '../generators/letter-tracing-generator'; -import { fontFamilyOf } from '../data/fontProfiles'; -import { - calcLetterFontSizes, - drawFourLineGrid, - drawLetterInFourLineGrid, - FOUR_LINE_GRID_H, - type LetterFontSizes, -} from './drawTools'; -const COLOR_BLACK = '#1a1a1a'; -const COLOR_LIGHT_RED = 'rgba(252, 46, 0, 0.3)'; - -/** 左右大写/小写两栏之间的间距(相对行宽 + 下限,避免过窄屏过小) */ -const COLUMN_GUTTER_RATIO = 0.02; -const COLUMN_GUTTER_MIN = 14; - -/** - * 单栏内字母排版区域占栏宽比例(<1 则同栏字母更紧凑、字间距更小) - */ -const INTRA_COLUMN_LETTER_BAND_RATIO = 1; - -type ColumnCompareData = Extract; - -/** - * 分栏对照绘制: - * 四线三格横跨整页宽度,字母分左右两侧书写—— - * 左栏大写、右栏小写(各 repetitions 个),中间留间距;同栏内字母略收紧。 - * 每侧第一个字母黑色,后续浅红色。 - */ -export default class ColumnCompareDraw extends BaseDrawService { - private letterSizes: LetterFontSizes = - calcLetterFontSizes(FOUR_LINE_GRID_H); - - async draw(data: ColumnCompareData) { - this.prepareDraw(); - await this.drawHeaderAndDivider(); - this.drawContent(data.leftRows, data.rightRows); - this.drawPrintFooter(); - } - - private drawContent(leftRows: TracingRow[], rightRows: TracingRow[]) { - const M = 24; - const lineW = this.canvasWidth - M * 2; - const maxR = Math.max(leftRows.length, rightRows.length); - - const footerReserve = 56; - const availableH = - this.canvasHeight - this.currentY - footerReserve - 12; - const rowGap = Math.max( - 4, - (availableH - maxR * FOUR_LINE_GRID_H) / (maxR + 1), - ); - - const y0 = this.currentY + rowGap; - - const columnGutter = Math.max( - COLUMN_GUTTER_MIN, - Math.round(lineW * COLUMN_GUTTER_RATIO), - ); - const halfContentW = (lineW - columnGutter) / 2; - const leftX = M; - const rightX = M + halfContentW + columnGutter; - - for (let i = 0; i < maxR; i++) { - const y = y0 + i * (FOUR_LINE_GRID_H + rowGap); - - drawFourLineGrid(this.ctx, M, y, lineW, FOUR_LINE_GRID_H); - - if (i < leftRows.length) { - this.drawLettersInHalf( - leftRows[i], - leftX, - y, - halfContentW, - INTRA_COLUMN_LETTER_BAND_RATIO, - ); - } - if (i < rightRows.length) { - this.drawLettersInHalf( - rightRows[i], - rightX, - y, - halfContentW, - INTRA_COLUMN_LETTER_BAND_RATIO, - ); - } - } - } - - /** - * 在四线三格的半侧区域内绘制字母。 - * 第一个字母黑色,后续字母浅红色。 - */ - private drawLettersInHalf( - row: TracingRow, - x: number, - y: number, - columnW: number, - letterBandRatio: number, - ) { - const n = row.cells.length; - if (n === 0) return; - - const bandW = columnW * letterBandRatio; - const inset = (columnW - bandW) / 2; - const slotW = bandW / n; - for (let i = 0; i < n; i++) { - const cell = row.cells[i]; - const color = i === 0 ? COLOR_BLACK : COLOR_LIGHT_RED; - const cx = x + inset + slotW * i + slotW / 2; - - drawLetterInFourLineGrid( - this.ctx, - cell.char, - cx, - y, - FOUR_LINE_GRID_H, - this.letterSizes, - { fontFamily: fontFamilyOf(this.letterSizes._profile), color }, - ); - } - } -} diff --git a/miniprogram/englishPages/shared/draw/drawTools.ts b/miniprogram/englishPages/shared/draw/drawTools.ts index 4cdc78e..68c1a18 100644 --- a/miniprogram/englishPages/shared/draw/drawTools.ts +++ b/miniprogram/englishPages/shared/draw/drawTools.ts @@ -97,8 +97,7 @@ export function drawLetterInFourLineGrid( bold?: boolean; }, ) { - const fontFamily = - options?.fontFamily ?? fontFamilyOf(sizes._profile); + const fontFamily = options?.fontFamily ?? fontFamilyOf(sizes._profile); const color = options?.color ?? '#1a1a1a'; const bold = options?.bold ?? false; diff --git a/miniprogram/englishPages/shared/draw/letterTracingDraw.ts b/miniprogram/englishPages/shared/draw/letterTracingDraw.ts index 204327e..3d91afa 100644 --- a/miniprogram/englishPages/shared/draw/letterTracingDraw.ts +++ b/miniprogram/englishPages/shared/draw/letterTracingDraw.ts @@ -1,9 +1,9 @@ import { BaseDrawService } from '../../../core/draw/baseDraw'; import type { LetterTracingData } from '../generators/letter-tracing-generator'; -import PictureTracingDraw from './pictureTracingDraw'; +import singleLetterDraw from './singleLetterDraw'; import CasePairingDraw from './casePairingDraw'; -import ColumnCompareDraw from './columnCompareDraw'; -import OverviewDraw from './overviewDraw'; +import TwoColumnDraw from './twoColumnDraw'; +import UpperLowerDraw from './upperLowerDraw'; /** * 字母描红绘制分发层。 @@ -12,10 +12,10 @@ import OverviewDraw from './overviewDraw'; export default class LetterTracingDraw extends BaseDrawService { async draw(data: LetterTracingData) { const DrawClass = { - 'picture-tracing': PictureTracingDraw, - 'case-pairing': CasePairingDraw, - 'column-compare': ColumnCompareDraw, - overview: OverviewDraw, + 'letter-tracing-single': singleLetterDraw, + 'letter-tracing-upper-lower': UpperLowerDraw, + 'letter-tracing-case-pairing': CasePairingDraw, + 'letter-tracing-two-column': TwoColumnDraw, }[data.mode]; const delegate = new DrawClass(this.canvas, this.ctx, this.options); diff --git a/miniprogram/englishPages/shared/draw/pictureTracingDraw.ts b/miniprogram/englishPages/shared/draw/singleLetterDraw.ts similarity index 97% rename from miniprogram/englishPages/shared/draw/pictureTracingDraw.ts rename to miniprogram/englishPages/shared/draw/singleLetterDraw.ts index e6a0941..12137aa 100644 --- a/miniprogram/englishPages/shared/draw/pictureTracingDraw.ts +++ b/miniprogram/englishPages/shared/draw/singleLetterDraw.ts @@ -19,23 +19,26 @@ const ACCENT_RED = 'rgba(252, 46, 0, 1)'; const TITLE_TEXT_FONT = '14px sans-serif'; const TITLE_ICON_GAP = 4; -type PictureData = Extract; +type SingleLetterData = Extract< + LetterTracingData, + { mode: 'letter-tracing-single' } +>; -export default class PictureTracingDraw extends BaseDrawService { +export default class SingleLetterDraw extends BaseDrawService { private letterSizes: LetterFontSizes = calcLetterFontSizes(FOUR_LINE_GRID_H); private get letterFace(): string { return fontFamilyOf(this.letterSizes._profile); } - async draw(data: PictureData) { + async draw(data: SingleLetterData) { this.prepareDraw(); await this.drawHeaderAndDivider(); await this.drawContent(data); this.drawPrintFooter(); } - private async drawContent(data: PictureData) { + private async drawContent(data: SingleLetterData) { const { teaching, highlightGrid, traceImgPath } = data; const ctx = this.ctx; @@ -79,7 +82,7 @@ export default class PictureTracingDraw extends BaseDrawService { ); } - this.drawSentence( + this.drawSingleLetterSentence( teaching.sentence, teaching.upper, TOP_X, @@ -228,7 +231,7 @@ export default class PictureTracingDraw extends BaseDrawService { ctx.restore(); } - private drawSentence( + private drawSingleLetterSentence( sentence: string, letter: string, x: number, diff --git a/miniprogram/englishPages/shared/draw/twoColumnDraw.ts b/miniprogram/englishPages/shared/draw/twoColumnDraw.ts new file mode 100644 index 0000000..51de86b --- /dev/null +++ b/miniprogram/englishPages/shared/draw/twoColumnDraw.ts @@ -0,0 +1,124 @@ +import { BaseDrawService } from '../../../core/draw/baseDraw'; +import type { + LetterTracingData, + TracingRow, +} from '../generators/letter-tracing-generator'; +import { fontFamilyOf } from '../data/fontProfiles'; +import { + calcLetterFontSizes, + drawFourLineGrid, + drawLetterInFourLineGrid, + FOUR_LINE_GRID_H, + type LetterFontSizes, +} from './drawTools'; + +const COLOR_BLACK = '#1a1a1a'; +const COLOR_LIGHT_RED = '#FF8E8E'; +const COLUMN_GUTTER = 18; + +type TwoColumnData = Extract< + LetterTracingData, + { mode: 'letter-tracing-two-column' } +>; + +/** + * 两列练习绘制: + * 左栏 A–M(Aa Bb … Mm)、右栏 N–Z(Nn Oo … Zz),每栏 13 行。 + * 每行一个完整四线三格,格内绘制大小写配对字母。 + * 第一行黑色示范,后续行浅红色渐淡。两栏间距 18px。 + */ +export default class TwoColumnDraw extends BaseDrawService { + private letterSizes: LetterFontSizes = + calcLetterFontSizes(FOUR_LINE_GRID_H); + + async draw(data: TwoColumnData) { + this.prepareDraw(); + await this.drawHeaderAndDivider(); + this.drawContent(data.leftRows, data.rightRows); + this.drawPrintFooter(); + } + + private drawContent(leftRows: TracingRow[], rightRows: TracingRow[]) { + const M = 24; + const lineW = this.canvasWidth - M * 2; + const colW = (lineW - COLUMN_GUTTER) / 2; + const maxR = Math.max(leftRows.length, rightRows.length); + + const footerReserve = 56; + const availableH = + this.canvasHeight - this.currentY - footerReserve - 12; + const rowGap = Math.max( + 4, + (availableH - maxR * FOUR_LINE_GRID_H) / (maxR + 1), + ); + const y0 = this.currentY + rowGap; + + const leftX = M; + const rightX = M + colW + COLUMN_GUTTER; + + for (let i = 0; i < maxR; i++) { + const y = y0 + i * (FOUR_LINE_GRID_H + rowGap); + + if (i < leftRows.length) { + drawFourLineGrid(this.ctx, leftX, y, colW, FOUR_LINE_GRID_H); + this.drawPairRow(leftRows[i], leftX, y, colW); + } + if (i < rightRows.length) { + drawFourLineGrid(this.ctx, rightX, y, colW, FOUR_LINE_GRID_H); + this.drawPairRow(rightRows[i], rightX, y, colW); + } + } + } + + /** + * 在一个四线三格内绘制大小写配对(如 A a)。 + * rowIndex=0 黑色示范,>0 浅红色。 + * cells 里每个 cell 的 char 是大写、pairChar 是小写, + * 按 repetitions 排列(第一个实色 → 后续渐淡)。 + */ + private drawPairRow(row: TracingRow, x: number, y: number, colW: number) { + const fontFamily = fontFamilyOf(this.letterSizes._profile); + const cells = row.cells; + const n = cells.length; + if (n === 0) return; + + const pairCount = n; + const slotW = colW / pairCount; + + const pairGap = 25; + + for (let i = 0; i < pairCount; i++) { + const cell = cells[i]; + const color = i === 0 ? COLOR_BLACK : COLOR_LIGHT_RED; + const alpha = i >= pairCount - 2 ? 0.5 : 1; + const pairCx = x + i * slotW + slotW / 2; + + this.ctx.save(); + this.ctx.globalAlpha = alpha; + + drawLetterInFourLineGrid( + this.ctx, + cell.char, + pairCx - pairGap / 2, + y, + FOUR_LINE_GRID_H, + this.letterSizes, + { fontFamily, color }, + ); + + if (cell.pairChar) { + drawLetterInFourLineGrid( + this.ctx, + cell.pairChar, + pairCx + pairGap / 2, + y, + FOUR_LINE_GRID_H, + this.letterSizes, + { fontFamily, color }, + ); + } + + this.ctx.restore(); + } + } +} diff --git a/miniprogram/englishPages/shared/draw/overviewDraw.ts b/miniprogram/englishPages/shared/draw/upperLowerDraw.ts similarity index 92% rename from miniprogram/englishPages/shared/draw/overviewDraw.ts rename to miniprogram/englishPages/shared/draw/upperLowerDraw.ts index 108ea51..3e539ef 100644 --- a/miniprogram/englishPages/shared/draw/overviewDraw.ts +++ b/miniprogram/englishPages/shared/draw/upperLowerDraw.ts @@ -14,7 +14,10 @@ import { type LetterFontSizes, } from './drawTools'; -type OverviewData = Extract; +type UpperLowerData = Extract< + LetterTracingData, + { mode: 'letter-tracing-upper-lower' } +>; /** 页面左右边距(margin,px),用于排版时留白 */ const M = 24; @@ -40,24 +43,24 @@ const TITLE_FACE = fontFamilyOf(TITLE_PROFILE); * 字母总览:上区大写、下区小写;每区标题 + 4 行四线三格(7+7+6+6)。 * 列宽按 7 列均分;后两行 6 字左对齐,与前两行逐列对齐(占前 6 列)。 */ -export default class OverviewDraw extends BaseDrawService { +export default class UpperLowerDraw extends BaseDrawService { private letterSizes: LetterFontSizes = calcLetterFontSizes( FOUR_LINE_GRID_H, DASHED_PROFILE, ); - async draw(data: OverviewData) { + async draw(data: UpperLowerData) { this.prepareDraw(); await Promise.allSettled([ loadLetterFont(DASHED_PROFILE), loadLetterFont(TITLE_PROFILE), ]); await this.drawHeaderAndDivider(); - this.drawOverviewBody(data.upperRows, data.lowerRows); + this.drawUpperLowerBody(data.upperRows, data.lowerRows); await this.drawPrintFooter(); } - private drawOverviewBody( + private drawUpperLowerBody( upperRows: string[][], lowerRows: string[][], ): void { diff --git a/miniprogram/englishPages/shared/generators/letter-tracing-generator.ts b/miniprogram/englishPages/shared/generators/letter-tracing-generator.ts index 8642901..7ccd63f 100644 --- a/miniprogram/englishPages/shared/generators/letter-tracing-generator.ts +++ b/miniprogram/englishPages/shared/generators/letter-tracing-generator.ts @@ -3,17 +3,17 @@ import { ALPHABET_BY_LETTER, LETTERS_UPPER } from '../data/alphabet'; /** 字母描红的布局模式(与产品文档六类练习一致) */ export type LetterTracingMode = /** 1 看图识字:教学区 + 描红行(前三行大写、后三行小写) */ - | 'picture-tracing' + | 'letter-tracing-single' /** 2 字母总览:Uppercase / Lowercase 分区,每区四行 7+7+6+6 */ - | 'overview' + | 'letter-tracing-upper-lower' /** 3 大小写配对:左栏 A–M、右栏 N–Z,每行最多 4 组 Aa */ - | 'case-pairing' + | 'letter-tracing-case-pairing' /** 4 分栏对照:半组字母,左栏大写、右栏小写,每行 4 个 */ - | 'column-compare' + | 'letter-tracing-two-column' /** 5 三字母精练:每页聚焦 3 个字母,各含大写行 + 小写行 */ - | 'triple-focus' + | 'letter-tracing-triple' /** 6 单字母逐行:每行一个字母,13 字母半表(A–M 或 N–Z) */ - | 'single-line'; + | 'letter-tracing-single-line'; /** 字母大小写模式(部分模式不使用) */ export type LetterCaseMode = 'upper' | 'lower' | 'both'; @@ -70,7 +70,7 @@ export interface TeachingBlock { export type LetterTracingData = | { - mode: 'picture-tracing'; + mode: 'letter-tracing-single'; teaching: TeachingBlock; highlightGrid: HighlightGrid; /** Trace It 区域使用的字母笔顺图路径(同 teaching.letterImgPath) */ @@ -79,17 +79,17 @@ export type LetterTracingData = tracingLineCount: number; } | { - mode: 'case-pairing'; - leftVisualRows: TracingRow[][]; - rightVisualRows: TracingRow[][]; - } - | { - mode: 'column-compare'; + mode: 'letter-tracing-case-pairing'; leftRows: TracingRow[]; rightRows: TracingRow[]; } | { - mode: 'overview'; + mode: 'letter-tracing-two-column'; + leftRows: TracingRow[]; + rightRows: TracingRow[]; + } + | { + mode: 'letter-tracing-upper-lower'; upperRows: string[][]; lowerRows: string[][]; }; @@ -212,7 +212,7 @@ export function generateLetterTracing( ): LetterTracingData { const { mode, repetitions, fadePattern } = config; - if (mode === 'picture-tracing') { + if (mode === 'letter-tracing-single') { const key = normalizeLetter(config.selectedLetter ?? 'A'); const meta = ALPHABET_BY_LETTER[key] ?? ALPHABET_BY_LETTER.A; const upper = key; @@ -237,7 +237,7 @@ export function generateLetterTracing( const highlightGrid = buildHighlightGrid(upper, 3, 6); return { - mode: 'picture-tracing', + mode: 'letter-tracing-single', teaching, highlightGrid, traceImgPath: letterImgPath, @@ -245,23 +245,7 @@ export function generateLetterTracing( }; } - if (mode === 'case-pairing') { - const leftLetters = LETTERS_UPPER.slice(0, 13); - const rightLetters = LETTERS_UPPER.slice(13); - const pack = (letters: string[]) => - chunk(letters, 4).map((group) => - group.map((L) => - rowForChar(L, repetitions, fadePattern, L.toLowerCase()), - ), - ); - return { - mode: 'case-pairing', - leftVisualRows: pack(leftLetters), - rightVisualRows: pack(rightLetters), - }; - } - - if (mode === 'column-compare') { + if (mode === 'letter-tracing-case-pairing') { const half = config.alphabetHalf === 'N-Z' ? LETTERS_UPPER.slice(13) @@ -272,12 +256,26 @@ export function generateLetterTracing( const rightRows = half.map((L) => rowForChar(L.toLowerCase(), repetitions, fadePattern), ); - return { mode: 'column-compare', leftRows, rightRows }; + return { mode: 'letter-tracing-case-pairing', leftRows, rightRows }; } - // overview + if (mode === 'letter-tracing-two-column') { + const leftLetters = LETTERS_UPPER.slice(0, 13); + const rightLetters = LETTERS_UPPER.slice(13); + const toRows = (letters: string[]) => + letters.map((L) => + rowForChar(L, repetitions, fadePattern, L.toLowerCase()), + ); + return { + mode: 'letter-tracing-two-column', + leftRows: toRows(leftLetters), + rightRows: toRows(rightLetters), + }; + } + + // letter-tracing-upper-lower return { - mode: 'overview', + mode: 'letter-tracing-upper-lower', upperRows: overviewRowsFrom([...LETTERS_UPPER]), lowerRows: overviewRowsFrom(LETTERS_UPPER.map((L) => L.toLowerCase())), }; @@ -291,7 +289,7 @@ export function mergeLetterTracingConfig( return { mode: (overrides.mode ?? base.mode ?? - 'picture-tracing') as LetterTracingMode, + 'letter-tracing-single') as LetterTracingMode, letterCase: (overrides.letterCase ?? base.letterCase ?? 'upper') as LetterCaseMode, diff --git a/miniprogram/pages/home/home.data.ts b/miniprogram/pages/home/home.data.ts index a33e210..8e6a047 100644 --- a/miniprogram/pages/home/home.data.ts +++ b/miniprogram/pages/home/home.data.ts @@ -743,9 +743,9 @@ export const HOME_CURRENT_CAPABILITY_DATA: HomeDisplayDataset = { ageBand: '4-7岁', difficulty: '入门', icon: '🔤', - path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-full', + path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-two-column', available: true, - source: 'letter-tracing-full', + source: 'letter-tracing-two-column', }, { id: 'current-english-3', @@ -767,9 +767,9 @@ export const HOME_CURRENT_CAPABILITY_DATA: HomeDisplayDataset = { ageBand: '4-7岁', difficulty: '入门', icon: '✏️', - path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-two-column', + path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-case-pairing', available: true, - source: 'letter-tracing-two-column', + source: 'letter-tracing-case-pairing', }, ], }, diff --git a/project.private.config.json b/project.private.config.json index cdfe7d1..ae56091 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -1,64 +1,64 @@ { - "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", - "projectname": "doodle-mini", - "setting": { - "compileHotReLoad": true, - "urlCheck": true, - "coverView": false, - "lazyloadPlaceholderEnable": false, - "skylineRenderEnable": true, - "preloadBackgroundData": false, - "autoAudits": false, - "useApiHook": true, - "useApiHostProcess": true, - "showShadowRootInWxmlPanel": false, - "useStaticServer": false, - "useLanDebug": false, - "showES6CompileOption": false, - "checkInvalidKey": true, - "ignoreDevUnusedFiles": true, - "bigPackageSizeSupport": false - }, - "libVersion": "3.7.12", - "condition": { - "miniprogram": { - "list": [ - { - "name": "englishPages/letterTracing/letterTracing", - "pathName": "englishPages/letterTracing/letterTracing", - "query": "id=letter-tracing-upper-lower", - "scene": null, - "launchMode": "default" - }, - { - "name": "englishPages/letterTracing/letterTracing", - "pathName": "englishPages/letterTracing/letterTracing", - "query": "id=letter-tracing-full", - "launchMode": "default", - "scene": null - }, - { - "name": "englishPages/letterTracing/letterTracing", - "pathName": "englishPages/letterTracing/letterTracing", - "query": "id=letter-tracing-single", - "launchMode": "default", - "scene": null - }, - { - "name": "pages/index/index", - "pathName": "pages/index/index", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "englishPages/letterTracing/letterTracing", - "pathName": "englishPages/letterTracing/letterTracing", - "query": "id=letter-tracing-single", - "launchMode": "default", - "scene": null + "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", + "projectname": "doodle-mini", + "setting": { + "compileHotReLoad": true, + "urlCheck": true, + "coverView": false, + "lazyloadPlaceholderEnable": false, + "skylineRenderEnable": true, + "preloadBackgroundData": false, + "autoAudits": false, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": false, + "useStaticServer": false, + "useLanDebug": false, + "showES6CompileOption": false, + "checkInvalidKey": true, + "ignoreDevUnusedFiles": true, + "bigPackageSizeSupport": false + }, + "libVersion": "3.7.12", + "condition": { + "miniprogram": { + "list": [ + { + "name": "englishPages/letterTracing/letterTracing", + "pathName": "englishPages/letterTracing/letterTracing", + "query": "id=letter-tracing-upper-lower", + "scene": null, + "launchMode": "default" + }, + { + "name": "englishPages/letterTracing/letterTracing", + "pathName": "englishPages/letterTracing/letterTracing", + "query": "id=letter-tracing-two-column", + "launchMode": "default", + "scene": null + }, + { + "name": "englishPages/letterTracing/letterTracing", + "pathName": "englishPages/letterTracing/letterTracing", + "query": "id=letter-tracing-single", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/index/index", + "pathName": "pages/index/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "englishPages/letterTracing/letterTracing", + "pathName": "englishPages/letterTracing/letterTracing", + "query": "id=letter-tracing-single", + "launchMode": "default", + "scene": null + } + ] } - ] } - } -} \ No newline at end of file +}