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 {
+}