import { getImage } from '../../utils/index'; interface DrawCompareContentParams { canvas: WechatMiniprogram.Canvas; ctx: RenderingContext; compareData: { problems: Array<{ leftCount: number; rightCount: number; leftImageIndex: number; rightImageIndex: number; imageType: 'fruits' | 'twelve-animals'; }>; }; canvasWidth: number; startY: number; } /** * 绘制圆角矩形(虚线边框) */ 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([]); // 重置为实线 } /** * 绘制数一数比大小内容区域 * 两列布局,每列左右两个虚线框,中间圆圈 */ export async function drawCompareContent({ canvas, ctx, compareData, canvasWidth, startY, }: DrawCompareContentParams): Promise { const { problems } = compareData; // 布局参数 const leftMargin = 30; const rightMargin = 30; const topMargin = 12; const rowSpacing = 116; // 行之间的间距 const columnSpacing = 40; // 两列之间的间距(可调节) // 计算每列的宽度(减去边距和列间距) const availableWidth = canvasWidth - leftMargin - rightMargin - columnSpacing; const columnWidth = availableWidth / 2; // 框的尺寸 const boxWidth = 100; const boxHeight = 100; const borderRadius = 12; const circleRadius = 18; // 圆圈半径 // 框与圆圈之间的间距 const boxToCircleSpacing = 12; // 计算左侧列和右侧列的起始X位置 const leftColumnStartX = leftMargin; const rightColumnStartX = leftMargin + columnWidth + columnSpacing; // 计算每列内部的布局(左框 | 圆圈 | 右框,居中) const totalWidthPerColumn = boxWidth + boxToCircleSpacing + circleRadius * 2 + boxToCircleSpacing + boxWidth; const columnPadding = (columnWidth - totalWidthPerColumn) / 2; let currentY = startY + topMargin; // 计算分割线位置(两列中间) // const dividerX = leftMargin + columnWidth + columnSpacing / 2; // 固定绘制6行 const totalRows = 6; for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) { const rowY = currentY + rowIndex * rowSpacing; // 绘制左侧列 const leftProblemIndex = rowIndex * 2; if (leftProblemIndex < problems.length) { await drawCompareProblem( ctx, canvas, problems[leftProblemIndex], leftColumnStartX + columnPadding, rowY, boxWidth, boxHeight, borderRadius, circleRadius, boxToCircleSpacing, ); } // 绘制右侧列 const rightProblemIndex = rowIndex * 2 + 1; if (rightProblemIndex < problems.length) { await drawCompareProblem( ctx, canvas, problems[rightProblemIndex], rightColumnStartX + columnPadding, rowY, boxWidth, boxHeight, borderRadius, circleRadius, boxToCircleSpacing, ); } } // 绘制中间分割线(贯穿所有行) // if (totalRows > 0) { // ctx.strokeStyle = '#999'; // ctx.lineWidth = 1; // ctx.beginPath(); // const dividerStartY = currentY; // const dividerEndY = currentY + (totalRows - 1) * rowSpacing + boxHeight; // ctx.moveTo(dividerX, dividerStartY); // ctx.lineTo(dividerX, dividerEndY); // ctx.stroke(); // } } /** * 绘制一个比较问题(左框 | 圆圈 | 右框) */ async function drawCompareProblem( ctx: RenderingContext, canvas: WechatMiniprogram.Canvas, problem: { leftCount: number; rightCount: number; leftImageIndex: number; rightImageIndex: number; imageType: 'fruits' | 'twelve-animals'; }, startX: number, startY: number, boxWidth: number, boxHeight: number, borderRadius: number, circleRadius: number, boxToCircleSpacing: number, ) { const { leftCount, rightCount, leftImageIndex, rightImageIndex, imageType, } = problem; // 图片配置 const imageConfig = { 'twelve-animals': { folder: 'twelve-animals', maxIndex: 12, }, fruits: { folder: 'fruits', maxIndex: 22, }, }; const config = imageConfig[imageType] || imageConfig['twelve-animals']; // 计算左框位置 const leftBoxX = startX; const leftBoxY = startY; // 计算圆圈位置 const circleX = leftBoxX + boxWidth + boxToCircleSpacing + circleRadius; const circleY = startY + boxHeight / 2; // 计算右框位置 const rightBoxX = circleX + circleRadius + boxToCircleSpacing; const rightBoxY = startY; // 绘制左框 ctx.strokeStyle = '#999'; ctx.lineWidth = 1; drawRoundedRect( ctx, leftBoxX, leftBoxY, boxWidth, boxHeight, borderRadius, true, ); // 绘制左框内的图片 await drawImagesInBox( ctx, canvas, leftCount, leftImageIndex, config.folder, leftBoxX, leftBoxY, boxWidth, boxHeight, ); // 绘制实心圆圈 ctx.fillStyle = '#fff'; ctx.strokeStyle = '#333'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2); ctx.fill(); // 先填充白色 ctx.stroke(); // 再绘制边框 // 绘制右框 ctx.strokeStyle = '#999'; ctx.lineWidth = 1; drawRoundedRect( ctx, rightBoxX, rightBoxY, boxWidth, boxHeight, borderRadius, true, ); // 绘制右框内的图片 await drawImagesInBox( ctx, canvas, rightCount, rightImageIndex, config.folder, rightBoxX, rightBoxY, boxWidth, boxHeight, ); } /** * 在框内绘制多张图片 */ async function drawImagesInBox( ctx: RenderingContext, canvas: WechatMiniprogram.Canvas, count: number, imageIndex: number, folder: string, boxX: number, boxY: number, boxWidth: number, boxHeight: number, ) { const padding = 8; // 框内边距 const availableWidth = boxWidth - padding * 2; const availableHeight = boxHeight - padding * 2; // 根据数量确定每行的图片数和图片大小 let imagesPerRow: number; let imageSize: number; if (count <= 4) { imagesPerRow = count <= 2 ? count : 2; imageSize = Math.min(availableWidth / imagesPerRow, availableHeight / 2) - 4; } else if (count <= 6) { imagesPerRow = 3; imageSize = Math.min(availableWidth / 3, availableHeight / 2) - 4; } else { imagesPerRow = 3; imageSize = Math.min(availableWidth / 3, availableHeight / 3) - 4; } const rows = Math.ceil(count / imagesPerRow); const imageSpacing = (availableWidth - imageSize * imagesPerRow) / (imagesPerRow + 1); const rowSpacing = rows > 1 ? (availableHeight - imageSize * rows) / (rows + 1) : 0; // 加载图片 let boxImage: any = null; try { const imagePath = `/mathPages/assets/${folder}/${imageIndex}.png`; boxImage = await getImage(canvas, imagePath); } catch (error) { console.error(`加载${folder}/${imageIndex}图片失败:`, error); return; } // 绘制图片 for (let i = 0; i < count; i++) { const row = Math.floor(i / imagesPerRow); const col = i % imagesPerRow; const imageX = boxX + padding + imageSpacing + col * (imageSize + imageSpacing); const imageY = boxY + padding + (rows > 1 ? rowSpacing : (availableHeight - imageSize) / 2) + row * (imageSize + (rows > 1 ? rowSpacing : 0)); if (boxImage) { // 计算图片高度(等比例缩放) // @ts-ignore - 微信小程序图片对象有 width 和 height 属性 const scaledHeight = (boxImage.height / boxImage.width) * imageSize; ctx.drawImage(boxImage, imageX, imageY, imageSize, scaledHeight); } } }