diff --git a/src/components/Poster/index.tsx b/src/components/Poster/index.tsx new file mode 100644 index 0000000..110e536 --- /dev/null +++ b/src/components/Poster/index.tsx @@ -0,0 +1,294 @@ +import { useEffect } from "react"; +import { Canvas } from "@tarojs/components"; +import Taro from "@tarojs/taro"; + +function getImageWh(src): Promise<{ width: number; height: number }> { + return new Promise((resolve) => { + Taro.getImageInfo({ + src, + success: ({ width, height }) => resolve({ width, height }), + }); + }); +} + +const ringUrl = + "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/b635164f-ecec-434a-a00b-69614a918f2f.png"; + +const Poster = (props) => { + const carouselUrl = + "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/63f62c80-ac44-4f3b-bb6c-d7f6e8ebf76d.jpg"; + + const avatarUrl = + "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/aac792b0-6f81-4192-ae55-04bee417167c.png"; + useEffect(() => { + drawCard(); + }, []); + + const drawCard = async () => { + const query = Taro.createSelectorQuery(); + query + .select("#cardCanvas") + .fields({ node: true, size: true }) + .exec(async (res) => { + const canvas = res[0].node; + const ctx = canvas.getContext("2d"); + const dpr = Taro.getSystemInfoSync().pixelRatio; + const width = 600; // px + const height = 1000; + + canvas.width = width * dpr; + canvas.height = height * dpr; + ctx.scale(dpr, dpr); + + // 背景卡片 + // roundRect(ctx, 0, 0, width, height, 20, "#fff"); + + // 整体背景 + roundRectGradient(ctx, 0, 0, width, height, 24, "#BFFFEF", "#F2FFFC"); + + // 顶部图片 + const img = await loadImage(canvas, carouselUrl); + // roundRect(ctx, 20, 20, width - 40, width - 40, 20, "#fff"); + await drawCoverImage( + ctx, + carouselUrl, + img, + 10, + 10, + width - 20, + width - 20, + 20 + ); + + // 标签 + let left = drawTag(ctx, "单打", 18, 18); + drawTag(ctx, "NTRP 2.5 - 3.0", left + 4, 18); + + let top = width - 10; + left = 16; + + top += 16; + + // 用户头像(圆形) + const avatar = await loadImage(canvas, avatarUrl); + ctx.save(); + ctx.beginPath(); + ctx.arc(left + 30, top + 30, 30, 0, Math.PI * 2); + ctx.clip(); + ctx.drawImage(avatar, left, top, 60, 60); + ctx.restore(); + + // 头像右边 6 + left += 66; + + top += 40; + + // 用户名 + 邀请 + ctx.fillStyle = "#333"; + ctx.font = "bold 28px sans-serif"; + // ctx.fillText("华巴轮卡 邀你加入球局", 100, 370); + const nickNameText = "华巴轮卡 邀你加入"; + ctx.fillText(nickNameText, left, top); + let textW = ctx.measureText(nickNameText).width; + left += textW; + ctx.fillStyle = "#00B578"; + ctx.fillText("球局", left, top); + const ringImg = await loadImage(canvas, ringUrl); + ctx.drawImage(ringImg, left - 10, top - 30, 80, 36); + + left = 16; + top += 60; + + // 活动标题 + ctx.fillStyle = "#333"; + ctx.font = "bold 34px sans-serif"; + const r = drawTextWrap( + ctx, + "周一晚上浦东新区单打约球", + left, + top, + width - 32, + 40 + ); + + top = r.top + 50; + + // 时间 + ctx.font = "26px sans-serif"; + ctx.fillStyle = "#00B578"; + const day = "6月20日 (周五)"; + ctx.fillText(day, 80, top); + textW = ctx.measureText(day).width; + left = 80 + 8 + textW; + ctx.fillStyle = "#333"; + ctx.fillText("下午5点 2小时", left, top); + + // 地址 + ctx.fillStyle = "#666"; + ctx.font = "26px sans-serif"; + drawTextWrap( + ctx, + "因乐驰网球俱乐部 (嘉定江桥万达店)", + 80, + 560, + 480, + 34 + ); + + // 底部文字 + ctx.fillStyle = "#333"; + ctx.font = "24px sans-serif"; + ctx.fillText("有场 · 网球", 40, 960); + + // 小程序码 + // const qrcode = await loadImage(canvas, "小程序码路径"); + // ctx.drawImage(qrcode, 480, 880, 100, 100); + + // 导出图片 + Taro.canvasToTempFilePath({ + canvas, + success: (res) => { + console.log("导出路径", res.tempFilePath); + }, + fail: (err) => console.error(err), + }); + }); + }; + + const roundRectGradient = (ctx, x, y, w, h, r, color1, color2) => { + const gradient = ctx.createLinearGradient(x, y, x, y + h); + gradient.addColorStop(0, color1); + gradient.addColorStop(1, color2); + + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.arcTo(x + w, y, x + w, y + r, r); + ctx.lineTo(x + w, y + h - r); + ctx.arcTo(x + w, y + h, x + w - r, y + h, r); + ctx.lineTo(x + r, y + h); + ctx.arcTo(x, y + h, x, y + h - r, r); + ctx.lineTo(x, y + r); + ctx.arcTo(x, y, x + r, y, r); + ctx.closePath(); + + ctx.fillStyle = gradient; + ctx.fill(); + }; + + const drawCoverImage = async (ctx, src, img, x, y, w, h, r = 0) => { + const { width, height } = await getImageWh(src); + const imgW = width; + const imgH = height; + + // 计算缩放比例,取较大值,保证完全覆盖 + const scale = Math.max(w / imgW, h / imgH); + + // 缩放后的宽高 + const newW = imgW * scale; + const newH = imgH * scale; + + // 居中偏移 + const offsetX = x + (w - newW) / 2; + const offsetY = y + (h - newH) / 2; + + ctx.save(); + + // 如果需要圆角 + if (r > 0) { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.arcTo(x + w, y, x + w, y + r, r); + ctx.lineTo(x + w, y + h - r); + ctx.arcTo(x + w, y + h, x + w - r, y + h, r); + ctx.lineTo(x + r, y + h); + ctx.arcTo(x, y + h, x, y + h - r, r); + ctx.lineTo(x, y + r); + ctx.arcTo(x, y, x + r, y, r); + ctx.closePath(); + ctx.clip(); + } + + // 绘制图片 (cover 效果) + ctx.drawImage(img, offsetX, offsetY, newW, newH); + + ctx.restore(); + }; + + /** 加载图片 */ + const loadImage = (canvas, src) => { + return new Promise((resolve) => { + const img = canvas.createImage(); + img.onload = () => resolve(img); + img.src = src; + }); + }; + + /** 圆角矩形 */ + const roundRect = (ctx, x, y, w, h, r, fillStyle) => { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.arcTo(x + w, y, x + w, y + r, r); + ctx.lineTo(x + w, y + h - r); + ctx.arcTo(x + w, y + h, x + w - r, y + h, r); + ctx.lineTo(x + r, y + h); + ctx.arcTo(x, y + h, x, y + h - r, r); + ctx.lineTo(x, y + r); + ctx.arcTo(x, y, x + r, y, r); + ctx.closePath(); + ctx.fillStyle = fillStyle; + ctx.fill(); + }; + + /** 绘制标签 */ + const drawTag = (ctx, text, x, y) => { + ctx.font = "22px sans-serif"; + const padding = 12; + const textWidth = ctx.measureText(text).width; + roundRect(ctx, x, y, textWidth + padding * 2, 40, 20, "#fff"); + ctx.fillStyle = "#333"; + ctx.fillText(text, x + padding, y + 28); + return x + textWidth + padding * 2; + }; + + /** 文本换行 */ + const drawTextWrap = (ctx, text, x, y, maxWidth, lineHeight) => { + let line = ""; + const lines = []; + for (let char of text) { + const testLine = line + char; + if (ctx.measureText(testLine).width > maxWidth) { + lines.push(line); + line = char; + } else { + line = testLine; + } + } + if (line) lines.push(line); + lines.forEach((l, i) => { + ctx.fillText(l, x, y + i * lineHeight); + }); + const lastLineText = lines.at(-1); + return { + left: x + ctx.measureText(lastLineText), + top: y + (lines.length - 1) * lineHeight, + }; + }; + + return ( + + ); +}; + +export default Poster; diff --git a/src/components/index.ts b/src/components/index.ts index 73f42fe..9f7bb87 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -25,6 +25,7 @@ import GeneralNavbar from "./GeneralNavbar"; import RadarChart from './Radar' import EmptyState from './EmptyState'; import NTRPTestEntryCard from './NTRPTestEntryCard' +import Poster from './Poster' export { ActivityTypeSwitch, @@ -55,4 +56,5 @@ export { RadarChart, EmptyState, NTRPTestEntryCard, + Poster, }; diff --git a/src/game_pages/detail/index.tsx b/src/game_pages/detail/index.tsx index 3ee2601..85889cb 100644 --- a/src/game_pages/detail/index.tsx +++ b/src/game_pages/detail/index.tsx @@ -23,6 +23,7 @@ import { NTRPEvaluatePopup, GameManagePopup, Comments, + Poster, } from "@/components"; import DetailService, { MATCH_STATUS, @@ -175,7 +176,7 @@ function Coursel(props) { // 分享弹窗 const SharePopup = forwardRef( ({ id, from }: { id: string; from: string }, ref) => { - const [visible, setVisible] = useState(false); + const [visible, setVisible] = useState(true); useImperativeHandle(ref, () => ({ show: () => { @@ -216,35 +217,53 @@ const SharePopup = forwardRef( } return ( - { - setVisible(false); - }} - hideFooter - style={{ minHeight: "100px" }} - > - - - 分享卡片 + <> + {/* { + setVisible(false); + }} + hideFooter + style={{ minHeight: "100px" }} + > + + + 分享至 + + + + + - - - + */} + { + setVisible(false); + }} + showHeader={false} + position="center" + hideFooter + enableDragToClose={false} + style={{ minHeight: "100px" }} + > + + - - + + ); } ); diff --git a/src/game_pages/detail/style.module.scss b/src/game_pages/detail/style.module.scss index 5be9551..2df4699 100644 --- a/src/game_pages/detail/style.module.scss +++ b/src/game_pages/detail/style.module.scss @@ -22,3 +22,6 @@ } } } + +.posterWrap { +}