diff --git a/miniprogram/app.json b/miniprogram/app.json
index 9ec599c..374ee6a 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -10,7 +10,11 @@
{
"root": "mathPages",
"name": "mathPages",
- "pages": ["numberFind/numberFind", "countMatch/countMatch"],
+ "pages": [
+ "numberFind/numberFind",
+ "countMatch/countMatch",
+ "addition/addition"
+ ],
"independent": false
}
],
diff --git a/miniprogram/config/config.ts b/miniprogram/config/config.ts
index 14cca35..34c8487 100644
--- a/miniprogram/config/config.ts
+++ b/miniprogram/config/config.ts
@@ -1,6 +1,7 @@
export default {
printHeader: {
- header: 'wechat',
- appName: '涂鸦丫'
- }
-}
\ No newline at end of file
+ // header: 'wechat',
+ header: 'LogoImage',
+ appName: '涂鸦丫小程序',
+ },
+};
diff --git a/miniprogram/constants/mathFunctions.ts b/miniprogram/constants/mathFunctions.ts
index f53d453..c6927b4 100644
--- a/miniprogram/constants/mathFunctions.ts
+++ b/miniprogram/constants/mathFunctions.ts
@@ -35,6 +35,34 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
desc: '通过连线配对数字和对应的数量图形',
icon: '🔗',
},
+ {
+ id: 'addition-5',
+ page: 'addition',
+ title: '5以内加法',
+ desc: '练习5以内的加法运算,图形化展示',
+ icon: '➕',
+ },
+ {
+ id: 'addition-10',
+ page: 'addition',
+ title: '10以内加法',
+ desc: '练习10以内的加法运算,图形化展示',
+ icon: '➕',
+ },
+ {
+ id: 'subtraction-10',
+ page: 'addition',
+ title: '10以内减法',
+ desc: '练习10以内的减法运算,图形化展示',
+ icon: '➖',
+ },
+ {
+ id: 'addition-subtraction-10',
+ page: 'addition',
+ title: '10以内加减法',
+ desc: '练习10以内的加法和减法混合运算',
+ icon: '±',
+ },
{
id: 'missing-number',
title: '填上缺少的数字',
@@ -59,22 +87,4 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
desc: '数出物品数量,填写对应的数字',
icon: '✏️',
},
- {
- id: 'addition-5',
- title: '5以内加法',
- desc: '练习5以内的加法运算,图形化展示',
- icon: '➕',
- },
- {
- id: 'addition-10',
- title: '10以内加法',
- desc: '练习10以内的加法运算,图形化展示',
- icon: '➕',
- },
- {
- id: 'addition-subtraction-10',
- title: '10以内加减法',
- desc: '练习10以内的加法和减法混合运算',
- icon: '±',
- },
];
diff --git a/miniprogram/mathPages/addition/addition.json b/miniprogram/mathPages/addition/addition.json
new file mode 100644
index 0000000..a78fa9d
--- /dev/null
+++ b/miniprogram/mathPages/addition/addition.json
@@ -0,0 +1,12 @@
+{
+ "navigationBarTitleText": "加减法计算",
+ "navigationBarBackgroundColor": "#FFD719",
+ "homeButton": true,
+ "backgroundColor": "#F6F6F6",
+ "enablePullDownRefresh": false,
+ "usingComponents": {
+ "toy-button": "../../ui/button/button",
+ "share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
+ "van-action-sheet": "../../miniprogram_npm/@vant/weapp/action-sheet/index"
+ }
+}
diff --git a/miniprogram/mathPages/addition/addition.less b/miniprogram/mathPages/addition/addition.less
new file mode 100644
index 0000000..b628a25
--- /dev/null
+++ b/miniprogram/mathPages/addition/addition.less
@@ -0,0 +1,101 @@
+page {
+ background-color: #f6f6f6;
+}
+
+.page-container {
+ background-color: #f6f6f6;
+ padding: 0 24rpx;
+ box-sizing: border-box;
+ padding-bottom: 160rpx; // 为底部按钮预留空间
+
+ .empty {
+ height: 50rpx;
+ }
+}
+
+.wrapper {
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 36rpx 24rpx;
+ box-shadow: 0 6rpx 8rpx rgba(0, 0, 0, 0.15);
+ margin: 30rpx 0;
+ display: flex;
+ flex-direction: column;
+
+ .wrapper-title {
+ font-size: 32rpx;
+ color: #141414;
+ margin-bottom: 36rpx;
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .canvas-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 400rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ border: 2rpx dashed #dee2e6;
+ }
+
+ .canvas-content {
+ max-width: 100%;
+ border-radius: 8rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ }
+
+ .random-button-area {
+ margin-top: 40rpx;
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+
+ .type-selector {
+ flex: 1; // 占据剩余空间
+ height: 80rpx;
+ background-color: #fff;
+ border: 2rpx solid #e5e5e5;
+ border-radius: 16rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 24rpx;
+ box-sizing: border-box;
+
+ .type-selector-text {
+ font-size: 28rpx;
+ color: #333;
+ }
+
+ .type-selector-arrow {
+ font-size: 20rpx;
+ color: #999;
+ }
+ }
+
+ .random-button {
+ flex: 2;
+ }
+ }
+}
+
+/* 底部按钮区域 */
+.bottom-btn-box {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 180rpx;
+ display: flex;
+ justify-content: space-between;
+ background-color: #fff;
+ box-shadow: 0px -3.9rpx 5.2rpx rgba(0, 0, 0, 0.15);
+ padding: 24rpx;
+ padding-bottom: env(safe-area-inset-bottom);
+ box-sizing: border-box;
+ border-radius: 16rpx 16rpx 0 0;
+ z-index: 2;
+}
\ No newline at end of file
diff --git a/miniprogram/mathPages/addition/addition.ts b/miniprogram/mathPages/addition/addition.ts
new file mode 100644
index 0000000..3aa4a58
--- /dev/null
+++ b/miniprogram/mathPages/addition/addition.ts
@@ -0,0 +1,298 @@
+import { PAPER_SIZE } from '../../constants/colors';
+import { checkAndSaveImage } from '../../utils/saveImage';
+import { shouldShowShareGuide } from '../../utils/shareGuide';
+import AdditionDraw from '../service/additionDraw';
+import {
+ MATH_FUNCTION_TYPES,
+ MathFunctionType,
+} from '../../constants/mathFunctions';
+
+Page({
+ canvas: null as Canvas | null,
+ ctx: null as RenderingContext | null,
+ boxHeight: 0,
+ boxWidth: 0,
+ drawService: null as AdditionDraw | null,
+ calculationData: null as {
+ problems: Array<{
+ type: 'addition' | 'subtraction';
+ left: number;
+ right: number;
+ result: number;
+ }>;
+ } | null,
+
+ data: {
+ pageTitle: '加减法计算',
+ functionId: '',
+ hasContent: false,
+ showShareDialog: false,
+ showTypeSelector: false,
+ currentType: 'addition-5', // 'addition-5', 'addition-10', 'subtraction-10', 'addition-subtraction-10'
+ currentTypeName: '5以内加法',
+ typeActions: [
+ { name: '5以内加法', value: 'addition-5' },
+ { name: '10以内加法', value: 'addition-10' },
+ { name: '10以内减法', value: 'subtraction-10' },
+ { name: '10以内加减法', value: 'addition-subtraction-10' },
+ ],
+ imageType: 'twelve-animals', // 图片类型:twelve-animals 或 fruits
+ },
+
+ onLoad(options: { id?: string }) {
+ const functionId = options.id || 'addition-5';
+ this.setData({ functionId });
+
+ const functionItem = MATH_FUNCTION_TYPES.find(
+ (item: MathFunctionType) => item.id === functionId,
+ );
+
+ const pageTitle = functionItem?.title || '加减法计算';
+ this.setData({ pageTitle });
+ wx.setNavigationBarTitle({ title: pageTitle });
+
+ // 根据 functionId 设置默认类型
+ if (functionId === 'addition-5') {
+ this.setData({
+ currentType: 'addition-5',
+ currentTypeName: '5以内加法',
+ });
+ } else if (functionId === 'addition-10') {
+ this.setData({
+ currentType: 'addition-10',
+ currentTypeName: '10以内加法',
+ });
+ } else if (functionId === 'subtraction-10') {
+ this.setData({
+ currentType: 'subtraction-10',
+ currentTypeName: '10以内减法',
+ });
+ } else if (functionId === 'addition-subtraction-10') {
+ this.setData({
+ currentType: 'addition-subtraction-10',
+ currentTypeName: '10以内加减法',
+ });
+ }
+ },
+
+ onReady() {
+ this.initCanvas();
+ },
+
+ /**
+ * 初始化Canvas
+ */
+ initCanvas() {
+ const query = wx.createSelectorQuery();
+ query
+ .select('#canvasWrapper')
+ .boundingClientRect((rect) => {
+ if (rect) {
+ const { width, height } = PAPER_SIZE['A4'];
+ const boxWidth = rect.width;
+ const boxHeight = boxWidth / (width / height);
+
+ this.boxHeight = boxHeight;
+ this.boxWidth = boxWidth;
+
+ this.setData({ boxWidth, boxHeight });
+
+ const canvas = wx
+ .createSelectorQuery()
+ .select('#canvasContent');
+ canvas.fields({ node: true, size: true }).exec((res) => {
+ if (res[0]) {
+ const canvasNode = res[0].node;
+ const ctx = canvasNode.getContext('2d');
+ const dpr = wx.getSystemInfoSync().pixelRatio;
+
+ canvasNode.width = boxWidth * dpr;
+ canvasNode.height = boxHeight * dpr;
+ ctx.scale(dpr, dpr);
+
+ this.canvas = canvasNode;
+ this.ctx = ctx;
+
+ this.drawService = new AdditionDraw(
+ canvasNode,
+ ctx,
+ {
+ title: this.data.currentTypeName,
+ subTitle: '通过图形化方式学习加减法运算',
+ },
+ );
+
+ // 初始随机生成
+ this.onRandom();
+ }
+ });
+ }
+ })
+ .exec();
+ },
+
+ /**
+ * 绘制Canvas内容
+ */
+ async drawCanvas() {
+ if (!this.ctx || !this.drawService || !this.calculationData) {
+ return;
+ }
+
+ try {
+ // 更新 Header 的 Title
+ if (this.drawService) {
+ this.drawService.options.title = this.data.currentTypeName;
+ }
+
+ await this.drawService.draw(
+ this.calculationData,
+ this.data.currentType,
+ );
+ this.setData({ hasContent: true });
+ } catch (error) {
+ console.error('绘制失败:', error);
+ this.setData({ hasContent: false });
+ }
+ },
+
+ /**
+ * 随机生成
+ */
+ onRandom() {
+ const problems: Array<{
+ type: 'addition' | 'subtraction';
+ left: number;
+ right: number;
+ result: number;
+ }> = [];
+
+ const type = this.data.currentType;
+
+ // 生成6道题目
+ for (let i = 0; i < 5; i++) {
+ if (type === 'addition-5') {
+ // 5以内加法:和 ≤ 5
+ // left >= 1, right >= 1, left + right <= 5
+ const maxSum = 5;
+ const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 4
+ const maxRight = maxSum - left; // 确保 left + right <= 5
+ const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
+ const result = left + right;
+ problems.push({ type: 'addition', left, right, result });
+ } else if (type === 'addition-10') {
+ // 10以内加法:和 ≤ 10
+ // left >= 1, right >= 1, left + right <= 10
+ const maxSum = 10;
+ const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
+ const maxRight = maxSum - left; // 确保 left + right <= 10
+ const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
+ const result = left + right;
+ problems.push({ type: 'addition', left, right, result });
+ } else if (type === 'subtraction-10') {
+ // 10以内减法:被减数 ≤ 10
+ // left <= 10, left - right = result, result >= 1
+ const maxLeft = 10;
+ const left = Math.floor(Math.random() * maxLeft) + 1; // 1 到 10
+ const maxRight = left - 1; // 确保 result >= 1
+ const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
+ const result = left - right;
+ problems.push({ type: 'subtraction', left, right, result });
+ } else if (type === 'addition-subtraction-10') {
+ // 加减法混合
+ if (Math.random() < 0.5) {
+ // 加法:和 ≤ 10
+ const maxSum = 10;
+ const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
+ const maxRight = maxSum - left; // 确保 left + right <= 10
+ const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
+ const result = left + right;
+ problems.push({ type: 'addition', left, right, result });
+ } else {
+ // 减法:被减数 ≤ 10
+ const maxLeft = 10;
+ const left = Math.floor(Math.random() * maxLeft) + 1; // 1 到 10
+ const maxRight = left - 1; // 确保 result >= 1
+ const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
+ const result = left - right;
+ problems.push({ type: 'subtraction', left, right, result });
+ }
+ }
+ }
+
+ this.calculationData = { problems };
+ this.drawCanvas();
+ },
+
+ /**
+ * 导出打印
+ */
+ exportToPrint() {
+ if (!this.canvas || !this.data.hasContent) {
+ return;
+ }
+
+ if (shouldShowShareGuide()) {
+ this.setData({ showShareDialog: true });
+ return;
+ }
+
+ checkAndSaveImage(this.canvas);
+ },
+
+ /**
+ * 分享小程序
+ */
+ onShareAppMessage() {
+ return {
+ title: '涂鸦丫-数学学习涂鸦卡',
+ path: `/mathPages/addition/addition?id=${this.data.functionId}`,
+ imageUrl:
+ 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
+ };
+ },
+
+ onShareTimeline() {
+ return {
+ title: '涂鸦丫-数学学习涂鸦卡',
+ query: `id=${this.data.functionId}`,
+ imageUrl:
+ 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
+ };
+ },
+
+ /** 关闭分享引导弹窗 */
+ onCloseShareDialog() {
+ this.setData({ showShareDialog: false });
+ },
+
+ /** 分享成功回调 */
+ onShareSuccess() {
+ this.setData({ showShareDialog: false });
+ if (this.canvas) {
+ checkAndSaveImage(this.canvas);
+ }
+ },
+
+ /** 显示类型选择器 */
+ onShowTypeSelector() {
+ this.setData({ showTypeSelector: true });
+ },
+
+ /** 关闭类型选择器 */
+ onCloseTypeSelector() {
+ this.setData({ showTypeSelector: false });
+ },
+
+ /** 选择类型 */
+ onSelectType(event: any) {
+ const { name, value } = event.detail;
+ this.setData({
+ currentType: value,
+ currentTypeName: name,
+ showTypeSelector: false,
+ });
+ // 重新生成数据
+ this.onRandom();
+ },
+});
diff --git a/miniprogram/mathPages/addition/addition.wxml b/miniprogram/mathPages/addition/addition.wxml
new file mode 100644
index 0000000..604a209
--- /dev/null
+++ b/miniprogram/mathPages/addition/addition.wxml
@@ -0,0 +1,69 @@
+
+
+ 预览打印效果
+
+
+
+
+
+
+
+
+
+ {{currentTypeName}}
+ ▼
+
+
+ 随机生成
+
+
+
+
+
+
+
+
+ 分享
+
+
+ 下载打印
+
+
+
diff --git a/miniprogram/mathPages/assets/fruits/1.png b/miniprogram/mathPages/assets/fruits/1.png
index 4e08518..fdd46cc 100644
Binary files a/miniprogram/mathPages/assets/fruits/1.png and b/miniprogram/mathPages/assets/fruits/1.png differ
diff --git a/miniprogram/mathPages/assets/fruits/10.png b/miniprogram/mathPages/assets/fruits/10.png
index 2f58b1f..c899e0f 100644
Binary files a/miniprogram/mathPages/assets/fruits/10.png and b/miniprogram/mathPages/assets/fruits/10.png differ
diff --git a/miniprogram/mathPages/assets/fruits/11.png b/miniprogram/mathPages/assets/fruits/11.png
index f8fe214..5fab469 100644
Binary files a/miniprogram/mathPages/assets/fruits/11.png and b/miniprogram/mathPages/assets/fruits/11.png differ
diff --git a/miniprogram/mathPages/assets/fruits/12.png b/miniprogram/mathPages/assets/fruits/12.png
index 5d99cfe..40e7828 100644
Binary files a/miniprogram/mathPages/assets/fruits/12.png and b/miniprogram/mathPages/assets/fruits/12.png differ
diff --git a/miniprogram/mathPages/assets/fruits/13.png b/miniprogram/mathPages/assets/fruits/13.png
index 1a31aed..b107bf0 100644
Binary files a/miniprogram/mathPages/assets/fruits/13.png and b/miniprogram/mathPages/assets/fruits/13.png differ
diff --git a/miniprogram/mathPages/assets/fruits/14.png b/miniprogram/mathPages/assets/fruits/14.png
index 873e624..a248eca 100644
Binary files a/miniprogram/mathPages/assets/fruits/14.png and b/miniprogram/mathPages/assets/fruits/14.png differ
diff --git a/miniprogram/mathPages/assets/fruits/15.png b/miniprogram/mathPages/assets/fruits/15.png
index 34fc0c3..7453066 100644
Binary files a/miniprogram/mathPages/assets/fruits/15.png and b/miniprogram/mathPages/assets/fruits/15.png differ
diff --git a/miniprogram/mathPages/assets/fruits/16.png b/miniprogram/mathPages/assets/fruits/16.png
index d48f3d9..3dd0a10 100644
Binary files a/miniprogram/mathPages/assets/fruits/16.png and b/miniprogram/mathPages/assets/fruits/16.png differ
diff --git a/miniprogram/mathPages/assets/fruits/17.png b/miniprogram/mathPages/assets/fruits/17.png
index dfb8798..6a9ea31 100644
Binary files a/miniprogram/mathPages/assets/fruits/17.png and b/miniprogram/mathPages/assets/fruits/17.png differ
diff --git a/miniprogram/mathPages/assets/fruits/18.png b/miniprogram/mathPages/assets/fruits/18.png
index c05ca6d..8266b0a 100644
Binary files a/miniprogram/mathPages/assets/fruits/18.png and b/miniprogram/mathPages/assets/fruits/18.png differ
diff --git a/miniprogram/mathPages/assets/fruits/19.png b/miniprogram/mathPages/assets/fruits/19.png
index a69e258..760c1c0 100644
Binary files a/miniprogram/mathPages/assets/fruits/19.png and b/miniprogram/mathPages/assets/fruits/19.png differ
diff --git a/miniprogram/mathPages/assets/fruits/2.png b/miniprogram/mathPages/assets/fruits/2.png
index 445cff5..d9ff943 100644
Binary files a/miniprogram/mathPages/assets/fruits/2.png and b/miniprogram/mathPages/assets/fruits/2.png differ
diff --git a/miniprogram/mathPages/assets/fruits/20.png b/miniprogram/mathPages/assets/fruits/20.png
index 492b3ac..23ea1b7 100644
Binary files a/miniprogram/mathPages/assets/fruits/20.png and b/miniprogram/mathPages/assets/fruits/20.png differ
diff --git a/miniprogram/mathPages/assets/fruits/21.png b/miniprogram/mathPages/assets/fruits/21.png
index fcca685..c0d80df 100644
Binary files a/miniprogram/mathPages/assets/fruits/21.png and b/miniprogram/mathPages/assets/fruits/21.png differ
diff --git a/miniprogram/mathPages/assets/fruits/22.png b/miniprogram/mathPages/assets/fruits/22.png
index 917ceb8..5e5e5a5 100644
Binary files a/miniprogram/mathPages/assets/fruits/22.png and b/miniprogram/mathPages/assets/fruits/22.png differ
diff --git a/miniprogram/mathPages/assets/fruits/3.png b/miniprogram/mathPages/assets/fruits/3.png
index 141d3cd..d45404a 100644
Binary files a/miniprogram/mathPages/assets/fruits/3.png and b/miniprogram/mathPages/assets/fruits/3.png differ
diff --git a/miniprogram/mathPages/assets/fruits/4.png b/miniprogram/mathPages/assets/fruits/4.png
index 8ced475..dc19393 100644
Binary files a/miniprogram/mathPages/assets/fruits/4.png and b/miniprogram/mathPages/assets/fruits/4.png differ
diff --git a/miniprogram/mathPages/assets/fruits/5.png b/miniprogram/mathPages/assets/fruits/5.png
index 885011b..e6f2301 100644
Binary files a/miniprogram/mathPages/assets/fruits/5.png and b/miniprogram/mathPages/assets/fruits/5.png differ
diff --git a/miniprogram/mathPages/assets/fruits/6.png b/miniprogram/mathPages/assets/fruits/6.png
index 761c03d..bb5d18e 100644
Binary files a/miniprogram/mathPages/assets/fruits/6.png and b/miniprogram/mathPages/assets/fruits/6.png differ
diff --git a/miniprogram/mathPages/assets/fruits/7.png b/miniprogram/mathPages/assets/fruits/7.png
index cba3cab..d4a6cbd 100644
Binary files a/miniprogram/mathPages/assets/fruits/7.png and b/miniprogram/mathPages/assets/fruits/7.png differ
diff --git a/miniprogram/mathPages/assets/fruits/8.png b/miniprogram/mathPages/assets/fruits/8.png
index 051d4cb..e5c8272 100644
Binary files a/miniprogram/mathPages/assets/fruits/8.png and b/miniprogram/mathPages/assets/fruits/8.png differ
diff --git a/miniprogram/mathPages/assets/fruits/9.png b/miniprogram/mathPages/assets/fruits/9.png
index 9328d40..ec65c31 100644
Binary files a/miniprogram/mathPages/assets/fruits/9.png and b/miniprogram/mathPages/assets/fruits/9.png differ
diff --git a/miniprogram/mathPages/service/additionContentDraw.ts b/miniprogram/mathPages/service/additionContentDraw.ts
new file mode 100644
index 0000000..a8464ef
--- /dev/null
+++ b/miniprogram/mathPages/service/additionContentDraw.ts
@@ -0,0 +1,576 @@
+import { getImage } from '../../utils/index';
+
+interface DrawAdditionContentParams {
+ canvas: WechatMiniprogram.Canvas;
+ ctx: RenderingContext;
+ problems: Array<{
+ type: 'addition' | 'subtraction';
+ left: number;
+ right: number;
+ result: number;
+ }>;
+ canvasWidth: number;
+ startY: number;
+ imageType?: string; // 'twelve-animals' 或 'fruits'
+}
+
+/**
+ * 绘制圆角矩形(虚线或实线边框)
+ */
+function drawRoundedRect(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ radius: number,
+ isDashed: boolean = true,
+) {
+ 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([2, 2]); // 虚线
+ } else {
+ ctx.setLineDash([]); // 实线
+ }
+ ctx.stroke();
+ ctx.setLineDash([]); // 重置为实线
+}
+
+/**
+ * 绘制实线圆角方框(用于填写答案)
+ */
+function drawAnswerBox(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+) {
+ const radius = 6; // 圆角大小
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 1;
+ ctx.setLineDash([]); // 实线
+ 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();
+ ctx.stroke();
+}
+
+/**
+ * 绘制右侧答案框
+ */
+function drawAnswerBoxArea(
+ ctx: RenderingContext,
+ params: {
+ rightBoxX: number;
+ boxY: number;
+ rightBoxWidth: number;
+ borderRadius: number;
+ boxHeight: number;
+ padding: number;
+ problemType: 'addition' | 'subtraction';
+ },
+) {
+ const {
+ rightBoxX,
+ boxY,
+ rightBoxWidth,
+ borderRadius,
+ boxHeight,
+ padding,
+ problemType,
+ } = params;
+
+ const plusSignSize = 18; // 加号大小(缩小)
+ const minusSignSize = 18; // 减号大小(缩小)
+ const equalsSignSize = 18; // 等号大小(缩小)
+ const spacing = 8; // 加号左右间距(方便修改和调试)
+ const answerBoxWidth = 34; // 答案框宽度
+ const answerBoxHeight = 34; // 答案框高度
+
+ // 绘制右侧答案框边框
+ ctx.strokeStyle = '#999';
+ ctx.lineWidth = 1;
+ drawRoundedRect(
+ ctx,
+ rightBoxX,
+ boxY,
+ rightBoxWidth,
+ boxHeight,
+ borderRadius,
+ true,
+ );
+
+ const rightBoxCenterY = boxY + boxHeight / 2;
+ const rightBoxAvailableWidth = rightBoxWidth - padding * 2;
+ const answerStartY = rightBoxCenterY - answerBoxHeight / 2;
+
+ // 根据问题类型确定运算符号大小
+ const operatorSignSize =
+ problemType === 'addition' ? plusSignSize : minusSignSize;
+
+ // 计算总宽度并水平居中(加法和减法逻辑相同)
+ const totalAnswerWidth =
+ answerBoxWidth +
+ spacing +
+ operatorSignSize +
+ spacing +
+ answerBoxWidth +
+ spacing +
+ equalsSignSize +
+ spacing +
+ answerBoxWidth;
+ const answerStartX =
+ rightBoxX + padding + (rightBoxAvailableWidth - totalAnswerWidth) / 2;
+
+ // 计算各个元素的位置
+ const box1X = answerStartX;
+ const operatorX = box1X + answerBoxWidth + spacing;
+ const box2X = operatorX + plusSignSize + spacing;
+ const equalsX = box2X + answerBoxWidth + spacing;
+ const box3X = equalsX + equalsSignSize + spacing;
+
+ // 绘制第一个方框(圆角)
+ drawAnswerBox(ctx, box1X, answerStartY, answerBoxWidth, answerBoxHeight);
+
+ // 绘制运算符号(加号或减号)
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ if (problemType === 'addition') {
+ // 加号:横线和竖线
+ ctx.moveTo(operatorX, rightBoxCenterY);
+ ctx.lineTo(operatorX + operatorSignSize, rightBoxCenterY);
+ ctx.moveTo(
+ operatorX + operatorSignSize / 2,
+ rightBoxCenterY - operatorSignSize / 2,
+ );
+ ctx.lineTo(
+ operatorX + operatorSignSize / 2,
+ rightBoxCenterY + operatorSignSize / 2,
+ );
+ } else {
+ // 减号:只有横线
+ ctx.moveTo(operatorX, rightBoxCenterY);
+ ctx.lineTo(operatorX + operatorSignSize, rightBoxCenterY);
+ }
+ ctx.stroke();
+
+ // 绘制第二个方框(圆角)
+ drawAnswerBox(ctx, box2X, answerStartY, answerBoxWidth, answerBoxHeight);
+
+ // 绘制等号(缩短)
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.moveTo(equalsX, rightBoxCenterY - 3);
+ ctx.lineTo(equalsX + equalsSignSize, rightBoxCenterY - 3);
+ ctx.moveTo(equalsX, rightBoxCenterY + 3);
+ ctx.lineTo(equalsX + equalsSignSize, rightBoxCenterY + 3);
+ ctx.stroke();
+
+ // 绘制第三个方框(答案,圆角)
+ drawAnswerBox(ctx, box3X, answerStartY, answerBoxWidth, answerBoxHeight);
+}
+
+// 根据图片类型设置图片大小
+function getImageSize(imageType: 'twelve-animals' | 'fruits'): number {
+ return imageType === 'twelve-animals' ? 40 : 36;
+}
+
+/**
+ * 绘制加减法计算内容区域
+ * 左侧框:展示图片(加法:加号左右展示图片;减法:总数图片,后几个加划线)
+ * 右侧框:填写运算方式(实线方框 + 实线方框 = 实线方框 或 实线方框 - 实线方框 = 实线方框)
+ */
+export async function drawAdditionContent({
+ canvas,
+ ctx,
+ problems,
+ canvasWidth,
+ startY,
+ imageType: _imageType = '', // 不再使用,每个运算会随机选择图片类型
+}: DrawAdditionContentParams): Promise {
+ // 根据图片类型确定图片目录和最大索引
+ const imageConfig = {
+ 'twelve-animals': {
+ folder: 'twelve-animals',
+ maxIndex: 12,
+ },
+ fruits: {
+ folder: 'fruits',
+ maxIndex: 22,
+ },
+ };
+
+ const leftMargin = 24;
+ const rightMargin = 24;
+ const itemSpacing = 140;
+ // 调整左右框宽度比例:6:4 (约420:280)
+ const leftBoxWidth = 334; // 左侧图片框固定宽度
+ const rightBoxWidth = 196; // 右侧答案框固定宽度
+ const boxHeight = 110;
+ const borderRadius = 12;
+
+ const imageSpacing = 0; // 图片之间的间距(水平方向)
+ const rowSpacing = 4; // 第一行和第二行之间的间距(垂直方向)
+ const padding = 15; // 框内边距
+ const startYPos = startY + 20;
+ const plusSignSize = 20; // 加号大小(缩小)
+ const plusSignSpacing = 15; // 加号左右间距(方便修改和调试)
+
+ // 计算左侧和右侧的起始X位置
+ const leftBoxX = leftMargin;
+ const rightBoxX = canvasWidth - rightMargin - rightBoxWidth;
+
+ // 为每个题目随机选择图片类型和图片索引
+ const problemImageConfigs: Array<{
+ type: 'twelve-animals' | 'fruits';
+ imageIndex: number;
+ }> = [];
+
+ for (let i = 0; i < problems.length; i++) {
+ // 随机选择图片类型
+ const imageTypes: ('twelve-animals' | 'fruits')[] = [
+ 'twelve-animals',
+ 'fruits',
+ ];
+ const randomType =
+ imageTypes[Math.floor(Math.random() * imageTypes.length)];
+ const config = imageConfig[randomType];
+
+ // 为当前题目生成唯一的图片索引
+ const availableImageIndices = Array.from(
+ { length: config.maxIndex },
+ (_, i) => i + 1,
+ );
+ const randomIndex = Math.floor(
+ Math.random() * availableImageIndices.length,
+ );
+ const imageIndex = availableImageIndices[randomIndex];
+
+ problemImageConfigs.push({
+ type: randomType,
+ imageIndex,
+ });
+ }
+
+ // 绘制每一行的内容
+ for (let i = 0; i < problems.length; i++) {
+ const problem = problems[i];
+ const boxY = startYPos + i * itemSpacing;
+ const imageConfigItem = problemImageConfigs[i];
+ const config = imageConfig[imageConfigItem.type];
+
+ // 根据图片类型获取图片大小
+ const imageSize = getImageSize(imageConfigItem.type);
+
+ // 加载图片
+ let image: any = null;
+ try {
+ const imagePath = `/mathPages/assets/${config.folder}/${imageConfigItem.imageIndex}.png`;
+ image = await getImage(canvas, imagePath);
+ } catch (error) {
+ console.error('图片加载失败:', error);
+ }
+
+ // ========== 绘制左侧图片框 ==========
+ ctx.strokeStyle = '#999';
+ ctx.lineWidth = 1;
+ drawRoundedRect(
+ ctx,
+ leftBoxX,
+ boxY,
+ leftBoxWidth,
+ boxHeight,
+ borderRadius,
+ true,
+ );
+
+ const leftBoxCenterY = boxY + boxHeight / 2;
+ const leftBoxContentX = leftBoxX + padding;
+
+ if (problem.type === 'addition') {
+ // 加法:左侧图片 + 加号 + 右侧图片
+ const leftImageCount = problem.left;
+ const rightImageCount = problem.right;
+
+ /**
+ * 根据数量计算每行最多展示的图片数
+ */
+ const getMaxImagesPerRow = (count: number): number => {
+ if (count <= 6) return 3;
+ if (count <= 8) return 4;
+ return 5; // 8-10
+ };
+
+ const leftMaxPerRow = getMaxImagesPerRow(leftImageCount);
+ const rightMaxPerRow = getMaxImagesPerRow(rightImageCount);
+ const leftRows = Math.min(
+ 2,
+ Math.ceil(leftImageCount / leftMaxPerRow),
+ );
+ const rightRows = Math.min(
+ 2,
+ Math.ceil(rightImageCount / rightMaxPerRow),
+ );
+
+ // 计算左侧图片区域的宽度和高度
+ // 需要根据实际行数计算每行的最大宽度
+ const leftFirstRowCount = Math.min(leftImageCount, leftMaxPerRow);
+ const leftSecondRowCount = Math.max(
+ 0,
+ Math.min(leftMaxPerRow, leftImageCount - leftMaxPerRow),
+ );
+ const leftImagesWidth =
+ Math.max(
+ leftFirstRowCount * imageSize +
+ (leftFirstRowCount - 1) * imageSpacing,
+ leftSecondRowCount * imageSize +
+ (leftSecondRowCount - 1) * imageSpacing,
+ ) || imageSize; // 至少为 imageSize
+
+ // 计算右侧图片区域的宽度和高度
+ const rightFirstRowCount = Math.min(
+ rightImageCount,
+ rightMaxPerRow,
+ );
+ const rightSecondRowCount = Math.max(
+ 0,
+ Math.min(rightMaxPerRow, rightImageCount - rightMaxPerRow),
+ );
+ const rightImagesWidth =
+ Math.max(
+ rightFirstRowCount * imageSize +
+ (rightFirstRowCount - 1) * imageSpacing,
+ rightSecondRowCount * imageSize +
+ (rightSecondRowCount - 1) * imageSpacing,
+ ) || imageSize; // 至少为 imageSize
+
+ // 获取图片实际高度(用于垂直居中计算)
+ let imageActualHeight = imageSize;
+ if (image) {
+ // @ts-ignore
+ imageActualHeight = (image.height / image.width) * imageSize;
+ }
+
+ // 计算左侧图片区域的实际高度(考虑图片的实际高度和行间距)
+ const leftImagesHeight =
+ leftRows * imageActualHeight + (leftRows - 1) * rowSpacing;
+ // 计算右侧图片区域的实际高度
+ const rightImagesHeight =
+ rightRows * imageActualHeight + (rightRows - 1) * rowSpacing;
+
+ // 计算加号位置(居中,包含左右间距)
+ const totalContentWidth =
+ leftImagesWidth +
+ plusSignSpacing +
+ plusSignSize +
+ plusSignSpacing +
+ rightImagesWidth;
+ const availableWidth = leftBoxWidth - padding * 2;
+ const contentStartX =
+ leftBoxContentX + (availableWidth - totalContentWidth) / 2;
+
+ const leftImagesStartX = contentStartX;
+ // 垂直居中:使用图片实际高度计算
+ const leftImagesStartY = leftBoxCenterY - leftImagesHeight / 2;
+
+ // 绘制左侧图片(最多两行)
+ for (let j = 0; j < Math.min(leftImageCount, 10); j++) {
+ const row = Math.floor(j / leftMaxPerRow);
+ const col = j % leftMaxPerRow;
+
+ // 如果超过两行,停止绘制
+ if (row >= 2) break;
+
+ const imagesInThisRow = Math.min(
+ leftMaxPerRow,
+ leftImageCount - row * leftMaxPerRow,
+ );
+ const rowWidth =
+ imagesInThisRow * imageSize +
+ (imagesInThisRow - 1) * imageSpacing;
+ const rowStartX =
+ leftImagesStartX + (leftImagesWidth - rowWidth) / 2;
+
+ const imageX = rowStartX + col * (imageSize + imageSpacing);
+ // 使用图片实际高度和行间距计算垂直位置
+ const imageY =
+ leftImagesStartY + row * (imageActualHeight + rowSpacing);
+
+ if (image) {
+ // @ts-ignore
+ const scaledHeight =
+ (image.height / image.width) * imageSize;
+ ctx.drawImage(
+ image,
+ imageX,
+ imageY,
+ imageSize,
+ scaledHeight,
+ );
+ }
+ }
+
+ // 绘制加号(左右有间距)
+ const plusX =
+ leftImagesStartX +
+ leftImagesWidth +
+ plusSignSpacing +
+ plusSignSize / 2;
+ const plusY = leftBoxCenterY;
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ // 横线
+ ctx.moveTo(plusX - plusSignSize / 2, plusY);
+ ctx.lineTo(plusX + plusSignSize / 2, plusY);
+ // 竖线
+ ctx.moveTo(plusX, plusY - plusSignSize / 2);
+ ctx.lineTo(plusX, plusY + plusSignSize / 2);
+ ctx.stroke();
+
+ const rightImagesStartX =
+ plusX + plusSignSize / 2 + plusSignSpacing;
+ // 垂直居中:使用图片实际高度计算
+ const rightImagesStartY = leftBoxCenterY - rightImagesHeight / 2;
+
+ // 绘制右侧图片(最多两行)
+ for (let j = 0; j < Math.min(rightImageCount, 10); j++) {
+ const row = Math.floor(j / rightMaxPerRow);
+ const col = j % rightMaxPerRow;
+
+ // 如果超过两行,停止绘制
+ if (row >= 2) break;
+
+ const imagesInThisRow = Math.min(
+ rightMaxPerRow,
+ rightImageCount - row * rightMaxPerRow,
+ );
+ const rowWidth =
+ imagesInThisRow * imageSize +
+ (imagesInThisRow - 1) * imageSpacing;
+ const rowStartX =
+ rightImagesStartX + (rightImagesWidth - rowWidth) / 2;
+
+ const imageX = rowStartX + col * (imageSize + imageSpacing);
+ // 使用图片实际高度和行间距计算垂直位置
+ const imageY =
+ rightImagesStartY + row * (imageActualHeight + rowSpacing);
+
+ if (image) {
+ // @ts-ignore
+ const scaledHeight =
+ (image.height / image.width) * imageSize;
+ ctx.drawImage(
+ image,
+ imageX,
+ imageY,
+ imageSize,
+ scaledHeight,
+ );
+ }
+ }
+ } else {
+ // 减法:展示总数量的图片,后几个加划线
+ const totalCount = problem.left; // 总数
+ const subtractCount = problem.right; // 要减去的数量
+
+ // 获取图片实际高度(用于垂直居中计算)
+ let imageActualHeight = imageSize;
+ if (image) {
+ // @ts-ignore
+ imageActualHeight = (image.height / image.width) * imageSize;
+ }
+
+ // 计算图片布局
+ const imagesPerRow = 5;
+ const totalRows = Math.ceil(totalCount / imagesPerRow);
+ // 使用图片实际高度和行间距计算总高度
+ const totalHeight =
+ totalRows * imageActualHeight + (totalRows - 1) * rowSpacing;
+ const imagesStartY = leftBoxCenterY - totalHeight / 2;
+
+ // 绘制所有图片
+ for (let j = 0; j < totalCount; j++) {
+ const row = Math.floor(j / imagesPerRow);
+ const col = j % imagesPerRow;
+ const imagesInThisRow = Math.min(
+ imagesPerRow,
+ totalCount - row * imagesPerRow,
+ );
+ const rowWidth =
+ imagesInThisRow * imageSize +
+ (imagesInThisRow - 1) * imageSpacing;
+ const rowStartX =
+ leftBoxContentX +
+ (leftBoxWidth - padding * 2 - rowWidth) / 2;
+
+ const imageX = rowStartX + col * (imageSize + imageSpacing);
+ // 使用图片实际高度和行间距计算垂直位置
+ const imageY =
+ imagesStartY + row * (imageActualHeight + rowSpacing);
+
+ if (image) {
+ // @ts-ignore
+ const scaledHeight =
+ (image.height / image.width) * imageSize;
+ ctx.drawImage(
+ image,
+ imageX,
+ imageY,
+ imageSize,
+ scaledHeight,
+ );
+
+ // 如果是后几个图片,绘制虚线删除线(表示减去)
+ if (j >= totalCount - subtractCount) {
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 2;
+ ctx.setLineDash([4, 3]); // 设置虚线样式
+ ctx.beginPath();
+ // 绘制第一条虚线对角线(从左上到右下)
+ ctx.moveTo(imageX, imageY);
+ ctx.lineTo(imageX + imageSize, imageY + scaledHeight);
+ // // 绘制第二条虚线对角线(从右上到左下)
+ // ctx.moveTo(imageX + imageSize, imageY);
+ // ctx.lineTo(imageX, imageY + scaledHeight);
+ ctx.stroke();
+ ctx.setLineDash([]); // 重置为实线,防止影响后续绘制
+ }
+ }
+ }
+ }
+
+ // ========== 绘制右侧答案框 ==========
+ drawAnswerBoxArea(ctx, {
+ rightBoxX,
+ boxY,
+ rightBoxWidth,
+ borderRadius,
+ boxHeight,
+ padding,
+ problemType: problem.type,
+ });
+ }
+}
diff --git a/miniprogram/mathPages/service/additionDraw.ts b/miniprogram/mathPages/service/additionDraw.ts
new file mode 100644
index 0000000..a362274
--- /dev/null
+++ b/miniprogram/mathPages/service/additionDraw.ts
@@ -0,0 +1,67 @@
+import { BaseMathDrawService } from './baseMathDraw';
+import { drawAdditionContent } from './additionContentDraw';
+
+/**
+ * 加减法计算绘制服务
+ * 组合使用基础绘制服务和内容区域绘制服务
+ */
+class AdditionDraw extends BaseMathDrawService {
+ calculationData: {
+ problems: Array<{
+ type: 'addition' | 'subtraction';
+ left: number;
+ right: number;
+ result: number;
+ }>;
+ } | null;
+
+ constructor(
+ canvas: Canvas,
+ ctx: RenderingContext,
+ options?: Record,
+ ) {
+ super(canvas, ctx, options);
+ this.calculationData = null;
+ }
+
+ async draw(
+ calculationData: {
+ problems: Array<{
+ type: 'addition' | 'subtraction';
+ left: number;
+ right: number;
+ result: number;
+ }>;
+ },
+ _calculationType: string = 'addition-5', // 保留参数以保持接口兼容性
+ _imageType?: string, // 可选,不再使用,每个运算会随机选择图片类型
+ ) {
+ if (!calculationData || !calculationData.problems) {
+ return;
+ }
+
+ this.setPrintConfig();
+ this.calculationData = calculationData;
+ this.clear();
+ this.setPaper();
+
+ // 绘制Header
+ if (this.headerType !== 'minimal') {
+ await this.drawHeader();
+ } else {
+ this.drawMiniHeader();
+ }
+
+ // 绘制内容区域(加减法计算)
+ this.drawDivider();
+ await drawAdditionContent({
+ canvas: this.canvas,
+ ctx: this.ctx,
+ problems: this.calculationData.problems,
+ canvasWidth: this.canvasWidth,
+ startY: this.currentY,
+ });
+ }
+}
+
+export default AdditionDraw;
diff --git a/project.private.config.json b/project.private.config.json
index c6f27d4..3378cfc 100644
--- a/project.private.config.json
+++ b/project.private.config.json
@@ -23,12 +23,19 @@
"condition": {
"miniprogram": {
"list": [
+ {
+ "name": "mathPages/addition/addition",
+ "pathName": "mathPages/addition/addition",
+ "query": "id=addition-5",
+ "scene": null,
+ "launchMode": "default"
+ },
{
"name": "mathPages/countMatch/countMatch",
"pathName": "mathPages/countMatch/countMatch",
"query": "id=number-coloring",
- "scene": null,
- "launchMode": "default"
+ "launchMode": "default",
+ "scene": null
},
{
"name": "pages/mathIndex/mathIndex",