import WordDrawService from '../../service/wordDrawService'; import { downloadPrint } from '../../utils/downloadPrint'; import { getWordsSvgData } from '../../utils/getWordsSvgJson'; import { WORDS } from '../../core/data/words'; import { CharacterItem } from '../../types/characterType'; import tracker from '../../utils/tracker'; import { createPage } from '../../base/pageMixin'; import { defaultShareConfig } from '../../config/config'; import { addFavorite, removeFavorite, batchCheckFavorited, } from '../../utils/favorites'; import type { DebugPublishMeta } from '../../utils/debugPublish'; import { HANDWRITING_SHEET_WORKSHEET_DEFINITIONS, HANDWRITING_SHEET_WORKSHEET_ID, getModeInfo, getPublishMetaByMode, } from './handwritingSheet.config'; const MAX_WORDS = 11; const RANDOM_WORD_COUNT = 8; const DEFAULT_WORDS = '东西南北日月山河风雨'; const CATEGORY_TAGS = WORDS.slice(0, 3).map((cat) => ({ categoryId: cat.categoryId, icon: cat.icon, categoryName: cat.categoryName, })); createPage( { canvas: null as Canvas | null, ctx: null as RenderingContext | null, wordDrawService: null as WordDrawService | null, svgWords: {} as Record, maxRow: 0 as number, maxCol: 0 as number, _favoritedMap: {} as Record, data: { pageTitle: HANDWRITING_SHEET_WORKSHEET_DEFINITIONS[0].title, functionId: HANDWRITING_SHEET_WORKSHEET_ID, hasContent: false, words: [] as string[], inputWords: [] as string[], selectedWords: [] as string[], inputValue: '', showSelectWordPopup: false, pickerCurrentTab: 0, categoryTags: CATEGORY_TAGS, showShareDialog: false, isPreviewFavorite: false, isDevEnv: false, debugPublishVisible: false, debugPublishLoading: false, debugPublishMeta: null as DebugPublishMeta | null, }, async onLoad() { this.syncDebugPublishEnv(); this.loadFavoritedMap(); this.initPageInfo( HANDWRITING_SHEET_WORKSHEET_ID, HANDWRITING_SHEET_WORKSHEET_DEFINITIONS[0].title, ); await this.loadSvgWords(); this.initDefaultWords(); }, async loadSvgWords() { try { wx.showToast({ title: '加载字体中...', icon: 'loading' }); this.svgWords = await getWordsSvgData(); wx.hideToast(); if (this.data.words.length > 0) { this.renderPracticeContent().catch(console.error); } } catch (error) { console.error('加载SVG汉字数据失败:', error); wx.hideToast(); wx.showModal({ title: '加载失败', content: '无法加载汉字数据,请检查网络连接后重试', showCancel: true, cancelText: '取消', confirmText: '重试', success: (res) => { if (res.confirm) { this.loadSvgWords(); } }, }); } }, async onCanvasReady(e: WechatMiniprogram.CustomEvent) { const { canvas, ctx } = e.detail; if (!canvas || !ctx) return; this.canvas = canvas; this.ctx = ctx; this.wordDrawService = new WordDrawService(canvas, ctx); await this.wordDrawService.drawLayout(); const { maxRow, maxCol } = this.wordDrawService.getMaxGridLayout(); this.maxRow = maxRow; this.maxCol = maxCol; if (this.data.words.length > 0) { await this.renderPracticeContent(); } }, onPreviewRefresh() { this.refreshWords(); }, async onPreviewFavorite() { const next = !this.data.isPreviewFavorite; this.setData({ isPreviewFavorite: next }); this._favoritedMap[HANDWRITING_SHEET_WORKSHEET_ID] = next; if (next) { await addFavorite(HANDWRITING_SHEET_WORKSHEET_ID); } else { await removeFavorite(HANDWRITING_SHEET_WORKSHEET_ID); } wx.showToast({ title: next ? '收藏成功' : '已取消收藏', icon: 'none', }); }, async loadFavoritedMap() { this._favoritedMap = await batchCheckFavorited([ HANDWRITING_SHEET_WORKSHEET_ID, ]); if (this._favoritedMap[HANDWRITING_SHEET_WORKSHEET_ID]) { this.setData({ isPreviewFavorite: true }); } }, onInputChange(e: WechatMiniprogram.Input) { const value = e.detail.value as string; this.syncWordsFromInput(value, false); }, onInputConfirm(e: WechatMiniprogram.Input) { const value = e.detail.value as string; this.syncWordsFromInput(value, true); }, onClearInput() { this.clearWords(); }, syncWordsFromInput(value: string, showLimitToast: boolean) { const selectedWords = this.data.selectedWords as string[]; const maxInputCount = Math.max(0, MAX_WORDS - selectedWords.length); const nextInputWords = this.splitToSingleChars(value).slice( 0, maxInputCount, ); const nextWords = [...nextInputWords, ...selectedWords]; if ( showLimitToast && nextInputWords.length < this.splitToSingleChars(value).length ) { wx.showToast({ title: `最多输入 ${maxInputCount} 个字`, icon: 'none', }); } this.setData( { inputValue: nextInputWords.join(''), inputWords: nextInputWords, words: nextWords, }, () => this.renderPracticeContent().catch(console.error), ); }, openSelectWordPopup() { this.setData({ showSelectWordPopup: true, pickerCurrentTab: 0 }); }, closeSelectWordPopup() { this.setData({ showSelectWordPopup: false }); }, onCategoryTap(e: WechatMiniprogram.TouchEvent) { const categoryId = Number(e.currentTarget.dataset.categoryId); this.setData({ showSelectWordPopup: true, pickerCurrentTab: categoryId, }); }, onChangeWord(e: WechatMiniprogram.CustomEvent) { const nextSelectedWords = e.detail.selectedWords as string[]; const inputWords = this.data.inputWords as string[]; if (inputWords.length + nextSelectedWords.length > MAX_WORDS) { wx.showToast({ title: `最多只能添加 ${MAX_WORDS} 个字`, icon: 'none', }); return; } this.setData( { selectedWords: nextSelectedWords, words: [...inputWords, ...nextSelectedWords], showSelectWordPopup: false, }, () => this.renderPracticeContent().catch(console.error), ); }, deleteWordChip(e: WechatMiniprogram.TouchEvent) { const index = Number(e.currentTarget.dataset.index); if (Number.isNaN(index)) return; const inputWords = [...(this.data.inputWords as string[])]; const selectedWords = [...(this.data.selectedWords as string[])]; if (index < inputWords.length) { inputWords.splice(index, 1); this.setData( { inputWords, inputValue: inputWords.join(''), words: [...inputWords, ...selectedWords], }, () => this.renderPracticeContent().catch(console.error), ); return; } const selectedIndex = index - inputWords.length; if (selectedIndex < 0 || selectedIndex >= selectedWords.length) return; selectedWords.splice(selectedIndex, 1); this.setData( { selectedWords, words: [...inputWords, ...selectedWords], }, () => this.renderPracticeContent().catch(console.error), ); }, clearWords() { this.setData( { words: [], inputWords: [], selectedWords: [], inputValue: '', showSelectWordPopup: false, }, () => this.renderPracticeContent().catch(console.error), ); }, initDefaultWords() { const defaults = this.getSupportedWords( this.splitToSingleChars(DEFAULT_WORDS), ).slice(0, MAX_WORDS); if (defaults.length === 0) { this.refreshWords(); return; } this.setData( { words: defaults, inputWords: defaults, selectedWords: [], inputValue: defaults.join(''), }, () => this.renderPracticeContent().catch(console.error), ); }, refreshWords() { const candidates = WORDS.slice(0, 3).reduce( (acc: string[], category) => acc.concat(category.words), [] as string[], ); const shuffled = Array.from(new Set(candidates)).sort( () => Math.random() - 0.5, ); const supported = this.getSupportedWords(shuffled).slice( 0, RANDOM_WORD_COUNT, ); if (supported.length === 0) { wx.showToast({ title: '随机到的字暂不支持,重试一下', icon: 'none', }); return; } this.setData( { words: supported, inputWords: supported, selectedWords: [], inputValue: supported.join(''), }, () => this.renderPracticeContent().catch(console.error), ); }, splitToSingleChars(text: string): string[] { const matches = text.match(/[\u4e00-\u9fff]/g); return matches || []; }, async renderPracticeContent() { if (!this.canvas || !this.wordDrawService) return; const words = this.data.words as string[]; if (words.length === 0) { await this.wordDrawService.drawContentEmpty(); this.setData({ hasContent: false }); return; } const supportedWords = this.getSupportedWords(words); if (supportedWords.length === 0) { wx.showToast({ title: '暂不支持这些汉字', icon: 'none' }); await this.wordDrawService.drawContentEmpty(); this.setData({ hasContent: false }); return; } let maxRow = this.maxRow; let maxCol = this.maxCol; if (!maxRow || !maxCol) { const layout = this.wordDrawService.getMaxGridLayout(); this.maxRow = layout.maxRow; this.maxCol = layout.maxCol; maxRow = layout.maxRow; maxCol = layout.maxCol; } const characterData = this.processCharacterLayout( supportedWords, maxRow, maxCol, ); await this.wordDrawService.drawPracticeContent(characterData); this.setData({ hasContent: characterData.length > 0 }); }, getSupportedWords(words: string[]): string[] { return words.filter((word) => this.svgWords[word]); }, processCharacterLayout( words: string[], maxRow: number, maxCol: number, ): CharacterItem[] { let rowIndex = 0; const characterData: CharacterItem[] = []; words.forEach((word: string) => { const strokes = this.svgWords[word]; const strokeCount = strokes.length; const totalCells = 1 + strokeCount; const totalRows = Math.ceil(totalCells / maxCol); rowIndex += totalRows; if (rowIndex <= maxRow) { characterData.push({ character: word, strokes }); } }); return characterData; }, async exportToPrint() { await downloadPrint(this.canvas, { errorToast: '请先生成练字贴', trackerName: '练字贴', }); }, onShare() {}, onShareAppMessage() { tracker.reportShare('练字贴'); return { ...defaultShareConfig, path: `/chinesePages/handwritingSheet/handwritingSheet?id=${HANDWRITING_SHEET_WORKSHEET_ID}`, }; }, onShareTimeline() { tracker.reportShare('练字贴'); return { ...defaultShareConfig, query: `id=${HANDWRITING_SHEET_WORKSHEET_ID}`, }; }, onCloseShareDialog() { this.setData({ showShareDialog: false }); }, onShareSuccess() { this.setData({ showShareDialog: false }); }, getPublishMeta(): DebugPublishMeta { const meta = getPublishMetaByMode(HANDWRITING_SHEET_WORKSHEET_ID); if (!meta) { throw new Error('当前题型配置不存在'); } return meta; }, }, { shareConfig: defaultShareConfig, pageInfoLookup: getModeInfo, }, );