feat:九九乘法表
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user