feat: 详情页分享海报重新绘制
This commit is contained in:
@@ -4,6 +4,7 @@ import { View, Image, Text, Button } from "@tarojs/components";
|
||||
import Taro, { useRouter } from "@tarojs/taro";
|
||||
import classnames from "classnames";
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import { generatePosterImage, base64ToTempFilePath, delay } from "@/utils";
|
||||
import { withAuth } from "@/components";
|
||||
import GeneralNavbar from "@/components/GeneralNavbar";
|
||||
@@ -17,6 +18,8 @@ import { genNTRPRequirementText } from "@/utils/helper";
|
||||
import { waitForAuthInit } from "@/utils/authInit";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
dayjs.locale("zh-cn");
|
||||
|
||||
function SharePoster(props) {
|
||||
const [url, setUrl] = useState("");
|
||||
const { fetchUserInfo } = useUserActions();
|
||||
@@ -63,8 +66,9 @@ function SharePoster(props) {
|
||||
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",
|
||||
image_list[0] && image_list[0].startsWith("http")
|
||||
? image_list[0]
|
||||
: "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png",
|
||||
nickname,
|
||||
avatarUrl: avatar_url,
|
||||
title,
|
||||
@@ -84,10 +88,7 @@ function SharePoster(props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<GeneralNavbar
|
||||
title="生成分享图"
|
||||
className={styles.navbar}
|
||||
/>
|
||||
<GeneralNavbar title="生成分享图" className={styles.navbar} />
|
||||
{url && (
|
||||
<View className={styles.posterContainer}>
|
||||
<View className={styles.posterWrap}>
|
||||
|
||||
@@ -3,6 +3,8 @@ import Taro from "@tarojs/taro";
|
||||
// const qrCodeUrl =
|
||||
// "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/5e013195-fc79-4082-bf06-9aa79aea65ae.png";
|
||||
|
||||
const bgUrl = "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/5e2c85ab-fb0c-4026-974d-1e0725181542.png";
|
||||
|
||||
const ringUrl =
|
||||
"https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/b635164f-ecec-434a-a00b-69614a918f2f.png";
|
||||
|
||||
@@ -64,6 +66,8 @@ function loadImage(canvas: any, src: string): Promise<any> {
|
||||
});
|
||||
}
|
||||
|
||||
const deg2rad = d => d * Math.PI / 180;
|
||||
|
||||
/** 圆角矩形渐变 */
|
||||
function roundRectGradient(
|
||||
ctx: any,
|
||||
@@ -131,6 +135,57 @@ async function drawCoverImage(
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/** 绘制 cover 图片(支持圆角 + 旋转) */
|
||||
async function drawRotateCoverImage(
|
||||
ctx: any,
|
||||
canvas: any,
|
||||
src: string,
|
||||
img: any,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
r = 0,
|
||||
rotate = 0 // 旋转角度(弧度)
|
||||
) {
|
||||
const { width, height } = await getImageWh(src);
|
||||
const scale = Math.max(w / width, h / height);
|
||||
const newW = width * scale;
|
||||
const newH = height * scale;
|
||||
const offsetX = x + (w - newW) / 2;
|
||||
const offsetY = y + (h - newH) / 2;
|
||||
|
||||
ctx.save();
|
||||
|
||||
// 以 cover 区域中心为旋转点
|
||||
const cx = x + w / 2;
|
||||
const cy = y + h / 2;
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate(rotate);
|
||||
ctx.translate(-cx, -cy);
|
||||
|
||||
// ------- clipping(注意要在旋转变换之后做 path)-------
|
||||
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();
|
||||
}
|
||||
|
||||
/** 圆角矩形 */
|
||||
function roundRect(
|
||||
ctx: any,
|
||||
@@ -156,9 +211,47 @@ function roundRect(
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
/** 圆角矩形(支持旋转) */
|
||||
function roundRotateRect(
|
||||
ctx: any,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
r: number,
|
||||
fillStyle: string,
|
||||
rotate: number = 0 // 弧度
|
||||
) {
|
||||
ctx.save();
|
||||
|
||||
// 以圆角矩形中心作为旋转点
|
||||
const cx = x + w / 2;
|
||||
const cy = y + h / 2;
|
||||
|
||||
ctx.translate(cx, cy);
|
||||
ctx.rotate(rotate);
|
||||
ctx.translate(-cx, -cy);
|
||||
|
||||
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();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/** 绘制标签 */
|
||||
function drawTag(ctx: any, text: string, x: number, y: number) {
|
||||
ctx.font = "22px sans-serif";
|
||||
ctx.font = "600 22px sans-serif";
|
||||
const padding = 12;
|
||||
const textWidth = ctx.measureText(text).width;
|
||||
roundRect(ctx, x, y, textWidth + padding * 2, 40, 20, "#fff");
|
||||
@@ -212,26 +305,42 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
// 背景渐变
|
||||
roundRectGradient(ctx, 0, 0, width, height, 24, "#BFFFEF", "#F2FFFC");
|
||||
|
||||
const bgImg = await loadImage(canvas, bgUrl);
|
||||
ctx.drawImage(bgImg, 0, 0, width, height);
|
||||
|
||||
roundRotateRect(ctx, 70, 100, width - 140, width - 140, 20, '#fff', deg2rad(-6));
|
||||
// 顶部图片
|
||||
const mainImg = await loadImage(canvas, data.mainCoursal);
|
||||
await drawCoverImage(
|
||||
await drawRotateCoverImage(
|
||||
ctx,
|
||||
canvas,
|
||||
data.mainCoursal,
|
||||
mainImg,
|
||||
10,
|
||||
10,
|
||||
width - 20,
|
||||
width - 20,
|
||||
20
|
||||
75,
|
||||
105,
|
||||
width - 150,
|
||||
width - 150,
|
||||
20,
|
||||
deg2rad(-6),
|
||||
);
|
||||
// await drawCoverImage(
|
||||
// ctx,
|
||||
// canvas,
|
||||
// data.mainCoursal,
|
||||
// mainImg,
|
||||
// 10,
|
||||
// 10,
|
||||
// width - 20,
|
||||
// width - 20,
|
||||
// 20
|
||||
// );
|
||||
|
||||
// 标签
|
||||
let left = drawTag(ctx, data.playType, 18, 18);
|
||||
drawTag(ctx, data.ntrp, left + 4, 18);
|
||||
let left = drawTag(ctx, data.playType, 20, 20);
|
||||
drawTag(ctx, data.ntrp, left + 8, 20);
|
||||
|
||||
let top = width - 10 + 16;
|
||||
left = 16;
|
||||
left = 20;
|
||||
|
||||
// 用户头像
|
||||
const avatarImg = await loadImage(canvas, data.avatarUrl);
|
||||
@@ -246,7 +355,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
top += 40;
|
||||
|
||||
// 用户名 + 邀请
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.font = "bold 28px sans-serif";
|
||||
const nickNameText = `${data.nickname} 邀你加入`;
|
||||
ctx.fillText(nickNameText, left, top);
|
||||
@@ -258,16 +367,16 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
const ringImg = await loadImage(canvas, ringUrl);
|
||||
ctx.drawImage(ringImg, left - 10, top - 30, 80, 36);
|
||||
|
||||
left = 16;
|
||||
left = 20;
|
||||
top += 60;
|
||||
|
||||
// 活动标题
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.font = "bold 34px sans-serif";
|
||||
let r = drawTextWrap(ctx, data.title, left, top, width - 32, 40);
|
||||
|
||||
top = r.top + 40;
|
||||
left = 16;
|
||||
top = r.top + 30;
|
||||
left = 20;
|
||||
|
||||
const dateImg = await loadImage(canvas, dateIcon);
|
||||
await drawCoverImage(
|
||||
@@ -279,34 +388,34 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
top,
|
||||
40,
|
||||
40,
|
||||
8
|
||||
12
|
||||
);
|
||||
|
||||
left += 40 + 8;
|
||||
left += 40 + 16;
|
||||
top += 30;
|
||||
|
||||
ctx.font = "26px sans-serif";
|
||||
ctx.font = "500 26px sans-serif";
|
||||
ctx.fillStyle = "#00B578";
|
||||
ctx.fillText(data.date, left, top);
|
||||
textW = ctx.measureText(data.date).width;
|
||||
left += 8 + textW;
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillText(data.time, left, top);
|
||||
|
||||
left = 16;
|
||||
left = 20;
|
||||
top += 24;
|
||||
|
||||
const mapImg = await loadImage(canvas, mapIcon);
|
||||
await drawCoverImage(ctx, canvas, mapIcon, mapImg, left, top, 40, 40, 8);
|
||||
await drawCoverImage(ctx, canvas, mapIcon, mapImg, left, top, 40, 40, 12);
|
||||
|
||||
left += 40 + 8;
|
||||
left += 40 + 16;
|
||||
top += 30;
|
||||
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = "26px sans-serif";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.font = "500 26px sans-serif";
|
||||
r = drawTextWrap(ctx, data.locationName, left, top, width - 32 - left, 34);
|
||||
|
||||
left = 16;
|
||||
left = 20;
|
||||
top = r.top + 60;
|
||||
|
||||
const logoWh = await getImageWh(logoText);
|
||||
@@ -314,22 +423,23 @@ export async function generatePosterImage(data: any): Promise<string> {
|
||||
ctx.drawImage(
|
||||
logoTextImg,
|
||||
left,
|
||||
top,
|
||||
// top,
|
||||
height - logoWh.height - 30 - 30,
|
||||
400,
|
||||
400 / (logoWh.width / logoWh.height)
|
||||
);
|
||||
|
||||
const qrImg = await loadImage(canvas, data.qrCodeUrl);
|
||||
|
||||
roundRectGradient(ctx, width - 12 - 150, top - 50, 140, 140, 20, "#fff", "#fff")
|
||||
ctx.drawImage(qrImg, width - 12 - 145, top - 45, 130, 130);
|
||||
// roundRectGradient(ctx, width - 12 - 150, height - 22 - 140, 140, 140, 20, "#fff", "#fff")
|
||||
ctx.drawImage(qrImg, width - 22 - 100, height - 22 - 100 - 2, 100, 100);
|
||||
|
||||
left = 16;
|
||||
top += 400 / (logoWh.width / logoWh.height) + 30;
|
||||
left = 20;
|
||||
// top += 400 / (logoWh.width / logoWh.height) + 30;
|
||||
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.font = "20px sans-serif";
|
||||
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, top);
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.45)";
|
||||
ctx.font = "400 20px sans-serif";
|
||||
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, height - 30/* top */);
|
||||
|
||||
// 导出图片
|
||||
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
||||
|
||||
Reference in New Issue
Block a user