feat:v2.4.3版本,10以内的加减计算
@@ -10,7 +10,11 @@
|
|||||||
{
|
{
|
||||||
"root": "mathPages",
|
"root": "mathPages",
|
||||||
"name": "mathPages",
|
"name": "mathPages",
|
||||||
"pages": ["numberFind/numberFind", "countMatch/countMatch"],
|
"pages": [
|
||||||
|
"numberFind/numberFind",
|
||||||
|
"countMatch/countMatch",
|
||||||
|
"addition/addition"
|
||||||
|
],
|
||||||
"independent": false
|
"independent": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
printHeader: {
|
printHeader: {
|
||||||
header: 'wechat',
|
// header: 'wechat',
|
||||||
appName: '涂鸦丫'
|
header: 'LogoImage',
|
||||||
}
|
appName: '涂鸦丫小程序',
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -35,6 +35,34 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
|||||||
desc: '通过连线配对数字和对应的数量图形',
|
desc: '通过连线配对数字和对应的数量图形',
|
||||||
icon: '🔗',
|
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',
|
id: 'missing-number',
|
||||||
title: '填上缺少的数字',
|
title: '填上缺少的数字',
|
||||||
@@ -59,22 +87,4 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
|||||||
desc: '数出物品数量,填写对应的数字',
|
desc: '数出物品数量,填写对应的数字',
|
||||||
icon: '✏️',
|
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: '±',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<view class="page-container">
|
||||||
|
<view class="wrapper">
|
||||||
|
<text class="wrapper-title">预览打印效果</text>
|
||||||
|
|
||||||
|
<!-- 预览打印效果 -->
|
||||||
|
<view id="canvasWrapper" class="canvas-wrapper">
|
||||||
|
<canvas
|
||||||
|
type="2d"
|
||||||
|
id="canvasContent"
|
||||||
|
class="canvas-content"
|
||||||
|
style="width:{{boxWidth}}px;height:{{boxHeight}}px" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 随机生成按钮 -->
|
||||||
|
<view class="random-button-area">
|
||||||
|
<view class="type-selector" bind:tap="onShowTypeSelector">
|
||||||
|
<text class="type-selector-text">{{currentTypeName}}</text>
|
||||||
|
<text class="type-selector-arrow">▼</text>
|
||||||
|
</view>
|
||||||
|
<toy-button
|
||||||
|
class="random-button"
|
||||||
|
type="primary"
|
||||||
|
bind:click="onRandom"
|
||||||
|
width="100%"
|
||||||
|
height="80rpx"
|
||||||
|
icon="refresh"
|
||||||
|
icon-class-prefix="toy-icon">
|
||||||
|
随机生成
|
||||||
|
</toy-button>
|
||||||
|
</view>
|
||||||
|
<van-action-sheet
|
||||||
|
show="{{showTypeSelector}}"
|
||||||
|
actions="{{typeActions}}"
|
||||||
|
bind:select="onSelectType"
|
||||||
|
bind:cancel="onCloseTypeSelector"
|
||||||
|
bind:close="onCloseTypeSelector"
|
||||||
|
cancel-text="取消"
|
||||||
|
custom-class="custom-action-sheet" />
|
||||||
|
</view>
|
||||||
|
<view class="empty"></view>
|
||||||
|
</view>
|
||||||
|
<view class="bottom-btn-box">
|
||||||
|
<toy-button
|
||||||
|
openType="share"
|
||||||
|
type="green"
|
||||||
|
flat="{{true}}"
|
||||||
|
bind:click="onShareAppMessage"
|
||||||
|
width="220rpx"
|
||||||
|
height="80rpx"
|
||||||
|
icon="wechat"
|
||||||
|
icon-class-prefix="toy-icon">
|
||||||
|
分享
|
||||||
|
</toy-button>
|
||||||
|
<toy-button
|
||||||
|
type="primary"
|
||||||
|
flat="{{true}}"
|
||||||
|
bind:click="exportToPrint"
|
||||||
|
width="420rpx"
|
||||||
|
height="80rpx"
|
||||||
|
disabled="{{!hasContent}}"
|
||||||
|
icon="download"
|
||||||
|
icon-class-prefix="toy-icon">
|
||||||
|
下载打印
|
||||||
|
</toy-button>
|
||||||
|
</view>
|
||||||
|
<share-guide-popup
|
||||||
|
show="{{showShareDialog}}"
|
||||||
|
bind:onClose="onCloseShareDialog"
|
||||||
|
bind:onShareSuccess="onShareSuccess" />
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.0 KiB |
@@ -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<void> {
|
||||||
|
// 根据图片类型确定图片目录和最大索引
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string, any>,
|
||||||
|
) {
|
||||||
|
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;
|
||||||
@@ -23,12 +23,19 @@
|
|||||||
"condition": {
|
"condition": {
|
||||||
"miniprogram": {
|
"miniprogram": {
|
||||||
"list": [
|
"list": [
|
||||||
|
{
|
||||||
|
"name": "mathPages/addition/addition",
|
||||||
|
"pathName": "mathPages/addition/addition",
|
||||||
|
"query": "id=addition-5",
|
||||||
|
"scene": null,
|
||||||
|
"launchMode": "default"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "mathPages/countMatch/countMatch",
|
"name": "mathPages/countMatch/countMatch",
|
||||||
"pathName": "mathPages/countMatch/countMatch",
|
"pathName": "mathPages/countMatch/countMatch",
|
||||||
"query": "id=number-coloring",
|
"query": "id=number-coloring",
|
||||||
"scene": null,
|
"launchMode": "default",
|
||||||
"launchMode": "default"
|
"scene": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pages/mathIndex/mathIndex",
|
"name": "pages/mathIndex/mathIndex",
|
||||||
|
|||||||