import Taro from "@tarojs/taro"; import { View, Canvas } from "@tarojs/components"; import { useEffect, forwardRef, useImperativeHandle } from "react"; const RadarChart: React.FC = forwardRef((props, ref) => { const { data } = props; const maxValue = 100; const levels = 5; const radius = 100; const center = { x: 160, y: 160 }; useEffect(() => { if (data.length > 0) { const { texts, vals } = data.reduce( (res, item) => { const [text, val] = item; return { texts: [...res.texts, text], vals: [...res.vals, val], }; }, { texts: [], vals: [] } ); renderCanvas(texts, vals); } }, [data]); function renderCanvas(labels, values) { const query = Taro.createSelectorQuery(); query .select("#radarCanvas") .fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node as HTMLCanvasElement; const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; const dpr = Taro.getWindowInfo().pixelRatio; canvas.width = res[0].width * dpr; canvas.height = res[0].height * dpr; ctx.scale(dpr, dpr); // === 绘制圆形网格 === for (let i = levels; i >= 1; i--) { const r = (radius / levels) * i; ctx.beginPath(); ctx.arc(center.x, center.y, r, 0, Math.PI * 2); if (i % 2 === 1) { ctx.fillStyle = "#fff"; ctx.fill(); } else { ctx.fillStyle = "#CAFCF0"; ctx.fill(); } ctx.strokeStyle = "#bbb"; ctx.stroke(); } // === 坐标轴 & 标签 === labels.forEach((label, i) => { const angle = ((Math.PI * 2) / labels.length) * i - Math.PI / 2; const x = center.x + radius * Math.cos(angle); const y = center.y + radius * Math.sin(angle); // 坐标轴 ctx.beginPath(); ctx.moveTo(center.x, center.y); ctx.lineTo(x, y); ctx.strokeStyle = "#bbb"; ctx.stroke(); // 标签 const offset = 10; const textX = center.x + (radius + offset) * Math.cos(angle); const textY = center.y + (radius + offset) * Math.sin(angle); ctx.font = "12px sans-serif"; ctx.fillStyle = "#333"; ctx.textBaseline = "middle"; if ( Math.abs(angle) < 0.01 || Math.abs(Math.abs(angle) - Math.PI) < 0.01 ) { ctx.textAlign = "center"; } else if (angle > -Math.PI / 2 && angle < Math.PI / 2) { ctx.textAlign = "left"; } else { ctx.textAlign = "right"; } ctx.fillText(label, textX, textY); }); // === 数据区域 === ctx.beginPath(); values.forEach((val, i) => { const angle = ((Math.PI * 2) / labels.length) * i - Math.PI / 2; const r = (val / maxValue) * radius; const x = center.x + r * Math.cos(angle); const y = center.y + r * Math.sin(angle); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.closePath(); ctx.fillStyle = "rgba(0,200,180,0.3)"; ctx.fill(); ctx.strokeStyle = "#00c8b4"; ctx.lineWidth = 3; ctx.stroke(); }); } useImperativeHandle(ref, () => ({ generateImage: () => new Promise((resolve, reject) => { const query = Taro.createSelectorQuery(); query .select("#radarCanvas") .fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node; // ⚠️ 关键:传 canvas,而不是 canvasId Taro.canvasToTempFilePath({ canvas, success: (res) => resolve(res.tempFilePath), fail: (err) => reject(err), }); }); }), })); return ( ); }); export default RadarChart;