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, 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'; this.applyWorksheet(worksheetId); }, 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, }); }, 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(); }, applyWorksheet( worksheetId: string, options?: { preserveLetter?: boolean; redraw?: boolean; }, ) { 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'; const selectedLetter = options?.preserveLetter ? this.data.selectedLetter : normalizePick(merged.selectedLetter); const letterCaseLower = traceMode === 'single-letter' ? merged.letterCase === 'lower' : this.data.letterCaseLower; 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; this.drawService.options.subTitle = this.data.subTitle || ''; } if (options?.redraw) { this.drawCanvas(); } }, ); }, }, { shareConfig: defaultShareConfig, pageInfoLookup, }, ); function normalizePick(c?: string): string { if (!c || c.length === 0) return 'A'; return c.trim().toUpperCase().charAt(0); }