import { getNumberColors } from '../../constants/colors'; interface DrawNumberColorContentParams { canvas: WechatMiniprogram.Canvas; ctx: RenderingContext; colorData: { numbers: number[]; }; canvasWidth: number; startY: number; patternType?: string; // 'circle' 或 'caterpillar' } /** * 绘制圆角矩形(虚线边框) */ 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, 6]); // 虚线 } else { ctx.setLineDash([]); } ctx.stroke(); ctx.setLineDash([]); // 重置为实线 } /** * 绘制毛毛虫头部 */ function drawCaterpillarHead( ctx: RenderingContext, x: number, y: number, size: number, color: string, ) { const headRadius = 18; // 固定头部半径 const headCenterX = x + headRadius; const headCenterY = y + size / 2; // 绘制头部(圆形) ctx.fillStyle = color; ctx.beginPath(); ctx.arc(headCenterX, headCenterY, headRadius, 0, Math.PI * 2); ctx.fill(); // 绘制头部圆圈实线边框 ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.setLineDash([]); // 确保是实线 ctx.beginPath(); ctx.arc(headCenterX, headCenterY, headRadius, 0, Math.PI * 2); ctx.stroke(); // 绘制触角(在头部圆圈的正上方,带弧度的弯曲) const antennaLength = headRadius * 0.6; const antennaStartY = headCenterY - headRadius; // 头部圆圈的正上方 const antennaOffset = headRadius * 0.3; // 触角左右偏移距离 // 左侧触角:从顶部左侧开始,先向上,然后向左弯曲(带弧度) const leftAntennaStartX = headCenterX - antennaOffset; const leftAntennaStartY = antennaStartY; const leftAntennaMidY = leftAntennaStartY - antennaLength * 0.5; // 向上延伸的中点 const leftAntennaEndX = leftAntennaStartX - antennaLength * 0.4; // 向左弯曲 const leftAntennaEndY = leftAntennaMidY - antennaLength * 0.3; // 向上并向左 // 右侧触角:从顶部右侧开始,先向上,然后向右弯曲(带弧度) const rightAntennaStartX = headCenterX + antennaOffset; const rightAntennaStartY = antennaStartY; const rightAntennaMidY = rightAntennaStartY - antennaLength * 0.5; // 向上延伸的中点 const rightAntennaEndX = rightAntennaStartX + antennaLength * 0.4; // 向右弯曲 const rightAntennaEndY = rightAntennaMidY - antennaLength * 0.3; // 向上并向右 ctx.strokeStyle = '#000'; ctx.lineWidth = 1.5; ctx.lineCap = 'round'; // 圆角端点 // 绘制左侧触角(使用二次贝塞尔曲线实现平滑弧度) ctx.beginPath(); ctx.moveTo(leftAntennaStartX, leftAntennaStartY); // 使用二次贝塞尔曲线:起点、控制点、终点 // 控制点在中间位置,使曲线平滑向左弯曲 const leftControlX = leftAntennaStartX - antennaLength * 0.2; const leftControlY = leftAntennaMidY; ctx.quadraticCurveTo( leftControlX, leftControlY, leftAntennaEndX, leftAntennaEndY, ); ctx.stroke(); // 绘制右侧触角(使用二次贝塞尔曲线实现平滑弧度) ctx.beginPath(); ctx.moveTo(rightAntennaStartX, rightAntennaStartY); // 使用二次贝塞尔曲线:起点、控制点、终点 // 控制点在中间位置,使曲线平滑向右弯曲 const rightControlX = rightAntennaStartX + antennaLength * 0.2; const rightControlY = rightAntennaMidY; ctx.quadraticCurveTo( rightControlX, rightControlY, rightAntennaEndX, rightAntennaEndY, ); ctx.stroke(); // 绘制触角末端的小圆点 ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(leftAntennaEndX, leftAntennaEndY, 2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(rightAntennaEndX, rightAntennaEndY, 2, 0, Math.PI * 2); ctx.fill(); // 绘制眼睛 const eyeSize = headRadius * 0.15; const eyeOffsetX = headRadius * 0.25; const eyeOffsetY = headRadius * 0.22; ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc( headCenterX - eyeOffsetX - 2, headCenterY - eyeOffsetY, eyeSize, 0, Math.PI * 2, ); ctx.fill(); ctx.beginPath(); ctx.arc( headCenterX + eyeOffsetX - 2, headCenterY - eyeOffsetY, eyeSize, 0, Math.PI * 2, ); ctx.fill(); // 绘制眼珠 ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc( headCenterX - eyeOffsetX - 2, headCenterY - eyeOffsetY, eyeSize * 0.6, 0, Math.PI * 2, ); ctx.fill(); ctx.beginPath(); ctx.arc( headCenterX + eyeOffsetX - 2, headCenterY - eyeOffsetY, eyeSize * 0.6, 0, Math.PI * 2, ); ctx.fill(); // 绘制嘴巴(微笑) ctx.strokeStyle = '#000'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc( headCenterX - 2, headCenterY + headRadius * 0.22, headRadius * 0.3, 0, Math.PI, ); ctx.stroke(); } /** * 绘制毛毛虫身体(带弧度的圆圈) */ function drawCaterpillarBody( ctx: RenderingContext, startX: number, startY: number, count: number, bodyRadius: number, color: string, ) { const spacing = bodyRadius * 2; // 身体圆圈之间的间距(稍微重叠) const waveAmplitude = bodyRadius * 0.4; // 波浪幅度 for (let i = 0; i < count; i++) { // 计算每个身体圆圈的位置(带弧度,形成弯曲效果) const baseX = startX + i * spacing; // 使用正弦函数创建波浪效果,使身体有弧度 const waveOffset = Math.sin((i * Math.PI) / 2.5) * waveAmplitude; const bodyX = baseX; const bodyY = startY + waveOffset; // 绘制身体圆圈 ctx.fillStyle = color; ctx.beginPath(); ctx.arc(bodyX, bodyY, bodyRadius, 0, Math.PI * 2); ctx.fill(); // 绘制边框(可选,根据设计需求) ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.stroke(); } } /** * 绘制圆圈模式的内容 */ function drawCirclePattern( ctx: RenderingContext, x: number, y: number, width: number, height: number, number: number, numberColor: string, isFirstRow: boolean, ) { const totalCircles = 10; const circleRadius = 16; // 固定半径 const circleSpacing = 6; // 固定间距 // 计算所有圆圈的总宽度 const totalWidth = totalCircles * circleRadius * 2 + (totalCircles - 1) * circleSpacing; // 计算水平居中位置 const startX = x + (width - totalWidth) / 2 + circleRadius; // 计算垂直居中位置 const centerY = y + height / 2; // 绘制10个虚线圆圈(一行) for (let i = 0; i < totalCircles; i++) { const circleX = startX + i * (circleRadius * 2 + circleSpacing); // 绘制虚线圆圈 ctx.strokeStyle = numberColor; ctx.lineWidth = 1; ctx.setLineDash([3, 3]); ctx.beginPath(); ctx.arc(circleX, centerY, circleRadius, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]); // 第一行:根据数字数量涂色 if (isFirstRow && i < number) { ctx.fillStyle = numberColor; ctx.beginPath(); ctx.arc(circleX, centerY, circleRadius - 1, 0, Math.PI * 2); ctx.fill(); } } } /** * 绘制毛毛虫模式的内容 */ function drawCaterpillarPattern( ctx: RenderingContext, x: number, y: number, width: number, height: number, number: number, numberColor: string, isFirstRow: boolean, ) { const padding = 15; const headSize = 45; // 头部区域高度(用于垂直居中) const headRadius = 18; // 固定头部半径 const bodyRadius = 16; // 固定身体半径 if (isFirstRow) { // 第一行:绘制完整的毛毛虫(头部+身体) const headX = x + padding; const headY = y + (height - headSize) / 2; // 绘制头部 drawCaterpillarHead(ctx, headX, headY, headSize, numberColor); // 计算头部的实际宽度(头部半径 * 2) const headWidth = headRadius * 2; // 头部的实际宽度 = 36 // 绘制身体(从头部右侧边缘开始,留一点间距) const bodyStartX = headX + headWidth + 17; // 头部右边缘 + 5px间距 const bodyStartY = headY + headSize / 2; // 与头部中心Y对齐 drawCaterpillarBody( ctx, bodyStartX, bodyStartY, number, bodyRadius, numberColor, ); } else { // 其他行:只绘制头部 const headX = x + padding; const headY = y + (height - headSize) / 2; drawCaterpillarHead(ctx, headX, headY, headSize, numberColor); } } /** * 绘制按数字涂颜色内容区域 */ export async function drawNumberColorContent({ canvas, ctx, colorData, canvasWidth, startY, patternType = 'circle', }: DrawNumberColorContentParams): Promise { const { numbers } = colorData; const leftMargin = 24; const rightMargin = 24; const itemSpacing = 115; // 行间距 const leftBoxWidth = 100; const rightBoxWidth = 420; const boxHeight = 80; const borderRadius = 12; const startYPos = startY + 20; // 获取所有可用颜色值 const availableColors = getNumberColors(); // 为每一行随机分配颜色,确保不重复 const shuffledColors = [...availableColors]; // Fisher-Yates 洗牌算法 for (let i = shuffledColors.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffledColors[i], shuffledColors[j]] = [ shuffledColors[j], shuffledColors[i], ]; } // 为每一行分配颜色(确保不重复) // 如果行数超过颜色数量,从剩余颜色中随机选择,但确保相邻行不同 const rowColors: string[] = []; for (let i = 0; i < numbers.length; i++) { if (i < shuffledColors.length) { // 前 N 行(N <= 颜色数量)使用不同的颜色 rowColors.push(shuffledColors[i]); } else { // 如果行数超过颜色数量,从剩余颜色中选择(确保与上一行不同) const remainingColors = shuffledColors.filter( (color) => color !== rowColors[i - 1], ); const randomColor = remainingColors[ Math.floor(Math.random() * remainingColors.length) ]; rowColors.push(randomColor); } } // 计算左侧和右侧的起始X位置 const leftBoxX = leftMargin; const rightBoxX = canvasWidth - rightMargin - rightBoxWidth; // 绘制每一行 for (let i = 0; i < numbers.length; i++) { const boxY = startYPos + i * itemSpacing; const number = numbers[i]; const numberColor = rowColors[i]; // 使用随机分配的颜色 const isFirstRow = i === 0; // 绘制左侧数字框(虚线) ctx.strokeStyle = '#999'; ctx.lineWidth = 1; drawRoundedRect( ctx, leftBoxX, boxY, leftBoxWidth, boxHeight, borderRadius, true, ); // 绘制数字(带颜色) ctx.fillStyle = numberColor; ctx.font = `bold ${56}px "Microsoft Yahei"`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText( String(number), leftBoxX + leftBoxWidth / 2, boxY + boxHeight / 2, ); // 绘制右侧内容框(虚线) ctx.strokeStyle = '#999'; ctx.lineWidth = 1; drawRoundedRect( ctx, rightBoxX, boxY, rightBoxWidth, boxHeight, borderRadius, true, ); // 根据模式绘制右侧内容 if (patternType === 'circle') { drawCirclePattern( ctx, rightBoxX, boxY, rightBoxWidth, boxHeight, number, numberColor, isFirstRow, ); } else if (patternType === 'caterpillar') { drawCaterpillarPattern( ctx, rightBoxX, boxY, rightBoxWidth, boxHeight, number, numberColor, isFirstRow, ); } } }