diff --git a/miniprogram/app.json b/miniprogram/app.json
index b2f4855..81efe87 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -16,7 +16,8 @@
"addition/addition",
"missingNumber/missingNumber",
"compare/compare",
- "countingSelect/countingSelect"
+ "countingSelect/countingSelect",
+ "numberDecompose/numberDecompose"
],
"independent": false
}
diff --git a/miniprogram/constants/mathFunctions.ts b/miniprogram/constants/mathFunctions.ts
index f73b9b0..14ecb9b 100644
--- a/miniprogram/constants/mathFunctions.ts
+++ b/miniprogram/constants/mathFunctions.ts
@@ -66,6 +66,13 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
desc: '在数字序列中找出并填写缺失的数字',
icon: '❓',
},
+ {
+ id: 'number-decompose',
+ page: 'numberDecompose',
+ title: '10以内数的分与合',
+ desc: '学习数的分解与组合',
+ icon: '🔢',
+ },
// 第四阶段:简单运算(从易到难)
{
id: 'addition-5',
diff --git a/miniprogram/mathPages/numberDecompose/numberDecompose.json b/miniprogram/mathPages/numberDecompose/numberDecompose.json
new file mode 100644
index 0000000..bd1bf92
--- /dev/null
+++ b/miniprogram/mathPages/numberDecompose/numberDecompose.json
@@ -0,0 +1,12 @@
+{
+ "navigationBarTitleText": "10以内数的分与合",
+ "navigationBarBackgroundColor": "#FFD719",
+ "homeButton": true,
+ "backgroundColor": "#F6F6F6",
+ "enablePullDownRefresh": false,
+ "usingComponents": {
+ "share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
+ "math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons",
+ "math-type-selector": "../components/math-type-selector/math-type-selector"
+ }
+}
diff --git a/miniprogram/mathPages/numberDecompose/numberDecompose.less b/miniprogram/mathPages/numberDecompose/numberDecompose.less
new file mode 100644
index 0000000..12602d2
--- /dev/null
+++ b/miniprogram/mathPages/numberDecompose/numberDecompose.less
@@ -0,0 +1 @@
+@import '../common/mathPage.less';
\ No newline at end of file
diff --git a/miniprogram/mathPages/numberDecompose/numberDecompose.ts b/miniprogram/mathPages/numberDecompose/numberDecompose.ts
new file mode 100644
index 0000000..2e0775d
--- /dev/null
+++ b/miniprogram/mathPages/numberDecompose/numberDecompose.ts
@@ -0,0 +1,211 @@
+import NumberDecomposeDraw from '../service/numberDecomposeDraw';
+import {
+ getMathPageCommonMethods,
+ CanvasDataState,
+} from '../common/mathPageMixin';
+
+// 获取公共方法
+const commonMethods = getMathPageCommonMethods({
+ pagePath: 'numberDecompose/numberDecompose',
+});
+
+type DecomposeMode = 'with-image' | 'decompose' | 'compose';
+
+Page({
+ 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,
+ currentType: 'with-image',
+ currentTypeName: '有图片模式',
+ typeActions: [
+ { name: '有图片模式', value: 'with-image' },
+ { name: '分模式', value: 'decompose' },
+ { name: '组合模式', value: 'compose' },
+ ],
+ } as CanvasDataState & {
+ showTypeSelector: boolean;
+ currentType: DecomposeMode;
+ currentTypeName: string;
+ typeActions: Array<{ name: string; value: DecomposeMode }>;
+ },
+
+ onLoad(options: { id?: string; mode?: DecomposeMode }) {
+ const functionId = options.id || 'number-decompose';
+ const mode = options.mode || 'with-image';
+ const currentTypeName =
+ mode === 'with-image'
+ ? '有图片模式'
+ : mode === 'decompose'
+ ? '分模式'
+ : '组合模式';
+ this.setData({
+ currentType: mode,
+ currentTypeName,
+ });
+ this.initPageInfo(functionId, '10以内数的分与合');
+ },
+
+ onReady() {
+ this.initCanvas({
+ createDrawService: (canvas, ctx, options) => {
+ 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.currentType;
+ const problems: Array<{
+ whole: number;
+ part1: number | null;
+ part2: number | null;
+ imageIndex?: number;
+ imageType?: 'fruits' | 'twelve-animals';
+ }> = [];
+
+ if (mode === 'with-image') {
+ // 有图片模式:6个题目,一行两列,总共三行
+ const problemCount = 6;
+ for (let i = 0; i < problemCount; i++) {
+ // 生成总数(2-10)
+ const whole = Math.floor(Math.random() * 9) + 2;
+ // 随机选择一个部分(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;
+ const problem = {
+ whole,
+ part1: showPart1 ? part1 : null,
+ part2: showPart1 ? null : part2,
+ };
+
+ // 随机选择图片类型和索引
+ 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({
+ ...problem,
+ imageIndex,
+ imageType,
+ });
+ }
+ } else if (mode === 'decompose') {
+ // 分模式:12个题目,一行3个,总共4行
+ const problemCount = 15;
+ for (let i = 0; i < problemCount; i++) {
+ // 生成总数(2-10)
+ const whole = Math.floor(Math.random() * 9) + 2;
+ // 随机选择一个部分(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;
+ problems.push({
+ whole,
+ part1: showPart1 ? part1 : null,
+ part2: showPart1 ? null : part2,
+ });
+ }
+ } else if (mode === 'compose') {
+ // 组合模式:12个题目,一行3个,总共4行
+ const problemCount = 15;
+ for (let i = 0; i < problemCount; i++) {
+ // 生成总数(2-10)
+ const whole = Math.floor(Math.random() * 9) + 2;
+ // 随机选择一个部分(1 到 whole-1)
+ const part1 = Math.floor(Math.random() * (whole - 1)) + 1;
+ const part2 = whole - part1;
+
+ // 组合模式:两个部分都显示,根节点为 null
+ problems.push({
+ whole: null, // 根节点需要填写
+ part1,
+ part2,
+ });
+ }
+ }
+
+ this.decomposeData = { problems, mode };
+ },
+
+ // ========== 使用公共方法 ==========
+ initCanvas: commonMethods.initCanvas,
+ exportToPrint: commonMethods.exportToPrint,
+ onShareAppMessage: commonMethods.onShareAppMessage,
+ onShareTimeline: commonMethods.onShareTimeline,
+ onCloseShareDialog: commonMethods.onCloseShareDialog,
+ onShareSuccess: commonMethods.onShareSuccess,
+ initPageInfo: commonMethods.initPageInfo,
+
+ /** 选择类型 */
+ onSelectType(event: any) {
+ const { name, value } = event.detail;
+ this.setData({
+ currentType: value,
+ currentTypeName: name,
+ });
+ // 重新生成数据
+ this.onRandom();
+ },
+});
diff --git a/miniprogram/mathPages/numberDecompose/numberDecompose.wxml b/miniprogram/mathPages/numberDecompose/numberDecompose.wxml
new file mode 100644
index 0000000..15a8a9c
--- /dev/null
+++ b/miniprogram/mathPages/numberDecompose/numberDecompose.wxml
@@ -0,0 +1,31 @@
+
+
+ 预览打印效果
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/mathPages/service/numberDecomposeContentDraw.ts b/miniprogram/mathPages/service/numberDecomposeContentDraw.ts
new file mode 100644
index 0000000..e55f4a3
--- /dev/null
+++ b/miniprogram/mathPages/service/numberDecomposeContentDraw.ts
@@ -0,0 +1,497 @@
+import { getImage } from '../../utils/index';
+import { getRandomNumberColor } from '../../constants/colors';
+
+interface DrawNumberDecomposeContentParams {
+ canvas: WechatMiniprogram.Canvas;
+ ctx: RenderingContext;
+ decomposeData: {
+ problems: Array<{
+ whole: number | null;
+ part1: number | null;
+ part2: number | null;
+ imageIndex?: number;
+ imageType?: 'fruits' | 'twelve-animals';
+ }>;
+ mode: 'with-image' | 'decompose' | 'compose';
+ };
+ canvasWidth: number;
+ startY: number;
+}
+
+/**
+ * 绘制圆角矩形(虚线边框)
+ */
+function drawRoundedRect(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ radius: number,
+ isDashed: boolean = false,
+) {
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+
+ if (isDashed) {
+ ctx.setLineDash([6 / 3, 6 / 3]); // 约2px的虚线
+ } else {
+ ctx.setLineDash([]);
+ }
+ ctx.stroke();
+ ctx.setLineDash([]); // 重置为实线
+}
+
+/**
+ * 绘制数的分与合内容区域
+ */
+export async function drawNumberDecomposeContent({
+ canvas,
+ ctx,
+ decomposeData,
+ canvasWidth,
+ startY,
+}: DrawNumberDecomposeContentParams): Promise {
+ const { problems, mode } = decomposeData;
+
+ if (mode === 'with-image') {
+ await drawWithImageMode(canvas, ctx, problems, canvasWidth, startY);
+ } else if (mode === 'decompose') {
+ await drawDecomposeOrComposeMode(
+ ctx,
+ problems,
+ canvasWidth,
+ startY,
+ false,
+ );
+ } else if (mode === 'compose') {
+ await drawDecomposeOrComposeMode(
+ ctx,
+ problems,
+ canvasWidth,
+ startY,
+ true,
+ );
+ }
+}
+
+/**
+ * 有图片模式:一行两列,总共三行(6个题目)
+ */
+async function drawWithImageMode(
+ canvas: WechatMiniprogram.Canvas,
+ ctx: RenderingContext,
+ problems: Array<{
+ whole: number | null;
+ part1: number | null;
+ part2: number | null;
+ imageIndex?: number;
+ imageType?: 'fruits' | 'twelve-animals';
+ }>,
+ canvasWidth: number,
+ startY: number,
+) {
+ const leftMargin = 30;
+ const rightMargin = 30;
+ const topMargin = 20;
+ const boxSpacing = 40;
+ const rowSpacing = 40;
+
+ // 计算每个框的尺寸
+ const availableWidth = canvasWidth - leftMargin - rightMargin;
+ const boxWidth = (availableWidth - boxSpacing) / 2; // 2列,1个间距
+ const imageAreaHeight = 116; // 图片区域高度
+ const treeAreaHeight = 80; // 二叉树区域高度
+
+ let currentY = startY + topMargin;
+
+ // 绘制2列3行
+ for (let row = 0; row < 3; row++) {
+ for (let col = 0; col < 2; col++) {
+ const problemIndex = row * 2 + col;
+ if (problemIndex >= problems.length) {
+ continue;
+ }
+
+ const problem = problems[problemIndex];
+ const boxX = leftMargin + col * (boxWidth + boxSpacing);
+ const boxY =
+ currentY +
+ row * (imageAreaHeight + treeAreaHeight + rowSpacing);
+
+ // 绘制一个完整的题目框
+ await drawWithImageProblem(
+ ctx,
+ canvas,
+ problem,
+ boxX,
+ boxY,
+ boxWidth,
+ imageAreaHeight,
+ treeAreaHeight,
+ );
+ }
+ }
+}
+
+/**
+ * 分模式/组合模式:一行3个,总共5行(15个题目)
+ * @param isInverted true 为组合模式(倒置二叉树),false 为分模式(正常二叉树)
+ */
+async function drawDecomposeOrComposeMode(
+ ctx: RenderingContext,
+ problems: Array<{
+ whole: number | null;
+ part1: number | null;
+ part2: number | null;
+ }>,
+ canvasWidth: number,
+ startY: number,
+ isInverted: boolean,
+) {
+ const leftMargin = 30;
+ const rightMargin = 30;
+ const topMargin = 20;
+ const boxSpacing = 15;
+ const rowSpacing = 140;
+
+ // 计算每个框的尺寸
+ const availableWidth = canvasWidth - leftMargin - rightMargin;
+ const boxWidth = (availableWidth - boxSpacing * 2) / 3; // 3列,2个间距
+ const boxHeight = 80; // 二叉树区域高度
+
+ let currentY = startY + topMargin;
+
+ // 绘制3列5行
+ for (let row = 0; row < 5; row++) {
+ for (let col = 0; col < 3; col++) {
+ const problemIndex = row * 3 + col;
+ if (problemIndex >= problems.length) {
+ continue;
+ }
+
+ const problem = problems[problemIndex];
+ const boxX = leftMargin + col * (boxWidth + boxSpacing);
+ const boxY = currentY + row * rowSpacing;
+
+ // 绘制二叉树
+ drawTree(ctx, problem, boxX, boxY, boxWidth, boxHeight, isInverted);
+ }
+ }
+}
+
+/**
+ * 绘制有图片模式的单个题目
+ */
+async function drawWithImageProblem(
+ ctx: RenderingContext,
+ canvas: WechatMiniprogram.Canvas,
+ problem: {
+ whole: number | null;
+ part1: number | null;
+ part2: number | null;
+ imageIndex?: number;
+ imageType?: 'fruits' | 'twelve-animals';
+ },
+ boxX: number,
+ boxY: number,
+ boxWidth: number,
+ imageAreaHeight: number,
+ treeAreaHeight: number,
+) {
+ const borderRadius = 12;
+
+ // 绘制实线框(只绘制图片区域的框)
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 1;
+ drawRoundedRect(
+ ctx,
+ boxX,
+ boxY,
+ boxWidth,
+ imageAreaHeight,
+ borderRadius,
+ false,
+ );
+
+ // 图片区域(增加底部间距,避免与二叉树根节点重叠)
+ const imageBottomPadding = 20; // 图片区域底部额外间距
+ const effectiveImageHeight = imageAreaHeight - imageBottomPadding;
+
+ if (problem.imageIndex && problem.imageType && problem.whole) {
+ await drawImagesInBox(
+ ctx,
+ canvas,
+ problem.whole,
+ problem.imageIndex,
+ problem.imageType,
+ boxX,
+ boxY,
+ boxWidth,
+ effectiveImageHeight,
+ );
+ }
+
+ // 二叉树区域(增加与图片区域的间距)
+ const treeTopSpacing = -5; // 二叉树区域顶部间距
+ const treeY = boxY + imageAreaHeight + treeTopSpacing;
+ drawTree(
+ ctx,
+ problem,
+ boxX,
+ treeY,
+ boxWidth,
+ treeAreaHeight,
+ false, // 不是倒置
+ );
+}
+
+/**
+ * 绘制二叉树结构
+ * @param isInverted 是否倒置(组合模式为true)
+ */
+function drawTree(
+ ctx: RenderingContext,
+ problem: {
+ whole: number | null;
+ part1: number | null;
+ part2: number | null;
+ },
+ boxX: number,
+ boxY: number,
+ boxWidth: number,
+ boxHeight: number,
+ isInverted: boolean,
+) {
+ const nodeRadius = 18;
+ const nodeSpacing = 40; // 节点之间的水平间距
+ const verticalSpacing = 70; // 根节点和子节点的垂直间距
+
+ if (isInverted) {
+ // 组合模式:倒置二叉树
+ // 顶部:两个子节点(part1, part2)
+ // 底部:根节点(whole,需要填写)
+
+ const centerX = boxX + boxWidth / 2;
+ const topY = boxY + boxHeight / 2 - verticalSpacing / 2;
+ const bottomY = boxY + boxHeight / 2 + verticalSpacing / 2;
+
+ // 绘制两个子节点(顶部)
+ const leftNodeX = centerX - nodeSpacing / 2 - nodeRadius;
+ const rightNodeX = centerX + nodeSpacing / 2 + nodeRadius;
+
+ // 左子节点
+ drawNode(ctx, leftNodeX, topY, nodeRadius, problem.part1);
+ // 右子节点
+ drawNode(ctx, rightNodeX, topY, nodeRadius, problem.part2);
+
+ // 绘制根节点(底部)
+ drawNode(ctx, centerX, bottomY, nodeRadius, problem.whole);
+
+ // 绘制连接线(从子节点到根节点)
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.moveTo(leftNodeX, topY + nodeRadius);
+ ctx.lineTo(centerX, bottomY - nodeRadius);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(rightNodeX, topY + nodeRadius);
+ ctx.lineTo(centerX, bottomY - nodeRadius);
+ ctx.stroke();
+ } else {
+ // 分解模式:正常二叉树
+ // 顶部:根节点(whole)
+ // 底部:两个子节点(part1, part2,其中一个需要填写)
+
+ const centerX = boxX + boxWidth / 2;
+ const topY = boxY + boxHeight / 2 - verticalSpacing / 2;
+ const bottomY = boxY + boxHeight / 2 + verticalSpacing / 2;
+
+ // 绘制根节点(顶部)
+ drawNode(ctx, centerX, topY, nodeRadius, problem.whole);
+
+ // 绘制两个子节点(底部)
+ const leftNodeX = centerX - nodeSpacing / 2 - nodeRadius;
+ const rightNodeX = centerX + nodeSpacing / 2 + nodeRadius;
+
+ // 左子节点
+ drawNode(ctx, leftNodeX, bottomY, nodeRadius, problem.part1);
+ // 右子节点
+ drawNode(ctx, rightNodeX, bottomY, nodeRadius, problem.part2);
+
+ // 绘制连接线(从根节点到子节点)
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.moveTo(centerX, topY + nodeRadius);
+ ctx.lineTo(leftNodeX, bottomY - nodeRadius);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(centerX, topY + nodeRadius);
+ ctx.lineTo(rightNodeX, bottomY - nodeRadius);
+ ctx.stroke();
+ }
+}
+
+/**
+ * 绘制节点(圆圈和数字)
+ */
+function drawNode(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ radius: number,
+ value: number | null,
+) {
+ // 绘制圆角正方形
+ const size = radius * 2; // 正方形边长
+ const cornerRadius = 8; // 圆角半径
+ const halfSize = size / 2;
+
+ ctx.fillStyle = '#fff';
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 2;
+
+ // 绘制圆角矩形
+ ctx.beginPath();
+ ctx.moveTo(x - halfSize + cornerRadius, y - halfSize);
+ ctx.lineTo(x + halfSize - cornerRadius, y - halfSize);
+ ctx.quadraticCurveTo(
+ x + halfSize,
+ y - halfSize,
+ x + halfSize,
+ y - halfSize + cornerRadius,
+ );
+ ctx.lineTo(x + halfSize, y + halfSize - cornerRadius);
+ ctx.quadraticCurveTo(
+ x + halfSize,
+ y + halfSize,
+ x + halfSize - cornerRadius,
+ y + halfSize,
+ );
+ ctx.lineTo(x - halfSize + cornerRadius, y + halfSize);
+ ctx.quadraticCurveTo(
+ x - halfSize,
+ y + halfSize,
+ x - halfSize,
+ y + halfSize - cornerRadius,
+ );
+ ctx.lineTo(x - halfSize, y - halfSize + cornerRadius);
+ ctx.quadraticCurveTo(
+ x - halfSize,
+ y - halfSize,
+ x - halfSize + cornerRadius,
+ y - halfSize,
+ );
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+
+ // 如果有值,绘制数字
+ if (value !== null) {
+ ctx.fillStyle = getRandomNumberColor();
+ ctx.font = `bold ${24}px "Microsoft Yahei"`;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(String(value), x, y);
+ }
+}
+
+/**
+ * 在框内绘制多张图片
+ */
+async function drawImagesInBox(
+ ctx: RenderingContext,
+ canvas: WechatMiniprogram.Canvas,
+ count: number,
+ imageIndex: number,
+ imageType: 'fruits' | 'twelve-animals',
+ boxX: number,
+ boxY: number,
+ boxWidth: number,
+ boxHeight: number,
+) {
+ // 图片配置
+ const imageConfig = {
+ 'twelve-animals': {
+ folder: 'twelve-animals',
+ maxIndex: 12,
+ },
+ fruits: {
+ folder: 'fruits',
+ maxIndex: 22,
+ },
+ };
+
+ const config = imageConfig[imageType] || imageConfig['twelve-animals'];
+
+ const padding = 8;
+ const availableWidth = boxWidth - padding * 2;
+ const availableHeight = boxHeight - padding * 2;
+
+ // 根据数量确定每行的图片数和图片大小
+ let imagesPerRow: number;
+ let imageSize: number;
+
+ if (count <= 4) {
+ imagesPerRow = count <= 2 ? count : 2;
+ imageSize =
+ Math.min(availableWidth / imagesPerRow, availableHeight / 2) - 4;
+ } else if (count <= 6) {
+ imagesPerRow = 3;
+ imageSize = Math.min(availableWidth / 3, availableHeight / 2) - 4;
+ } else {
+ imagesPerRow = 3;
+ imageSize = Math.min(availableWidth / 3, availableHeight / 3) - 4;
+ }
+
+ const rows = Math.ceil(count / imagesPerRow);
+ const imageSpacing =
+ (availableWidth - imageSize * imagesPerRow) / (imagesPerRow + 1);
+ const rowSpacing =
+ rows > 1 ? (availableHeight - imageSize * rows) / (rows + 1) : 0;
+
+ // 加载图片
+ let boxImage: any = null;
+ try {
+ const imagePath = `/mathPages/assets/${config.folder}/${imageIndex}.png`;
+ boxImage = await getImage(canvas, imagePath);
+ } catch (error) {
+ console.error(`加载${config.folder}/${imageIndex}图片失败:`, error);
+ return;
+ }
+
+ // 绘制图片
+ for (let i = 0; i < count; i++) {
+ const row = Math.floor(i / imagesPerRow);
+ const col = i % imagesPerRow;
+
+ const imageX =
+ boxX + padding + imageSpacing + col * (imageSize + imageSpacing);
+ const imageY =
+ boxY +
+ padding +
+ (rows > 1 ? rowSpacing : (availableHeight - imageSize) / 2) +
+ row * (imageSize + (rows > 1 ? rowSpacing : 0));
+
+ if (boxImage) {
+ // 计算图片高度(等比例缩放)
+ // @ts-ignore - 微信小程序图片对象有 width 和 height 属性
+ const scaledHeight = (boxImage.height / boxImage.width) * imageSize;
+
+ ctx.drawImage(boxImage, imageX, imageY, imageSize, scaledHeight);
+ }
+ }
+}
diff --git a/miniprogram/mathPages/service/numberDecomposeDraw.ts b/miniprogram/mathPages/service/numberDecomposeDraw.ts
new file mode 100644
index 0000000..9afe799
--- /dev/null
+++ b/miniprogram/mathPages/service/numberDecomposeDraw.ts
@@ -0,0 +1,67 @@
+import { BaseMathDrawService } from './baseMathDraw';
+import { drawNumberDecomposeContent } from './numberDecomposeContentDraw';
+
+/**
+ * 10以内数的分与合绘制服务
+ * 组合使用基础绘制服务和内容区域绘制服务
+ */
+class NumberDecomposeDraw extends BaseMathDrawService {
+ decomposeData: {
+ problems: Array<{
+ whole: number | null;
+ part1: number | null;
+ part2: number | null;
+ imageIndex?: number;
+ imageType?: 'fruits' | 'twelve-animals';
+ }>;
+ mode: 'with-image' | 'decompose' | 'compose';
+ } | null;
+
+ constructor(
+ canvas: Canvas,
+ ctx: RenderingContext,
+ options?: Record,
+ ) {
+ super(canvas, ctx, options);
+ this.decomposeData = null;
+ }
+
+ async draw(decomposeData: {
+ problems: Array<{
+ whole: number | null;
+ part1: number | null;
+ part2: number | null;
+ imageIndex?: number;
+ imageType?: 'fruits' | 'twelve-animals';
+ }>;
+ mode: 'with-image' | 'decompose' | 'compose';
+ }) {
+ if (!decomposeData || !decomposeData.problems) {
+ return;
+ }
+
+ this.setPrintConfig();
+ this.decomposeData = decomposeData;
+ this.clear();
+ this.setPaper();
+
+ // 绘制Header
+ if (this.headerType !== 'minimal') {
+ await this.drawHeader();
+ } else {
+ this.drawMiniHeader();
+ }
+
+ // 绘制内容区域
+ this.drawDivider();
+ await drawNumberDecomposeContent({
+ canvas: this.canvas,
+ ctx: this.ctx,
+ decomposeData: this.decomposeData,
+ canvasWidth: this.canvasWidth,
+ startY: this.currentY,
+ });
+ }
+}
+
+export default NumberDecomposeDraw;
diff --git a/project.private.config.json b/project.private.config.json
index bc824fc..4443c26 100644
--- a/project.private.config.json
+++ b/project.private.config.json
@@ -23,12 +23,19 @@
"condition": {
"miniprogram": {
"list": [
+ {
+ "name": "mathPages/numberDecompose/numberDecompose",
+ "pathName": "mathPages/numberDecompose/numberDecompose",
+ "query": "id=number-decompose&mode=with-image",
+ "scene": null,
+ "launchMode": "default"
+ },
{
"name": "mathPages/countingSelect/countingSelect",
"pathName": "mathPages/countingSelect/countingSelect",
"query": "id=counting-fill",
- "scene": null,
- "launchMode": "default"
+ "launchMode": "default",
+ "scene": null
},
{
"name": "mathPages/compare/compare",