import NumberDecomposeDraw from '../shared/service/numberDecomposeDraw'; import { createMathPage, CanvasDataState, } from '../shared/common/mathPageMixin'; type DecomposeMode = 'with-image' | 'decompose' | 'compose'; createMathPage({ canvas: null as Canvas | null, ctx: null as RenderingContext | null, boxHeight: 0, boxWidth: 0, drawService: null as NumberDecomposeDraw | null, decomposeData: null as { problems: Array<{ whole: number | null; // 总数(null表示组合模式需要填写) part1: number | null; // 第一个部分(null表示需要填写) part2: number | null; // 第二个部分(null表示需要填写) imageIndex?: number; // 图片索引(有图片模式需要) imageType?: 'fruits' | 'twelve-animals'; // 图片类型 }>; mode: DecomposeMode; } | null, data: { pageTitle: '10以内数的分与合', subTitle: '学习数的分解与组合', functionId: '', hasContent: false, showShareDialog: false, showTypeSelector: true, currentMode: 'with-image', currentModeName: '有图片模式', typeActions: [ { name: '有图片模式', value: 'with-image' }, { name: '分模式', value: 'decompose' }, { name: '组合模式', value: 'compose' }, ], maxNumber: 10, // 最大数字:10 或 20 } as CanvasDataState & { showTypeSelector: boolean; currentMode: DecomposeMode; currentModeName: string; typeActions: Array<{ name: string; value: DecomposeMode }>; maxNumber: number; }, onLoad(options: { id?: string; mode?: DecomposeMode }) { const functionId = options.id || 'number-decompose'; const is20Within = functionId === 'number-decompose-20'; const maxNumber = is20Within ? 20 : 10; // 20以内只有两种模式,10以内有三种模式 let defaultMode: DecomposeMode; let typeActions: Array<{ name: string; value: DecomposeMode }>; let defaultTypeName: string; if (is20Within) { // 20以内:只有分模式和组合模式 defaultMode = options.mode || 'decompose'; typeActions = [ { name: '20以内的分解', value: 'decompose' }, { name: '20以内的组合', value: 'compose' }, ]; defaultTypeName = defaultMode === 'decompose' ? '20以内的分解' : '20以内的组合'; } else { // 10以内:有图片模式、分模式、组合模式 defaultMode = options.mode || 'with-image'; typeActions = [ { name: '有图片模式', value: 'with-image' }, { name: '分模式', value: 'decompose' }, { name: '组合模式', value: 'compose' }, ]; defaultTypeName = defaultMode === 'with-image' ? '有图片模式' : defaultMode === 'decompose' ? '分模式' : '组合模式'; } this.setData({ currentMode: defaultMode, currentModeName: defaultTypeName, typeActions, maxNumber, }); this.initPageInfo( functionId, is20Within ? '20以内数的分与合' : '10以内数的分与合', ); }, onReady() { this.initCanvas({ createDrawService: ( canvas: Canvas, ctx: RenderingContext, options?: Record, ) => { return new NumberDecomposeDraw(canvas, ctx, options); }, drawServiceOptions: { subTitle: this.data.subTitle, }, onCanvasReady: () => { // Canvas 初始化完成后,生成初始数据 this.onRandom(); }, }); }, /** * 绘制Canvas内容 */ async drawCanvas() { if (!this.ctx || !this.drawService || !this.decomposeData) { return; } try { await this.drawService.draw(this.decomposeData); this.setData({ hasContent: true }); } catch (error) { console.error('绘制失败:', error); this.setData({ hasContent: false }); } }, /** * 随机生成 */ onRandom() { this.generateDecomposeData(); this.drawCanvas(); }, /** * 生成分解数据(确保不重复) */ generateDecomposeData() { const mode = this.data.currentMode; const maxNumber = this.data.maxNumber; const is20Within = maxNumber === 20; const problems: Array<{ whole: number | null; part1: number | null; part2: number | null; imageIndex?: number; imageType?: 'fruits' | 'twelve-animals'; }> = []; // 用于去重的 Set,存储题目唯一标识 // 格式:对于分解模式 "whole:part1:part2:showPart1",对于组合模式 "part1:part2" const usedKeys = new Set(); // 确定根节点的数字范围 const minWhole = is20Within ? 11 : 2; // 20以内从11开始,10以内从2开始 const maxWhole = maxNumber; if (mode === 'with-image') { // 有图片模式:9个题目,一行三列,总共三行(仅10以内) const problemCount = 9; let attempts = 0; const maxAttempts = problemCount * 50; // 最大尝试次数 while (problems.length < problemCount && attempts < maxAttempts) { attempts++; // 生成总数(2-10) const whole = Math.floor(Math.random() * (maxWhole - minWhole + 1)) + minWhole; // 随机选择一个部分(1 到 whole-1) const part1 = Math.floor(Math.random() * (whole - 1)) + 1; const part2 = whole - part1; // 随机决定显示 part1 还是 part2(另一个为 null) const showPart1 = Math.random() < 0.5; // 生成唯一标识(统一格式:part1 <= part2) const minPart = Math.min(part1, part2); const maxPart = Math.max(part1, part2); const key = `${whole}:${minPart}:${maxPart}:${showPart1}`; // 检查是否已存在 if (usedKeys.has(key)) { continue; } usedKeys.add(key); // 随机选择图片类型和索引 const imageType: 'fruits' | 'twelve-animals' = Math.random() < 0.5 ? 'fruits' : 'twelve-animals'; const maxImageIndex = imageType === 'fruits' ? 22 : 12; const imageIndex = Math.floor(Math.random() * maxImageIndex) + 1; problems.push({ whole, part1: showPart1 ? part1 : null, part2: showPart1 ? null : part2, imageIndex, imageType, }); } } else if (mode === 'decompose') { // 分模式:15个题目,一行3个,总共5行 const problemCount = 15; let attempts = 0; const maxAttempts = problemCount * 50; // 最大尝试次数 while (problems.length < problemCount && attempts < maxAttempts) { attempts++; // 生成总数(2-10 或 11-20) const whole = Math.floor(Math.random() * (maxWhole - minWhole + 1)) + minWhole; // 随机选择一个部分(1 到 whole-1) const part1 = Math.floor(Math.random() * (whole - 1)) + 1; const part2 = whole - part1; // 随机决定显示 part1 还是 part2(另一个为 null) const showPart1 = Math.random() < 0.5; // 生成唯一标识(统一格式:part1 <= part2) const minPart = Math.min(part1, part2); const maxPart = Math.max(part1, part2); const key = `${whole}:${minPart}:${maxPart}:${showPart1}`; // 检查是否已存在 if (usedKeys.has(key)) { continue; } usedKeys.add(key); problems.push({ whole, part1: showPart1 ? part1 : null, part2: showPart1 ? null : part2, }); } } else if (mode === 'compose') { // 组合模式:15个题目,一行3个,总共5行 const problemCount = 15; let attempts = 0; const maxAttempts = problemCount * 50; // 最大尝试次数 while (problems.length < problemCount && attempts < maxAttempts) { attempts++; // 生成总数(2-10 或 11-20) const whole = Math.floor(Math.random() * (maxWhole - minWhole + 1)) + minWhole; // 随机选择一个部分(1 到 whole-1) const part1 = Math.floor(Math.random() * (whole - 1)) + 1; const part2 = whole - part1; // 生成唯一标识(统一格式:part1 <= part2) const minPart = Math.min(part1, part2); const maxPart = Math.max(part1, part2); const key = `${minPart}:${maxPart}`; // 检查是否已存在 if (usedKeys.has(key)) { continue; } usedKeys.add(key); // 组合模式:两个部分都显示,根节点为 null problems.push({ whole: null, // 根节点需要填写 part1, part2, }); } } this.decomposeData = { problems, mode }; }, /** 选择类型 */ onSelectType(event: any) { const { name, value } = event.detail; this.setData({ currentMode: value, currentModeName: name, }); // 重新生成数据 this.onRandom(); }, });