Files
doodle-mini/miniprogram/mathPages/shared/service/countMatchContentDraw.ts
T
2025-12-15 17:00:06 +08:00

295 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { getImage } from '../../../utils/index';
import { getRandomNumberColor } from '../../../constants/mathFunctions';
interface DrawCountMatchContentParams {
canvas: WechatMiniprogram.Canvas;
ctx: RenderingContext;
matchData: {
leftNumbers: number[];
rightNumbers: number[]; // 打乱顺序后的数字数组
};
canvasWidth: number;
startY: number;
imageType?: string; // 'twelve-animals' 或 'fruits'
}
/**
* 绘制圆角矩形(虚线边框)
*/
function drawRoundedRect(
ctx: RenderingContext,
x: number,
y: number,
width: number,
height: number,
radius: number,
isDashed: boolean = true,
) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
if (isDashed) {
ctx.setLineDash([6 / 3, 6 / 3]); // 约2px的虚线
} else {
ctx.setLineDash([]);
}
ctx.stroke();
ctx.setLineDash([]); // 重置为实线
}
/**
* 绘制数一数连一连内容区域
* 左侧显示数字(虚线圆角框),右侧显示对应数量的图片(虚线圆角框)
* 支持根据 imageType 选择不同的图片文件夹(twelve-animals 或 fruits
*/
export async function drawCountMatchContent({
canvas,
ctx,
matchData,
canvasWidth,
startY,
imageType = 'twelve-animals',
}: DrawCountMatchContentParams): Promise<void> {
const { leftNumbers, rightNumbers } = matchData;
// 根据图片类型确定图片目录和最大索引
const imageConfig = {
'twelve-animals': {
folder: 'twelve-animals',
maxIndex: 12,
},
fruits: {
folder: 'fruits',
maxIndex: 22,
},
};
const config =
imageConfig[imageType as keyof typeof imageConfig] ||
imageConfig['twelve-animals'];
const leftMargin = 24;
const rightMargin = 24;
const itemSpacing = 140;
const LeftBoxWidth = 120;
const rightBoxWidth = 250;
const boxHeight = 110;
const borderRadius = 12;
const imageSpacing = 5; // 图片之间的水平间距(同一行内)
const rowSpacing = 10; // 行之间的垂直间距(两行之间)
const imagesPerRow = 5; // 每行最多5张图片
const maxImages = 10; // 最多10张图片
const padding = 10; // 框内边距
const startYPos = startY + 20;
const linePointRadius = 8;
const linePointSpacing = 12;
/**
* 根据图片数量动态计算图片宽度
* @param count 图片数量
* @returns 图片宽度
*/
const getImageWidth = (count: number): number => {
if (count <= 2) {
return 90;
} else if (count <= 3) {
return 70;
} else if (count <= 4) {
return 55;
} else {
return 40;
}
};
// 计算左侧和右侧的起始X位置
const leftBoxX = leftMargin;
const rightBoxX = canvasWidth - rightMargin - rightBoxWidth;
// 绘制左侧数字框
for (let i = 0; i < leftNumbers.length; i++) {
const boxY = startYPos + i * itemSpacing;
const number = leftNumbers[i];
// 绘制虚线圆角矩形框
ctx.strokeStyle = '#999';
ctx.lineWidth = 1;
drawRoundedRect(
ctx,
leftBoxX,
boxY,
LeftBoxWidth,
boxHeight,
borderRadius,
true,
);
// 绘制数字(使用随机颜色)
ctx.fillStyle = getRandomNumberColor();
ctx.font = `bold ${56}px "Microsoft Yahei"`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(
String(number),
leftBoxX + LeftBoxWidth / 2,
boxY + boxHeight / 2,
);
// 绘制左侧框右侧的连线圆点(直径14,半径7)
const leftCircleX =
leftBoxX + LeftBoxWidth + linePointSpacing + linePointRadius;
const leftCircleY = boxY + boxHeight / 2;
ctx.fillStyle = '#93D333';
ctx.beginPath();
ctx.arc(leftCircleX, leftCircleY, linePointRadius, 0, Math.PI * 2);
ctx.fill();
}
// 为每个框生成唯一的图片索引,确保不同框使用不同的图片
const availableImageIndices = Array.from(
{ length: config.maxIndex },
(_, i) => i + 1,
);
const boxImageIndices: number[] = [];
for (let i = 0; i < rightNumbers.length; i++) {
const randomIndex = Math.floor(
Math.random() * availableImageIndices.length,
);
const imageIndex = availableImageIndices.splice(randomIndex, 1)[0];
boxImageIndices.push(imageIndex);
}
// 绘制右侧图片框
for (let i = 0; i < rightNumbers.length; i++) {
const boxY = startYPos + i * itemSpacing;
const number = rightNumbers[i]; // 这个数字决定了要绘制多少张图片
const boxImageIndex = boxImageIndices[i]; // 这个框使用的图片索引(所有图片都一样)
// 绘制虚线圆角矩形框
ctx.strokeStyle = '#999';
ctx.lineWidth = 1;
drawRoundedRect(
ctx,
rightBoxX,
boxY,
rightBoxWidth,
boxHeight,
borderRadius,
true,
);
// 计算要绘制的图片数量(最多10张)
const imageCount = Math.min(number, maxImages);
// 根据图片数量动态计算图片宽度
const imageWidth = getImageWidth(imageCount);
// 加载当前框使用的图片(只加载一次,用于计算高度和绘制)
let boxImage: any = null;
try {
const imagePath = `/mathPages/assets/${config.folder}/${boxImageIndex}.png`;
boxImage = await getImage(canvas, imagePath);
} catch (error) {
console.error(
`加载${config.folder}/${boxImageIndex}图片失败:`,
error,
);
}
// 计算图片高度(等比例缩放)
let imageHeight = imageWidth; // 默认高度
if (boxImage) {
// @ts-ignore - 微信小程序图片对象有 width 和 height 属性
imageHeight = (boxImage.height / boxImage.width) * imageWidth;
}
// 计算每行的图片数量
const imagesInFirstRow = Math.min(imageCount, imagesPerRow);
const imagesInSecondRow =
imageCount > imagesPerRow ? imageCount - imagesPerRow : 0;
// 计算图片的总高度(考虑行数)
const rowCount = imagesInSecondRow > 0 ? 2 : 1;
const totalImageHeight =
rowCount * imageHeight + (rowCount - 1) * rowSpacing;
// 垂直居中:框顶部 + 内边距 + (框高度 - 上下内边距 - 图片总高度) / 2
const startImageY =
boxY + padding + (boxHeight - padding * 2 - totalImageHeight) / 2;
// 计算每行的起始X位置(用于居中)
const firstRowImageCount = imagesInFirstRow;
const firstRowWidth =
firstRowImageCount * imageWidth +
(firstRowImageCount - 1) * imageSpacing;
const firstRowStartX =
rightBoxX +
padding +
(rightBoxWidth - padding * 2 - firstRowWidth) / 2;
const secondRowImageCount = imagesInSecondRow;
const secondRowWidth =
secondRowImageCount * imageWidth +
(secondRowImageCount - 1) * imageSpacing;
const secondRowStartX =
rightBoxX +
padding +
(rightBoxWidth - padding * 2 - secondRowWidth) / 2;
// 绘制图片(同一个框内使用同一张图片)
for (let imgIndex = 0; imgIndex < imageCount; imgIndex++) {
const row = Math.floor(imgIndex / imagesPerRow);
const col = imgIndex % imagesPerRow;
// 计算图片的X位置(根据行数选择不同的起始位置)
const rowStartX = row === 0 ? firstRowStartX : secondRowStartX;
const imageX = rowStartX + col * (imageWidth + imageSpacing);
// 计算图片的Y位置
const imageY = startImageY + row * (imageHeight + rowSpacing);
if (boxImage) {
// 计算图片高度(等比例缩放)
// @ts-ignore - 微信小程序图片对象有 width 和 height 属性
const scaledHeight =
(boxImage.height / boxImage.width) * imageWidth;
ctx.drawImage(
boxImage,
imageX,
imageY,
imageWidth,
scaledHeight,
);
} else {
// 如果图片加载失败,绘制一个小圆点作为备用
ctx.fillStyle = '#ccc';
ctx.beginPath();
ctx.arc(
imageX + imageWidth / 2,
imageY + imageHeight / 2,
imageWidth / 4,
0,
Math.PI * 2,
);
ctx.fill();
}
}
// 绘制右侧框左侧的连线圆点(直径14,半径7)
const rightCircleX = rightBoxX - linePointSpacing - linePointRadius; // 间距15 + 半径linePointRadius
const rightCircleY = boxY + boxHeight / 2;
ctx.fillStyle = '#93D333';
ctx.beginPath();
ctx.arc(rightCircleX, rightCircleY, linePointRadius, 0, Math.PI * 2);
ctx.fill();
}
}