feat:2.6.3 破十法、平十法、借十法
This commit is contained in:
@@ -20,7 +20,10 @@
|
|||||||
"numberDecompose/numberDecompose",
|
"numberDecompose/numberDecompose",
|
||||||
"numberSort/numberSort",
|
"numberSort/numberSort",
|
||||||
"numberObjectMatch/numberObjectMatch",
|
"numberObjectMatch/numberObjectMatch",
|
||||||
"makeTen/makeTen"
|
"makeTen/makeTen",
|
||||||
|
"breakTen/breakTen",
|
||||||
|
"flatTen/flatTen",
|
||||||
|
"borrowTen/borrowTen"
|
||||||
],
|
],
|
||||||
"independent": false
|
"independent": false
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -8,6 +8,54 @@ export interface MathFunctionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
||||||
|
{
|
||||||
|
id: 'make-ten',
|
||||||
|
page: 'makeTen',
|
||||||
|
title: '凑十法练习',
|
||||||
|
desc: '通过凑十法学习20以内进位加法',
|
||||||
|
icon: '➕',
|
||||||
|
img: '/assets/mathEntrance/make-ten.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'break-ten',
|
||||||
|
page: 'breakTen',
|
||||||
|
title: '破十法练习',
|
||||||
|
desc: '通过破十法学习20以内退位减法',
|
||||||
|
icon: '➖',
|
||||||
|
img: '/assets/mathEntrance/break-ten.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'flat-ten',
|
||||||
|
page: 'flatTen',
|
||||||
|
title: '平十法练习',
|
||||||
|
desc: '通过平十法学习20以内退位减法',
|
||||||
|
icon: '➖',
|
||||||
|
img: '/assets/mathEntrance/flat-ten.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'borrow-ten',
|
||||||
|
page: 'borrowTen',
|
||||||
|
title: '借十法练习',
|
||||||
|
desc: '通过借十法学习20以上退位减法',
|
||||||
|
icon: '➖',
|
||||||
|
img: '/assets/mathEntrance/borrow-ten.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-decompose-20',
|
||||||
|
page: 'numberDecompose',
|
||||||
|
title: '20以内数的分与合',
|
||||||
|
desc: '把数字分一分,合一合',
|
||||||
|
icon: '🔢',
|
||||||
|
img: '/assets/mathEntrance/number-decompose-20.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'number-decompose',
|
||||||
|
page: 'numberDecompose',
|
||||||
|
title: '10以内数的分与合',
|
||||||
|
desc: '把数字分一分,合一合',
|
||||||
|
icon: '🔢',
|
||||||
|
img: '/assets/mathEntrance/number-decompose.png',
|
||||||
|
},
|
||||||
// 第一阶段:认识数字(最基础)
|
// 第一阶段:认识数字(最基础)
|
||||||
{
|
{
|
||||||
id: 'number-find',
|
id: 'number-find',
|
||||||
@@ -100,14 +148,7 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
|||||||
icon: '🔢',
|
icon: '🔢',
|
||||||
img: '/assets/mathEntrance/number-sort.png',
|
img: '/assets/mathEntrance/number-sort.png',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'number-decompose',
|
|
||||||
page: 'numberDecompose',
|
|
||||||
title: '10以内数的分与合',
|
|
||||||
desc: '把数字分一分,合一合',
|
|
||||||
icon: '🔢',
|
|
||||||
img: '/assets/mathEntrance/number-decompose.png',
|
|
||||||
},
|
|
||||||
// 第四阶段:简单运算(从易到难)
|
// 第四阶段:简单运算(从易到难)
|
||||||
{
|
{
|
||||||
id: 'addition-5',
|
id: 'addition-5',
|
||||||
@@ -141,20 +182,4 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
|||||||
icon: '±',
|
icon: '±',
|
||||||
img: '/assets/mathEntrance/addition-subtraction-10.png',
|
img: '/assets/mathEntrance/addition-subtraction-10.png',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'number-decompose-20',
|
|
||||||
page: 'numberDecompose',
|
|
||||||
title: '20以内数的分与合',
|
|
||||||
desc: '把数字分一分,合一合',
|
|
||||||
icon: '🔢',
|
|
||||||
img: '/assets/mathEntrance/number-decompose-20.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'make-ten',
|
|
||||||
page: 'makeTen',
|
|
||||||
title: '凑十法练习',
|
|
||||||
desc: '通过凑十法学习20以内进位加法',
|
|
||||||
icon: '➕',
|
|
||||||
img: '/assets/mathEntrance/make-ten.png',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "借十法练习",
|
||||||
|
"navigationBarBackgroundColor": "#FFD719",
|
||||||
|
"homeButton": true,
|
||||||
|
"backgroundColor": "#F6F6F6",
|
||||||
|
"enablePullDownRefresh": false,
|
||||||
|
"usingComponents": {
|
||||||
|
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
|
||||||
|
"math-type-selector": "../../components/math-type-selector/math-type-selector",
|
||||||
|
"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,195 @@
|
|||||||
|
import BreakTenDraw from '../shared/service/breakTenDraw';
|
||||||
|
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 BreakTenDraw | null,
|
||||||
|
borrowTenData: null as {
|
||||||
|
problems: Array<{
|
||||||
|
minuend: number; // 被减数
|
||||||
|
subtrahend: number; // 减数
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
} | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
pageTitle: '借十法练习',
|
||||||
|
functionId: '',
|
||||||
|
hasContent: false,
|
||||||
|
showShareDialog: false,
|
||||||
|
currentMode: 'within-50', // 'within-30', 'within-50', 'within-100'
|
||||||
|
currentModeName: '50以内',
|
||||||
|
typeActions: [
|
||||||
|
{ name: '30以内', value: 'within-30' },
|
||||||
|
{ name: '50以内', value: 'within-50' },
|
||||||
|
{ name: '100以内', value: 'within-100' },
|
||||||
|
],
|
||||||
|
} as CanvasDataState & {
|
||||||
|
currentMode: string;
|
||||||
|
currentModeName: string;
|
||||||
|
typeActions: Array<{ name: string; value: string }>;
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(options: { id?: string }) {
|
||||||
|
const functionId = options.id || 'borrow-ten';
|
||||||
|
this.initPageInfo(functionId, '借十法练习');
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.initCanvas({
|
||||||
|
createDrawService: (
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
return new BreakTenDraw(canvas, ctx, options);
|
||||||
|
},
|
||||||
|
drawServiceOptions: {
|
||||||
|
mnemonic: '拆大数,借出10,减小数,加剩数', // 自定义口诀
|
||||||
|
},
|
||||||
|
onCanvasReady: () => {
|
||||||
|
// 初始随机生成
|
||||||
|
this.onRandom();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制Canvas内容
|
||||||
|
*/
|
||||||
|
async drawCanvas() {
|
||||||
|
if (!this.ctx || !this.drawService || !this.borrowTenData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.drawService.draw(this.borrowTenData);
|
||||||
|
this.setData({ hasContent: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('绘制失败:', error);
|
||||||
|
this.setData({ hasContent: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成
|
||||||
|
*/
|
||||||
|
onRandom() {
|
||||||
|
// 根据当前模式确定被减数的最大值
|
||||||
|
let maxMinuend = 30; // 默认30以内
|
||||||
|
if (this.data.currentMode === 'within-50') {
|
||||||
|
maxMinuend = 50;
|
||||||
|
} else if (this.data.currentMode === 'within-100') {
|
||||||
|
maxMinuend = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按被减数分组,列出每个被减数对应的所有符合条件的减数组合
|
||||||
|
const problemsByMinuend: Map<
|
||||||
|
number,
|
||||||
|
Array<{
|
||||||
|
minuend: number;
|
||||||
|
subtrahend: number;
|
||||||
|
result: number;
|
||||||
|
}>
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
|
// 遍历所有可能的被减数(20到maxMinuend)和减数(1-10)组合
|
||||||
|
for (let minuend = 20; minuend <= maxMinuend; minuend++) {
|
||||||
|
const minuendOnes = minuend % 10; // 被减数的个位数
|
||||||
|
|
||||||
|
for (let subtrahend = 1; subtrahend <= 10; subtrahend++) {
|
||||||
|
// 检查条件:
|
||||||
|
// 1. 被减数的个位 < 减数(借十法的关键要求:减数要大于被减数的个位数)
|
||||||
|
// 2. 被减数 > 减数(确保结果大于0)
|
||||||
|
if (minuendOnes < subtrahend && minuend > subtrahend) {
|
||||||
|
if (!problemsByMinuend.has(minuend)) {
|
||||||
|
problemsByMinuend.set(minuend, []);
|
||||||
|
}
|
||||||
|
problemsByMinuend.get(minuend)!.push({
|
||||||
|
minuend,
|
||||||
|
subtrahend,
|
||||||
|
result: minuend - subtrahend,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从每个被减数组中选择题目,确保每个被减数都有代表
|
||||||
|
const problems: Array<{
|
||||||
|
minuend: number;
|
||||||
|
subtrahend: number;
|
||||||
|
result: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
// 获取所有被减数列表并打乱
|
||||||
|
const minuends = Array.from(problemsByMinuend.keys());
|
||||||
|
for (let i = minuends.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[minuends[i], minuends[j]] = [minuends[j], minuends[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从每个被减数组中随机选择,确保分布均匀
|
||||||
|
// 如果被减数数量 >= 9,每个被减数至少选1个;如果 < 9,尽量均匀分配
|
||||||
|
const targetCount = 9;
|
||||||
|
const minuendCount = minuends.length;
|
||||||
|
|
||||||
|
if (minuendCount >= targetCount) {
|
||||||
|
// 被减数数量足够,从每个被减数组中随机选择1个
|
||||||
|
for (let i = 0; i < targetCount; i++) {
|
||||||
|
const minuend = minuends[i];
|
||||||
|
const problemsForMinuend = problemsByMinuend.get(minuend)!;
|
||||||
|
const randomIndex = Math.floor(
|
||||||
|
Math.random() * problemsForMinuend.length,
|
||||||
|
);
|
||||||
|
problems.push(problemsForMinuend[randomIndex]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 被减数数量不足,尽量均匀分配
|
||||||
|
const perMinuend = Math.floor(targetCount / minuendCount);
|
||||||
|
const remainder = targetCount % minuendCount;
|
||||||
|
|
||||||
|
for (let i = 0; i < minuendCount; i++) {
|
||||||
|
const minuend = minuends[i];
|
||||||
|
const problemsForMinuend = problemsByMinuend.get(minuend)!;
|
||||||
|
const count = perMinuend + (i < remainder ? 1 : 0); // 前remainder个多选1个
|
||||||
|
|
||||||
|
// 从该被减数组中随机选择count个(不重复)
|
||||||
|
const shuffled = [...problemsForMinuend];
|
||||||
|
for (let j = shuffled.length - 1; j > 0; j--) {
|
||||||
|
const k = Math.floor(Math.random() * (j + 1));
|
||||||
|
[shuffled[j], shuffled[k]] = [shuffled[k], shuffled[j]];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < Math.min(count, shuffled.length); j++) {
|
||||||
|
problems.push(shuffled[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后打乱所有题目顺序
|
||||||
|
for (let i = problems.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[problems[i], problems[j]] = [problems[j], problems[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.borrowTenData = { 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": {
|
||||||
|
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
|
||||||
|
"math-type-selector": "../../components/math-type-selector/math-type-selector",
|
||||||
|
"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,128 @@
|
|||||||
|
import BreakTenDraw from '../shared/service/breakTenDraw';
|
||||||
|
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 BreakTenDraw | null,
|
||||||
|
breakTenData: null as {
|
||||||
|
problems: Array<{
|
||||||
|
minuend: number; // 被减数
|
||||||
|
subtrahend: number; // 减数
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
} | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
pageTitle: '破十法练习',
|
||||||
|
subTitle: '通过破十法学习20以内退位减法',
|
||||||
|
functionId: '',
|
||||||
|
hasContent: false,
|
||||||
|
showShareDialog: false,
|
||||||
|
} as CanvasDataState,
|
||||||
|
|
||||||
|
onLoad(options: { id?: string }) {
|
||||||
|
const functionId = options.id || 'break-ten';
|
||||||
|
this.initPageInfo(functionId, '破十法练习');
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.initCanvas({
|
||||||
|
createDrawService: (
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
return new BreakTenDraw(canvas, ctx, options);
|
||||||
|
},
|
||||||
|
drawServiceOptions: {
|
||||||
|
subTitle: this.data.subTitle,
|
||||||
|
},
|
||||||
|
onCanvasReady: () => {
|
||||||
|
// 初始随机生成
|
||||||
|
this.onRandom();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制Canvas内容
|
||||||
|
*/
|
||||||
|
async drawCanvas() {
|
||||||
|
if (!this.ctx || !this.drawService || !this.breakTenData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.drawService.draw(this.breakTenData);
|
||||||
|
this.setData({ hasContent: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('绘制失败:', error);
|
||||||
|
this.setData({ hasContent: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成
|
||||||
|
*/
|
||||||
|
onRandom() {
|
||||||
|
const problems: Array<{
|
||||||
|
minuend: number;
|
||||||
|
subtrahend: number;
|
||||||
|
result: number;
|
||||||
|
}> = [];
|
||||||
|
// 用于记录已生成的题目,避免重复
|
||||||
|
const usedProblems = new Set<string>();
|
||||||
|
|
||||||
|
// 生成9道题目(3行3列)
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 1000; // 最大尝试次数,避免无限循环
|
||||||
|
|
||||||
|
while (problems.length < 9 && attempts < maxAttempts) {
|
||||||
|
attempts++;
|
||||||
|
// 生成两个数,确保:
|
||||||
|
// 1. 被减数大于10且小于等于20
|
||||||
|
// 2. 减数小于10
|
||||||
|
// 3. 结果大于0(被减数 > 减数)
|
||||||
|
const minuend = Math.floor(Math.random() * 10) + 11; // 11-20
|
||||||
|
const subtrahend = Math.floor(Math.random() * 9) + 1; // 1-9
|
||||||
|
|
||||||
|
// 确保被减数 > 减数
|
||||||
|
if (minuend <= subtrahend) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = minuend - subtrahend;
|
||||||
|
|
||||||
|
// 检查是否重复
|
||||||
|
const problemKey = `${minuend},${subtrahend}`;
|
||||||
|
if (usedProblems.has(problemKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到已使用集合
|
||||||
|
usedProblems.add(problemKey);
|
||||||
|
|
||||||
|
problems.push({
|
||||||
|
minuend,
|
||||||
|
subtrahend,
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果尝试次数过多仍未生成足够的题目,输出警告
|
||||||
|
if (problems.length < 9) {
|
||||||
|
console.warn(
|
||||||
|
`只生成了 ${problems.length} 道题目,可能符合条件的题目组合不足`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.breakTenData = { 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,13 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "平十法练习",
|
||||||
|
"navigationBarBackgroundColor": "#FFD719",
|
||||||
|
"homeButton": true,
|
||||||
|
"backgroundColor": "#F6F6F6",
|
||||||
|
"enablePullDownRefresh": false,
|
||||||
|
"usingComponents": {
|
||||||
|
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
|
||||||
|
"toy-button": "../../ui/button/button",
|
||||||
|
"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,121 @@
|
|||||||
|
import FlatTenDraw from '../shared/service/flatTenDraw';
|
||||||
|
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 FlatTenDraw | null,
|
||||||
|
flatTenData: null as {
|
||||||
|
problems: Array<{
|
||||||
|
minuend: number; // 被减数
|
||||||
|
subtrahend: number; // 减数
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
} | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
pageTitle: '平十法练习',
|
||||||
|
functionId: '',
|
||||||
|
hasContent: false,
|
||||||
|
showShareDialog: false,
|
||||||
|
} as CanvasDataState,
|
||||||
|
|
||||||
|
onLoad(options: { id?: string }) {
|
||||||
|
const functionId = options.id || 'flat-ten';
|
||||||
|
this.initPageInfo(functionId, '平十法练习');
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.initCanvas({
|
||||||
|
createDrawService: (
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
return new FlatTenDraw(canvas, ctx, options);
|
||||||
|
},
|
||||||
|
drawServiceOptions: {},
|
||||||
|
onCanvasReady: () => {
|
||||||
|
// 初始随机生成
|
||||||
|
this.onRandom();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制Canvas内容
|
||||||
|
*/
|
||||||
|
async drawCanvas() {
|
||||||
|
if (!this.ctx || !this.drawService || !this.flatTenData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.drawService.draw(this.flatTenData);
|
||||||
|
this.setData({ hasContent: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('绘制失败:', error);
|
||||||
|
this.setData({ hasContent: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成
|
||||||
|
*/
|
||||||
|
onRandom() {
|
||||||
|
// 先列出所有符合条件的题目组合
|
||||||
|
const allValidProblems: Array<{
|
||||||
|
minuend: number;
|
||||||
|
subtrahend: number;
|
||||||
|
result: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
// 遍历所有可能的被减数(11-19)和减数(1-10)组合
|
||||||
|
for (let minuend = 11; minuend < 20; minuend++) {
|
||||||
|
const minuendOnes = minuend % 10; // 被减数的个位数
|
||||||
|
|
||||||
|
for (let subtrahend = 1; subtrahend <= 10; subtrahend++) {
|
||||||
|
// 检查条件:
|
||||||
|
// 1. 减数 > 被减数的个位数(平十法的关键要求)
|
||||||
|
// 2. 被减数 > 减数(确保结果大于0)
|
||||||
|
if (subtrahend > minuendOnes && minuend > subtrahend) {
|
||||||
|
allValidProblems.push({
|
||||||
|
minuend,
|
||||||
|
subtrahend,
|
||||||
|
result: minuend - subtrahend,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从所有符合条件的组合中随机选择9个,确保分布均匀
|
||||||
|
const problems: Array<{
|
||||||
|
minuend: number;
|
||||||
|
subtrahend: number;
|
||||||
|
result: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
// 如果符合条件的组合少于9个,使用所有组合
|
||||||
|
const targetCount = Math.min(9, allValidProblems.length);
|
||||||
|
|
||||||
|
// 使用 Fisher-Yates 洗牌算法随机选择
|
||||||
|
const shuffled = [...allValidProblems];
|
||||||
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取前9个
|
||||||
|
for (let i = 0; i < targetCount; i++) {
|
||||||
|
problems.push(shuffled[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flatTenData = { 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,334 @@
|
|||||||
|
/**
|
||||||
|
* 破十法练习绘制服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseDrawService } from '../../../service/baseDraw';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 破十法数据
|
||||||
|
*/
|
||||||
|
export interface BreakTenData {
|
||||||
|
problems: Array<{
|
||||||
|
minuend: number; // 被减数
|
||||||
|
subtrahend: number; // 减数
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 破十法练习绘制服务
|
||||||
|
*/
|
||||||
|
class BreakTenDraw extends BaseDrawService {
|
||||||
|
breakTenData: BreakTenData | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
super(canvas, ctx, options);
|
||||||
|
this.breakTenData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制破十法练习内容
|
||||||
|
*/
|
||||||
|
async draw(breakTenData: BreakTenData) {
|
||||||
|
if (!breakTenData || !breakTenData.problems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.breakTenData = breakTenData;
|
||||||
|
this.prepareDraw();
|
||||||
|
await this.drawHeaderAndDivider();
|
||||||
|
|
||||||
|
// 绘制口诀区域
|
||||||
|
this.drawMnemonic();
|
||||||
|
|
||||||
|
// 绘制虚线分割
|
||||||
|
this.drawDashedDivider(this.currentY + 5, 40, '#999', 1);
|
||||||
|
this.currentY += 20;
|
||||||
|
|
||||||
|
// 绘制题目区域(3行3列)
|
||||||
|
this.drawProblems(breakTenData.problems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制口诀
|
||||||
|
*/
|
||||||
|
private drawMnemonic() {
|
||||||
|
const { ctx, canvasWidth } = this;
|
||||||
|
// 支持通过 options 自定义口诀,默认为破十法口诀
|
||||||
|
const mnemonic =
|
||||||
|
this.options.mnemonic || '看大数、分出十,一减一加,算得数';
|
||||||
|
const fontSize = 24; // 适合小朋友的字体大小
|
||||||
|
|
||||||
|
// 使用适合小朋友的字体
|
||||||
|
ctx.font = `bold ${fontSize}px "YouYuan", "PingFang SC", "Microsoft Yahei", sans-serif`;
|
||||||
|
ctx.fillStyle = '#343434';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
|
// 居中绘制口诀
|
||||||
|
const mnemonicY = this.currentY + 30;
|
||||||
|
ctx.fillText(mnemonic, canvasWidth / 2, mnemonicY);
|
||||||
|
|
||||||
|
// 更新当前Y位置
|
||||||
|
this.currentY = mnemonicY + 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制题目区域(3行3列)
|
||||||
|
*/
|
||||||
|
private drawProblems(
|
||||||
|
problems: Array<{
|
||||||
|
minuend: number;
|
||||||
|
subtrahend: number;
|
||||||
|
result: number;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const { ctx, canvasWidth } = this;
|
||||||
|
const rows = 3;
|
||||||
|
const cols = 3;
|
||||||
|
const startX = 10;
|
||||||
|
const startY = this.currentY + 20;
|
||||||
|
const itemWidth = (canvasWidth - startX * 2) / cols - 18;
|
||||||
|
const itemHeight = 200; // 每个题目的高度
|
||||||
|
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const index = row * cols + col;
|
||||||
|
if (index >= problems.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problem = problems[index];
|
||||||
|
const x = startX + col * itemWidth;
|
||||||
|
const y = startY + row * itemHeight;
|
||||||
|
|
||||||
|
// 绘制单个题目
|
||||||
|
this.drawSingleProblem(
|
||||||
|
ctx,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
itemWidth,
|
||||||
|
itemHeight,
|
||||||
|
problem,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制单个题目
|
||||||
|
*/
|
||||||
|
private drawSingleProblem(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
_height: number,
|
||||||
|
problem: { minuend: number; subtrahend: number; result: number },
|
||||||
|
) {
|
||||||
|
const blueColor = '#8CBAFF';
|
||||||
|
const yellowColor = '#FFCB05';
|
||||||
|
const greenColor = '#5AE2B1';
|
||||||
|
const pinkColor = '#F2A4C0';
|
||||||
|
const lineColor = '#6CD2EA';
|
||||||
|
|
||||||
|
const boxSize = 30; // 方框大小
|
||||||
|
const fontSize = 18; // 数字字体大小
|
||||||
|
const verticalSpacing = 25; // 垂直间距
|
||||||
|
const nodeSpacing = 40; // 二叉树节点之间的水平间距
|
||||||
|
const horizontalSpacing = 10; // 第一层元素之间的水平间距
|
||||||
|
|
||||||
|
// 第一层:被减数 - 减数 = 答案框
|
||||||
|
const firstLayerY = y + 15;
|
||||||
|
const centerX = x + width / 2;
|
||||||
|
|
||||||
|
// 第一层元素位置
|
||||||
|
const minuendBoxX = centerX - boxSize - horizontalSpacing;
|
||||||
|
const minusX = minuendBoxX + boxSize + horizontalSpacing;
|
||||||
|
const subtrahendBoxX = minusX + horizontalSpacing;
|
||||||
|
const equalsX = subtrahendBoxX + boxSize + horizontalSpacing - 2;
|
||||||
|
const answerBoxX = equalsX + 10 + horizontalSpacing;
|
||||||
|
|
||||||
|
// 绘制被减数方框
|
||||||
|
this.drawTopLayerBox(
|
||||||
|
ctx,
|
||||||
|
minuendBoxX,
|
||||||
|
firstLayerY,
|
||||||
|
boxSize,
|
||||||
|
problem.minuend,
|
||||||
|
fontSize,
|
||||||
|
blueColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制减号
|
||||||
|
this.drawMinusSign(ctx, minusX, firstLayerY + boxSize / 2, 12);
|
||||||
|
|
||||||
|
// 绘制减数方框
|
||||||
|
this.drawTopLayerBox(
|
||||||
|
ctx,
|
||||||
|
subtrahendBoxX,
|
||||||
|
firstLayerY,
|
||||||
|
boxSize,
|
||||||
|
problem.subtrahend,
|
||||||
|
fontSize,
|
||||||
|
yellowColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制等号
|
||||||
|
this.drawEqualsSign(ctx, equalsX, firstLayerY + boxSize / 2, 12);
|
||||||
|
|
||||||
|
// 绘制答案框(空白)
|
||||||
|
this.drawSquareBox(ctx, answerBoxX, firstLayerY, boxSize, greenColor);
|
||||||
|
|
||||||
|
// 第二层:被减数拆分成二叉树结构(两个空方框)
|
||||||
|
// 被减数拆分成 10 和 (被减数 - 10)
|
||||||
|
const secondLayerY = firstLayerY + boxSize + verticalSpacing;
|
||||||
|
const minuendBoxCenterX = minuendBoxX + boxSize / 2;
|
||||||
|
|
||||||
|
// 绘制二叉树的两个子节点(空方框)
|
||||||
|
const leftNodeX = minuendBoxCenterX - nodeSpacing / 2 - boxSize / 2;
|
||||||
|
const rightNodeX = minuendBoxCenterX + nodeSpacing / 2 - boxSize / 2;
|
||||||
|
|
||||||
|
// 绘制左子节点(10,空方框)
|
||||||
|
this.drawSquareBox(ctx, leftNodeX, secondLayerY, boxSize, blueColor);
|
||||||
|
|
||||||
|
// 绘制右子节点(被减数 - 10,空方框)
|
||||||
|
this.drawSquareBox(ctx, rightNodeX, secondLayerY, boxSize, pinkColor);
|
||||||
|
|
||||||
|
// 绘制连接线(从被减数到两个子节点)
|
||||||
|
ctx.strokeStyle = lineColor;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(minuendBoxCenterX, firstLayerY + boxSize);
|
||||||
|
ctx.lineTo(leftNodeX + boxSize / 2, secondLayerY);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(minuendBoxCenterX, firstLayerY + boxSize);
|
||||||
|
ctx.lineTo(rightNodeX + boxSize / 2, secondLayerY);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 第三层:在减数正下方绘制一个方框(10)
|
||||||
|
const thirdLayerY = secondLayerY + boxSize + verticalSpacing + 15;
|
||||||
|
const subtrahendBoxCenterX = subtrahendBoxX + boxSize / 2;
|
||||||
|
const thirdBoxX = subtrahendBoxX - boxSize / 2;
|
||||||
|
const thirdBoxCenterX = thirdBoxX + boxSize / 2;
|
||||||
|
const minusX2 = subtrahendBoxX;
|
||||||
|
|
||||||
|
// 绘制第三层方框(空方框,应该是10)
|
||||||
|
this.drawSquareBox(ctx, thirdBoxX, thirdLayerY, boxSize, blueColor);
|
||||||
|
|
||||||
|
// 从减数正下方绘制直线至第2.5层的位置
|
||||||
|
const layer2_5Y =
|
||||||
|
secondLayerY +
|
||||||
|
boxSize +
|
||||||
|
(thirdLayerY - secondLayerY - boxSize) / 2 +
|
||||||
|
4;
|
||||||
|
ctx.strokeStyle = '#888';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(subtrahendBoxCenterX, firstLayerY + boxSize);
|
||||||
|
ctx.lineTo(subtrahendBoxCenterX, layer2_5Y);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 计算相交点(第2.5层位置,在subtrahendBoxCenterX处)
|
||||||
|
const intersectionY = layer2_5Y;
|
||||||
|
|
||||||
|
// 从第二层右边的方框(右子节点)下绘制直角折线到相交点
|
||||||
|
const rightNodeCenterX = rightNodeX + boxSize / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(rightNodeCenterX, secondLayerY + boxSize);
|
||||||
|
ctx.lineTo(rightNodeCenterX, intersectionY); // 垂直向下
|
||||||
|
ctx.lineTo(subtrahendBoxCenterX, intersectionY); // 水平到相交点
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 绘制相交点到第三层方框的直线
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(thirdBoxCenterX, intersectionY);
|
||||||
|
ctx.lineTo(thirdBoxCenterX, thirdLayerY); // 垂直向下到第三层
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 在相交位置上方绘制小减号(使用drawSymbol方法)
|
||||||
|
this.drawSymbol(minusX2, layer2_5Y - 10, '-', 12);
|
||||||
|
|
||||||
|
// 从第二层左侧的方框(左子节点)下绘制直角折线连接第三层方框
|
||||||
|
const leftNodeCenterX = leftNodeX + boxSize / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(leftNodeCenterX, secondLayerY + boxSize);
|
||||||
|
ctx.lineTo(leftNodeCenterX, thirdLayerY + boxSize / 2); // 垂直向下到第三层
|
||||||
|
ctx.lineTo(thirdBoxX, thirdLayerY + boxSize / 2); // 水平连接到第三层方框
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 在第二层左侧方框和第三层方框之间的折线中间绘制加号
|
||||||
|
const plusBetweenX =
|
||||||
|
leftNodeCenterX + (thirdBoxX - leftNodeCenterX) / 2;
|
||||||
|
this.drawSymbol(plusBetweenX, thirdLayerY + 2, '+', 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制第一层方框(边框1px,#999,无填充,显示数字)
|
||||||
|
*/
|
||||||
|
private drawTopLayerBox(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
size: number,
|
||||||
|
number: number,
|
||||||
|
fontSize: number,
|
||||||
|
color: string,
|
||||||
|
) {
|
||||||
|
// 绘制方框
|
||||||
|
this.drawSquareBox(ctx, x, y, size, color);
|
||||||
|
|
||||||
|
// 绘制数字
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.font = `bold ${fontSize}px "YouYuan", "PingFang SC", "Microsoft Yahei", sans-serif`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(String(number), x + size / 2, y + size / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制减号
|
||||||
|
*/
|
||||||
|
private drawMinusSign(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
size: number,
|
||||||
|
) {
|
||||||
|
ctx.strokeStyle = '#000';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.beginPath();
|
||||||
|
// 横线
|
||||||
|
ctx.moveTo(x - size / 2, y);
|
||||||
|
ctx.lineTo(x + size / 2, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制等号
|
||||||
|
*/
|
||||||
|
private drawEqualsSign(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
size: number,
|
||||||
|
) {
|
||||||
|
ctx.strokeStyle = '#000';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.beginPath();
|
||||||
|
// 上横线
|
||||||
|
ctx.moveTo(x, y - size / 3);
|
||||||
|
ctx.lineTo(x + size, y - size / 3);
|
||||||
|
// 下横线
|
||||||
|
ctx.moveTo(x, y + size / 3);
|
||||||
|
ctx.lineTo(x + size, y + size / 3);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BreakTenDraw;
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
/**
|
||||||
|
* 平十法练习绘制服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseDrawService } from '../../../service/baseDraw';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平十法数据
|
||||||
|
*/
|
||||||
|
export interface FlatTenData {
|
||||||
|
problems: Array<{
|
||||||
|
minuend: number; // 被减数
|
||||||
|
subtrahend: number; // 减数
|
||||||
|
result: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平十法练习绘制服务
|
||||||
|
*/
|
||||||
|
class FlatTenDraw extends BaseDrawService {
|
||||||
|
flatTenData: FlatTenData | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
super(canvas, ctx, options);
|
||||||
|
this.flatTenData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制平十法练习内容
|
||||||
|
*/
|
||||||
|
async draw(flatTenData: FlatTenData) {
|
||||||
|
if (!flatTenData || !flatTenData.problems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flatTenData = flatTenData;
|
||||||
|
this.prepareDraw();
|
||||||
|
await this.drawHeaderAndDivider();
|
||||||
|
|
||||||
|
// 绘制口诀区域
|
||||||
|
this.drawMnemonic();
|
||||||
|
|
||||||
|
// 绘制虚线分割
|
||||||
|
this.drawDashedDivider(this.currentY + 5, 40, '#999', 1);
|
||||||
|
this.currentY += 20;
|
||||||
|
|
||||||
|
// 绘制题目区域(3行3列)
|
||||||
|
this.drawProblems(flatTenData.problems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制口诀
|
||||||
|
*/
|
||||||
|
private drawMnemonic() {
|
||||||
|
const { ctx, canvasWidth } = this;
|
||||||
|
const mnemonic = '看大数的个位,拆小数,连续减,算得数';
|
||||||
|
const fontSize = 24; // 适合小朋友的字体大小
|
||||||
|
|
||||||
|
// 使用适合小朋友的字体
|
||||||
|
ctx.font = `bold ${fontSize}px "YouYuan", "PingFang SC", "Microsoft Yahei", sans-serif`;
|
||||||
|
ctx.fillStyle = '#343434';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
|
// 居中绘制口诀
|
||||||
|
const mnemonicY = this.currentY + 30;
|
||||||
|
ctx.fillText(mnemonic, canvasWidth / 2, mnemonicY);
|
||||||
|
|
||||||
|
// 更新当前Y位置
|
||||||
|
this.currentY = mnemonicY + 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制题目区域(3行3列)
|
||||||
|
*/
|
||||||
|
private drawProblems(
|
||||||
|
problems: Array<{
|
||||||
|
minuend: number;
|
||||||
|
subtrahend: number;
|
||||||
|
result: number;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const { ctx, canvasWidth } = this;
|
||||||
|
const rows = 3;
|
||||||
|
const cols = 3;
|
||||||
|
const startX = 10;
|
||||||
|
const startY = this.currentY + 20;
|
||||||
|
const itemWidth = (canvasWidth - startX * 2) / cols - 18;
|
||||||
|
const itemHeight = 200; // 每个题目的高度
|
||||||
|
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const index = row * cols + col;
|
||||||
|
if (index >= problems.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problem = problems[index];
|
||||||
|
const x = startX + col * itemWidth;
|
||||||
|
const y = startY + row * itemHeight;
|
||||||
|
|
||||||
|
// 绘制单个题目
|
||||||
|
this.drawSingleProblem(
|
||||||
|
ctx,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
itemWidth,
|
||||||
|
itemHeight,
|
||||||
|
problem,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制单个题目
|
||||||
|
*/
|
||||||
|
private drawSingleProblem(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
_height: number,
|
||||||
|
problem: { minuend: number; subtrahend: number; result: number },
|
||||||
|
) {
|
||||||
|
const blueColor = '#8CBAFF';
|
||||||
|
const yellowColor = '#FFCB05';
|
||||||
|
const greenColor = '#5AE2B1';
|
||||||
|
const pinkColor = '#F2A4C0';
|
||||||
|
const lineColor = '#6CD2EA';
|
||||||
|
|
||||||
|
const boxSize = 30; // 方框大小
|
||||||
|
const fontSize = 18; // 数字字体大小
|
||||||
|
const verticalSpacing = 25; // 垂直间距
|
||||||
|
const nodeSpacing = 40; // 二叉树节点之间的水平间距
|
||||||
|
const horizontalSpacing = 10; // 第一层元素之间的水平间距
|
||||||
|
|
||||||
|
// 第一层:被减数 - 减数 = 答案框
|
||||||
|
const firstLayerY = y + 15;
|
||||||
|
const centerX = x + width / 2;
|
||||||
|
|
||||||
|
// 第一层元素位置
|
||||||
|
const minuendBoxX = centerX - boxSize - horizontalSpacing;
|
||||||
|
const minusX = minuendBoxX + boxSize + horizontalSpacing;
|
||||||
|
const subtrahendBoxX = minusX + horizontalSpacing;
|
||||||
|
const equalsX = subtrahendBoxX + boxSize + horizontalSpacing - 2;
|
||||||
|
const answerBoxX = equalsX + 10 + horizontalSpacing;
|
||||||
|
|
||||||
|
// 绘制被减数方框
|
||||||
|
this.drawTopLayerBox(
|
||||||
|
ctx,
|
||||||
|
minuendBoxX,
|
||||||
|
firstLayerY,
|
||||||
|
boxSize,
|
||||||
|
problem.minuend,
|
||||||
|
fontSize,
|
||||||
|
blueColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制减号
|
||||||
|
this.drawMinusSign(ctx, minusX, firstLayerY + boxSize / 2, 12);
|
||||||
|
|
||||||
|
// 绘制减数方框
|
||||||
|
this.drawTopLayerBox(
|
||||||
|
ctx,
|
||||||
|
subtrahendBoxX,
|
||||||
|
firstLayerY,
|
||||||
|
boxSize,
|
||||||
|
problem.subtrahend,
|
||||||
|
fontSize,
|
||||||
|
yellowColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制等号
|
||||||
|
this.drawEqualsSign(ctx, equalsX, firstLayerY + boxSize / 2, 12);
|
||||||
|
|
||||||
|
// 绘制答案框(空白)
|
||||||
|
this.drawSquareBox(ctx, answerBoxX, firstLayerY, boxSize, greenColor);
|
||||||
|
|
||||||
|
// 第二层:以减数为根节点拆分二叉树结构(两个空方框)
|
||||||
|
// 减数拆分成两部分:第一部分 = 被减数的个位数,第二部分 = 减数 - 第一部分
|
||||||
|
const secondLayerY = firstLayerY + boxSize + verticalSpacing;
|
||||||
|
const subtrahendBoxCenterX = subtrahendBoxX + boxSize / 2;
|
||||||
|
|
||||||
|
// 计算减数的拆分:第一部分是被减数的个位数,第二部分是减数减去第一部分
|
||||||
|
const minuendOnes = problem.minuend % 10; // 被减数的个位数
|
||||||
|
const part1 = minuendOnes; // 第一部分
|
||||||
|
const part2 = problem.subtrahend - part1; // 第二部分
|
||||||
|
|
||||||
|
// 绘制二叉树的两个子节点(空方框)
|
||||||
|
const leftNodeX = subtrahendBoxCenterX - nodeSpacing / 2 - boxSize / 2;
|
||||||
|
const rightNodeX = subtrahendBoxCenterX + nodeSpacing / 2 - boxSize / 2;
|
||||||
|
|
||||||
|
// 绘制左子节点(第一部分,空方框)
|
||||||
|
this.drawSquareBox(ctx, leftNodeX, secondLayerY, boxSize, blueColor);
|
||||||
|
|
||||||
|
// 绘制右子节点(第二部分,空方框)
|
||||||
|
this.drawSquareBox(ctx, rightNodeX, secondLayerY, boxSize, pinkColor);
|
||||||
|
|
||||||
|
// 绘制连接线(从减数到两个子节点)
|
||||||
|
ctx.strokeStyle = lineColor;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(subtrahendBoxCenterX, firstLayerY + boxSize);
|
||||||
|
ctx.lineTo(leftNodeX + boxSize / 2, secondLayerY);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(subtrahendBoxCenterX, firstLayerY + boxSize);
|
||||||
|
ctx.lineTo(rightNodeX + boxSize / 2, secondLayerY);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 第三层:在被减数正下方绘制一个方框
|
||||||
|
const thirdLayerY = secondLayerY + boxSize + verticalSpacing + 15;
|
||||||
|
const minuendBoxCenterX = minuendBoxX + boxSize / 2;
|
||||||
|
const thirdBoxX = minuendBoxCenterX; // 第三层方框X轴是被减数方框的中点
|
||||||
|
const minusX2 = minuendBoxX + boxSize;
|
||||||
|
const minusX3 = subtrahendBoxCenterX;
|
||||||
|
|
||||||
|
// 绘制第三层方框(空方框,应该是10)
|
||||||
|
this.drawSquareBox(ctx, thirdBoxX, thirdLayerY, boxSize, blueColor);
|
||||||
|
|
||||||
|
// 从被减数正下方绘制直线至第2.5层的位置
|
||||||
|
const layer2_5Y =
|
||||||
|
secondLayerY +
|
||||||
|
boxSize +
|
||||||
|
(thirdLayerY - secondLayerY - boxSize) / 2 +
|
||||||
|
4;
|
||||||
|
ctx.strokeStyle = '#888';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(minuendBoxCenterX, firstLayerY + boxSize);
|
||||||
|
ctx.lineTo(minuendBoxCenterX, layer2_5Y);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 计算相交点(第2.5层位置)
|
||||||
|
const intersectionY = layer2_5Y;
|
||||||
|
|
||||||
|
// 从第二层左侧的方框(左子节点)下绘制直角折线到相交点
|
||||||
|
const leftNodeCenterX = leftNodeX + boxSize / 2;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(leftNodeCenterX, secondLayerY + boxSize);
|
||||||
|
ctx.lineTo(leftNodeCenterX, intersectionY); // 垂直向下
|
||||||
|
ctx.lineTo(minuendBoxCenterX, intersectionY); // 水平到相交点
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 在相交位置上方绘制小减号(使用drawSymbol方法)
|
||||||
|
this.drawSymbol(minusX2, layer2_5Y - 10, '-', 12);
|
||||||
|
|
||||||
|
// 以被减数框的X轴+方框size为X轴向下绘制直线和第三层的方框相连
|
||||||
|
const minuendRightX = minuendBoxX + boxSize;
|
||||||
|
ctx.strokeStyle = '#888';
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(minuendRightX, intersectionY);
|
||||||
|
ctx.lineTo(minuendRightX, thirdLayerY); // 垂直向下到第三层
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 从第二层右侧的方框(右子节点)下绘制直角折线连接第三层方框
|
||||||
|
const rightNodeCenterX = rightNodeX + boxSize / 2;
|
||||||
|
// ctx.strokeStyle = 'red';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(rightNodeCenterX, secondLayerY + boxSize);
|
||||||
|
ctx.lineTo(rightNodeCenterX, thirdLayerY + boxSize / 2); // 垂直向下到第三层
|
||||||
|
ctx.lineTo(thirdBoxX + boxSize, thirdLayerY + boxSize / 2); // 水平连接到第三层方框中心
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
this.drawSymbol(minusX3, thirdLayerY + 2, '-', 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制第一层方框(边框1px,#999,无填充,显示数字)
|
||||||
|
*/
|
||||||
|
private drawTopLayerBox(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
size: number,
|
||||||
|
number: number,
|
||||||
|
fontSize: number,
|
||||||
|
color: string,
|
||||||
|
) {
|
||||||
|
// 绘制方框
|
||||||
|
this.drawSquareBox(ctx, x, y, size, color);
|
||||||
|
|
||||||
|
// 绘制数字
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.font = `bold ${fontSize}px "YouYuan", "PingFang SC", "Microsoft Yahei", sans-serif`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(String(number), x + size / 2, y + size / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制减号
|
||||||
|
*/
|
||||||
|
private drawMinusSign(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
size: number,
|
||||||
|
) {
|
||||||
|
ctx.strokeStyle = '#000';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.beginPath();
|
||||||
|
// 横线
|
||||||
|
ctx.moveTo(x - size / 2, y);
|
||||||
|
ctx.lineTo(x + size / 2, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制等号
|
||||||
|
*/
|
||||||
|
private drawEqualsSign(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
size: number,
|
||||||
|
) {
|
||||||
|
ctx.strokeStyle = '#000';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.beginPath();
|
||||||
|
// 上横线
|
||||||
|
ctx.moveTo(x, y - size / 3);
|
||||||
|
ctx.lineTo(x + size, y - size / 3);
|
||||||
|
// 下横线
|
||||||
|
ctx.moveTo(x, y + size / 3);
|
||||||
|
ctx.lineTo(x + size, y + size / 3);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FlatTenDraw;
|
||||||
@@ -59,7 +59,7 @@ class MakeTenDraw extends BaseDrawService {
|
|||||||
private drawMnemonic() {
|
private drawMnemonic() {
|
||||||
const { ctx, canvasWidth } = this;
|
const { ctx, canvasWidth } = this;
|
||||||
const mnemonic = '看大数,拆小数,凑成10,加剩数';
|
const mnemonic = '看大数,拆小数,凑成10,加剩数';
|
||||||
const fontSize = 28; // 适合小朋友的字体大小
|
const fontSize = 24; // 适合小朋友的字体大小
|
||||||
|
|
||||||
// 使用适合小朋友的字体
|
// 使用适合小朋友的字体
|
||||||
ctx.font = `bold ${fontSize}px "YouYuan", "PingFang SC", "Microsoft Yahei", sans-serif`;
|
ctx.font = `bold ${fontSize}px "YouYuan", "PingFang SC", "Microsoft Yahei", sans-serif`;
|
||||||
@@ -222,7 +222,7 @@ class MakeTenDraw extends BaseDrawService {
|
|||||||
ctx.lineTo(rightNodeX + boxSize / 2, secondLayerY);
|
ctx.lineTo(rightNodeX + boxSize / 2, secondLayerY);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
const thirdLayerY = secondLayerY + boxSize + verticalSpacing + 10;
|
const thirdLayerY = secondLayerY + boxSize + verticalSpacing + 15;
|
||||||
|
|
||||||
let bigBoxX = 0;
|
let bigBoxX = 0;
|
||||||
let bigBoxCenterX = 0;
|
let bigBoxCenterX = 0;
|
||||||
@@ -296,7 +296,7 @@ class MakeTenDraw extends BaseDrawService {
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// 在第二层另一个方框和第三层方框之间的折线中间绘制加号
|
// 在第二层另一个方框和第三层方框之间的折线中间绘制加号
|
||||||
this.drawSymbol(secondPlusX, thirdLayerY + 3, '+', 12);
|
this.drawSymbol(secondPlusX, thirdLayerY + 2, '+', 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,12 +23,33 @@
|
|||||||
"condition": {
|
"condition": {
|
||||||
"miniprogram": {
|
"miniprogram": {
|
||||||
"list": [
|
"list": [
|
||||||
|
{
|
||||||
|
"name": "mathPages/borrowTen/borrowTen",
|
||||||
|
"pathName": "mathPages/borrowTen/borrowTen",
|
||||||
|
"query": "id=borrow-ten",
|
||||||
|
"scene": null,
|
||||||
|
"launchMode": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mathPages/flatTen/flatTen",
|
||||||
|
"pathName": "mathPages/flatTen/flatTen",
|
||||||
|
"query": "id=flat-ten",
|
||||||
|
"launchMode": "default",
|
||||||
|
"scene": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mathPages/breakTen/breakTen",
|
||||||
|
"pathName": "mathPages/breakTen/breakTen",
|
||||||
|
"query": "id=break-ten",
|
||||||
|
"launchMode": "default",
|
||||||
|
"scene": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "mathPages/makeTen/makeTen",
|
"name": "mathPages/makeTen/makeTen",
|
||||||
"pathName": "mathPages/makeTen/makeTen",
|
"pathName": "mathPages/makeTen/makeTen",
|
||||||
"query": "id=make-ten",
|
"query": "id=make-ten",
|
||||||
"scene": null,
|
"launchMode": "default",
|
||||||
"launchMode": "default"
|
"scene": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "focusPages/dotConnect/dotConnect",
|
"name": "focusPages/dotConnect/dotConnect",
|
||||||
|
|||||||
Reference in New Issue
Block a user