Files
doodle-mini/miniprogram/englishPages/letterTracing/letterTracing.ts
T

277 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import LetterTracingDraw from '../shared/draw/letterTracingDraw';
import { createPage, type CanvasDataState } from '../../base/pageMixin';
import { defaultShareConfig } from '../../config/config';
import { getWorksheetById } from '../../core/data/worksheets';
import {
generateLetterTracing,
mergeLetterTracingConfig,
normalizeLetter,
type LetterTracingGeneratorConfig,
type LetterTracingMode,
} from '../shared/generators/letter-tracing-generator';
import { LETTERS_UPPER } from '../shared/data/alphabet';
import { loadLetterFont } from '../shared/draw/drawTools';
/** 各 worksheetId 对应的页面标题和描述 */
const PAGE_META: Record<string, { title: string; desc: string }> = {
'letter-tracing-single': {
title: '看图描红',
desc: '配图、例句与六行描红',
},
'letter-tracing-full': {
title: '分栏对照',
desc: '半组字母左大写右小写',
},
'letter-tracing-upper-lower': {
title: '字母总览',
desc: 'Uppercase / Lowercase 总览',
},
'letter-tracing-two-column': {
title: '大小写配对',
desc: '左 AM、右 NZ 配对描红',
},
};
/** 根据 worksheetId 查找页面元信息 */
function pageInfoLookup(id: string) {
return PAGE_META[id];
}
/** 模式选择器的选项列表 */
const MODE_OPTIONS = [
{ id: 'letter-tracing-single', label: '看图描红' },
{ id: 'letter-tracing-full', label: '分栏对照' },
{ id: 'letter-tracing-upper-lower', label: '字母总览' },
{ id: 'letter-tracing-two-column', label: '大小写配对' },
] as const;
type PageData = CanvasDataState & {
worksheetId: string;
traceMode: LetterTracingMode;
selectedLetter: string;
letterCaseLower: boolean;
showLetterPicker: boolean;
showCaseToggle: boolean;
showNextLetter: boolean;
letterGrid: string[];
modeOptions: ReadonlyArray<{ id: string; label: string }>;
};
createPage(
{
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as LetterTracingDraw | null,
data: {
pageTitle: '字母描红',
functionId: '',
hasContent: false,
showShareDialog: false,
worksheetId: 'letter-tracing-single',
traceMode: 'picture-tracing',
selectedLetter: 'A',
letterCaseLower: false,
showLetterPicker: true,
showCaseToggle: false,
showNextLetter: true,
letterGrid: LETTERS_UPPER,
modeOptions: [...MODE_OPTIONS],
} as unknown as PageData,
/** 页面加载:从路由参数获取 worksheetId、letter、case 并应用,同时预加载字体 */
onLoad(options: {
id?: string;
letter?: string;
case?: 'upper' | 'lower';
}) {
loadLetterFont().catch(() => {});
const worksheetId =
options.id && PAGE_META[options.id]
? options.id
: 'letter-tracing-single';
this.applyWorksheet(worksheetId);
const updates: Partial<PageData> = {};
if (options.letter) {
const normalized = normalizeLetter(options.letter);
if (normalized) updates.selectedLetter = normalized;
}
if (options.case === 'lower') {
updates.letterCaseLower = true;
} else if (options.case === 'upper') {
updates.letterCaseLower = false;
}
if (Object.keys(updates).length > 0) {
this.setData(
updates as Partial<PageData> & WechatMiniprogram.IAnyObject,
);
}
},
/** 用户点击模式选择器时切换 worksheet */
onSelectMode(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string | undefined;
if (!id || id === this.data.worksheetId) return;
this.applyWorksheet(id, {
preserveLetter: true,
redraw: true,
});
},
/** 页面就绪:初始化 Canvas → 首次绘制(标题行前缀使用系统 emoji,无需 loadFontFace */
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, unknown>,
) => new LetterTracingDraw(canvas, ctx, options),
drawServiceOptions: {
title: this.data.pageTitle,
},
onCanvasReady: () => {
this.drawCanvas();
},
});
},
/** 构建运行时生成器配置:合并 worksheet 预设 + 当前页面状态 */
buildRuntimeConfig(): LetterTracingGeneratorConfig {
const def = getWorksheetById(this.data.worksheetId);
const base = (def?.generatorConfig ??
{}) as Partial<LetterTracingGeneratorConfig>;
const letterCase =
this.data.traceMode === 'picture-tracing'
? (base.letterCase ?? 'upper')
: this.data.letterCaseLower
? 'lower'
: 'upper';
return mergeLetterTracingConfig(base, {
selectedLetter: this.data.selectedLetter,
letterCase,
...(this.data.traceMode === 'column-compare'
? {
alphabetHalf: this.data.letterCaseLower
? 'N-Z'
: 'A-M',
}
: {}),
});
},
/** 执行 Canvas 绘制:等待字体就绪 → 生成数据 → 调用 drawService 渲染 */
async drawCanvas() {
if (!this.ctx || !this.drawService) return;
try {
await loadLetterFont();
const config = this.buildRuntimeConfig();
const data = generateLetterTracing(config);
console.log('drawCanvas data', data);
await (this.drawService as LetterTracingDraw).draw(data);
this.setData({ hasContent: true });
} catch (e) {
console.error('letterTracing draw failed', e);
this.setData({ hasContent: false });
}
},
/** 用户在字母网格中选择了一个字母 */
onSelectLetter(e: WechatMiniprogram.TouchEvent) {
const letter = e.currentTarget.dataset.letter as string;
if (!letter) return;
this.setData({ selectedLetter: letter });
this.drawCanvas();
},
/** 切换为大写模式并重绘 */
onSelectCaseUpper() {
this.setData({ letterCaseLower: false });
this.drawCanvas();
},
/** 切换为小写模式并重绘 */
onSelectCaseLower() {
this.setData({ letterCaseLower: true });
this.drawCanvas();
},
/** 跳到下一个字母(循环 A→Z→A)并重绘 */
onNextLetter() {
const list = LETTERS_UPPER;
const i = list.indexOf(this.data.selectedLetter);
const next = list[(i + 1) % list.length];
this.setData({ selectedLetter: next });
this.drawCanvas();
},
/**
* 应用指定的 worksheet 配置到页面状态
* 根据 worksheet 的 generatorConfig 决定 UI 控件的显隐(字母选择器、大小写切换等)
* @param worksheetId - 要应用的 worksheet ID
* @param options.preserveLetter - 是否保留当前选中的字母(模式切换时使用)
* @param options.redraw - 是否立即重绘 Canvas
*/
applyWorksheet(
worksheetId: string,
options?: {
preserveLetter?: boolean;
redraw?: boolean;
},
) {
const def = getWorksheetById(worksheetId);
console.log('def', def);
const base = (def?.generatorConfig ??
{}) as Partial<LetterTracingGeneratorConfig>;
const merged = mergeLetterTracingConfig(base, {});
const traceMode = merged.mode;
const showLetterPicker = traceMode === 'picture-tracing';
const showCaseToggle = traceMode === 'column-compare';
const showNextLetter = traceMode === 'picture-tracing';
const selectedLetter = options?.preserveLetter
? (this.data.selectedLetter ?? 'A')
: normalizeLetter(merged.selectedLetter ?? '');
const letterCaseLower =
traceMode === 'column-compare'
? merged.alphabetHalf === 'N-Z'
: traceMode === 'picture-tracing'
? merged.letterCase === 'lower'
: false;
this.setData(
{
worksheetId,
functionId: worksheetId,
traceMode,
selectedLetter,
letterCaseLower,
showLetterPicker,
showCaseToggle,
showNextLetter,
},
() => {
this.initPageInfo(
worksheetId,
PAGE_META[worksheetId]?.title ?? '字母描红',
);
if (this.drawService) {
this.drawService.options.title = this.data.pageTitle;
}
if (options?.redraw) {
this.drawCanvas();
}
},
);
},
},
{
shareConfig: defaultShareConfig,
pageInfoLookup,
},
);