From a67383d12cdf1af13762025f3b3d515e94808bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Thu, 4 Dec 2025 10:40:58 +0800 Subject: [PATCH] 1 --- src/components/Radar/indexV2.tsx | 281 +++++++----------- .../detail/components/EmptyState/index.scss | 113 +++++++ .../detail/components/EmptyState/index.tsx | 95 ++++++ src/game_pages/detail/index.tsx | 19 +- src/other_pages/ntrp-evaluate/index.tsx | 11 - src/static/ntrp/ntrp_share_logo.png | Bin 2991 -> 4861 bytes 6 files changed, 331 insertions(+), 188 deletions(-) create mode 100644 src/game_pages/detail/components/EmptyState/index.scss create mode 100644 src/game_pages/detail/components/EmptyState/index.tsx diff --git a/src/components/Radar/indexV2.tsx b/src/components/Radar/indexV2.tsx index 6ad5d4f..361e0b7 100644 --- a/src/components/Radar/indexV2.tsx +++ b/src/components/Radar/indexV2.tsx @@ -1,6 +1,6 @@ import Taro from "@tarojs/taro"; import { View, Canvas } from "@tarojs/components"; -import { useEffect, forwardRef, useImperativeHandle } from "react"; +import { forwardRef, useImperativeHandle } from "react"; import shareLogoSvg from "@/static/ntrp/ntrp_share_logo.png"; import docCopySvg from "@/static/ntrp/ntrp_doc_copy.svg"; @@ -29,15 +29,27 @@ export interface RadarChartV2Ref { } const RadarChartV2 = forwardRef((props, ref) => { - const { data, title, ntrpLevel, levelDescription, qrCodeUrl, bottomText } = props; + const { data } = props; const maxValue = 100; const levels = 5; - const radius = 100; - const center = { x: 160, y: 160 }; + const radius = 100; // 半径 100,直径 200(圆圈保持 200*200) - useEffect(() => { - if (data.length > 0) { + // 在 exportCanvasV2 中绘制雷达图的函数 + function drawRadarChart(ctx: CanvasRenderingContext2D, radarX: number, radarY: number, radarSize: number) { + // 雷达图中心点在 350*600 画布中的位置 + const center = { + x: radarX + radarSize / 2, // 75 + 100 = 175 + y: radarY + radarSize / 2 // 252 + 100 = 352 + }; + + // 启用抗锯齿 + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + + // 解析数据 const { texts, vals } = data.reduce( (res, item) => { const [text, val] = item; @@ -48,29 +60,6 @@ const RadarChartV2 = forwardRef((props, ref) }, { texts: [], vals: [] } ); - renderCanvas(texts, vals); - } - }, [data]); - - function renderCanvas(labels, values) { - const query = Taro.createSelectorQuery(); - query - .select("#radarCanvasV2") - .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); - - // 启用抗锯齿,消除锯齿 - ctx.imageSmoothingEnabled = true; - ctx.imageSmoothingQuality = 'high'; - // 设置线条端点样式为圆形,减少锯齿 - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; // === 绘制圆形网格 === for (let i = levels; i >= 1; i--) { @@ -84,25 +73,21 @@ const RadarChartV2 = forwardRef((props, ref) ctx.fillStyle = "#CAFCF0"; ctx.fill(); } - // 根据层级设置不同的线条颜色,中间圆圈使用更浅的颜色 + // 根据层级设置不同的线条颜色 if (i === 1) { - // 最内圈使用最浅的颜色 ctx.strokeStyle = "#E5E5E5"; } else if (i <= 3) { - // 中间圆圈使用较浅的颜色 ctx.strokeStyle = "#E0E0E0"; } else { - // 外圈使用稍深但仍然较浅的颜色 ctx.strokeStyle = "#D5D5D5"; } - // 设置线条宽度为1px,确保清晰 ctx.lineWidth = 1; ctx.stroke(); } // === 坐标轴 & 标签 === - labels.forEach((label, i) => { - const angle = ((Math.PI * 2) / labels.length) * i - Math.PI / 2; + 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); @@ -110,13 +95,12 @@ const RadarChartV2 = forwardRef((props, ref) ctx.beginPath(); ctx.moveTo(center.x, center.y); ctx.lineTo(x, y); - // 坐标轴线条也使用较浅的颜色,与圆圈保持一致 ctx.strokeStyle = "#E0E0E0"; ctx.lineWidth = 1; ctx.stroke(); - // 标签 - const offset = 10; + // 标签 - 文字显示在圆圈外面 + const offset = 10; // 文字距离圆圈的偏移量 const textX = center.x + (radius + offset) * Math.cos(angle); const textY = center.y + (radius + offset) * Math.sin(angle); @@ -124,6 +108,7 @@ const RadarChartV2 = forwardRef((props, ref) ctx.fillStyle = "#333"; ctx.textBaseline = "middle"; + // 调整文字对齐方式 if ( Math.abs(angle) < 0.01 || Math.abs(Math.abs(angle) - Math.PI) < 0.01 @@ -143,12 +128,9 @@ const RadarChartV2 = forwardRef((props, ref) const usableRadius = radius - baseRadius; ctx.beginPath(); - values.forEach((val, i) => { - const angle = ((Math.PI * 2) / labels.length) * i - Math.PI / 2; - - // 关键修改:值从第二个环开始 + 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); @@ -161,7 +143,6 @@ const RadarChartV2 = forwardRef((props, ref) ctx.strokeStyle = "#00c8b4"; ctx.lineWidth = 3; ctx.stroke(); - }); } // 加载图片工具函数 @@ -208,28 +189,16 @@ const RadarChartV2 = forwardRef((props, ref) } useImperativeHandle(ref, () => ({ - // 生成原始雷达图 + // 生成原始雷达图(已废弃,现在直接在 exportCanvasV2 中绘制) generateImage: () => - new Promise((resolve, reject) => { - const query = Taro.createSelectorQuery(); - query - .select("#radarCanvasV2") - .fields({ node: true, size: true }) - .exec((res) => { - const canvas = res[0].node; - Taro.canvasToTempFilePath({ - canvas, - success: (res) => resolve(res.tempFilePath), - fail: (err) => reject(err), - }); - }); - }), + Promise.resolve(""), // 生成完整图片(包含标题、雷达图、底部文字和二维码) generateFullImage: async (options: { title?: string; ntrpLevel?: string; levelDescription?: string; + avatarUrl?: string; qrCodeUrl?: string; bottomText?: string; width?: number; @@ -237,25 +206,9 @@ const RadarChartV2 = forwardRef((props, ref) }) => { return new Promise(async (resolve, reject) => { try { - // 默认尺寸,根据设计稿调整 - const width = options.width || 750; // 设计稿宽度 - const height = options.height || 1334; // 设计稿高度 - - // 先获取雷达图 - const radarImageUrl = await new Promise((resolveRadar, rejectRadar) => { - const query = Taro.createSelectorQuery(); - query - .select("#radarCanvasV2") - .fields({ node: true, size: true }) - .exec((res) => { - const canvas = res[0].node; - Taro.canvasToTempFilePath({ - canvas, - success: (res) => resolveRadar(res.tempFilePath), - fail: (err) => rejectRadar(err), - }); - }); - }); + // 固定尺寸:350*600 + const width = 350; // 固定宽度 + const height = 600; // 固定高度 // 创建导出画布 const query = Taro.createSelectorQuery(); @@ -270,35 +223,36 @@ const RadarChartV2 = forwardRef((props, ref) const canvas = res[0].node; const ctx = canvas.getContext("2d"); - const dpr = Taro.getWindowInfo().pixelRatio; - canvas.width = width * dpr; - canvas.height = height * dpr; - ctx.scale(dpr, dpr); + // 固定 canvas 尺寸,不使用 dpr + canvas.width = width; + canvas.height = height; // 启用抗锯齿 ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; - // 绘制背景 - ctx.fillStyle = "#E9FDF8"; + // 绘制背景 - 渐变背景 + const gradient = ctx.createLinearGradient(0, 0, 0, height); + gradient.addColorStop(0, 'rgba(191, 255, 239, 1)'); + gradient.addColorStop(1, 'rgba(242, 255, 252, 1)'); + ctx.fillStyle = gradient; ctx.fillRect(0, 0, width, height); // 根据设计稿调整内边距和布局 - const topPadding = 60; // 顶部内边距 - const sidePadding = 40; // 左右内边距 - const bottomPadding = 30; // 底部内边距 + const topPadding = 24; // 设计稿顶部内边距 + const sidePadding = 28; // 设计稿左右内边距(Frame 1912055062 的 x: 28) let currentY = topPadding; - // 绘制用户头像和装饰图片 - 参考 Result 组件布局,居中显示 + // 绘制用户头像和装饰图片 - 根据设计稿尺寸 if (options.avatarUrl) { try { - const avatarSize = 50; // Result 组件使用 0.5 倍数,所以是 50px + const avatarSize = 43.46; // 设计稿头像尺寸 const avatarImg = await loadImage(canvas, options.avatarUrl); // 头像区域总宽度(头像 + 装饰图片重叠部分) - const avatarWrapWidth = 50 + 44 - 10; // 头像宽度 + 装饰宽度 - 重叠部分 - const avatarX = (width - avatarWrapWidth) / 2 + 10; // 居中,考虑装饰图片的位置 + const avatarWrapWidth = 84.7; // 设计稿 Frame 1912055063 宽度 + const avatarX = sidePadding + (294 - avatarWrapWidth) / 2; // 294 是 Frame 1912055062 宽度,居中 const avatarY = currentY; // 绘制头像圆形背景 @@ -313,15 +267,14 @@ const RadarChartV2 = forwardRef((props, ref) // 绘制头像(圆形裁剪) ctx.beginPath(); - ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 - 1, 0, Math.PI * 2); + ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 - 0.97, 0, Math.PI * 2); ctx.clip(); - const innerAvatarSize = 45; // 头像内部尺寸 90px * 0.5 - ctx.drawImage(avatarImg, avatarX + 2.5, avatarY + 2.5, innerAvatarSize, innerAvatarSize); + ctx.drawImage(avatarImg, avatarX + 0.97, avatarY + 0.97, avatarSize - 1.94, avatarSize - 1.94); ctx.restore(); // 绘制装饰图片(DocCopy)- 在头像右侧 - const addonSize = 44; // 88px * 0.5 - const addonX = avatarX + avatarSize - 10; // margin-left: -10px (20px * 0.5) + const addonSize = 48; // 设计稿装饰图片尺寸 + const addonX = avatarX + avatarSize - 10; // 重叠部分 const addonY = avatarY; const addonRotation = 8 * (Math.PI / 180); // 旋转 8 度 @@ -336,7 +289,7 @@ const RadarChartV2 = forwardRef((props, ref) ctx.rotate(addonRotation); // 绘制装饰图片背景(圆角矩形,带渐变) - const borderRadius = 10; // 20px * 0.5 + const borderRadius = 9.66; // 设计稿圆角 ctx.fillStyle = "#FFFFFF"; ctx.beginPath(); roundRect(ctx, -addonSize / 2, -addonSize / 2, addonSize, addonSize, borderRadius); @@ -344,16 +297,16 @@ const RadarChartV2 = forwardRef((props, ref) // 添加渐变背景色 ctx.globalAlpha = 0.2; - ctx.fillStyle = "rgba(89, 255, 214, 1)"; // rgba(89, 255, 214, 0.2) + ctx.fillStyle = "rgba(89, 255, 214, 1)"; ctx.fill(); ctx.globalAlpha = 1.0; ctx.strokeStyle = "#FFFFFF"; - ctx.lineWidth = 2; + ctx.lineWidth = 1.93; // 设计稿 strokeWeight ctx.stroke(); // 绘制装饰图片 - const docSize = 24; // 48px * 0.5 + const docSize = 26.18; // 设计稿内部图片尺寸 const docRotation = -7 * (Math.PI / 180); // 内部旋转 -7 度 ctx.rotate(docRotation); ctx.drawImage(docCopyImg, -docSize / 2, -docSize / 2, docSize, docSize); @@ -362,27 +315,27 @@ const RadarChartV2 = forwardRef((props, ref) console.error("Failed to load docCopy image:", error); } - currentY += avatarSize + 20; // 头像到底部文字区域的间距 + currentY += 48 + 20; // 头像区域高度 + gap } catch (error) { console.error("Failed to load avatar image:", error); } } - // 绘制文字区域 - 完全参考 Result 组件样式,居中对齐 - const textCenterX = width / 2; - const textGap = 6; // gap: 6px + // 绘制文字区域 - 根据设计稿样式,居中对齐 + const textCenterX = sidePadding + 294 / 2; // Frame 1912055062 的中心 + const textGap = 6; // 设计稿 gap: 6px - // 绘制标题 - 14px, font-weight: 300 + // 绘制标题 - 14px, font-weight: 300, line-height: 1.2 if (options.title) { ctx.fillStyle = "#000000"; - ctx.font = "300 14px PingFang SC, sans-serif"; + ctx.font = "300 14px Noto Sans SC, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "top"; ctx.fillText(options.title, textCenterX, currentY); - currentY += 14 + textGap; // 行高 + gap + currentY += 14 * 1.2 + textGap; // line-height: 1.2 + gap } - // 绘制 NTRP 等级 - 36px, font-weight: 900, "NTRP" 黑色,等级数字 #00e5ad, gap: 8px + // 绘制 NTRP 等级 - 36px, font-weight: 900, "NTRP" 黑色,等级数字 #00E5AD if (options.ntrpLevel) { ctx.font = "900 36px Noto Sans SC, sans-serif"; ctx.textBaseline = "top"; @@ -391,7 +344,7 @@ const RadarChartV2 = forwardRef((props, ref) const levelText = formatNtrpDisplay(options.ntrpLevel); const ntrpWidth = ctx.measureText(ntrpText).width; const levelWidth = ctx.measureText(levelText).width; - const totalWidth = ntrpWidth + 8 + levelWidth; // gap: 8px + const totalWidth = ntrpWidth + levelWidth; // 设计稿中紧挨着 const startX = textCenterX - totalWidth / 2; // 绘制 "NTRP"(黑色) @@ -399,59 +352,58 @@ const RadarChartV2 = forwardRef((props, ref) ctx.textAlign = "left"; ctx.fillText(ntrpText, startX, currentY); - // 绘制等级数字(#00e5ad) + // 绘制等级数字(#00E5AD) ctx.fillStyle = "#00E5AD"; - ctx.fillText(levelText, startX + ntrpWidth + 8, currentY); + ctx.fillText(levelText, startX + ntrpWidth, currentY); - currentY += 44 + textGap; // line-height: 44px + gap + currentY += 36 * 1.222 + textGap; // line-height: 1.222 + gap } - // 绘制描述文本 - 16px, font-weight: 600 + // 绘制描述文本 - 16px, font-weight: 600, line-height: 1.4 if (options.levelDescription) { ctx.fillStyle = "#000000"; ctx.font = "600 16px PingFang SC, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "top"; ctx.fillText(options.levelDescription, textCenterX, currentY); - currentY += 16 + 40; // 行高 + 到雷达图的间距 + // 计算到雷达图的间距:雷达图 y: 252 - 当前 Y + const radarY = 252; // 设计稿雷达图位置 + currentY = radarY; // 直接设置到雷达图位置 } - // 绘制雷达图 - 根据设计稿调整尺寸和位置 - try { - const radarImg = await loadImage(canvas, radarImageUrl); - // 计算雷达图尺寸,根据设计稿调整 - const radarMaxSize = width - sidePadding * 2; - const radarActualSize = Math.min(radarMaxSize, 670); // 设计稿雷达图尺寸 - const radarX = (width - radarActualSize) / 2; // 居中 - const radarY = currentY; - ctx.drawImage(radarImg, radarX, radarY, radarActualSize, radarActualSize); - currentY += radarActualSize + 60; // 雷达图到底部的间距 - } catch (error) { - console.error("Failed to load radar image:", error); - } + // 直接绘制雷达图 - 根据设计稿调整尺寸和位置 + const radarSize = 200; // 固定雷达图尺寸为 200*200 + const radarX = 75; // 设计稿雷达图 x 位置 + const radarY = currentY; + // 直接在 exportCanvasV2 中绘制雷达图 + drawRadarChart(ctx, radarX, radarY, radarSize); + currentY += radarSize; // 雷达图高度 - // 绘制底部区域 - 根据设计稿调整 - const qrSize = 100; // 设计稿二维码尺寸 + // 绘制底部区域 - 根据设计稿调整(600px 高度) + const bottomAreaY = 527; // 设计稿底部区域 y 位置 + + const qrSize = 64; // 设计稿二维码尺寸:64*64 + const qrX = 276; // 设计稿二维码 x 位置 + const qrY = 523; // 设计稿二维码 y 位置 + const bottomTextContent = options.bottomText || "长按识别二维码,快来加入,有你就有场!"; - // 计算底部区域布局 - 从底部向上计算 - const bottomTextHeight = 28; // 底部文字高度 - const bottomTextLineHeight = 28; // 行高 - - // 先绘制底部文字,确定文字的实际高度 + // 绘制底部文字 - 设计稿:fontSize: 12, fontWeight: 400, line-height: 1.5 ctx.fillStyle = "rgba(0, 0, 0, 0.45)"; - ctx.font = "400 20px sans-serif"; // 设计稿字体大小 + ctx.font = "400 12px Noto Sans SC, sans-serif"; ctx.textAlign = "left"; - ctx.textBaseline = "bottom"; + ctx.textBaseline = "top"; - const textX = sidePadding; - const textY = height - bottomPadding; - const maxTextWidth = width - sidePadding * 2 - qrSize - 20; // 预留二维码和间距 + const textX = 20; // 设计稿文字 x 位置 + const textY = bottomAreaY; + const maxTextWidth = qrX - textX - 10; // 到二维码的间距 // 计算文字行数 const bottomWords = bottomTextContent.split(""); let bottomLine = ""; - let textLines = []; + let textLines: string[] = []; + const lineHeight = 12 * 1.5; // fontSize * line-height + for (let i = 0; i < bottomWords.length; i++) { const testBottomLine = bottomLine + bottomWords[i]; const metrics = ctx.measureText(testBottomLine); @@ -466,44 +418,26 @@ const RadarChartV2 = forwardRef((props, ref) textLines.push(bottomLine); } - const actualTextHeight = textLines.length * bottomTextLineHeight; - // 绘制底部文字 textLines.forEach((lineText, index) => { - ctx.fillText(lineText, textX, textY - (textLines.length - 1 - index) * bottomTextLineHeight); + ctx.fillText(lineText, textX, textY + index * lineHeight); }); - // 绘制底部 SVG logo 图片 - 在文字上方 - const logoSvgY = height - bottomPadding - actualTextHeight - 40; // 在文字上方,间距40px + // 绘制顶部标题和图标 - 设计稿 Frame 2036081426 + const topTitleY = bottomAreaY - 4; // gap: 4px + const topTitleX = textX; + // 绘制图标(28*28) try { - const logoSvgPath = shareLogoSvg; - // 先获取图片的实际尺寸,保持宽高比 - const logoInfo = await getImageInfo(logoSvgPath); - const logoSvgImg = await loadImage(canvas, logoSvgPath); - - // 根据设计稿,目标宽度为 235px,按比例计算高度 - const targetWidth = 235; - const aspectRatio = logoInfo.width / logoInfo.height; - const targetHeight = targetWidth / aspectRatio; - - const logoSvgX = sidePadding; // 左边对齐 - - // 按实际宽高比绘制,避免拉伸 - ctx.drawImage( - logoSvgImg, - logoSvgX, - logoSvgY, - targetWidth, - targetHeight - ); + const iconSize = 28; + const iconImg = await loadImage(canvas, shareLogoSvg); + ctx.drawImage(iconImg, topTitleX, topTitleY - iconSize - 4, 235,iconSize); } catch (error) { - console.error("Failed to load logo SVG:", error); + console.error("Failed to load icon:", error); } - // 绘制二维码 - 右下角,与文字底部对齐 - const qrX = width - sidePadding - qrSize; - const qrY = height - bottomPadding - qrSize; + + // 绘制二维码 - 设计稿位置 if (options.qrCodeUrl) { try { @@ -536,16 +470,11 @@ const RadarChartV2 = forwardRef((props, ref) return ( - {/* 隐藏的导出画布 */} ); diff --git a/src/game_pages/detail/components/EmptyState/index.scss b/src/game_pages/detail/components/EmptyState/index.scss new file mode 100644 index 0000000..54d1b1d --- /dev/null +++ b/src/game_pages/detail/components/EmptyState/index.scss @@ -0,0 +1,113 @@ +.empty_state_page { + width: 100%; + min-height: 100vh; + background: #3f3f3f; + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; + + &__content { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: 0; + position: relative; + min-height: calc(100vh - 98px); + } + + // 背景图片 + &__bg_image { + position: absolute; + top: 178px; + left: 84px; + width: 221.17px; + height: 200px; + background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIyIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIyMiAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMjIiIGhlaWdodD0iMjAwIiBmaWxsPSIjRkZGRkZGIiBmaWxsLW9wYWNpdHk9IjAuMiIvPgo8L3N2Zz4K'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + opacity: 0.2; + z-index: 0; + } + + // 提示文字 + &__tip { + position: absolute; + top: 418px; + left: 50%; + transform: translateX(-50%); + z-index: 1; + padding: 8px 16px; + background: rgba(0, 0, 0, 0.5); + border-radius: 4px; + white-space: nowrap; + + &_text { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 15px; + line-height: 1.6; + color: #ffffff; + text-align: center; + } + } + + // 按钮区域 + &__buttons { + position: absolute; + bottom: 110px; + left: 0; + right: 0; + display: flex; + flex-direction: column; + gap: 10px; + padding: 0 20px; + z-index: 1; + align-items: center; + } + + // 按钮 + &__button { + display: flex; + justify-content: center; + align-items: center; + width: 251px; + height: 52px; + border-radius: 16px; + box-shadow: 0px 8px 64px 0px rgba(0, 0, 0, 0.1), + 0px 4px 36px 0px rgba(0, 0, 0, 0.25); + backdrop-filter: blur(32px); + cursor: pointer; + transition: opacity 0.3s; + + &:active { + opacity: 0.8; + } + + // 主要按钮(去看看其他球局) + &--primary { + background: linear-gradient(154deg, rgba(255, 255, 255, 1) 0%, rgba(234, 234, 234, 1) 100%); + border: 2px solid rgba(0, 0, 0, 0.06); + } + + // 次要按钮(返回首页) + &--secondary { + background: linear-gradient(154deg, rgba(255, 255, 255, 1) 0%, rgba(234, 234, 234, 1) 100%); + border: 2px solid rgba(0, 0, 0, 0.06); + } + + &_text { + font-family: 'DingTalk JinBuTi'; + font-weight: 400; + font-size: 18px; + line-height: 1.11; + letter-spacing: -0.05em; + text-align: center; + color: #000000; + } + } +} + diff --git a/src/game_pages/detail/components/EmptyState/index.tsx b/src/game_pages/detail/components/EmptyState/index.tsx new file mode 100644 index 0000000..82438d2 --- /dev/null +++ b/src/game_pages/detail/components/EmptyState/index.tsx @@ -0,0 +1,95 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import { useGlobalState } from '@/store/global'; +import { GeneralNavbar } from '@/components'; +import './index.scss'; + +interface EmptyStateProps { + onGoToOtherGames?: () => void; + onGoToHome?: () => void; +} + +const EmptyState: React.FC = ({ onGoToOtherGames, onGoToHome }) => { + const { statusNavbarHeightInfo } = useGlobalState() || {}; + const { totalHeight = 98 } = statusNavbarHeightInfo || {}; + const [countdown, setCountdown] = useState(5); + + // 倒计时自动返回 + useEffect(() => { + if (countdown <= 0) { + handle_go_to_home(); + return; + } + + const timer = setTimeout(() => { + setCountdown(countdown - 1); + }, 1000); + + return () => clearTimeout(timer); + }, [countdown]); + + // 跳转到其他球局 + const handle_go_to_other_games = () => { + if (onGoToOtherGames) { + onGoToOtherGames(); + } else { + Taro.switchTab({ + url: '/main_pages/index', + }); + } + }; + + // 返回首页 + const handle_go_to_home = () => { + if (onGoToHome) { + onGoToHome(); + } else { + const pages = Taro.getCurrentPages(); + if (pages.length <= 1) { + Taro.switchTab({ + url: '/main_pages/index', + }); + } else { + Taro.navigateBack(); + } + } + }; + + return ( + + + + + {/* 背景图片 */} + + + {/* 提示文字 */} + + + 页面将在 {countdown}s 后自动返回球局首页 + + + + {/* 按钮区域 */} + + + 去看看其他球局 + + + 返回首页 + + + + + ); +}; + +export default EmptyState; + diff --git a/src/game_pages/detail/index.tsx b/src/game_pages/detail/index.tsx index 7525f48..04be311 100644 --- a/src/game_pages/detail/index.tsx +++ b/src/game_pages/detail/index.tsx @@ -22,6 +22,7 @@ import Participants from "./components/Participants"; import SupplementalNotes from "./components/SupplementalNotes"; import OrganizerInfo from "./components/OrganizerInfo"; import SharePopup from "./components/SharePopup"; +import EmptyState from "./components/EmptyState"; import { navto, toast } from "@/utils/helper"; import ArrowLeft from "@/static/detail/icon-arrow-left.svg"; // import Logo from "@/static/detail/icon-logo-go.svg"; @@ -29,6 +30,7 @@ import styles from "./index.module.scss"; function Index() { const [detail, setDetail] = useState({}); + const [showEmptyState, setShowEmptyState] = useState(false); const { params } = useRouter(); const [currentLocation, setCurrentLocation] = useState<[number, number]>([ 0, 0, @@ -95,10 +97,11 @@ function Index() { if (res.code === 0) { setDetail(res.data); fetchUserInfoById(res.data.publisher_id); + setShowEmptyState(false); } } catch (e) { if (e.message === "球局不存在") { - handleBack(); + setShowEmptyState(true); } } Taro.hideLoading(); @@ -172,6 +175,20 @@ function Index() { setGlass(top > 20); }, 16); + // 如果显示空状态,渲染空状态页面 + if (showEmptyState) { + return ( + { + Taro.switchTab({ + url: '/main_pages/index', + }); + }} + onGoToHome={handleBack} + /> + ); + } + return ( - {/* 隐藏的 RadarV2 用于生成完整图片,不显示在界面上 */} guX`>iwvi73c~na1aJ=p)doJ{u;b(`3QI>nmEQ}~Qxze}VQL&6R*#7@GvBBdw|Oc6n+kxN&bdEeCXe^QvEUK%-~ za2kfGvsBe}I6_0+HCk1;<~qsQ4K#@SH&7klqafvwutN;7gV;<~Ch9H75+%n{9txh3 zp3n?$f+TB!By1Ydi~-{~t}pguurXJ2Wfj3(i>Ft%D0?@e?>yCKnKs`14}<@2PZT=GdY>^-(ULqcUvv<`zgKpmv>CPv)EZKlzSv2OJt)qFzuyWBB^n!kp8Vi z(!DU`xYRXvG)ZZSe}**tHDO}OPve3xD9FZ;rw&wpc9w>+@f|egSklFVb8}NUE)hcIe;4cV8=awv>L!l!t+j? zM)y^X>zPAufAGK_z|MmgirFyE?R&N3d3>V28s_PD^4bU{orUiGqw@4>R~-C)UL3Ed zM#2ke5rd;l;h`|j-(L#je4RtIV05As%Ue!({$f~`^YA$IvyiL#Iv;D(75M2F&)!d6 zd|cDsyV=l*Dq4$(<&m6o>Dqu!%Bs*kG#%aWocc>|e}C}pzdCr^pB)u}K^c;DipZL& zXQiH<6cBrfVEgHMlhi~W22oZgqUs*0y<&|vZMp;wI*H!m9#B} z*K$H#k0FqnOh6us1#CV4jw6z?iFkkQYquNC{2QKc=A_?*CP)Dl4fujKO~@zf`z?xl z@O-96Z9S=6D;l*P*_EGGFk`- zf1v7)_xCf{Ja+1tl5kw-V__Og(1kAV;`9TENK-hr;%9ou|-)gkc+B z4CBRanRFko>DovxW8994^N&cS$d{z8lln^=E2Iuf;kVBrN!E;8{^stbfHTVjx_xZnf5Bky35Eo%NVOwP*~r< z%u*c_iyp?wyqsAyV4P25+{;iMSzuopSWD3ZS`ZFvMAdEzv5laURX7=YcC6!Bx4;8@ zD6H#PtP}bEXn4=pcMz$6^9Uy6^;G$a*jlCM$@@?k9_x5^B=)SEdZzk>^OR%hf4yut z&d+@@hlm6gg(tg|c@9C}#sm8pUg^kJ2!oP%98O~Mg35$oLd_%4G&&_&CA=?(?K>I1 zf1%@D55~TyvkevogIXYVMC1CXQoK*o+%LOw5u@oZ5pnN@<2dj=a_#seU44$bAVZbn zL|os8N%C&J`QI4J+>!Ad_Yb)qf1^#Jr-<8=ZcY=%G;w=|=uS>0Qh!aUTR@he^tw@Vb1D z9bS2I#3$7ubf095Ic!Zf3z0J*4!(bUhTA zvDNq5u`9n*edBB&Wfl;re_NGFLZRcH9XQ2UJvM^z?39Y-*gJU+BxwNL?-rtV#=#5m z^N1wsDQpkVVe@<|BUluZNXzXkVza8fl2oY&@jDtEINne=JVJ$fH@y{_^3Mpa*L3^pp>`krqPRP5E|9Au^=?Hi)cE zQiSV7#Hye1)-h7sKz^fv{E~;U3mV2&)5z6Gf;!An3K~hzG|6+9gF|U`65AU*yye%CSe}Ie}uFU4D?ou!cJfp zt_@ha*xa;@v;l*c=&#%BV$ph?2lB@#tIun+ZU=nRD zimcZ@LPp;={eZu0e}((3whwKF9m!*wL|zK1kz{pgyQWDV;)cw)HF7}JiI`1fmwZfI z)G@lAh0!E}I#dUfp8UmavbKdM|8d$%IW>`pO7}Om!t^AvS_p_>#nO1-PbTDLvLs1c zJM6Umh{!r0J}ZT3#qj=QtPVT&-d@C0wokmx-K0Pg7Gm!zf8pAZRZXI}gk+SlHMs1d zKFYj`NV7QFkw0l{SnBoisw!32Mt6-xr6+l-SySP$D24CZu4z*aulx;hEumB5s&@2+ zhT~1AN$x?bqf+~OQiTs9pScTrMvmgM)jvThO;Xg=+7i`iNi-WI>gc9kmss}CkV-rv zU6~#a$odVEf0Wj+om$i_{x^b-b&M}w5;wA*ZlT*JZ0vc%-Fj@)_tsokyJG( z2(nFmldf9ZN^X-aWmNY(aP}Z7Cq@nKQK0 z^OZF{e{eUEX)8TnCoi%ISEgtqDC@e&u1kz==*TS7zH8keqB0S46v&wbifep^$p|-A z`7i_#R1%4Ga(h)N{Xj1jSWS9mGXar9VkclBnpE?Mq=Ajip1xj^l_x?8oA-a`F<2X( zL8J=Lp|_WDr(h$P+=ED)GWzoe#O=s=boU(de}Y&{sBacqtE>g|uhw#Vwrj8Uf^v$f zMWTcOwxNiur#!cHk0cMTX}Tn7&*OtnU&MhuPhw{55iHpM29vzQk_g+jS^Yr5wur2} zV=DZ{;?}`^Vb>dx7J7uLbm;SavKg z=Q}+>yXIr@q_N^YQj1d=R}$7n_XW%9f5?}FN7AMYf1p&my>R@TfOjXh^IyzACg z(`o%Jd4G?khz9ir+7ffc`Df(KuOekkkhOU)rN7xGs~b9)T12@9g;d4_5&hp;&_!p1 z_taLg;eH@|vk5*1!CR&6bHARBRMSMdnIXbb+$B=-0boJS3qe;o8f?`tS^1Q3e_IVE z`&3fH#TseBL{g8WYsu@;8A|<#gI8TEA#X3$lZjl6<(rE=vN%L5j{5E-*Ux{O>oM9nB)w<$M8p5&h98TD@fh*# z0B_Thbk(8xDI#nZW_=8X!`m_=sMj(0 zp@_~--wNuSXB?HV0~tI)R%3bQ5s=dg?`>Q^lrWeIz1??|!#ERWCsoAGzMkunkNck` zy>cE}VVmX=EP8#bl6_z<_D)_9XJhF$qJ>#q`#~`btR2Mt8Lwb-+h=2WEhH@ZZqs|B z;p1&cZ5M5$k}53V))z66e_e$xa;VibQfTP`Z<&x+SQ8nTBz1*I|K3xN|KvliL%Uqt zog{51F1_ogynhkWuT#*a^h~hBNpnY`d4=|=CMo&A#0(Xsz*or!DW<`P7D3P30h5$Q zjwgSSnspZ9do2u(GNJWS~567>=`Q$^)m&xRt)!;QLoP%3n{EaP3iw#f#{EB54pPX{i=OwdmPlh&u}vf9$Z#JhuKd%XaXcSp0a{ zhUXADf{r2D59I~rD53)!orw35GM&oU>{#Uc=W(U#e-b_LTaSGHvL{Er;kWWO9}vu% zQ;=>87nVPy4bCh{*j>nFW-vCoze?9XI{T9co{pCnmt(R-#9h?!<6q0`@BiGx!W}u{ zjp0T)*{osWf6^MJK6Ws9o*Yc;6EJ)-wte1kXOA#%*@&<%mgDRN=7R_{AWQ!WQSvbu zl)5~R%{GlXrJ`V?|6j^HifGG(JD*oqwu#~cz^cqS46$9PpEN3zkW9+099{~8*sN-N z5$08MMJ>#)#?n-JDOUavBF(H2zPpTI{#C;Ii(#Gxe?;x)yzd|~rS3r=i+-t8(r%jl zr?1~!xcjcATYKXC(tCHAhFLd^^cmAg{lN2$7oL3N_4A$OVq|5<&smr)Ju-2nI z_(%H4Zy~aY87x^l$Mw;$ZR$79l`d`N=cmyhECdU|w!iu9w-1`Hzy63vB2$5DNuWot-&d3;=q?VYibbAZO zO>p`5J!GeN7lC~%5RZEpVu&HG8AR77ZPJ$bxfX?05&WWNl8H&zlEk(DQTY%<3^BwI dL;URE{{hQ;sxNz{%4Glm002ovPDHLkV1hr+Zr}g_ delta 2970 zcmV;L3uW~EC9fADiBL{Q4GJ0x0000DNk~Le0002X0000J2nGNE06v!aagiZ3e+xuO zL_t(|0qt8`Y#dh^{$?*;+i?;nO^K=`%5V|op&}b9QPP4kAzT^>6i0}nDxhqNA|XJH z^TY#4UQ4J*2$WPr2qY?RxQR_|xP3Gh}ob#XCe}A8~f(>loJ&G6f&)=8HG#+%4&Ojn%zmc-GT>Hqq zm&#%7m5}Gdtcd8``%fsK{kh4))hS32i_*B$@Xno- z?1_+mbk>5YsgK6X41ZSOeaBhJhtN>rty71)4&tBvXv0S@%^&%GdSL0hO2R=xvEVos z;Fxf1gVjx#rUS$1tE4en#T;MyB?rD$e}wFr8@DC%Ii6} zJf(*8qrIAiD-=QV*~jbT*=$8HU&sPMdz|ItkzyVXXpgytkzup`f64QP^Y`N!39}_moe4@EHtJBA`EZaLtknMfo0;ohdF0?nfB!8(BgF^FAsiZ_p(C2niAf7S zda87P4r?PQgo9|EKQW=s>oTM0(j0P#2c6T~*QO7x&#xu@^O7;jX&*voE=@a-P_LB` z()!5KkT){S?@8&A0mzFo)b z2Fc$Xl1iBg1{0Q~30Nkqgw3w3FlURbO~rT__Y8ggn}59W!pmPiaPucbBgGdY528x- z(7^-RXuR*RL?;LLfF}9#JX+}!V?2%Z10CQYtQ|`5w4N$GeN_s2V+O(NpuYPhT7(i`M-lPeY*}E) zjXa8QcZ{IHQ#^EC5vRTK&fx~Fm?x!x!TE9BBUoVlcOz}cc;cb$bbQE-{PNp~)un3|@bD4n0Kt!81 z;wm~9&ecFe``}TI<8tuaG9BT}D?sZJd#1wGv_hL64H;Kh9?@n}>Yqh$(0UfZ+O{aw ziD7Avb8wkl&=m2205SI*zZMa`HyS*qEub|XQu#Rae_TiRP^J1Y^eB!^^jS|P8xCXs zSFnB4VZ6TZ0BW^qWcp1eEH-S1iHj`+B>{z5nPk#Nx%MJ@j0HrO3m61V&liKKG4zX_QaVlDJ@wz_MtOe^^HeD zC7)>ye=B`@+7}VaX$W@@<+#=_Y;?Y3{5p#6;pYc(t;q9G1By7$N$FXjJ8xUoBTAwM z!@k0VAcZ~Kp2Xtf4pgg2n2iK1i&+wr<-@Eb>U9T=hSGG=7OOY0ZRmd1)@jD1vasaQ zK0b-U89rg%w}^4X*ARCj-sRvP23-sAvUPD0e}^G@4OObg-2+*u^pG|=c_deUM?{VxZ z3*`TUcgmCKOK!#9ZF#)))&O(KQm9lDs2T|vHD-MbFd?$tdTo(eoCaWS%96L1;xzME zf2nbaxq<>hdY%ngRLOULi`Pkz=n%*?&^cXkkVSaR8^uumjGcyXHzf#Dd`voATI)&l z9xA+j)X*xF#fUx{pO>6fB3ujUBQtk_UrWB{0y^3!3yN@gpvY;c0W>HcIS9{Xm*1U) zmo6sdsG&RL__g4BJ{K^ifQkrI)3uCMf2|>%t}$y-#X|Mh*wVirgPHqr&%h}x-2EvN zJ*U~0VwE}xi=m)aW$`|XVeT%y#6tPJ@RN>(>dQef8ml9 z5S$L~1Q#S)?VzJIp%jR6#e6$u*;U&y29hjY>?k$dc=HGN@XjCM-p!xE?S;RwjM(+& z_Z4P6mLvC1+(=DO*UwlP|ClBvXWzBOAtwDbj{O%m} z)zR5po|b5wz}hDi2*kvlq{eA}f25?wW3)Xj)eF9NI=_neFp{Zy0HQA^ae4_2o(EBc z8~p3ixS@D>&y{M;F#b_lHW~9&^ zyB5Z_92V2mndMRbNwiXYaFLl3t^gsAV6#p^V;1oiILGOaNHpoW%I}H3f3RM9G`Z&d zge#yUcy3XkwTWOh4_BMVb>ZTo2-oEqgQtx(Y3v{r;4!)m&vk7F+S8oW&+k4J;kkHp zUb9%~junDQx1@isB7C2&1DTvbUq((ufkKxdQj)Pi{@WbN&7HmxX9Hq z3>&3|DGa4QhW_+Un3dfyf75!?s?hs}P1Xf0W=XK1zu`1&tC=JbF-w78HEE1II)jE* zpIclTeU5=ZD)I|h}|FU#mo@Kv<_OeG)Ejx_Kryll}l zZ!0{AlM(dAxshdzW98(S@|HwrHK6k$t9Z=!+^>8zxciYufOk|if6enL!}$iY5Fbb~ zU&^4E#8}4v=4Y{e^9Na$YZpq3uQl6}JCXc~-W;)d*xVr}#7BV^dv+r@esQyF^itW0$pp98(bke{}nU#URKoiTLnHsO?AdI&8)!_!(x>1zCb*!PYg1Sgb-9{cp$Gu2A+ z@me+0s2dqJlRlVrlL;mG$eSm}0z&f(jirb5Ck<9?)8>}c#(!8ehG1Y?vkK4W zmiu=C9j>(?>u`qOi%g%4Qa~Tlzy2%H+Z{p`;sKlD;c>3b%re`FpS9^9kU>t$4o68bY+kxurb zzkjQfP7Ym6^d-LiwMYN_Mie~_E)!GnYpDt89hFYLglljGNtpkvk5(_W?t2kJ`G`V$ zt)hFV=}zI%C|bDYjBI>65_X*(`I|Gh_l zdFjZraKCu&Q