From 05b941613c3d2fcc6a983b57e0539c44ec6c23e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=9D=B0?= Date: Mon, 6 Oct 2025 14:52:31 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20ntrp=E9=80=BB=E8=BE=91=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=20&=20=E6=9B=BF=E6=8D=A2=E6=B5=B7=E6=8A=A5=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.ts | 6 + src/components/Comments/index.module.scss | 80 +++++-- src/components/Comments/index.tsx | 85 ++++++- src/components/NTRPEvaluatePopup/index.tsx | 11 - src/components/Poster/index.tsx | 2 +- src/components/Radar/index.tsx | 69 +++--- src/game_pages/detail/index.tsx | 254 +++++++++++---------- src/other_pages/ntrp-evaluate/index.tsx | 53 ++++- src/services/commentServices.ts | 5 + src/services/detailService.ts | 12 + src/utils/genPoster.ts | 60 ++++- 11 files changed, 430 insertions(+), 207 deletions(-) diff --git a/src/app.ts b/src/app.ts index be50fa9..713742a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,5 @@ import { Component, ReactNode } from "react"; +import Taro from "@tarojs/taro"; import "./nutui-theme.scss"; import "./app.scss"; import "./scss/qweather-icons/qweather-icons.css"; @@ -19,6 +20,11 @@ class App extends Component { console.log('launch options: ', options) console.log("小程序启动,初始化逻辑写这里"); clearSpecStorage() + Taro.onNeedPrivacyAuthorization((resolve) => { + // 这里可以自定义弹窗(或直接默认同意) + resolve({ event: 'agree' }); // 同意隐私协议 + }); + } componentDidMount() { diff --git a/src/components/Comments/index.module.scss b/src/components/Comments/index.module.scss index 62fc1ff..8859bc1 100644 --- a/src/components/Comments/index.module.scss +++ b/src/components/Comments/index.module.scss @@ -3,10 +3,10 @@ padding: 20px 20px 0; .commentCount { - border-bottom: 1px solid rgba(255, 255, 255, 0.10); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding-bottom: 8px; - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: "liga" off, "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 16px; @@ -24,7 +24,7 @@ align-items: center; gap: 6px; border-radius: 12px; - background: rgba(255, 255, 255, 0.20); + background: rgba(255, 255, 255, 0.2); .addCommentImage { width: 18px; @@ -33,7 +33,7 @@ .addCommentText { color: rgba(255, 255, 255, 0.65); - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: "liga" off, "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 14px; @@ -55,6 +55,45 @@ align-items: flex-start; justify-content: space-between; gap: 8px; + position: relative; + + &.blink::after { + content: ""; + --base: transparent; + --alert: rgba(255, 255, 255, 0.2); + --dur: 2000ms; + + position: absolute; + width: 100%; + height: 100%; + padding: 10px; + left: -10px; + top: -10px; + background: var(--base); + // border: 1px solid #ccc; + border-radius: 6px; + cursor: pointer; + animation: bg-blink var(--dur) infinite steps(10); + } + + @keyframes bg-blink { + 0% { + background: var(--base); + } + 50% { + background: var(--alert); + } + 100% { + background: var(--base); + } + } + + /* 无障碍:用户偏好减少动画则关闭 */ + @media (prefers-reduced-motion: reduce) { + .blink { + animation: none; + } + } .avatar { border-radius: 50%; @@ -83,7 +122,7 @@ .nickname { color: rgba(255, 255, 255, 0.65); - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: "liga" off, "clig" off; font-family: "PingFang SC"; font-size: 12px; font-style: normal; @@ -95,9 +134,9 @@ padding: 0 4px; height: 18px; border-radius: 3px; - background: rgba(255, 255, 255, 0.10); + background: rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.65); - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: "liga" off, "clig" off; font-family: "PingFang SC"; font-size: 10px; font-style: normal; @@ -107,8 +146,8 @@ } .content { - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: "liga" off, "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -126,19 +165,24 @@ justify-content: flex-start; gap: 6px; - .time, .location, .reply, .delete { - font-feature-settings: 'liga' off, 'clig' off; + .time, + .location, + .reply, + .delete { + font-feature-settings: "liga" off, "clig" off; font-family: "PingFang SC"; font-size: 12px; font-style: normal; line-height: 18px; - &.time, &.location { + &.time, + &.location { color: rgba(255, 255, 255, 0.45); font-weight: 400; } - &.reply, &.delete { + &.reply, + &.delete { color: rgba(255, 255, 255, 0.85); font-weight: 600; } @@ -147,8 +191,8 @@ } .viewMore { - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: "liga" off, "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -204,11 +248,11 @@ .emptyTip { color: rgba(255, 255, 255, 0.85); - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: "liga" off, "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; font-weight: 500; line-height: 24px; } -} \ No newline at end of file +} diff --git a/src/components/Comments/index.tsx b/src/components/Comments/index.tsx index e6706f5..8f2f962 100644 --- a/src/components/Comments/index.tsx +++ b/src/components/Comments/index.tsx @@ -8,7 +8,9 @@ import React, { import { View, Text, Image, Input } from "@tarojs/components"; import Taro from "@tarojs/taro"; import dayjs from "dayjs"; +import classnames from "classnames"; import CommentServices from "@/services/commentServices"; +import { delay } from "@/utils"; import type { BaseComment, Comment, @@ -133,6 +135,7 @@ function CommentItem(props: { level: number; publisher_id: number; comment: Comment | ReplyComment; + blink_id: number | undefined; loadMore: (c: Comment) => void; handleReply: (options: CommentInputReplyParamsType) => void; handleDelete: (options: { parent_id: number | null; id: number }) => void; @@ -144,12 +147,20 @@ function CommentItem(props: { loadMore: handleLoadMore, handleReply, handleDelete, + blink_id, } = props; const currentUserInfo = useUserInfo(); const isGamePublisher = publisher_id === comment.user.id; const isCommentPublisher = currentUserInfo.id === comment.user.id; return ( - + ( ([]); const inputRef = useRef(null); + const [blink_id, setBlinkId] = useState(); - const commentCountUpdateRef = useRef() + const commentCountUpdateRef = useRef(); useEffect(() => { - getComments(1); + init(); }, [game_id]); + async function init() { + if (!game_id) return; + await getComments(1); + if (message_id) { + scrollToComment(); + } + } + + async function scrollToComment() { + const res = await CommentServices.getCommentDetail({ + comment_id: message_id as number, + }); + if (res.code === 0) { + // 判断当前评论是否渲染到页面上,有的话直接跳转,没有的话先插入再跳转 + const query = Taro.createSelectorQuery(); + query + .select(`#comment_id_${res.data.id}`) + .boundingClientRect() // 或 .fields({id:true}) 根据需求 + .exec(async (resArr) => { + // resArr 是数组,长度为选择器数量 + const nodeInfo = resArr[0]; + if (!nodeInfo) { + // 节点不存在,执行插入逻辑 + const parent_id = res.data.parent_id; + if (parent_id) { + setComments((prev) => { + return prev.map((item) => { + if (item.id !== parent_id) return item; + return { + ...item, + replies: [res.data, ...item.replies], + }; + }); + }); + } + } + await delay(100); + Taro.pageScrollTo({ + selector: `#comment_id_${res.data.id}`, + duration: 300, + }); + setBlinkId(res.data.id); + setTimeout(() => { + setBlinkId(undefined); + }, 3300); + }); + } + } + useImperativeHandle(ref, () => ({ addComment: handleReply, getCommentCount: (onUpdate) => { - commentCountUpdateRef.current = onUpdate - onUpdate(comments.length) + commentCountUpdateRef.current = onUpdate; + onUpdate(comments.length); }, })); @@ -275,7 +337,7 @@ export default forwardRef(function Comments( setComments((prev) => { const res = [...prev]; res.splice(page * PAGESIZE - 1, newComments.length, ...newComments); - commentCountUpdateRef.current?.(res.length) + commentCountUpdateRef.current?.(res.length); return res; }); } @@ -303,7 +365,7 @@ export default forwardRef(function Comments( item.reply_count = res.data.count; } }); - commentCountUpdateRef.current?.(newComments.length) + commentCountUpdateRef.current?.(newComments.length); return newComments; }); } @@ -325,7 +387,7 @@ export default forwardRef(function Comments( const res = await CommentServices.createComment({ game_id, content: val }); if (res.code === 0) { setComments((prev) => { - commentCountUpdateRef.current?.(prev.length + 1) + commentCountUpdateRef.current?.(prev.length + 1); return [{ ...res.data, replies: [] }, ...prev]; }); toast("发布成功"); @@ -375,7 +437,7 @@ export default forwardRef(function Comments( }); } else { setComments((prev) => { - commentCountUpdateRef.current?.(prev.length - 1) + commentCountUpdateRef.current?.(prev.length - 1); return prev.filter((item) => item.id !== id); }); } @@ -400,6 +462,7 @@ export default forwardRef(function Comments( return ( { - - { - console.log(val); - setNtrp(val.values); - }} - /> - )} diff --git a/src/components/Poster/index.tsx b/src/components/Poster/index.tsx index be0db94..506f070 100644 --- a/src/components/Poster/index.tsx +++ b/src/components/Poster/index.tsx @@ -72,7 +72,7 @@ const Poster = (props, ref) => { .exec(async (res) => { const canvas = res[0].node; const ctx = canvas.getContext("2d"); - const dpr = Taro.getSystemInfoSync().pixelRatio; + const dpr = Taro.getWindowInfo().pixelRatio; const width = 600; // px const height = 1000; diff --git a/src/components/Radar/index.tsx b/src/components/Radar/index.tsx index 00d2556..a03702c 100644 --- a/src/components/Radar/index.tsx +++ b/src/components/Radar/index.tsx @@ -3,9 +3,9 @@ import { View, Canvas, Button } from "@tarojs/components"; import { useEffect, useRef, forwardRef, useImperativeHandle } from "react"; const RadarChart: React.FC = forwardRef((props, ref) => { - const { data } = props + const { data } = props; - const renderFnRef = useRef() + const renderFnRef = useRef(); // const labels = [ // "正手球质", // "正手控制", @@ -25,22 +25,25 @@ const RadarChart: React.FC = forwardRef((props, ref) => { 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: [] }) - renderFnRef.current && renderFnRef.current(texts, vals) + const { texts, vals } = data.reduce( + (res, item) => { + const [text, val] = item; + return { + texts: [...res.texts, text], + vals: [...res.vals, val], + }; + }, + { texts: [], vals: [] } + ); + renderFnRef.current && renderFnRef.current(texts, vals); } - }, [data]) + }, [data]); useReady(() => { - renderFnRef.current = renderCanvas + renderFnRef.current = renderCanvas; }); - function renderCanvas (labels, values) { + function renderCanvas(labels, values) { const query = Taro.createSelectorQuery(); query .select("#radarCanvas") @@ -48,7 +51,7 @@ const RadarChart: React.FC = forwardRef((props, ref) => { .exec((res) => { const canvas = res[0].node as HTMLCanvasElement; const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; - const dpr = Taro.getSystemInfoSync().pixelRatio; + const dpr = Taro.getWindowInfo().pixelRatio; canvas.width = res[0].width * dpr; canvas.height = res[0].height * dpr; ctx.scale(dpr, dpr); @@ -88,7 +91,10 @@ const RadarChart: React.FC = forwardRef((props, ref) => { ctx.fillStyle = "#333"; ctx.textBaseline = "middle"; - if (Math.abs(angle) < 0.01 || Math.abs(Math.abs(angle) - Math.PI) < 0.01) { + 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"; @@ -119,22 +125,23 @@ const RadarChart: React.FC = forwardRef((props, ref) => { } 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), - }) - }) - }) - })) - + 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), + }); + }); + }), + })); // 保存为图片 const saveImage = () => { diff --git a/src/game_pages/detail/index.tsx b/src/game_pages/detail/index.tsx index 401475b..a40c358 100644 --- a/src/game_pages/detail/index.tsx +++ b/src/game_pages/detail/index.tsx @@ -24,7 +24,12 @@ import { Comments, Poster, } from "@/components"; -import { generateShareImage, generatePosterImage } from "@/utils"; +import { + generateShareImage, + generatePosterImage, + base64ToTempFilePath, + delay, +} from "@/utils"; import DetailService, { MATCH_STATUS, IsSubstituteSupported, @@ -179,130 +184,130 @@ function Coursel(props) { ); } -const PosterPopup = forwardRef((props, ref) => { - const [visible, setVisible] = useState(false); - const [posterData, setPosterData] = useState(); - const posterRef = useRef(); - useImperativeHandle(ref, () => ({ - show: (detail, user) => { - setVisible(true); - const { - play_type, - skill_level_max, - skill_level_min, - image_list, - title, - start_time, - end_time, - location_name, - } = detail; - const { avatar_url, nickname } = user; - const startTime = dayjs(start_time); - const endTime = dayjs(end_time); - const dayofWeek = DayOfWeekMap.get(startTime.day()); - const gameLength = `${endTime.diff(startTime, "hour")}小时`; - setPosterData({ - playType: play_type, - ntrp: `NTRP ${genNTRPRequirementText( - skill_level_min, - skill_level_max - )}`, - mainCoursal: - image_list[0] || - "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png", - nickname, - avatarUrl: avatar_url, - title, - locationName: location_name, - date: `${startTime.format("M月D日")} (${dayofWeek})`, - time: `${startTime.format("ah")}点 ${gameLength}`, - }); - }, - })); +// const PosterPopup = forwardRef((props, ref) => { +// const [visible, setVisible] = useState(false); +// const [posterData, setPosterData] = useState(); +// const posterRef = useRef(); +// useImperativeHandle(ref, () => ({ +// show: (detail, user) => { +// setVisible(true); +// const { +// play_type, +// skill_level_max, +// skill_level_min, +// image_list, +// title, +// start_time, +// end_time, +// location_name, +// } = detail; +// const { avatar_url, nickname } = user; +// const startTime = dayjs(start_time); +// const endTime = dayjs(end_time); +// const dayofWeek = DayOfWeekMap.get(startTime.day()); +// const gameLength = `${endTime.diff(startTime, "hour")}小时`; +// setPosterData({ +// playType: play_type, +// ntrp: `NTRP ${genNTRPRequirementText( +// skill_level_min, +// skill_level_max +// )}`, +// mainCoursal: +// image_list[0] || +// "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png", +// nickname, +// avatarUrl: avatar_url, +// title, +// locationName: location_name, +// date: `${startTime.format("M月D日")} (${dayofWeek})`, +// time: `${startTime.format("ah")}点 ${gameLength}`, +// }); +// }, +// })); - useShareAppMessage(async () => { - const tempFilePath = await posterRef.current.generateImage(); - return { - // title: detail.title, - imageUrl: tempFilePath, - path: `/game_pages/detail/index?id=${props.id}&from=share`, - }; - }); +// useShareAppMessage(async () => { +// const tempFilePath = await posterRef.current.generateImage(); +// return { +// // title: detail.title, +// imageUrl: tempFilePath, +// path: `/game_pages/detail/index?id=${props.id}&from=share`, +// }; +// }); - useShareTimeline(async () => { - const tempFilePath = await posterRef.current.generateImage(); - return { - title: "分享", - imageUrl: tempFilePath, - path: `/game_pages/detail/index?id=${props.id}&from=share`, - }; - }); +// useShareTimeline(async () => { +// const tempFilePath = await posterRef.current.generateImage(); +// return { +// title: "分享", +// imageUrl: tempFilePath, +// path: `/game_pages/detail/index?id=${props.id}&from=share`, +// }; +// }); - function onClose() { - setVisible(false); - setPosterData(undefined); - Taro.updateShareMenu({ - isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版) - }); - } +// function onClose() { +// setVisible(false); +// setPosterData(undefined); +// Taro.updateShareMenu({ +// isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版) +// }); +// } - async function handleShare() { - const tempFilePath = await posterRef.current.generateImage(); - Taro.showShareImageMenu({ - path: tempFilePath, - }); - } - return ( - visible && ( - - - - {posterData && } - - - - - - - - - ) - ); -}); +// async function handleShare() { +// const tempFilePath = await posterRef.current.generateImage(); +// Taro.showShareImageMenu({ +// path: tempFilePath, +// }); +// } +// return ( +// visible && ( +// +// +// +// {posterData && } +// +// +// +// +// +// +// +// +// ) +// ); +// }); // 分享弹窗 const SharePopup = forwardRef(({ id, from, detail, userInfo }, ref) => { const [visible, setVisible] = useState(false); - const posterRef = useRef(); + // const posterRef = useRef(); useEffect(() => { changeMessageType(); @@ -370,6 +375,7 @@ const SharePopup = forwardRef(({ id, from, detail, userInfo }, ref) => { async function handlePost() { const { + id, play_type, skill_level_max, skill_level_min, @@ -385,6 +391,15 @@ const SharePopup = forwardRef(({ id, from, detail, userInfo }, ref) => { const dayofWeek = DayOfWeekMap.get(startTime.day()); const gameLength = `${endTime.diff(startTime, "hour")}小时`; Taro.showLoading({ title: "生成中..." }); + const qrCodeUrlRes = await DetailService.getQrCodeUrl({ + page: "/game_pages/detail/index", + scene: `id=${id}`, + }); + const qrCodeUrl = await base64ToTempFilePath( + qrCodeUrlRes.data.qr_code_base64 + ); + console.log(qrCodeUrl, "qrCodeUrl"); + await delay(100); const url = await generatePosterImage({ playType: play_type, ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`, @@ -397,6 +412,7 @@ const SharePopup = forwardRef(({ id, from, detail, userInfo }, ref) => { locationName: location_name, date: `${startTime.format("M月D日")} (${dayofWeek})`, time: `${startTime.format("ah")}点 ${gameLength}`, + qrCodeUrl, }); Taro.hideLoading(); setVisible(false); @@ -1512,8 +1528,8 @@ function Index() { /> {/* sticky bottom action bar */} diff --git a/src/other_pages/ntrp-evaluate/index.tsx b/src/other_pages/ntrp-evaluate/index.tsx index 2fc86fc..6702ad6 100644 --- a/src/other_pages/ntrp-evaluate/index.tsx +++ b/src/other_pages/ntrp-evaluate/index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, useId } from "react"; +import { useState, useEffect, useRef, useId, useMemo } from "react"; import { View, Text, Image, Button, Canvas } from "@tarojs/components"; import Taro, { useRouter, useShareAppMessage } from "@tarojs/taro"; import dayjs from "dayjs"; @@ -289,10 +289,15 @@ function Test() { setDisabled(questions[index]?.choosen === -1); }, [index, questions]); + const doneFlag = useMemo(() => { + const [q1, q2, q3] = questions; + return [q1, q2, q3].every((q) => q?.choosen === 0); + }, [questions]); + async function getQUestions() { const res = await evaluateService.getQuestions(); if (res.code === 0) { - setQuestions(res.data.map((item) => ({ ...item, choosen: 3 }))); + setQuestions(res.data.map((item) => ({ ...item, choosen: -1 }))); } } @@ -309,10 +314,12 @@ function Test() { setDisabled(true); try { const res = await evaluateService.submit({ - answers: questions.map((item) => ({ - question_id: item.id, - answer_index: item.choosen, - })), + answers: questions + .filter((item) => item.choosen >= 0) + .map((item) => ({ + question_id: item.id, + answer_index: item.choosen, + })), test_duration: (Date.now() - startTimeRef.current) / 1000, }); if (res.code === 0) { @@ -332,7 +339,7 @@ function Test() { if (disabled && direction > 0) { return; } - if (index === questions.length - 1 && direction > 0) { + if ((index === questions.length - 1 || doneFlag) && direction > 0) { handleSubmit(); return; } @@ -384,7 +391,7 @@ function Test() { onClick={() => handIndexChange(1)} > {index !== 0 && ( @@ -476,7 +483,7 @@ function Result() { .exec((res2) => { const canvas = res2[0].node; const ctx = canvas.getContext("2d"); - const dpr = Taro.getSystemInfoSync().pixelRatio; + const dpr = Taro.getWindowInfo().pixelRatio; const width = 300; const height = 400; canvas.width = width * dpr; @@ -515,11 +522,33 @@ function Result() { } async function handleSaveImage() { + console.log(userInfo); if (!userInfo.id) { return; } - const url = await genCardImage(); - Taro.saveImageToPhotosAlbum({ filePath: url }); + Taro.getSetting().then(async (res) => { + if (!res.authSetting["scope.writePhotosAlbum"]) { + Taro.authorize({ + scope: "scope.writePhotosAlbum", + success: async () => { + const url = await genCardImage(); + Taro.saveImageToPhotosAlbum({ filePath: url }); + }, + fail: () => { + Taro.showModal({ + title: "提示", + content: "需要开启相册权限才能保存图片", + success: (r) => { + if (r.confirm) Taro.openSetting(); + }, + }); + }, + }); + } else { + const url = await genCardImage(); + Taro.saveImageToPhotosAlbum({ filePath: url }); + } + }); } useShareAppMessage(async (res) => { @@ -553,7 +582,7 @@ function Result() { {/* avatar side */} diff --git a/src/services/commentServices.ts b/src/services/commentServices.ts index 08fe1fe..1c89fde 100644 --- a/src/services/commentServices.ts +++ b/src/services/commentServices.ts @@ -80,6 +80,11 @@ class CommentService { async getReplies(req: { comment_id: number, page: number, pageSize: number }): Promise> { return httpService.post("/comments/replies", req, { showLoading: true }); } + + // 获取评论的所有回复 + async getCommentDetail(req: { comment_id: number }): Promise> { + return httpService.post("/comments/detail", req, { showLoading: true }); + } } export default new CommentService(); diff --git a/src/services/detailService.ts b/src/services/detailService.ts index cb89880..5811751 100644 --- a/src/services/detailService.ts +++ b/src/services/detailService.ts @@ -151,6 +151,18 @@ class GameDetailService { showLoading: false }) } + + async getQrCodeUrl(req: { page: string, scene: string }): Promise> { + return httpService.post('/user/generate_qrcode', req, { + showLoading: false + }) + } } // 导出认证服务实例 diff --git a/src/utils/genPoster.ts b/src/utils/genPoster.ts index b25fb0e..107613d 100644 --- a/src/utils/genPoster.ts +++ b/src/utils/genPoster.ts @@ -1,7 +1,7 @@ import Taro from "@tarojs/taro"; -const qrCodeUrl = - "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/5e013195-fc79-4082-bf06-9aa79aea65ae.png"; +// const qrCodeUrl = +// "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/5e013195-fc79-4082-bf06-9aa79aea65ae.png"; const ringUrl = "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/b635164f-ecec-434a-a00b-69614a918f2f.png"; @@ -17,6 +17,58 @@ const mapIcon = const logoText = "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/9d8cbc9d-9601-4e2d-ab23-76420a4537d6.png"; +/** 将 base64 图片转换为临时文件路径 */ +// export function base64ToTempFilePath(base64Data: string): Promise { +// return new Promise((resolve, reject) => { +// const fsm = Taro.getFileSystemManager(); +// // 生成唯一文件名 +// const filePath = `${Taro.env.USER_DATA_PATH}/temp_qrcode_${Date.now()}.png`; + +// // 去掉 data:image/png;base64, 前缀(如果有) +// const base64 = base64Data.replace(/^data:image\/\w+;base64,/, ''); + +// fsm.writeFile({ +// filePath, +// data: base64, +// encoding: 'base64', +// success: () => fsm.access({ +// path: filePath, +// success: () => resolve(filePath), +// fail: (e) => reject(e), +// }), +// fail: reject, +// }); +// }); +// } + +export function base64ToTempFilePath(base64Data: string): Promise { + return new Promise((resolve, reject) => { + const fsm = Taro.getFileSystemManager(); + const filePath = `${Taro.env.USER_DATA_PATH}/temp_qrcode_${Date.now()}.png`; + + // 去掉 data:image/png;base64, 前缀(如果有) + const base64 = base64Data.replace(/^data:image\/\w+;base64,/, ''); + + // 将base64转换成ArrayBuffer + const arrayBuffer = Taro.base64ToArrayBuffer(base64); + + fsm.writeFile({ + filePath, + data: arrayBuffer, + encoding: 'binary', // 这里使用'binary' + success: () => { + fsm.access({ + path: filePath, + success: () => resolve(filePath), + fail: (e) => reject(e), + }); + }, + fail: reject, + }); + }); +} + + /** 获取图片宽高 */ function getImageWh(src: string): Promise<{ width: number; height: number }> { return new Promise((resolve) => { @@ -173,7 +225,7 @@ function drawTextWrap( /** 核心纯函数:生成海报图片 */ export async function generatePosterImage(data: any): Promise { console.log("start !!!!"); - const dpr = Taro.getSystemInfoSync().pixelRatio; + const dpr = Taro.getWindowInfo().pixelRatio; const width = 600; const height = 1000; @@ -292,7 +344,7 @@ export async function generatePosterImage(data: any): Promise { 400 / (logoWh.width / logoWh.height) ); - const qrImg = await loadImage(canvas, qrCodeUrl); + const qrImg = await loadImage(canvas, data.qrCodeUrl); ctx.drawImage(qrImg, width - 12 - 150, top - 50, 160, 160); left = 16; From ce6ab31d8baa26c20e58c047a477c155773d661e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=9D=B0?= Date: Mon, 6 Oct 2025 14:53:00 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/game_pages/detail/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_pages/detail/index.tsx b/src/game_pages/detail/index.tsx index a40c358..fd2a921 100644 --- a/src/game_pages/detail/index.tsx +++ b/src/game_pages/detail/index.tsx @@ -392,7 +392,7 @@ const SharePopup = forwardRef(({ id, from, detail, userInfo }, ref) => { const gameLength = `${endTime.diff(startTime, "hour")}小时`; Taro.showLoading({ title: "生成中..." }); const qrCodeUrlRes = await DetailService.getQrCodeUrl({ - page: "/game_pages/detail/index", + page: "game_pages/detail/index", scene: `id=${id}`, }); const qrCodeUrl = await base64ToTempFilePath(