295 lines
9.8 KiB
TypeScript
295 lines
9.8 KiB
TypeScript
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();
|
||
}
|
||
}
|