From c47ebce43ceb5c5ddf6b9dfe451d7370ad90ddff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=9D=B0?= Date: Sun, 8 Feb 2026 22:57:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E5=90=8E=E8=BF=98=E5=8F=AF=E4=BB=A5=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=92=8C=E5=8F=96=E6=B6=88=E3=80=81=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E5=8F=82=E4=B8=8E=E8=80=85=E5=8D=A1=E7=89=87=E5=B1=95?= =?UTF-8?q?=E7=A4=BANTRP=20=E7=AD=89=E7=BA=A7=E3=80=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=B5=B7=E6=8A=A5=E7=9A=84=E5=9B=BE=E7=89=87=E8=B4=A8=E9=87=8F?= =?UTF-8?q?=E9=99=8D=E4=BD=8E=E5=88=B01M=E4=BB=A5=E4=B8=8B,=20=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E6=B5=B7=E6=8A=A5=E4=B8=8E=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E9=A1=B5=E6=B5=B7=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/env.config.ts | 2 +- .../GameManagePopup/index.module.scss | 35 +- src/components/GameManagePopup/index.tsx | 8 +- src/components/Radar/indexV2.tsx | 1063 +++++++++-------- .../detail/components/Participants/index.tsx | 209 ++-- src/other_pages/ntrp-evaluate/index.tsx | 2 +- src/utils/genPoster.ts | 6 +- 7 files changed, 715 insertions(+), 610 deletions(-) diff --git a/config/env.config.ts b/config/env.config.ts index dc38fd9..4375a20 100644 --- a/config/env.config.ts +++ b/config/env.config.ts @@ -22,7 +22,7 @@ export interface EnvConfig { const baseConfig = { apiBaseURL: "https://tennis.bimwe.com", - ossBaseURL: "https://bimwe-oss.oss-cn-shanghai.aliyuncs.com", + ossBaseURL: "https://bimwe.oss-cn-shanghai.aliyuncs.com", appid: "wx815b533167eb7b53", // 测试号 timeout: 15000, enableLog: true, diff --git a/src/components/GameManagePopup/index.module.scss b/src/components/GameManagePopup/index.module.scss index eefab2e..24b370e 100644 --- a/src/components/GameManagePopup/index.module.scss +++ b/src/components/GameManagePopup/index.module.scss @@ -13,7 +13,9 @@ align-items: center; color: #000; text-align: center; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 16px; font-style: normal; @@ -32,7 +34,9 @@ padding-top: 24px; color: #000; text-align: center; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 16px; font-style: normal; @@ -48,8 +52,10 @@ align-items: center; .tips { - color: rgba(60, 60, 67, 0.60); - font-feature-settings: 'liga' off, 'clig' off; + color: rgba(60, 60, 67, 0.6); + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 16px; font-style: normal; @@ -62,13 +68,15 @@ margin-top: 8px; padding: 8px; border-radius: 4px; - background: #F0F0F0; + background: #f0f0f0; .input { width: 100%; &:placeholder-shown { - color: rgba(60, 60, 67, 0.30); - font-feature-settings: 'liga' off, 'clig' off; + color: rgba(60, 60, 67, 0.3); + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -84,11 +92,12 @@ justify-content: space-between; align-items: center; height: 44px; - border-top: 0.5px solid #CECECE; - background: #FFF; + border-top: 0.5px solid #cecece; + background: #fff; margin-top: 2px; - .confirm, .cancel { + .confirm, + .cancel { width: 50%; height: 44px; display: flex; @@ -96,7 +105,9 @@ align-items: center; color: #000; text-align: center; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 16px; font-style: normal; @@ -109,4 +120,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/GameManagePopup/index.tsx b/src/components/GameManagePopup/index.tsx index f03f290..1440b81 100644 --- a/src/components/GameManagePopup/index.tsx +++ b/src/components/GameManagePopup/index.tsx @@ -186,7 +186,7 @@ export default forwardRef(function GameManagePopup(props, ref) { .some((item) => item.user.id === userInfo.id); const finished = [MATCH_STATUS.FINISHED, MATCH_STATUS.CANCELED].includes( - detail.match_status + detail.match_status, ); const inTwoHours = dayjs(detail.start_time).diff(dayjs(), "hour") < 2; @@ -207,7 +207,7 @@ export default forwardRef(function GameManagePopup(props, ref) { style={{ minHeight: "unset" }} > - {!inTwoHours && !hasOtherParticiappants && ( + {!finished && !inTwoHours && !hasOtherParticiappants && ( 编辑活动 @@ -217,12 +217,12 @@ export default forwardRef(function GameManagePopup(props, ref) { 重新发布 )} - {!inTwoHours && !hasOtherParticiappants && ( + {!finished && !inTwoHours && !hasOtherParticiappants && ( 取消活动 )} - {hasJoin && ( + {!finished && hasJoin && ( 退出活动 diff --git a/src/components/Radar/indexV2.tsx b/src/components/Radar/indexV2.tsx index 7d223b4..62d5e25 100644 --- a/src/components/Radar/indexV2.tsx +++ b/src/components/Radar/indexV2.tsx @@ -29,30 +29,36 @@ export interface RadarChartV2Ref { }) => Promise; } -const RadarChartV2 = forwardRef((props, ref) => { - const { data } = props; +const RadarChartV2 = forwardRef( + (props, ref) => { + const { data } = props; - const maxValue = 100; - const levels = 5; + 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'; + // 在 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; @@ -61,508 +67,583 @@ const RadarChartV2 = forwardRef((props, ref) vals: [...res.vals, val], }; }, - { texts: [], vals: [] } + { 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; - + // === 绘制圆形网格 === + for (let i = levels; i >= 1; i--) { + const r = (radius / levels) * i; 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.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(); - } + } - // 加载图片工具函数 - function loadImage(canvas: any, src: string): Promise { - return new Promise((resolve, reject) => { - const img = canvas.createImage(); - img.onload = () => resolve(img); - img.onerror = (err) => reject(err); - img.src = src; - }); - } + // === 坐标轴 & 标签 === + 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); - // 获取图片信息(宽高) - 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, + // 坐标轴 + 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; - // 绘制圆角矩形 - 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(); - } + 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); - // 格式化 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; - } + 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(); + } - 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 + // 加载图片工具函数 + function loadImage(canvas: any, src: string): Promise { + return new Promise((resolve, reject) => { + const img = canvas.createImage(); + img.onload = () => resolve(img); + img.onerror = (err) => reject(err); + img.src = src; + }); + } - // 创建导出画布 - 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; - } + // 获取图片信息(宽高) + 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, + }); + }); + } - const canvas = res[0].node; - const ctx = canvas.getContext("2d"); - // 使用2倍图尺寸 - canvas.width = width; - canvas.height = height; + // 绘制圆角矩形 + 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(); + } - // 启用抗锯齿 - ctx.imageSmoothingEnabled = true; - ctx.imageSmoothingQuality = 'high'; + // 格式化 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; + } - // 绘制背景 - 使用 share_bg.png 背景图,撑满整个画布(从 OSS 动态加载) - try { - const shareBgUrl = `${OSS_BASE}/front/ball/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); - } + useImperativeHandle(ref, () => ({ + // 生成原始雷达图(已废弃,现在直接在 exportCanvasV2 中绘制) + generateImage: () => Promise.resolve(""), - // 根据设计稿调整内边距和布局(2倍图) - const topPadding = 24 * scale; // 设计稿顶部内边距 - const sidePadding = 28 * scale; // 设计稿左右内边距(Frame 1912055062 的 x: 28) - - let currentY = topPadding; + // 生成完整图片(包含标题、雷达图、底部文字和二维码) + 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 - // 绘制用户头像和装饰图片 - 根据设计稿尺寸(2倍图) - if (options.avatarUrl) { + // 创建导出画布 + 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 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); + const shareBgUrl = `${OSS_BASE}/front/ball/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.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 度 - + 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 docCopyImg = await loadImage(canvas, docCopyPng); + 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(); - - // 移动到旋转中心 - 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.arc( + avatarX + avatarSize / 2, + avatarY + avatarSize / 2, + avatarSize / 2, + 0, + Math.PI * 2, + ); + ctx.fillStyle = "#FFFFFF"; 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.strokeStyle = "#EFEFEF"; + ctx.lineWidth = 1 * scale; 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); + + // 计算头像绘制尺寸,保持宽高比 + 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 docCopy image:", error); + console.error("Failed to load QR code:", 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); + // 导出图片 + Taro.canvasToTempFilePath({ + canvas, + fileType: "png", + quality: 0.7, + success: (res) => { + resolve(res.tempFilePath); + }, + fail: (err) => { + reject(err); + }, + }); }); - - - // 绘制二维码 - 设计稿位置(带白色背景、边框、阴影和圆角) - - 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); - } - } + } catch (error) { + reject(error); + } + }); + }, + })); - // 导出图片 - Taro.canvasToTempFilePath({ - canvas, - fileType: 'png', - quality: 1, - success: (res) => { - resolve(res.tempFilePath); - }, - fail: (err) => { - reject(err); - }, - }); - }); - } catch (error) { - reject(error); - } - }); - }, - })); - - return ( - - {/* 隐藏的导出画布 - 2倍图尺寸 */} - - - ); -}); + return ( + + {/* 隐藏的导出画布 - 2倍图尺寸 */} + + + ); + }, +); RadarChartV2.displayName = "RadarChartV2"; export default RadarChartV2; - diff --git a/src/game_pages/detail/components/Participants/index.tsx b/src/game_pages/detail/components/Participants/index.tsx index 56e3e10..9c18c23 100644 --- a/src/game_pages/detail/components/Participants/index.tsx +++ b/src/game_pages/detail/components/Participants/index.tsx @@ -40,7 +40,7 @@ function isFull(counts) { function matchNtrpRequestment( target?: string, min?: string, - max?: string + max?: string, ): boolean { // 目标值为空或 undefined if (!target?.trim()) return true; @@ -110,7 +110,7 @@ export default function Participants(props) { user_action_status; const showApplicationEntry = [can_pay, can_substitute, is_substituting, waiting_start].every( - (item) => !item + (item) => !item, ) && can_join && dayjs(start_time).isAfter(dayjs()); @@ -138,7 +138,7 @@ export default function Participants(props) { Taro.navigateTo({ url: `/login_pages/index/index?redirect=${encodeURIComponent( - fullPath + fullPath, )}`, }); } @@ -153,7 +153,7 @@ export default function Participants(props) { const matchNtrpReq = matchNtrpRequestment( userInfo?.ntrp_level, skill_level_min, - skill_level_max + skill_level_max, ); function handleSelfEvaluate() { @@ -180,7 +180,7 @@ export default function Participants(props) { } function generateTextAndAction( - user_action_status: null | { [key: string]: boolean } + user_action_status: null | { [key: string]: boolean }, ): | undefined | { text: string | React.FC; action?: () => void; available?: boolean } { @@ -259,7 +259,7 @@ export default function Participants(props) { const res = await OrderService.getUnpaidOrder(id); if (res.code === 0) { navto( - `/order_pages/orderDetail/index?id=${res.data.order_info.order_id}` + `/order_pages/orderDetail/index?id=${res.data.order_info.order_id}`, ); } }), @@ -296,10 +296,11 @@ export default function Participants(props) { const { action = () => {} } = generateTextAndAction(user_action_status)!; const leftCount = max_participants - participant_count; - const leftSubstituteCount = (max_substitute_players || 0) - (substitute_count || 0); + const leftSubstituteCount = + (max_substitute_players || 0) - (substitute_count || 0); const showSubstituteApplicationEntry = [can_pay, can_join, is_substituting, waiting_start].every( - (item) => !item + (item) => !item, ) && can_substitute && dayjs(start_time).isAfter(dayjs()); @@ -336,7 +337,7 @@ export default function Participants(props) { refresherBackground="#FAFAFA" className={classnames( styles["participants-list-scroll"], - showApplicationEntry ? styles.withApplication : "" + showApplicationEntry ? styles.withApplication : "", )} scrollX > @@ -377,14 +378,14 @@ export default function Participants(props) { src={avatar_url} onClick={handleViewUserInfo.bind( null, - participant_user_id + participant_user_id, )} /> {nickname || "未知"} - {displayNtrp} + NTRP {displayNtrp} {role} @@ -400,97 +401,107 @@ export default function Participants(props) { )} {/* 候补区域 */} - {max_substitute_players > 0 && (substitute_count > 0 || showSubstituteApplicationEntry) && ( - - - 候补 - · - {leftSubstituteCount > 0 ? `剩余空位 ${leftSubstituteCount}` : "已满员"} - - - {/* 候补申请入口 */} - {showSubstituteApplicationEntry && ( - { - action?.(); - }} - > - - - 申请候补 - - - )} - {/* 候补成员列表 */} - 0 && + (substitute_count > 0 || showSubstituteApplicationEntry) && ( + + + 候补 + · + + {leftSubstituteCount > 0 + ? `剩余空位 ${leftSubstituteCount}` + : "已满员"} + + + + {/* 候补申请入口 */} + {showSubstituteApplicationEntry && ( + { + action?.(); + }} + > + + + 申请候补 + + )} - scrollX - > - - {substitute_members.map((substitute) => { - const { - is_organizer, - user: { - avatar_url, - nickname, - level, - ntrp_level, - id: substitute_user_id, - }, - } = substitute; - const role = is_organizer ? "组织者" : "参与者"; - // 优先使用 ntrp_level,如果没有则使用 level - const ntrpValue = ntrp_level || level; - // 格式化显示 NTRP,如果没有值则显示"初学者" - const displayNtrp = ntrpValue - ? formatNtrpDisplay(ntrpValue) - : "初学者"; - return ( - - - - {nickname || "未知"} - - - {displayNtrp} - - - {role} - - - ); - })} - - + + {substitute_members.map((substitute) => { + const { + is_organizer, + user: { + avatar_url, + nickname, + level, + ntrp_level, + id: substitute_user_id, + }, + } = substitute; + const role = is_organizer ? "组织者" : "参与者"; + // 优先使用 ntrp_level,如果没有则使用 level + const ntrpValue = ntrp_level || level; + // 格式化显示 NTRP,如果没有值则显示"初学者" + const displayNtrp = ntrpValue + ? formatNtrpDisplay(ntrpValue) + : "初学者"; + return ( + + + + {nickname || "未知"} + + + {displayNtrp} + + + {role} + + + ); + })} + + + - - )} + )} ); diff --git a/src/other_pages/ntrp-evaluate/index.tsx b/src/other_pages/ntrp-evaluate/index.tsx index 3a9d20f..3af9c91 100644 --- a/src/other_pages/ntrp-evaluate/index.tsx +++ b/src/other_pages/ntrp-evaluate/index.tsx @@ -16,7 +16,7 @@ import { useGlobalState } from "@/store/global"; import { delay, getCurrentFullPath } from "@/utils"; import { formatNtrpDisplay } from "@/utils/helper"; import { waitForAuthInit } from "@/utils/authInit"; -import httpService from "@/services/httpService"; +// import httpService from "@/services/httpService"; import DetailService from "@/services/detailService"; import { OSS_BASE } from "@/config/api"; import CloseIcon from "@/static/ntrp/ntrp_close_icon.svg"; diff --git a/src/utils/genPoster.ts b/src/utils/genPoster.ts index 19fcb59..a1ec2c4 100644 --- a/src/utils/genPoster.ts +++ b/src/utils/genPoster.ts @@ -282,7 +282,9 @@ function drawTextWrap( /** 核心纯函数:生成海报图片 */ export async function generatePosterImage(data: any): Promise { console.log("start !!!!"); - const dpr = Taro.getWindowInfo().pixelRatio; + // const dpr = Taro.getWindowInfo().pixelRatio; + const dpr = 1; + // console.log(dpr, 'dpr') const width = 600; const height = 1000; @@ -433,7 +435,7 @@ export async function generatePosterImage(data: any): Promise { const { tempFilePath } = await Taro.canvasToTempFilePath({ canvas, fileType: 'png', - quality: 1, + quality: 0.7, }); return tempFilePath; }