569 lines
23 KiB
TypeScript
569 lines
23 KiB
TypeScript
import Taro from "@tarojs/taro";
|
||
import { View, Canvas } from "@tarojs/components";
|
||
import { forwardRef, useImperativeHandle } from "react";
|
||
import shareLogoSvg from "@/static/ntrp/ntrp_share_logo.png";
|
||
import docCopyPng from "@/static/ntrp/ntrp_doc_copy.png";
|
||
import { OSS_BASE_URL } from "@/config/api";
|
||
|
||
interface RadarChartV2Props {
|
||
data: [string, number][];
|
||
title?: string;
|
||
ntrpLevel?: string;
|
||
levelDescription?: string;
|
||
avatarUrl?: string;
|
||
qrCodeUrl?: string;
|
||
bottomText?: string;
|
||
}
|
||
|
||
export interface RadarChartV2Ref {
|
||
generateImage: () => Promise<string>;
|
||
generateFullImage: (options: {
|
||
title?: string;
|
||
ntrpLevel?: string;
|
||
levelDescription?: string;
|
||
avatarUrl?: string;
|
||
qrCodeUrl?: string;
|
||
bottomText?: string;
|
||
width?: number;
|
||
height?: number;
|
||
}) => Promise<string>;
|
||
}
|
||
|
||
const RadarChartV2 = forwardRef<RadarChartV2Ref, RadarChartV2Props>((props, ref) => {
|
||
const { data } = props;
|
||
|
||
const maxValue = 100;
|
||
const levels = 5;
|
||
|
||
// 在 exportCanvasV2 中绘制雷达图的函数
|
||
function drawRadarChart(ctx: CanvasRenderingContext2D, radarX: number, radarY: number, radarSize: number) {
|
||
// 雷达图中心点位置(radarSize 已经是2倍图尺寸)
|
||
const center = {
|
||
x: radarX + radarSize / 2,
|
||
y: radarY + radarSize / 2
|
||
};
|
||
|
||
// 计算实际半径(radarSize 是直径,半径是直径的一半)
|
||
const radius = radarSize / 2;
|
||
|
||
// 启用抗锯齿
|
||
ctx.imageSmoothingEnabled = true;
|
||
ctx.imageSmoothingQuality = 'high';
|
||
ctx.lineCap = 'round';
|
||
ctx.lineJoin = 'round';
|
||
|
||
// 解析数据
|
||
const { texts, vals } = data.reduce(
|
||
(res, item) => {
|
||
const [text, val] = item;
|
||
return {
|
||
texts: [...res.texts, text],
|
||
vals: [...res.vals, val],
|
||
};
|
||
},
|
||
{ texts: [], vals: [] }
|
||
);
|
||
|
||
// === 绘制圆形网格 ===
|
||
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();
|
||
}
|
||
// 根据层级设置不同的线条颜色
|
||
if (i === 1) {
|
||
ctx.strokeStyle = "#E5E5E5";
|
||
} else if (i <= 3) {
|
||
ctx.strokeStyle = "#E0E0E0";
|
||
} else {
|
||
ctx.strokeStyle = "#D5D5D5";
|
||
}
|
||
ctx.lineWidth = 1 * (radarSize / 200); // 根据2倍图调整线宽
|
||
ctx.stroke();
|
||
}
|
||
|
||
// === 坐标轴 & 标签 ===
|
||
texts.forEach((label, i) => {
|
||
const angle = ((Math.PI * 2) / texts.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 = "#E0E0E0";
|
||
ctx.lineWidth = 1 * (radarSize / 200); // 根据2倍图调整线宽
|
||
ctx.stroke();
|
||
|
||
// 标签:沿轴线外侧延伸,文字中心对齐轴线端点(与 index.tsx 一致)
|
||
const labelOffset = 28 * (radarSize / 200); // 文字距离圆圈的偏移量(2倍图)
|
||
const textX = center.x + (radius + labelOffset) * Math.cos(angle);
|
||
const textY = center.y + (radius + labelOffset) * Math.sin(angle);
|
||
|
||
ctx.font = `${12 * (radarSize / 200)}px sans-serif`; // 根据2倍图调整字体大小
|
||
ctx.fillStyle = "#333";
|
||
ctx.textBaseline = "middle";
|
||
ctx.textAlign = "center";
|
||
|
||
ctx.fillText(label, textX, textY);
|
||
});
|
||
|
||
// === 数据区域 ===
|
||
const baseRadius = (radius / levels) * 1; // 第二个环为起点
|
||
const usableRadius = radius - baseRadius;
|
||
|
||
ctx.beginPath();
|
||
vals.forEach((val, i) => {
|
||
const angle = ((Math.PI * 2) / texts.length) * i - Math.PI / 2;
|
||
const r = baseRadius + (val / maxValue) * usableRadius;
|
||
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 * (radarSize / 200); // 根据2倍图调整线宽
|
||
ctx.stroke();
|
||
}
|
||
|
||
// 加载图片工具函数
|
||
function loadImage(canvas: any, src: string): Promise<any> {
|
||
return new Promise((resolve, reject) => {
|
||
const img = canvas.createImage();
|
||
img.onload = () => resolve(img);
|
||
img.onerror = (err) => reject(err);
|
||
img.src = src;
|
||
});
|
||
}
|
||
|
||
// 获取图片信息(宽高)
|
||
function getImageInfo(src: string): Promise<{ width: number; height: number }> {
|
||
return new Promise((resolve, reject) => {
|
||
(Taro as any).getImageInfo({
|
||
src,
|
||
success: (res: any) => resolve({ width: res.width, height: res.height }),
|
||
fail: reject,
|
||
});
|
||
});
|
||
}
|
||
|
||
|
||
// 绘制圆角矩形
|
||
function roundRect(ctx: any, x: number, y: number, width: number, height: number, radius: number) {
|
||
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();
|
||
}
|
||
|
||
// 格式化 NTRP 显示
|
||
function formatNtrpDisplay(level: string): string {
|
||
if (!level) return "";
|
||
// 检查是否包含 + 号
|
||
const hasPlus = level.includes("+");
|
||
const num = parseFloat(level);
|
||
if (isNaN(num)) return level;
|
||
const formatted = num % 1 === 0 ? num.toFixed(0) : num.toFixed(1);
|
||
return hasPlus ? `${formatted}+` : formatted;
|
||
}
|
||
|
||
useImperativeHandle(ref, () => ({
|
||
// 生成原始雷达图(已废弃,现在直接在 exportCanvasV2 中绘制)
|
||
generateImage: () =>
|
||
Promise.resolve(""),
|
||
|
||
// 生成完整图片(包含标题、雷达图、底部文字和二维码)
|
||
generateFullImage: async (options: {
|
||
title?: string;
|
||
ntrpLevel?: string;
|
||
levelDescription?: string;
|
||
avatarUrl?: string;
|
||
qrCodeUrl?: string;
|
||
bottomText?: string;
|
||
width?: number;
|
||
height?: number;
|
||
}) => {
|
||
return new Promise(async (resolve, reject) => {
|
||
try {
|
||
// 使用2倍图提高图片质量
|
||
const scale = 2; // 2倍图
|
||
const baseWidth = 350; // 基础宽度
|
||
const baseHeight = 600; // 基础高度
|
||
const width = baseWidth * scale; // 实际宽度 700
|
||
const height = baseHeight * scale; // 实际高度 1200
|
||
|
||
// 创建导出画布
|
||
const query = Taro.createSelectorQuery();
|
||
query
|
||
.select("#exportCanvasV2")
|
||
.fields({ node: true, size: true })
|
||
.exec(async (res) => {
|
||
if (!res || !res[0]) {
|
||
reject(new Error("Canvas not found"));
|
||
return;
|
||
}
|
||
|
||
const canvas = res[0].node;
|
||
const ctx = canvas.getContext("2d");
|
||
// 使用2倍图尺寸
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
|
||
// 启用抗锯齿
|
||
ctx.imageSmoothingEnabled = true;
|
||
ctx.imageSmoothingQuality = 'high';
|
||
|
||
// 绘制背景 - 使用 share_bg.png 背景图,撑满整个画布(从 OSS 动态加载)
|
||
try {
|
||
const shareBgUrl = `${OSS_BASE_URL}/images/share_bg.png`;
|
||
const bgImg = await loadImage(canvas, shareBgUrl);
|
||
ctx.drawImage(bgImg, 0, 0, width, height);
|
||
} catch (error) {
|
||
console.error("Failed to load background image:", error);
|
||
// 如果加载失败,使用白色背景作为兜底
|
||
ctx.fillStyle = "#FFFFFF";
|
||
ctx.fillRect(0, 0, width, height);
|
||
}
|
||
|
||
// 根据设计稿调整内边距和布局(2倍图)
|
||
const topPadding = 24 * scale; // 设计稿顶部内边距
|
||
const sidePadding = 28 * scale; // 设计稿左右内边距(Frame 1912055062 的 x: 28)
|
||
|
||
let currentY = topPadding;
|
||
|
||
// 绘制用户头像和装饰图片 - 根据设计稿尺寸(2倍图)
|
||
if (options.avatarUrl) {
|
||
try {
|
||
const avatarSize = 43.46 * scale; // 设计稿头像尺寸
|
||
const avatarImg = await loadImage(canvas, options.avatarUrl);
|
||
const avatarInfo = await getImageInfo(options.avatarUrl);
|
||
|
||
// 头像区域总宽度(头像 + 装饰图片重叠部分)
|
||
const avatarWrapWidth = 84.7 * scale; // 设计稿 Frame 1912055063 宽度
|
||
const avatarX = sidePadding + (294 * scale - avatarWrapWidth) / 2; // 294 是 Frame 1912055062 宽度,居中
|
||
const avatarY = currentY;
|
||
|
||
// 绘制头像圆形背景
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
||
ctx.fillStyle = "#FFFFFF";
|
||
ctx.fill();
|
||
ctx.strokeStyle = "#EFEFEF";
|
||
ctx.lineWidth = 1 * scale;
|
||
ctx.stroke();
|
||
|
||
// 计算头像绘制尺寸,保持宽高比
|
||
const innerSize = avatarSize - 1.94 * scale; // 内部可用尺寸
|
||
const avatarAspectRatio = avatarInfo.width / avatarInfo.height;
|
||
let drawWidth = innerSize;
|
||
let drawHeight = innerSize;
|
||
let drawX = avatarX + 0.97 * scale;
|
||
let drawY = avatarY + 0.97 * scale;
|
||
|
||
// 根据宽高比调整尺寸,保持比例不变形
|
||
if (avatarAspectRatio > 1) {
|
||
// 图片更宽,以高度为准
|
||
drawWidth = innerSize * avatarAspectRatio;
|
||
drawX = avatarX + avatarSize / 2 - drawWidth / 2;
|
||
} else {
|
||
// 图片更高或正方形,以宽度为准
|
||
drawHeight = innerSize / avatarAspectRatio;
|
||
drawY = avatarY + avatarSize / 2 - drawHeight / 2;
|
||
}
|
||
|
||
// 绘制头像(圆形裁剪)
|
||
ctx.beginPath();
|
||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 - 0.97 * scale, 0, Math.PI * 2);
|
||
ctx.clip();
|
||
ctx.drawImage(avatarImg, drawX, drawY, drawWidth, drawHeight);
|
||
ctx.restore();
|
||
|
||
// 绘制装饰图片(DocCopy)- 在头像右侧
|
||
const addonSize = 42 * scale; // 装饰图片和头像一样大
|
||
const addonX = avatarX + avatarSize - 5 * scale; // 重叠部分
|
||
const addonY = avatarY;
|
||
const addonRotation = 8 * (Math.PI / 180); // 旋转 8 度
|
||
|
||
try {
|
||
const docCopyImg = await loadImage(canvas, docCopyPng);
|
||
ctx.save();
|
||
|
||
// 移动到旋转中心
|
||
const centerX = addonX + addonSize / 2;
|
||
const centerY = addonY + addonSize / 2;
|
||
ctx.translate(centerX, centerY);
|
||
ctx.rotate(addonRotation);
|
||
|
||
// 绘制装饰图片背景(圆角矩形,带渐变)
|
||
const borderRadius = 9.66 * scale; // 设计稿圆角
|
||
ctx.fillStyle = "#FFFFFF";
|
||
ctx.beginPath();
|
||
roundRect(ctx, -addonSize / 2, -addonSize / 2, addonSize, addonSize, borderRadius);
|
||
ctx.fill();
|
||
|
||
// 添加渐变背景色
|
||
ctx.globalAlpha = 0.2;
|
||
ctx.fillStyle = "rgba(89, 255, 214, 1)";
|
||
ctx.fill();
|
||
ctx.globalAlpha = 1.0;
|
||
|
||
ctx.strokeStyle = "#FFFFFF";
|
||
ctx.lineWidth = 1.93 * scale; // 设计稿 strokeWeight
|
||
ctx.stroke();
|
||
|
||
// 绘制装饰图片
|
||
const docSize = 26.18 * scale; // 设计稿内部图片尺寸
|
||
const docRotation = -7 * (Math.PI / 180); // 内部旋转 -7 度
|
||
ctx.rotate(docRotation);
|
||
ctx.drawImage(docCopyImg, -docSize / 2, -docSize / 2, docSize, docSize);
|
||
ctx.restore();
|
||
} catch (error) {
|
||
console.error("Failed to load docCopy image:", error);
|
||
}
|
||
|
||
currentY += (48 + 20) * scale; // 头像区域高度 + gap
|
||
} catch (error) {
|
||
console.error("Failed to load avatar image:", error);
|
||
}
|
||
}
|
||
|
||
// 绘制文字区域 - 根据设计稿样式,居中对齐(2倍图)
|
||
const textCenterX = sidePadding + (294 * scale) / 2; // Frame 1912055062 的中心
|
||
const textGap = 6 * scale; // 设计稿 gap: 6px
|
||
|
||
// 绘制标题 - 14px, font-weight: 300, line-height: 1.2(2倍图)
|
||
if (options.title) {
|
||
ctx.fillStyle = "#000000";
|
||
ctx.font = `300 ${14 * scale}px Noto Sans SC, sans-serif`;
|
||
ctx.textAlign = "center";
|
||
ctx.textBaseline = "top";
|
||
ctx.fillText(options.title, textCenterX, currentY);
|
||
currentY += 14 * 1.2 * scale + textGap; // line-height: 1.2 + gap
|
||
}
|
||
|
||
// 绘制 NTRP 等级 - 36px, font-weight: 900, "NTRP" 黑色,等级数字 #00E5AD(2倍图)
|
||
if (options.ntrpLevel) {
|
||
ctx.font = `900 ${36 * scale}px Noto Sans SC, sans-serif`;
|
||
ctx.textBaseline = "top";
|
||
|
||
const ntrpText = "NTRP";
|
||
const levelText = formatNtrpDisplay(options.ntrpLevel);
|
||
const ntrpWidth = ctx.measureText(ntrpText).width;
|
||
const levelWidth = ctx.measureText(levelText).width;
|
||
const totalWidth = ntrpWidth + levelWidth; // 设计稿中紧挨着
|
||
const startX = textCenterX - totalWidth / 2;
|
||
|
||
// 绘制 "NTRP"(黑色)
|
||
ctx.fillStyle = "#000000";
|
||
ctx.textAlign = "left";
|
||
ctx.fillText(ntrpText, startX, currentY);
|
||
|
||
// 绘制等级数字(#00E5AD)
|
||
ctx.fillStyle = "#00E5AD";
|
||
ctx.fillText(levelText, startX + ntrpWidth, currentY);
|
||
|
||
currentY += 36 * 1.222 * scale + textGap; // line-height: 1.222 + gap
|
||
}
|
||
|
||
// 绘制描述文本 - 16px, font-weight: 600, line-height: 1.4(2倍图)
|
||
if (options.levelDescription) {
|
||
ctx.fillStyle = "#000000";
|
||
ctx.font = `600 ${16 * scale}px PingFang SC, sans-serif`;
|
||
ctx.textAlign = "center";
|
||
ctx.textBaseline = "top";
|
||
ctx.fillText(options.levelDescription, textCenterX, currentY);
|
||
// 计算到雷达图的间距:雷达图 y: 252 - 当前 Y
|
||
const radarY = 252 * scale; // 设计稿雷达图位置
|
||
currentY = radarY; // 直接设置到雷达图位置
|
||
}
|
||
|
||
// 直接绘制雷达图 - 根据设计稿调整尺寸和位置(2倍图)
|
||
const radarSize = 200 * scale; // 固定雷达图尺寸为 200*200
|
||
const radarX = 75 * scale; // 设计稿雷达图 x 位置
|
||
const radarY = currentY;
|
||
// 直接在 exportCanvasV2 中绘制雷达图
|
||
drawRadarChart(ctx, radarX, radarY, radarSize);
|
||
currentY += radarSize; // 雷达图高度
|
||
|
||
// 绘制底部区域 - 根据设计稿调整(2倍图)
|
||
const qrSize = 64 * scale; // 设计稿二维码尺寸:64*64
|
||
const qrX = 276 * scale; // 设计稿二维码 x 位置
|
||
const qrY = 523 * scale; // 设计稿二维码 y 位置
|
||
|
||
const bottomTextContent = options.bottomText || "长按识别二维码,快来加入,有你就有场!";
|
||
|
||
// 绘制底部文字 - 设计稿:fontSize: 12, fontWeight: 400, line-height: 1.5(2倍图)
|
||
ctx.fillStyle = "rgba(0, 0, 0, 0.45)";
|
||
ctx.font = `400 ${12 * scale}px Noto Sans SC, sans-serif`;
|
||
ctx.textAlign = "left";
|
||
ctx.textBaseline = "top";
|
||
|
||
const textX = 20 * scale; // 设计稿文字 x 位置
|
||
const maxTextWidth = qrX - textX - 10 * scale; // 到二维码的间距
|
||
|
||
// 计算文字行数
|
||
const bottomWords = bottomTextContent.split("");
|
||
let bottomLine = "";
|
||
let textLines: string[] = [];
|
||
const lineHeight = 12 * 1.5 * scale; // fontSize * line-height
|
||
|
||
for (let i = 0; i < bottomWords.length; i++) {
|
||
const testBottomLine = bottomLine + bottomWords[i];
|
||
const metrics = ctx.measureText(testBottomLine);
|
||
if (metrics.width > maxTextWidth && i > 0) {
|
||
textLines.push(bottomLine);
|
||
bottomLine = bottomWords[i];
|
||
} else {
|
||
bottomLine = testBottomLine;
|
||
}
|
||
}
|
||
if (bottomLine) {
|
||
textLines.push(bottomLine);
|
||
}
|
||
|
||
// 计算文字总高度
|
||
const textTotalHeight = textLines.length * lineHeight;
|
||
// 计算二维码底部位置
|
||
const qrBottomY = qrY + qrSize;
|
||
// 让文字底部和二维码底部对齐
|
||
const textY = qrBottomY - textTotalHeight;
|
||
|
||
// 绘制顶部标题和图标 - 设计稿 Frame 2036081426(2倍图)
|
||
// 图标应该在文字上方,gap: 4px
|
||
const iconGap = 4 * scale; // 图标和文字之间的间距
|
||
const topTitleX = textX;
|
||
|
||
// 绘制上面的标题 logo
|
||
try {
|
||
const iconSize = 28 * scale;
|
||
const iconImg = await loadImage(canvas, shareLogoSvg);
|
||
// 图标位置:文字顶部上方 iconSize + gap
|
||
const iconY = textY - iconSize - iconGap;
|
||
ctx.drawImage(iconImg, topTitleX, iconY, 235 * scale, iconSize);
|
||
} catch (error) {
|
||
console.error("Failed to load icon:", error);
|
||
}
|
||
|
||
// 绘制底部文字
|
||
textLines.forEach((lineText, index) => {
|
||
ctx.fillText(lineText, textX, textY + index * lineHeight);
|
||
});
|
||
|
||
|
||
// 绘制二维码 - 设计稿位置(带白色背景、边框、阴影和圆角)
|
||
|
||
if (options.qrCodeUrl) {
|
||
try {
|
||
const qrImg = await loadImage(canvas, options.qrCodeUrl);
|
||
|
||
// 二维码样式参数(2倍图)
|
||
const borderWidth = 2 * scale; // 边框宽度 2px
|
||
const borderRadius = 10 * scale; // 圆角 10px
|
||
const shadowOffsetX = 0; // 阴影 X 偏移
|
||
const shadowOffsetY = 1.04727 * scale; // 阴影 Y 偏移 1.04727px
|
||
const shadowBlur = 9.42545 * scale; // 阴影模糊 9.42545px
|
||
const shadowColor = "rgba(0, 0, 0, 0.12)"; // 阴影颜色
|
||
|
||
// 二维码实际绘制区域(留出边框空间)
|
||
const qrInnerSize = qrSize - borderWidth * 2;
|
||
const qrInnerX = qrX + borderWidth;
|
||
const qrInnerY = qrY + borderWidth;
|
||
|
||
// 保存上下文状态
|
||
ctx.save();
|
||
|
||
// 设置阴影
|
||
ctx.shadowOffsetX = shadowOffsetX;
|
||
ctx.shadowOffsetY = shadowOffsetY;
|
||
ctx.shadowBlur = shadowBlur;
|
||
ctx.shadowColor = shadowColor;
|
||
|
||
// 绘制白色背景(圆角矩形)
|
||
ctx.fillStyle = "#FFFFFF";
|
||
roundRect(ctx, qrX, qrY, qrSize, qrSize, borderRadius);
|
||
ctx.fill();
|
||
|
||
// 绘制白色边框
|
||
ctx.strokeStyle = "#FFFFFF";
|
||
ctx.lineWidth = borderWidth;
|
||
roundRect(ctx, qrX, qrY, qrSize, qrSize, borderRadius);
|
||
ctx.stroke();
|
||
|
||
// 清除阴影(二维码图片不需要阴影)
|
||
ctx.shadowOffsetX = 0;
|
||
ctx.shadowOffsetY = 0;
|
||
ctx.shadowBlur = 0;
|
||
ctx.shadowColor = "transparent";
|
||
|
||
// 绘制二维码图片(在圆角矩形内)
|
||
ctx.save();
|
||
// 创建圆角裁剪区域
|
||
roundRect(ctx, qrInnerX, qrInnerY, qrInnerSize, qrInnerSize, borderRadius - borderWidth);
|
||
ctx.clip();
|
||
// 绘制二维码图片
|
||
ctx.drawImage(qrImg, qrInnerX, qrInnerY, qrInnerSize, qrInnerSize);
|
||
ctx.restore();
|
||
|
||
// 恢复上下文状态
|
||
ctx.restore();
|
||
} catch (error) {
|
||
console.error("Failed to load QR code:", error);
|
||
}
|
||
}
|
||
|
||
// 导出图片
|
||
Taro.canvasToTempFilePath({
|
||
canvas,
|
||
fileType: 'png',
|
||
quality: 1,
|
||
success: (res) => {
|
||
resolve(res.tempFilePath);
|
||
},
|
||
fail: (err) => {
|
||
reject(err);
|
||
},
|
||
});
|
||
});
|
||
} catch (error) {
|
||
reject(error);
|
||
}
|
||
});
|
||
},
|
||
}));
|
||
|
||
return (
|
||
<View>
|
||
{/* 隐藏的导出画布 - 2倍图尺寸 */}
|
||
<Canvas
|
||
type="2d"
|
||
id="exportCanvasV2"
|
||
style={{ position: "fixed", top: "-9999px", left: "-9999px", width: "700px", height: "1200px" }}
|
||
/>
|
||
</View>
|
||
);
|
||
});
|
||
|
||
RadarChartV2.displayName = "RadarChartV2";
|
||
|
||
export default RadarChartV2;
|
||
|