import LetterTracingDraw from '../shared/service/letterTracingDraw'; import { createPage, type CanvasDataState } from '../../base/pageMixin'; import { defaultShareConfig } from '../../config/config'; import { getWorksheetById } from '../../core/data/worksheets'; import { generateLetterTracing, mergeLetterTracingConfig, type LetterTracingGeneratorConfig, type LetterTracingMode, } from '../../core/generators/letter-tracing-generator'; import { LETTERS_UPPER } from '../../core/data/alphabet'; const PAGE_META: Record = { 'letter-tracing-single': { title: '字母学习页', desc: '单字母配图与描红', }, 'letter-tracing-full': { title: '26 字母描红', desc: '整页字母书写练习', }, 'letter-tracing-upper-lower': { title: '大小写字母对照', desc: '一页认识 A-Z 与 a-z', }, 'letter-tracing-two-column': { title: '字母描红(两列)', desc: 'A–M / N–Z 两列练习', }, }; 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: 'single-letter', selectedLetter: 'A', letterCaseLower: false, showLetterPicker: true, showCaseToggle: false, showNextLetter: true, letterGrid: LETTERS_UPPER, modeOptions: [...MODE_OPTIONS], } as unknown as PageData, onLoad(options: { id?: string }) { const worksheetId = options.id && PAGE_META[options.id] ? options.id : 'letter-tracing-single'; const def = getWorksheetById(worksheetId); const base = (def?.generatorConfig ?? {}) as Partial; const merged = mergeLetterTracingConfig(base, {}); const traceMode = merged.mode; const showLetterPicker = traceMode === 'single-letter'; const showCaseToggle = traceMode === 'full-alphabet' || traceMode === 'two-column'; const showNextLetter = traceMode === 'single-letter'; this.setData({ worksheetId, functionId: worksheetId, traceMode, selectedLetter: normalizePick(merged.selectedLetter), letterCaseLower: merged.letterCase === 'lower', showLetterPicker, showCaseToggle, showNextLetter, }); this.initPageInfo(worksheetId, PAGE_META[worksheetId]?.title ?? '字母描红'); }, onSelectMode(e: WechatMiniprogram.TouchEvent) { const id = e.currentTarget.dataset.id as string | undefined; if (!id || id === this.data.worksheetId) return; wx.redirectTo({ url: `/englishPages/letterTracing/letterTracing?id=${id}`, }); }, onReady() { this.initCanvas({ createDrawService: ( canvas: Canvas, ctx: RenderingContext, options?: Record, ) => new LetterTracingDraw(canvas, ctx, options), drawServiceOptions: { title: this.data.pageTitle, subTitle: this.data.subTitle || '', }, onCanvasReady: () => { this.drawCanvas(); }, }); }, buildRuntimeConfig(): LetterTracingGeneratorConfig { const def = getWorksheetById(this.data.worksheetId); const base = (def?.generatorConfig ?? {}) as Partial; const letterCase = this.data.traceMode === 'single-letter' ? (base.letterCase ?? 'upper') : this.data.letterCaseLower ? 'lower' : 'upper'; return mergeLetterTracingConfig(base, { selectedLetter: this.data.selectedLetter, letterCase, }); }, async drawCanvas() { if (!this.ctx || !this.drawService) return; try { const config = this.buildRuntimeConfig(); const data = generateLetterTracing(config); 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(); }, 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(); }, }, { shareConfig: defaultShareConfig, pageInfoLookup, }, ); function normalizePick(c?: string): string { if (!c || c.length === 0) return 'A'; return c.trim().toUpperCase().charAt(0); }