feat:九九乘法表
This commit is contained in:
@@ -23,7 +23,10 @@
|
|||||||
"makeTen/makeTen",
|
"makeTen/makeTen",
|
||||||
"breakTen/breakTen",
|
"breakTen/breakTen",
|
||||||
"flatTen/flatTen",
|
"flatTen/flatTen",
|
||||||
"borrowTen/borrowTen"
|
"borrowTen/borrowTen",
|
||||||
|
"oneDigitAddition/oneDigitAddition",
|
||||||
|
"calculationPractice/calculationPractice",
|
||||||
|
"multiplicationTable/multiplicationTable"
|
||||||
],
|
],
|
||||||
"independent": false
|
"independent": false
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -10,7 +10,7 @@ export const defaultPrintConfig: PrintConfig = {
|
|||||||
// header: 'LogoImage',
|
// header: 'LogoImage',
|
||||||
header: 'wechat',
|
header: 'wechat',
|
||||||
appName: '涂鸦丫小程序',
|
appName: '涂鸦丫小程序',
|
||||||
appHint: '数学|专注力|练字|涂鸦',
|
appHint: '数学|专注力|识字|练字',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultShareConfig = {
|
export const defaultShareConfig = {
|
||||||
|
|||||||
@@ -8,6 +8,22 @@ export interface MathFunctionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
||||||
|
{
|
||||||
|
id: 'number-sort',
|
||||||
|
page: 'numberSort',
|
||||||
|
title: '数字排序',
|
||||||
|
desc: '数字排序,写出正确顺序',
|
||||||
|
icon: '🔢',
|
||||||
|
img: '/assets/mathEntrance/number-sort.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'one-digit-addition',
|
||||||
|
page: 'oneDigitAddition',
|
||||||
|
title: '一位数加法',
|
||||||
|
desc: '通过圆点学习一位数加法运算',
|
||||||
|
icon: '➕',
|
||||||
|
img: '/assets/mathEntrance/one-digit-addition.png',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'make-ten',
|
id: 'make-ten',
|
||||||
page: 'makeTen',
|
page: 'makeTen',
|
||||||
@@ -140,14 +156,6 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
|||||||
icon: '❓',
|
icon: '❓',
|
||||||
img: '/assets/mathEntrance/missing-number.png',
|
img: '/assets/mathEntrance/missing-number.png',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'number-sort',
|
|
||||||
page: 'numberSort',
|
|
||||||
title: '数字排序',
|
|
||||||
desc: '数字排序,写出正确顺序',
|
|
||||||
icon: '🔢',
|
|
||||||
img: '/assets/mathEntrance/number-sort.png',
|
|
||||||
},
|
|
||||||
|
|
||||||
// 第四阶段:简单运算(从易到难)
|
// 第四阶段:简单运算(从易到难)
|
||||||
{
|
{
|
||||||
@@ -182,4 +190,38 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
|||||||
icon: '±',
|
icon: '±',
|
||||||
img: '/assets/mathEntrance/addition-subtraction-10.png',
|
img: '/assets/mathEntrance/addition-subtraction-10.png',
|
||||||
},
|
},
|
||||||
|
// 计算练习题
|
||||||
|
{
|
||||||
|
id: 'practice-addition',
|
||||||
|
page: 'calculationPractice',
|
||||||
|
title: '加法运算',
|
||||||
|
desc: '练习10/20/50/100以内加法运算',
|
||||||
|
icon: '➕',
|
||||||
|
img: '/assets/mathEntrance/practice-addition.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'practice-subtraction',
|
||||||
|
page: 'calculationPractice',
|
||||||
|
title: '减法运算',
|
||||||
|
desc: '练习10/20/50/100以内减法运算',
|
||||||
|
icon: '➖',
|
||||||
|
img: '/assets/mathEntrance/practice-subtraction.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'practice-mixed',
|
||||||
|
page: 'calculationPractice',
|
||||||
|
title: '混合运算',
|
||||||
|
desc: '练习10/20/50/100以内加减法混合运算',
|
||||||
|
icon: '±',
|
||||||
|
img: '/assets/mathEntrance/practice-mixed.png',
|
||||||
|
},
|
||||||
|
// 九九乘法表
|
||||||
|
{
|
||||||
|
id: 'multiplication-table',
|
||||||
|
page: 'multiplicationTable',
|
||||||
|
title: '九九乘法表',
|
||||||
|
desc: '学习九九乘法口诀',
|
||||||
|
icon: '✖️',
|
||||||
|
img: '/assets/mathEntrance/multiplication-table.png',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"math-bottom-buttons": "../../components/math-bottom-buttons/math-bottom-buttons",
|
||||||
|
"math-type-selector": "../../components/math-type-selector/math-type-selector",
|
||||||
|
"draw-ad": "../../components/draw-ad/draw-ad"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// 计算练习题页面样式
|
||||||
|
@import '../../base/baseDrawPage.wxss';
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import CalculationPracticeDraw from '../shared/service/calculationPracticeDraw';
|
||||||
|
import {
|
||||||
|
createMathPage,
|
||||||
|
CanvasDataState,
|
||||||
|
} from '../shared/common/mathPageMixin';
|
||||||
|
|
||||||
|
type OperationType = 'addition' | 'subtraction' | 'mixed';
|
||||||
|
|
||||||
|
createMathPage({
|
||||||
|
canvas: null as Canvas | null,
|
||||||
|
ctx: null as RenderingContext | null,
|
||||||
|
boxHeight: 0,
|
||||||
|
boxWidth: 0,
|
||||||
|
drawService: null as CalculationPracticeDraw | null,
|
||||||
|
calculationPracticeData: null as {
|
||||||
|
problems: Array<{
|
||||||
|
left: number;
|
||||||
|
operator: '+' | '-';
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
} | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
pageTitle: '计算练习题',
|
||||||
|
functionId: '',
|
||||||
|
hasContent: false,
|
||||||
|
showShareDialog: false,
|
||||||
|
operationType: 'addition' as OperationType, // 运算类型:加法、减法、混合
|
||||||
|
currentMode: 'within-10', // 默认10以内
|
||||||
|
currentModeName: '10以内',
|
||||||
|
typeActions: [
|
||||||
|
{ name: '10以内', value: 'within-10' },
|
||||||
|
{ name: '20以内', value: 'within-20' },
|
||||||
|
{ name: '50以内', value: 'within-50' },
|
||||||
|
{ name: '100以内', value: 'within-100' },
|
||||||
|
],
|
||||||
|
} as CanvasDataState & {
|
||||||
|
operationType: OperationType;
|
||||||
|
currentMode: string;
|
||||||
|
currentModeName: string;
|
||||||
|
typeActions: Array<{ name: string; value: string }>;
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(options: { id?: string; type?: string }) {
|
||||||
|
const functionId = options.id || 'practice-addition';
|
||||||
|
|
||||||
|
// 根据 functionId 确定运算类型
|
||||||
|
let operationType: OperationType = 'addition';
|
||||||
|
if (functionId === 'practice-subtraction') {
|
||||||
|
operationType = 'subtraction';
|
||||||
|
} else if (functionId === 'practice-mixed') {
|
||||||
|
operationType = 'mixed';
|
||||||
|
} else if (options.type) {
|
||||||
|
operationType = options.type as OperationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageTitle = '计算练习题';
|
||||||
|
if (operationType === 'addition') {
|
||||||
|
pageTitle = '加法运算';
|
||||||
|
} else if (operationType === 'subtraction') {
|
||||||
|
pageTitle = '减法运算';
|
||||||
|
} else if (operationType === 'mixed') {
|
||||||
|
pageTitle = '混合运算';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
operationType,
|
||||||
|
pageTitle,
|
||||||
|
});
|
||||||
|
this.initPageInfo(functionId, pageTitle);
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.initCanvas({
|
||||||
|
createDrawService: (
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
return new CalculationPracticeDraw(canvas, ctx, options);
|
||||||
|
},
|
||||||
|
drawServiceOptions: {},
|
||||||
|
onCanvasReady: () => {
|
||||||
|
// 初始随机生成
|
||||||
|
this.onRandom();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制Canvas内容
|
||||||
|
*/
|
||||||
|
async drawCanvas() {
|
||||||
|
if (!this.ctx || !this.drawService || !this.calculationPracticeData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.drawService.draw(this.calculationPracticeData);
|
||||||
|
this.setData({ hasContent: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('绘制失败:', error);
|
||||||
|
this.setData({ hasContent: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成
|
||||||
|
*/
|
||||||
|
onRandom() {
|
||||||
|
// 根据当前模式确定最大值
|
||||||
|
let maxValue = 10;
|
||||||
|
if (this.data.currentMode === 'within-20') {
|
||||||
|
maxValue = 20;
|
||||||
|
} else if (this.data.currentMode === 'within-50') {
|
||||||
|
maxValue = 50;
|
||||||
|
} else if (this.data.currentMode === 'within-100') {
|
||||||
|
maxValue = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problems: Array<{
|
||||||
|
left: number;
|
||||||
|
operator: '+' | '-';
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}> = [];
|
||||||
|
const usedProblems = new Set<string>();
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 2000;
|
||||||
|
|
||||||
|
const targetCount = 11 * 3; // 12行3列 = 36道题
|
||||||
|
|
||||||
|
while (problems.length < targetCount && attempts < maxAttempts) {
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
let left: number;
|
||||||
|
let right: number;
|
||||||
|
let operator: '+' | '-';
|
||||||
|
let result: number;
|
||||||
|
|
||||||
|
// 根据运算类型生成题目
|
||||||
|
if (this.data.operationType === 'addition') {
|
||||||
|
// 加法:left + right <= maxValue
|
||||||
|
left = Math.floor(Math.random() * (maxValue - 1)) + 1; // 1 到 maxValue-1
|
||||||
|
const maxRight = maxValue - left;
|
||||||
|
right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
|
||||||
|
operator = '+';
|
||||||
|
result = left + right;
|
||||||
|
} else if (this.data.operationType === 'subtraction') {
|
||||||
|
// 减法:left - right >= 0, left <= maxValue
|
||||||
|
left = Math.floor(Math.random() * maxValue) + 1; // 1 到 maxValue
|
||||||
|
right = Math.floor(Math.random() * left) + 1; // 1 到 left
|
||||||
|
operator = '-';
|
||||||
|
result = left - right;
|
||||||
|
} else {
|
||||||
|
// 混合运算:随机选择加法或减法
|
||||||
|
const isAddition = Math.random() < 0.5;
|
||||||
|
if (isAddition) {
|
||||||
|
left = Math.floor(Math.random() * (maxValue - 1)) + 1;
|
||||||
|
const maxRight = maxValue - left;
|
||||||
|
right = Math.floor(Math.random() * maxRight) + 1;
|
||||||
|
operator = '+';
|
||||||
|
result = left + right;
|
||||||
|
} else {
|
||||||
|
left = Math.floor(Math.random() * maxValue) + 1;
|
||||||
|
right = Math.floor(Math.random() * left) + 1;
|
||||||
|
operator = '-';
|
||||||
|
result = left - right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 "left,operator,right" 作为唯一标识,避免重复
|
||||||
|
const problemKey = `${left},${operator},${right}`;
|
||||||
|
if (usedProblems.has(problemKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedProblems.add(problemKey);
|
||||||
|
problems.push({
|
||||||
|
left,
|
||||||
|
operator,
|
||||||
|
right,
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (problems.length < targetCount) {
|
||||||
|
console.warn(
|
||||||
|
`只生成了 ${problems.length} 道题目,可能符合条件的题目组合不足`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.calculationPracticeData = { problems };
|
||||||
|
this.drawCanvas();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 选择类型 */
|
||||||
|
onSelectType(event: any) {
|
||||||
|
const { name, value } = event.detail;
|
||||||
|
this.setData({
|
||||||
|
currentMode: value,
|
||||||
|
currentModeName: name,
|
||||||
|
});
|
||||||
|
// 重新生成数据
|
||||||
|
this.onRandom();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<!-- 模版不能跨包引用 -->
|
||||||
|
<import src="../shared/templates/canvas-page-template.wxml" />
|
||||||
|
|
||||||
|
<template
|
||||||
|
is="canvasPageTemplate"
|
||||||
|
data="{{boxWidth: boxWidth, boxHeight: boxHeight, hasTypeSelector: true, currentModeName: currentModeName, typeActions: typeActions, adType: 'mathDraw', disabled: !hasContent, showShareDialog: showShareDialog}}" />
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"math-bottom-buttons": "../../components/math-bottom-buttons/math-bottom-buttons",
|
||||||
|
"draw-ad": "../../components/draw-ad/draw-ad"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// 九九乘法表页面样式
|
||||||
|
@import '../../base/baseDrawPage.wxss';
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import MultiplicationTableDraw from '../shared/service/multiplicationTableDraw';
|
||||||
|
import {
|
||||||
|
createMathPage,
|
||||||
|
CanvasDataState,
|
||||||
|
} from '../shared/common/mathPageMixin';
|
||||||
|
|
||||||
|
createMathPage({
|
||||||
|
canvas: null as Canvas | null,
|
||||||
|
ctx: null as RenderingContext | null,
|
||||||
|
boxHeight: 0,
|
||||||
|
boxWidth: 0,
|
||||||
|
drawService: null as MultiplicationTableDraw | null,
|
||||||
|
multiplicationTableData: null as {} | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
pageTitle: '九九乘法表',
|
||||||
|
functionId: '',
|
||||||
|
hasContent: false,
|
||||||
|
showShareDialog: false,
|
||||||
|
} as CanvasDataState,
|
||||||
|
|
||||||
|
onLoad(options: { id?: string }) {
|
||||||
|
const functionId = options.id || 'multiplication-table';
|
||||||
|
this.setData({
|
||||||
|
pageTitle: '九九乘法表',
|
||||||
|
});
|
||||||
|
this.initPageInfo(functionId, '九九乘法表');
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.initCanvas({
|
||||||
|
createDrawService: (
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
return new MultiplicationTableDraw(canvas, ctx, options);
|
||||||
|
},
|
||||||
|
drawServiceOptions: {
|
||||||
|
title: '九九乘法表',
|
||||||
|
},
|
||||||
|
onCanvasReady: () => {
|
||||||
|
// 初始绘制
|
||||||
|
this.drawCanvas();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制Canvas内容
|
||||||
|
*/
|
||||||
|
async drawCanvas() {
|
||||||
|
if (!this.ctx || !this.drawService) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.drawService.draw({});
|
||||||
|
this.setData({ hasContent: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('绘制失败:', error);
|
||||||
|
this.setData({ hasContent: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成(重新绘制,颜色会随机变化)
|
||||||
|
*/
|
||||||
|
onRandom() {
|
||||||
|
this.drawCanvas();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<!-- 模版不能跨包引用 -->
|
||||||
|
<import src="../shared/templates/canvas-page-template.wxml" />
|
||||||
|
|
||||||
|
<template
|
||||||
|
is="canvasPageTemplate"
|
||||||
|
data="{{boxWidth: boxWidth, boxHeight: boxHeight, hasTypeSelector: false, adType: 'mathDraw', disabled: !hasContent, showShareDialog: showShareDialog}}" />
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"math-bottom-buttons": "../../components/math-bottom-buttons/math-bottom-buttons",
|
||||||
|
"draw-ad": "../../components/draw-ad/draw-ad"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// 一位数加法练习页面样式
|
||||||
|
|
||||||
|
@import '../../base/baseDrawPage.wxss';
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import OneDigitAdditionDraw from '../shared/service/oneDigitAdditionDraw';
|
||||||
|
import {
|
||||||
|
createMathPage,
|
||||||
|
CanvasDataState,
|
||||||
|
} from '../shared/common/mathPageMixin';
|
||||||
|
|
||||||
|
createMathPage({
|
||||||
|
canvas: null as Canvas | null,
|
||||||
|
ctx: null as RenderingContext | null,
|
||||||
|
boxHeight: 0,
|
||||||
|
boxWidth: 0,
|
||||||
|
drawService: null as OneDigitAdditionDraw | null,
|
||||||
|
oneDigitAdditionData: null as {
|
||||||
|
problems: Array<{
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
} | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
pageTitle: '一位数加法',
|
||||||
|
subTitle: '通过圆点学习一位数加法运算',
|
||||||
|
functionId: '',
|
||||||
|
hasContent: false,
|
||||||
|
showShareDialog: false,
|
||||||
|
} as CanvasDataState,
|
||||||
|
|
||||||
|
onLoad(options: { id?: string }) {
|
||||||
|
const functionId = options.id || 'one-digit-addition';
|
||||||
|
this.initPageInfo(functionId, '一位数加法');
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.initCanvas({
|
||||||
|
createDrawService: (
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
return new OneDigitAdditionDraw(canvas, ctx, options);
|
||||||
|
},
|
||||||
|
drawServiceOptions: {
|
||||||
|
subTitle: this.data.subTitle,
|
||||||
|
},
|
||||||
|
onCanvasReady: () => {
|
||||||
|
// 初始随机生成
|
||||||
|
this.onRandom();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制Canvas内容
|
||||||
|
*/
|
||||||
|
async drawCanvas() {
|
||||||
|
if (!this.ctx || !this.drawService || !this.oneDigitAdditionData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.drawService.draw(this.oneDigitAdditionData);
|
||||||
|
this.setData({ hasContent: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('绘制失败:', error);
|
||||||
|
this.setData({ hasContent: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成
|
||||||
|
*/
|
||||||
|
onRandom() {
|
||||||
|
const problems: Array<{
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}> = [];
|
||||||
|
const usedProblems = new Set<string>(); // 用于记录已生成的题目,避免重复
|
||||||
|
|
||||||
|
// 生成6道不重复的题目(6行1列)
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 1000; // 最大尝试次数,避免无限循环
|
||||||
|
|
||||||
|
while (problems.length < 6 && attempts < maxAttempts) {
|
||||||
|
attempts++;
|
||||||
|
// 两个加数的和 <= 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;
|
||||||
|
|
||||||
|
// 使用 "left,right" 作为唯一标识,避免重复
|
||||||
|
const problemKey = `${left},${right}`;
|
||||||
|
if (usedProblems.has(problemKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
usedProblems.add(problemKey);
|
||||||
|
problems.push({
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (problems.length < 6) {
|
||||||
|
console.warn(
|
||||||
|
`只生成了 ${problems.length} 道题目,可能符合条件的题目组合不足`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oneDigitAdditionData = { problems };
|
||||||
|
this.drawCanvas();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<!-- 模版不能跨包引用 -->
|
||||||
|
<import src="../shared/templates/canvas-page-template.wxml" />
|
||||||
|
|
||||||
|
<template
|
||||||
|
is="canvasPageTemplate"
|
||||||
|
data="{{boxWidth: boxWidth, boxHeight: boxHeight, hasTypeSelector: false, adType: 'mathDraw', disabled: !hasContent, showShareDialog: showShareDialog}}" />
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* 计算练习题绘制服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseDrawService } from '../../../service/baseDraw';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算练习题数据
|
||||||
|
*/
|
||||||
|
export interface CalculationPracticeData {
|
||||||
|
problems: Array<{
|
||||||
|
left: number;
|
||||||
|
operator: '+' | '-';
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算练习题绘制服务
|
||||||
|
*/
|
||||||
|
class CalculationPracticeDraw extends BaseDrawService {
|
||||||
|
practiceData: CalculationPracticeData | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
super(canvas, ctx, options);
|
||||||
|
this.practiceData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制计算练习题内容
|
||||||
|
*/
|
||||||
|
async draw(practiceData: CalculationPracticeData) {
|
||||||
|
if (!practiceData || !practiceData.problems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.practiceData = practiceData;
|
||||||
|
this.prepareDraw();
|
||||||
|
await this.drawHeaderAndDivider();
|
||||||
|
await this.drawContent({
|
||||||
|
canvas: this.canvas,
|
||||||
|
ctx: this.ctx,
|
||||||
|
problems: this.practiceData.problems,
|
||||||
|
canvasWidth: this.canvasWidth,
|
||||||
|
startY: this.currentY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制内容区域
|
||||||
|
*/
|
||||||
|
private async drawContent(params: {
|
||||||
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
ctx: RenderingContext;
|
||||||
|
problems: Array<{
|
||||||
|
left: number;
|
||||||
|
operator: '+' | '-';
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
canvasWidth: number;
|
||||||
|
startY: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
const { ctx, problems, canvasWidth, startY } = params;
|
||||||
|
const rowStartY = startY + 30; // 顶部间距
|
||||||
|
const margin = 30;
|
||||||
|
const rows = 12;
|
||||||
|
const cols = 3;
|
||||||
|
const rowSpacing = 60; // 行间距
|
||||||
|
const colSpacing = 15; // 列间距
|
||||||
|
|
||||||
|
// 计算可用宽度
|
||||||
|
const availableWidth = canvasWidth - margin * 2;
|
||||||
|
const colWidth = (availableWidth - colSpacing * (cols - 1)) / cols;
|
||||||
|
|
||||||
|
// 数字和符号相关参数
|
||||||
|
const fontSize = 18;
|
||||||
|
const numberSpacing = 5; // 数字之间的间距
|
||||||
|
const boxSize = 28; // 答案框大小
|
||||||
|
const boxBorderColor = '#555'; // 答案框边框颜色
|
||||||
|
const symbolWidth = 16; // 符号宽度
|
||||||
|
|
||||||
|
// 计算每道题的起始位置(10行3列)
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const problemIndex = row * cols + col;
|
||||||
|
if (problemIndex >= problems.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problem = problems[problemIndex];
|
||||||
|
const problemX = margin + col * (colWidth + colSpacing);
|
||||||
|
const problemY = rowStartY + row * rowSpacing;
|
||||||
|
|
||||||
|
// 绘制单道题
|
||||||
|
this.drawSingleProblem(
|
||||||
|
ctx,
|
||||||
|
problemX,
|
||||||
|
problemY,
|
||||||
|
colWidth,
|
||||||
|
problem,
|
||||||
|
{
|
||||||
|
fontSize,
|
||||||
|
numberSpacing,
|
||||||
|
boxSize,
|
||||||
|
boxBorderColor,
|
||||||
|
symbolWidth,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制单道题
|
||||||
|
*/
|
||||||
|
private drawSingleProblem(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
problem: {
|
||||||
|
left: number;
|
||||||
|
operator: '+' | '-';
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
fontSize: number;
|
||||||
|
numberSpacing: number;
|
||||||
|
boxSize: number;
|
||||||
|
boxBorderColor: string;
|
||||||
|
symbolWidth: number;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
fontSize,
|
||||||
|
numberSpacing,
|
||||||
|
boxSize,
|
||||||
|
boxBorderColor,
|
||||||
|
symbolWidth,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// 计算每个元素的宽度(统一使用,确保对齐)
|
||||||
|
const numberWidth = fontSize * 1.5; // 数字宽度(考虑两位数)
|
||||||
|
// const elementWidth = Math.max(numberWidth, boxSize);
|
||||||
|
const elementWidth = numberWidth;
|
||||||
|
|
||||||
|
// 计算总宽度并居中
|
||||||
|
const totalWidth =
|
||||||
|
elementWidth +
|
||||||
|
numberSpacing +
|
||||||
|
symbolWidth +
|
||||||
|
numberSpacing +
|
||||||
|
elementWidth +
|
||||||
|
numberSpacing +
|
||||||
|
symbolWidth +
|
||||||
|
numberSpacing +
|
||||||
|
elementWidth;
|
||||||
|
|
||||||
|
const startX = x + (width - totalWidth) / 2;
|
||||||
|
let currentX = startX;
|
||||||
|
|
||||||
|
// 答案框的Y坐标
|
||||||
|
const resultBoxY = y;
|
||||||
|
// 数字和符号的Y坐标(与答案框垂直居中对齐)
|
||||||
|
const centerY = resultBoxY + boxSize / 2;
|
||||||
|
|
||||||
|
// 绘制左侧数字
|
||||||
|
const leftCenterX = currentX + elementWidth / 2;
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.font = `${fontSize}px Arial`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(String(problem.left), leftCenterX, centerY);
|
||||||
|
|
||||||
|
currentX += elementWidth + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制运算符(与数字垂直居中对齐)
|
||||||
|
const operatorX = currentX + symbolWidth / 2;
|
||||||
|
this.drawSymbol(
|
||||||
|
operatorX,
|
||||||
|
centerY,
|
||||||
|
problem.operator,
|
||||||
|
symbolWidth * 0.8,
|
||||||
|
);
|
||||||
|
currentX = operatorX + symbolWidth / 2 + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制右侧数字
|
||||||
|
const rightCenterX = currentX + elementWidth / 2;
|
||||||
|
ctx.fillText(String(problem.right), rightCenterX, centerY);
|
||||||
|
|
||||||
|
currentX += elementWidth + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制等号(与数字垂直居中对齐)
|
||||||
|
const equalsX = currentX + symbolWidth / 2;
|
||||||
|
this.drawSymbol(equalsX, centerY, '=', symbolWidth * 0.8);
|
||||||
|
currentX = equalsX + symbolWidth / 2 + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制答案框
|
||||||
|
const resultBoxX = currentX + (elementWidth - boxSize) / 2;
|
||||||
|
this.drawRoundedRect(resultBoxX, resultBoxY, boxSize, boxSize, {
|
||||||
|
isDashed: false,
|
||||||
|
radius: 4,
|
||||||
|
color: boxBorderColor,
|
||||||
|
lineWidth: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CalculationPracticeDraw;
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
/**
|
||||||
|
* 九九乘法表绘制服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseDrawService } from '../../../service/baseDraw';
|
||||||
|
import { getRandomUniqueNumberColors } from '../../../constants/colors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字转中文(1-99)
|
||||||
|
*/
|
||||||
|
function numberToChinese(num: number): string {
|
||||||
|
const digits = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
|
||||||
|
const tens = [
|
||||||
|
'',
|
||||||
|
'十',
|
||||||
|
'二十',
|
||||||
|
'三十',
|
||||||
|
'四十',
|
||||||
|
'五十',
|
||||||
|
'六十',
|
||||||
|
'七十',
|
||||||
|
'八十',
|
||||||
|
'九十',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (num === 0) return '零';
|
||||||
|
if (num < 10) return digits[num];
|
||||||
|
if (num === 10) return '十';
|
||||||
|
if (num < 20) return '十' + digits[num % 10];
|
||||||
|
if (num % 10 === 0) return tens[Math.floor(num / 10)];
|
||||||
|
return tens[Math.floor(num / 10)] + digits[num % 10];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成九九乘法表汉字口诀
|
||||||
|
* @param a 第一个乘数
|
||||||
|
* @param b 第二个乘数
|
||||||
|
* @param result 结果
|
||||||
|
*/
|
||||||
|
function getChineseFormula(a: number, b: number, result: number): string {
|
||||||
|
const aChinese = numberToChinese(a);
|
||||||
|
const bChinese = numberToChinese(b);
|
||||||
|
const resultChinese = numberToChinese(result);
|
||||||
|
return `${aChinese}${bChinese}得${resultChinese}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 九九乘法表数据
|
||||||
|
*/
|
||||||
|
export interface MultiplicationTableData {
|
||||||
|
// 不需要额外数据,九九乘法表是固定的
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 九九乘法表绘制服务
|
||||||
|
*/
|
||||||
|
class MultiplicationTableDraw extends BaseDrawService {
|
||||||
|
tableData: MultiplicationTableData | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
super(canvas, ctx, options);
|
||||||
|
this.tableData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制九九乘法表内容
|
||||||
|
*/
|
||||||
|
async draw(tableData?: MultiplicationTableData) {
|
||||||
|
this.tableData = tableData || {};
|
||||||
|
this.prepareDraw();
|
||||||
|
await this.drawHeaderAndDivider();
|
||||||
|
await this.drawContent({
|
||||||
|
canvas: this.canvas,
|
||||||
|
ctx: this.ctx,
|
||||||
|
canvasWidth: this.canvasWidth,
|
||||||
|
startY: this.currentY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制内容区域
|
||||||
|
*/
|
||||||
|
private async drawContent(params: {
|
||||||
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
ctx: RenderingContext;
|
||||||
|
canvasWidth: number;
|
||||||
|
startY: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
const { ctx, canvasWidth, startY } = params;
|
||||||
|
|
||||||
|
// 获取9种颜色(1-9列,每列一种颜色)
|
||||||
|
const colors = getRandomUniqueNumberColors(9);
|
||||||
|
|
||||||
|
// 布局参数
|
||||||
|
const margin = 30; // 左右边距
|
||||||
|
const topMargin = 40; // 顶部间距
|
||||||
|
const sectionSpacing = 40; // 上下两部分之间的间距
|
||||||
|
const dividerMargin = 20; // 分割线左右边距
|
||||||
|
|
||||||
|
// 计算可用宽度
|
||||||
|
const availableWidth = canvasWidth - margin * 2;
|
||||||
|
const cellWidth = availableWidth / 9; // 每列宽度
|
||||||
|
const rowHeight = 26; // 每行高度(减小行高)
|
||||||
|
const fontSize = 12; // 字体大小(减小字体)
|
||||||
|
|
||||||
|
let currentY = startY + topMargin;
|
||||||
|
|
||||||
|
// ========== 绘制数字版九九乘法表 ==========
|
||||||
|
const numberTableStartY = currentY;
|
||||||
|
|
||||||
|
// 绘制表格边框和内容
|
||||||
|
for (let row = 1; row <= 9; row++) {
|
||||||
|
const rowY = numberTableStartY + (row - 1) * rowHeight;
|
||||||
|
for (let col = 1; col <= row; col++) {
|
||||||
|
const colX = margin + (col - 1) * cellWidth;
|
||||||
|
const color = colors[col - 1];
|
||||||
|
|
||||||
|
// 绘制单元格边框
|
||||||
|
ctx.strokeStyle = '#333';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeRect(colX, rowY, cellWidth, rowHeight);
|
||||||
|
|
||||||
|
// 生成公式:col x row = result
|
||||||
|
const result = col * row;
|
||||||
|
const formula = `${col}×${row}=${result}`;
|
||||||
|
|
||||||
|
// 绘制公式(带颜色)
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.font = `bold ${fontSize}px "Microsoft Yahei"`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(
|
||||||
|
formula,
|
||||||
|
colX + cellWidth / 2,
|
||||||
|
rowY + rowHeight / 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数字版结束位置
|
||||||
|
const numberTableEndY = numberTableStartY + 9 * rowHeight;
|
||||||
|
currentY = numberTableEndY + sectionSpacing;
|
||||||
|
|
||||||
|
// ========== 绘制虚线分割线 ==========
|
||||||
|
this.drawLine(
|
||||||
|
dividerMargin,
|
||||||
|
currentY,
|
||||||
|
canvasWidth - dividerMargin,
|
||||||
|
currentY,
|
||||||
|
{
|
||||||
|
isDashed: true,
|
||||||
|
dashPattern: [4, 4],
|
||||||
|
color: '#999',
|
||||||
|
lineWidth: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
currentY += sectionSpacing;
|
||||||
|
|
||||||
|
// ========== 绘制汉字版九九乘法表 ==========
|
||||||
|
const chineseTableStartY = currentY;
|
||||||
|
const hzFontSize = 8;
|
||||||
|
|
||||||
|
// 绘制表格边框和内容
|
||||||
|
for (let row = 1; row <= 9; row++) {
|
||||||
|
const rowY = chineseTableStartY + (row - 1) * rowHeight;
|
||||||
|
for (let col = 1; col <= row; col++) {
|
||||||
|
const colX = margin + (col - 1) * cellWidth;
|
||||||
|
const color = colors[col - 1]; // 使用和数字版相同的颜色
|
||||||
|
|
||||||
|
// 绘制单元格边框
|
||||||
|
ctx.strokeStyle = '#333';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeRect(colX, rowY, cellWidth, rowHeight);
|
||||||
|
|
||||||
|
// 生成汉字口诀
|
||||||
|
const result = col * row;
|
||||||
|
const chineseFormula = getChineseFormula(col, row, result);
|
||||||
|
|
||||||
|
// 绘制汉字口诀(带颜色)
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.font = `bold ${hzFontSize}px "Microsoft Yahei"`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(
|
||||||
|
chineseFormula,
|
||||||
|
colX + cellWidth / 2,
|
||||||
|
rowY + rowHeight / 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MultiplicationTableDraw;
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
/**
|
||||||
|
* 一位数加法绘制服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseDrawService } from '../../../service/baseDraw';
|
||||||
|
import { getRandomNumberColor } from '../../../constants/colors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一位数加法数据
|
||||||
|
*/
|
||||||
|
export interface OneDigitAdditionData {
|
||||||
|
problems: Array<{
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一位数加法绘制服务
|
||||||
|
*/
|
||||||
|
class OneDigitAdditionDraw extends BaseDrawService {
|
||||||
|
additionData: OneDigitAdditionData | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
super(canvas, ctx, options);
|
||||||
|
this.additionData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制一位数加法内容
|
||||||
|
*/
|
||||||
|
async draw(additionData: OneDigitAdditionData) {
|
||||||
|
if (!additionData || !additionData.problems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.additionData = additionData;
|
||||||
|
this.prepareDraw();
|
||||||
|
await this.drawHeaderAndDivider();
|
||||||
|
await this.drawContent({
|
||||||
|
canvas: this.canvas,
|
||||||
|
ctx: this.ctx,
|
||||||
|
problems: this.additionData.problems,
|
||||||
|
canvasWidth: this.canvasWidth,
|
||||||
|
startY: this.currentY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制内容区域
|
||||||
|
*/
|
||||||
|
private async drawContent(params: {
|
||||||
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
ctx: RenderingContext;
|
||||||
|
problems: Array<{
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
canvasWidth: number;
|
||||||
|
startY: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
const { ctx, problems, canvasWidth, startY } = params;
|
||||||
|
const rowStartY = startY + 30; // 顶部间距
|
||||||
|
const margin = 50;
|
||||||
|
const rows = 6;
|
||||||
|
const rowSpacing = 110; // 行间距
|
||||||
|
|
||||||
|
// 计算可用宽度(单列居中)
|
||||||
|
const availableWidth = canvasWidth - margin * 2;
|
||||||
|
const colCenterX = canvasWidth / 2; // 列的中心点
|
||||||
|
|
||||||
|
// 圆点相关参数(适度调大)
|
||||||
|
const dotRadius = 5; // 圆点半径(调大)
|
||||||
|
const dotSpacing = 8; // 圆点之间的间距(调大)
|
||||||
|
const dotsPerRow = 5; // 每行最多5个圆点
|
||||||
|
const dotRowSpacing = 8; // 圆点行之间的间距(调大)
|
||||||
|
|
||||||
|
// 数字和符号相关参数
|
||||||
|
const fontSize = 26;
|
||||||
|
const numberSpacing = 14; // 数字之间的间距
|
||||||
|
const boxSize = 36; // 答案框大小
|
||||||
|
const boxBorderColor = '#555'; // 答案框边框颜色
|
||||||
|
|
||||||
|
// 计算每道题的起始位置(6行1列)
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
const problemIndex = row;
|
||||||
|
if (problemIndex >= problems.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problem = problems[problemIndex];
|
||||||
|
const problemY = rowStartY + row * rowSpacing;
|
||||||
|
|
||||||
|
// 绘制单道题(在页面中居中)
|
||||||
|
this.drawSingleProblem(
|
||||||
|
ctx,
|
||||||
|
colCenterX - availableWidth / 2, // 列的起始X坐标
|
||||||
|
problemY,
|
||||||
|
availableWidth, // 使用可用宽度
|
||||||
|
problem,
|
||||||
|
{
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRowSpacing,
|
||||||
|
fontSize,
|
||||||
|
numberSpacing,
|
||||||
|
boxSize,
|
||||||
|
boxBorderColor,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制行之间的虚线分割(除了最后一行)
|
||||||
|
if (row < rows - 1) {
|
||||||
|
const dividerY = rowStartY + (row + 1) * rowSpacing - 20;
|
||||||
|
this.drawDashedDivider(dividerY, margin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制单道题
|
||||||
|
*/
|
||||||
|
private drawSingleProblem(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
problem: { left: number; right: number; result: number },
|
||||||
|
options: {
|
||||||
|
dotRadius: number;
|
||||||
|
dotSpacing: number;
|
||||||
|
dotsPerRow: number;
|
||||||
|
dotRowSpacing: number;
|
||||||
|
fontSize: number;
|
||||||
|
numberSpacing: number;
|
||||||
|
boxSize: number;
|
||||||
|
boxBorderColor: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRowSpacing,
|
||||||
|
fontSize,
|
||||||
|
numberSpacing,
|
||||||
|
boxSize,
|
||||||
|
boxBorderColor,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// 为整个运算公式生成统一的随机颜色
|
||||||
|
const problemColor = getRandomNumberColor();
|
||||||
|
|
||||||
|
// 计算5个圆点的最大宽度(统一使用,确保间距一致)
|
||||||
|
const maxDotsWidth = this.calculateDotsWidth(
|
||||||
|
dotsPerRow, // 使用5个圆点的宽度
|
||||||
|
dotsPerRow,
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 计算每个元素的宽度(统一使用5个圆点的宽度)
|
||||||
|
const leftElementWidth = Math.max(maxDotsWidth, fontSize);
|
||||||
|
const rightElementWidth = Math.max(maxDotsWidth, fontSize);
|
||||||
|
const resultElementWidth = Math.max(maxDotsWidth, boxSize);
|
||||||
|
|
||||||
|
// 计算总宽度并居中
|
||||||
|
const symbolWidth = 24; // 符号宽度(缩小)
|
||||||
|
const totalWidth =
|
||||||
|
leftElementWidth +
|
||||||
|
numberSpacing +
|
||||||
|
symbolWidth + // 加号宽度
|
||||||
|
numberSpacing +
|
||||||
|
rightElementWidth +
|
||||||
|
numberSpacing +
|
||||||
|
symbolWidth + // 等号宽度
|
||||||
|
numberSpacing +
|
||||||
|
resultElementWidth;
|
||||||
|
|
||||||
|
const startX = x + (width - totalWidth) / 2;
|
||||||
|
let currentX = startX;
|
||||||
|
|
||||||
|
// 计算统一的圆点区域高度(始终使用两行的高度,保持每行高度一致)
|
||||||
|
const dotsAreaHeight = 2 * dotRadius * 2 + dotRowSpacing; // 2行的高度
|
||||||
|
// 圆点区域的底部Y坐标(所有圆点都从底部开始绘制)
|
||||||
|
const dotsBottomY = y + dotsAreaHeight;
|
||||||
|
// 数字/方框的Y坐标(下方,与圆点底部对齐)
|
||||||
|
const resultBoxY = dotsBottomY + 12;
|
||||||
|
const numberY = resultBoxY + (boxSize - fontSize) / 2 + 2;
|
||||||
|
|
||||||
|
// 绘制左侧圆点和数字
|
||||||
|
const leftCenterX = currentX + leftElementWidth / 2;
|
||||||
|
const leftDotsWidth = this.calculateDotsWidth(
|
||||||
|
problem.left,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
);
|
||||||
|
this.drawDots(
|
||||||
|
ctx,
|
||||||
|
leftCenterX - leftDotsWidth / 2,
|
||||||
|
dotsBottomY, // 从底部开始绘制
|
||||||
|
problem.left,
|
||||||
|
problemColor,
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRowSpacing,
|
||||||
|
);
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.font = `bold ${fontSize}px Arial`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
ctx.fillText(String(problem.left), leftCenterX, numberY);
|
||||||
|
|
||||||
|
currentX += leftElementWidth + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制加号(与数字垂直居中对齐)
|
||||||
|
const plusX = currentX + symbolWidth / 2;
|
||||||
|
const plusY = numberY + fontSize / 2;
|
||||||
|
this.drawSymbol(plusX, plusY, '+', symbolWidth * 0.8);
|
||||||
|
currentX = plusX + symbolWidth / 2 + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制右侧圆点和数字
|
||||||
|
const rightCenterX = currentX + rightElementWidth / 2;
|
||||||
|
const rightDotsWidth = this.calculateDotsWidth(
|
||||||
|
problem.right,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
);
|
||||||
|
this.drawDots(
|
||||||
|
ctx,
|
||||||
|
rightCenterX - rightDotsWidth / 2,
|
||||||
|
dotsBottomY, // 从底部开始绘制
|
||||||
|
problem.right,
|
||||||
|
problemColor,
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRowSpacing,
|
||||||
|
);
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.fillText(String(problem.right), rightCenterX, numberY);
|
||||||
|
|
||||||
|
currentX += rightElementWidth + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制等号(与数字垂直居中对齐)
|
||||||
|
const equalsX = currentX + symbolWidth / 2;
|
||||||
|
const equalsY = numberY + fontSize / 2;
|
||||||
|
this.drawSymbol(equalsX, equalsY, '=', symbolWidth * 0.8);
|
||||||
|
currentX = equalsX + symbolWidth / 2 + numberSpacing;
|
||||||
|
|
||||||
|
// 绘制结果圆点和答案框
|
||||||
|
const resultCenterX = currentX + resultElementWidth / 2;
|
||||||
|
const resultDotsWidth = this.calculateDotsWidth(
|
||||||
|
problem.result,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
);
|
||||||
|
this.drawDots(
|
||||||
|
ctx,
|
||||||
|
resultCenterX - resultDotsWidth / 2,
|
||||||
|
dotsBottomY, // 从底部开始绘制
|
||||||
|
problem.result,
|
||||||
|
problemColor,
|
||||||
|
dotRadius,
|
||||||
|
dotSpacing,
|
||||||
|
dotsPerRow,
|
||||||
|
dotRowSpacing,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制答案框(与数字垂直对齐)
|
||||||
|
const resultBoxX = resultCenterX - boxSize / 2;
|
||||||
|
this.drawRoundedRect(resultBoxX, resultBoxY, boxSize, boxSize, {
|
||||||
|
isDashed: false,
|
||||||
|
radius: 4,
|
||||||
|
color: boxBorderColor,
|
||||||
|
lineWidth: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制圆点
|
||||||
|
* y 参数是圆点区域的底部Y坐标
|
||||||
|
* 先绘制下面一行(满5个后),再绘制上面一行
|
||||||
|
*/
|
||||||
|
private drawDots(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number, // 圆点区域的底部Y坐标
|
||||||
|
count: number,
|
||||||
|
color: string,
|
||||||
|
radius: number,
|
||||||
|
spacing: number,
|
||||||
|
dotsPerRow: number,
|
||||||
|
rowSpacing: number,
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
let row: number;
|
||||||
|
let col: number;
|
||||||
|
|
||||||
|
if (count <= dotsPerRow) {
|
||||||
|
// 只有一行,在底部(row = 0 表示最下面一行)
|
||||||
|
row = 0;
|
||||||
|
col = i;
|
||||||
|
} else {
|
||||||
|
// 多行情况:先绘制下面一行(前5个),再绘制上面一行(剩余的)
|
||||||
|
if (i < dotsPerRow) {
|
||||||
|
// 下面一行(row = 0 表示最下面一行)
|
||||||
|
row = 0;
|
||||||
|
col = i;
|
||||||
|
} else {
|
||||||
|
// 上面一行(row = 1 表示上面一行)
|
||||||
|
row = 1;
|
||||||
|
col = i - dotsPerRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从底部向上计算Y坐标
|
||||||
|
// row = 0 时在最下面,row = 1 时在上面
|
||||||
|
const dotX = x + col * (radius * 2 + spacing);
|
||||||
|
const dotY = y - (row * (radius * 2 + rowSpacing) + radius * 2);
|
||||||
|
|
||||||
|
this.drawDot(ctx, dotX + radius, dotY + radius, radius, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算圆点区域的宽度
|
||||||
|
*/
|
||||||
|
private calculateDotsWidth(
|
||||||
|
count: number,
|
||||||
|
dotsPerRow: number,
|
||||||
|
radius: number,
|
||||||
|
spacing: number,
|
||||||
|
): number {
|
||||||
|
const actualCols = Math.min(count, dotsPerRow);
|
||||||
|
if (actualCols === 0) return 0;
|
||||||
|
return actualCols * radius * 2 + (actualCols - 1) * spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算圆点区域的高度
|
||||||
|
*/
|
||||||
|
private calculateDotsHeight(
|
||||||
|
count: number,
|
||||||
|
dotsPerRow: number,
|
||||||
|
radius: number,
|
||||||
|
rowSpacing: number,
|
||||||
|
): number {
|
||||||
|
const rows = Math.ceil(count / dotsPerRow);
|
||||||
|
if (rows === 0) return 0;
|
||||||
|
return rows * radius * 2 + (rows - 1) * rowSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OneDigitAdditionDraw;
|
||||||
@@ -172,6 +172,58 @@ export class BaseDrawService {
|
|||||||
ctx.setLineDash([]); // 重置为实线
|
ctx.setLineDash([]); // 重置为实线
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制线条(逻辑像素,尺寸除以3)
|
||||||
|
* @param x1 起点X坐标
|
||||||
|
* @param y1 起点Y坐标
|
||||||
|
* @param x2 终点X坐标
|
||||||
|
* @param y2 终点Y坐标
|
||||||
|
* @param options 可选参数
|
||||||
|
* @param options.isDashed 是否为虚线,默认为true(虚线)
|
||||||
|
* @param options.dashPattern 虚线模式,默认为[4, 4]
|
||||||
|
* @param options.color 线条颜色,默认为'#999'
|
||||||
|
* @param options.lineWidth 线条宽度,默认为1
|
||||||
|
*/
|
||||||
|
drawLine(
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
x2: number,
|
||||||
|
y2: number,
|
||||||
|
options?: {
|
||||||
|
isDashed?: boolean;
|
||||||
|
dashPattern?: number[];
|
||||||
|
color?: string;
|
||||||
|
lineWidth?: number;
|
||||||
|
},
|
||||||
|
): void {
|
||||||
|
const { ctx } = this;
|
||||||
|
const {
|
||||||
|
isDashed = true,
|
||||||
|
dashPattern = [4, 4],
|
||||||
|
color = '#999',
|
||||||
|
lineWidth = 1,
|
||||||
|
} = options || {};
|
||||||
|
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.lineWidth = lineWidth;
|
||||||
|
|
||||||
|
// 设置虚线或实线
|
||||||
|
if (isDashed) {
|
||||||
|
ctx.setLineDash(dashPattern);
|
||||||
|
} else {
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制线条
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x1, y1);
|
||||||
|
ctx.lineTo(x2, y2);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 重置为实线(避免影响后续绘制)
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 绘制网格线(逻辑像素,尺寸除以3)
|
* 绘制网格线(逻辑像素,尺寸除以3)
|
||||||
* @param gridStartX 网格起始X坐标
|
* @param gridStartX 网格起始X坐标
|
||||||
@@ -287,7 +339,7 @@ export class BaseDrawService {
|
|||||||
* 绘制符号(使用路径绘制)
|
* 绘制符号(使用路径绘制)
|
||||||
* @param x 符号中心X坐标
|
* @param x 符号中心X坐标
|
||||||
* @param y 符号中心Y坐标
|
* @param y 符号中心Y坐标
|
||||||
* @param symbol 符号类型:'+', '-', '×', '✓'
|
* @param symbol 符号类型:'+', '-', '=', '×', '✓'
|
||||||
* @param size 符号大小
|
* @param size 符号大小
|
||||||
*/
|
*/
|
||||||
drawSymbol(x: number, y: number, symbol: string, size: number): void {
|
drawSymbol(x: number, y: number, symbol: string, size: number): void {
|
||||||
@@ -325,6 +377,19 @@ export class BaseDrawService {
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '=':
|
||||||
|
// 等号:两条横线
|
||||||
|
const equalsSpacing = size * 0.15; // 两条线之间的间距
|
||||||
|
ctx.beginPath();
|
||||||
|
// 上横线
|
||||||
|
ctx.moveTo(-strokeLength, -equalsSpacing);
|
||||||
|
ctx.lineTo(strokeLength, -equalsSpacing);
|
||||||
|
// 下横线
|
||||||
|
ctx.moveTo(-strokeLength, equalsSpacing);
|
||||||
|
ctx.lineTo(strokeLength, equalsSpacing);
|
||||||
|
ctx.stroke();
|
||||||
|
break;
|
||||||
|
|
||||||
case '×':
|
case '×':
|
||||||
// 乘号:两条斜线
|
// 乘号:两条斜线
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
|
|||||||
@@ -23,12 +23,33 @@
|
|||||||
"condition": {
|
"condition": {
|
||||||
"miniprogram": {
|
"miniprogram": {
|
||||||
"list": [
|
"list": [
|
||||||
|
{
|
||||||
|
"name": "mathPages/multiplicationTable/multiplicationTable",
|
||||||
|
"pathName": "mathPages/multiplicationTable/multiplicationTable",
|
||||||
|
"query": "id=multiplication-table",
|
||||||
|
"scene": null,
|
||||||
|
"launchMode": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mathPages/calculationPractice/calculationPractice",
|
||||||
|
"pathName": "mathPages/calculationPractice/calculationPractice",
|
||||||
|
"query": "id=practice-addition",
|
||||||
|
"launchMode": "default",
|
||||||
|
"scene": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mathPages/oneDigitAddition/oneDigitAddition",
|
||||||
|
"pathName": "mathPages/oneDigitAddition/oneDigitAddition",
|
||||||
|
"query": "id=one-digit-addition",
|
||||||
|
"launchMode": "default",
|
||||||
|
"scene": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "mathPages/borrowTen/borrowTen",
|
"name": "mathPages/borrowTen/borrowTen",
|
||||||
"pathName": "mathPages/borrowTen/borrowTen",
|
"pathName": "mathPages/borrowTen/borrowTen",
|
||||||
"query": "id=borrow-ten",
|
"query": "id=borrow-ten",
|
||||||
"scene": null,
|
"launchMode": "default",
|
||||||
"launchMode": "default"
|
"scene": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mathPages/flatTen/flatTen",
|
"name": "mathPages/flatTen/flatTen",
|
||||||
|
|||||||
Reference in New Issue
Block a user