import LetterTracingDraw from './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 './generators/letter-tracing-generator'; import { LETTERS_UPPER, LETTERS_PAIRS } from '../shared/data/alphabet'; import { DEFAULT_LETTER_PROFILE, getFontProfile, type FontProfile, } from '../shared/data/fontProfiles'; import { loadLetterFont } from '../shared/draw/drawTools'; /** 练习模式定义(顺序与产品文档一致),同时用于模式选择器和页面元信息 */ const MODES = [ { id: 'letter-tracing-single', icon: 'start-a', label: '默认字帖', desc: '配图、例句与描红', }, { id: 'letter-tracing-upper-lower', icon: 'draw-o', // label: '字母总览', label: '基础描红', desc: 'Uppercase / Lowercase 总览', }, { id: 'letter-tracing-case-pairing', icon: 'font-size', label: '大小写对照', desc: '半组字母左大写右小写', }, { id: 'letter-tracing-two-column', icon: 'two-columns', label: '两列描红', desc: '左 A–M、右 N–Z 配对描红', }, { id: 'letter-tracing-half', icon: 'square-half', label: '13字母半表', desc: '每行一个字母,13 字母半表', }, { id: 'letter-tracing-three', icon: 'ABC-list', label: '三字母精练', desc: '每页聚焦 3 个字母深度书写', }, ] as const; const MODE_MAP = Object.fromEntries(MODES.map((m) => [m.id, m])) as Record< string, (typeof MODES)[number] >; /** 三字母精练分组:26 字母每 3 个一组 */ const TRIPLE_GROUPS: { label: string; letters: string[] }[] = []; for (let i = 0; i < LETTERS_UPPER.length; i += 3) { const group = LETTERS_UPPER.slice(i, i + 3); const label = group.length > 1 ? `${group[0]}–${group[group.length - 1]}` : group[0]; TRIPLE_GROUPS.push({ label, letters: [...group] }); } /** 根据 worksheetId 查找页面元信息 */ function pageInfoLookup(id: string) { const m = MODE_MAP[id]; return m ? { title: m.label, desc: m.desc } : undefined; } type PageData = CanvasDataState & { worksheetId: string; traceMode: LetterTracingMode; selectedLetter: string; selectedLetterPair: string; letterCaseLower: boolean; showLetterPicker: boolean; showCaseToggle: boolean; showNextLetter: boolean; showTripleGroupPicker: boolean; tripleGroups: { label: string; letters: string[] }[]; selectedTripleGroupIdx: number; letterGrid: string[]; modeOptions: ReadonlyArray<{ id: string; label: string; desc: string }>; }; createPage( { canvas: null as Canvas | null, ctx: null as RenderingContext | null, boxHeight: 0, boxWidth: 0, drawService: null as LetterTracingDraw | null, fontProfile: DEFAULT_LETTER_PROFILE as FontProfile, data: { pageTitle: '字母描红', functionId: '', hasContent: false, showShareDialog: false, worksheetId: 'letter-tracing-single', traceMode: 'letter-tracing-single', selectedLetter: 'A', selectedLetterPair: 'Aa', letterCaseLower: false, showLetterPicker: true, showCaseToggle: false, showNextLetter: true, showTripleGroupPicker: false, tripleGroups: TRIPLE_GROUPS, selectedTripleGroupIdx: 0, letterGrid: LETTERS_PAIRS, modeOptions: [...MODES], } as unknown as PageData, /** 页面加载:从路由参数获取 worksheetId、letter、case、font 并应用,同时预加载字体 */ onLoad(options: { id?: string; letter?: string; case?: 'upper' | 'lower'; font?: string; }) { if (options.font) { this.fontProfile = getFontProfile(options.font); } loadLetterFont(this.fontProfile).catch(() => {}); const worksheetId = options.id && MODE_MAP[options.id] ? options.id : 'letter-tracing-single'; this.applyWorksheet(worksheetId); const updates: Partial = {}; if (options.letter) { const normalized = normalizeLetter(options.letter); if (normalized) { updates.selectedLetter = normalized; updates.selectedLetterPair = `${normalized}${normalized.toLowerCase()}`; } } 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 & 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, ) => 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; const letterCase = this.data.traceMode === 'letter-tracing-single' ? (base.letterCase ?? 'upper') : this.data.letterCaseLower ? 'lower' : 'upper'; const needsHalf = this.data.traceMode === 'letter-tracing-case-pairing' || this.data.traceMode === 'letter-tracing-half'; const isTriple = this.data.traceMode === 'letter-tracing-three'; return mergeLetterTracingConfig(base, { selectedLetter: this.data.selectedLetter, letterCase, ...(needsHalf ? { alphabetHalf: this.data.letterCaseLower ? 'N-Z' : 'A-M', } : {}), ...(isTriple ? { tripleLetters: TRIPLE_GROUPS[this.data.selectedTripleGroupIdx] ?.letters, } : {}), }); }, /** 执行 Canvas 绘制:等待字体就绪 → 生成数据 → 调用 drawService 渲染 */ async drawCanvas() { if (!this.ctx || !this.drawService) return; try { await loadLetterFont(this.fontProfile); 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 }); } }, /** 用户在字母网格中选择了一个字母(item 为 "Aa" 格式,取首字母作为 selectedLetter) */ onSelectLetter(e: WechatMiniprogram.TouchEvent) { const raw = e.currentTarget.dataset.letter as string; if (!raw) return; const letter = raw.charAt(0).toUpperCase(); this.setData({ selectedLetter: letter, selectedLetterPair: `${letter}${letter.toLowerCase()}`, }); 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, selectedLetterPair: `${next}${next.toLowerCase()}`, }); this.drawCanvas(); }, /** 用户在三字母分组选择器中选择了一组 */ onSelectTripleGroup(e: WechatMiniprogram.TouchEvent) { const idx = Number(e.currentTarget.dataset.idx); if (isNaN(idx) || idx === this.data.selectedTripleGroupIdx) return; this.setData({ selectedTripleGroupIdx: idx }); 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; const merged = mergeLetterTracingConfig(base, {}); const traceMode = merged.mode; const showLetterPicker = traceMode === 'letter-tracing-single'; const showCaseToggle = traceMode === 'letter-tracing-case-pairing' || traceMode === 'letter-tracing-half'; const showNextLetter = traceMode === 'letter-tracing-single'; const showTripleGroupPicker = traceMode === 'letter-tracing-three'; const selectedLetter = options?.preserveLetter ? (this.data.selectedLetter ?? 'A') : normalizeLetter(merged.selectedLetter ?? ''); const letterCaseLower = traceMode === 'letter-tracing-case-pairing' || traceMode === 'letter-tracing-half' ? merged.alphabetHalf === 'N-Z' : traceMode === 'letter-tracing-single' ? merged.letterCase === 'lower' : false; this.setData( { worksheetId, functionId: worksheetId, traceMode, selectedLetter, selectedLetterPair: `${selectedLetter}${selectedLetter.toLowerCase()}`, letterCaseLower, showLetterPicker, showCaseToggle, showNextLetter, showTripleGroupPicker, }, () => { this.initPageInfo( worksheetId, MODE_MAP[worksheetId]?.label ?? '字母描红', ); if (this.drawService) { this.drawService.options.title = this.data.pageTitle; } if (options?.redraw) { this.drawCanvas(); } }, ); }, }, { shareConfig: defaultShareConfig, pageInfoLookup, }, );