Merge branch 'feat/liujie'
This commit is contained in:
@@ -4,6 +4,7 @@ import { View, Image, Text, Button } from "@tarojs/components";
|
|||||||
import Taro, { useRouter } from "@tarojs/taro";
|
import Taro, { useRouter } from "@tarojs/taro";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import "dayjs/locale/zh-cn";
|
||||||
import { generatePosterImage, base64ToTempFilePath, delay } from "@/utils";
|
import { generatePosterImage, base64ToTempFilePath, delay } from "@/utils";
|
||||||
import { withAuth } from "@/components";
|
import { withAuth } from "@/components";
|
||||||
import GeneralNavbar from "@/components/GeneralNavbar";
|
import GeneralNavbar from "@/components/GeneralNavbar";
|
||||||
@@ -17,6 +18,8 @@ import { genNTRPRequirementText } from "@/utils/helper";
|
|||||||
import { waitForAuthInit } from "@/utils/authInit";
|
import { waitForAuthInit } from "@/utils/authInit";
|
||||||
import styles from "./index.module.scss";
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
|
dayjs.locale("zh-cn");
|
||||||
|
|
||||||
function SharePoster(props) {
|
function SharePoster(props) {
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
const { fetchUserInfo } = useUserActions();
|
const { fetchUserInfo } = useUserActions();
|
||||||
@@ -63,8 +66,9 @@ function SharePoster(props) {
|
|||||||
playType: play_type,
|
playType: play_type,
|
||||||
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
ntrp: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`,
|
||||||
mainCoursal:
|
mainCoursal:
|
||||||
image_list[0] ||
|
image_list[0] && image_list[0].startsWith("http")
|
||||||
"https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png",
|
? image_list[0]
|
||||||
|
: "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/0621b8cf-f7d6-43ad-b852-7dc39f29a782.png",
|
||||||
nickname,
|
nickname,
|
||||||
avatarUrl: avatar_url,
|
avatarUrl: avatar_url,
|
||||||
title,
|
title,
|
||||||
@@ -84,10 +88,7 @@ function SharePoster(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GeneralNavbar
|
<GeneralNavbar title="生成分享图" className={styles.navbar} />
|
||||||
title="生成分享图"
|
|
||||||
className={styles.navbar}
|
|
||||||
/>
|
|
||||||
{url && (
|
{url && (
|
||||||
<View className={styles.posterContainer}>
|
<View className={styles.posterContainer}>
|
||||||
<View className={styles.posterWrap}>
|
<View className={styles.posterWrap}>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import Taro from "@tarojs/taro";
|
|||||||
// const qrCodeUrl =
|
// const qrCodeUrl =
|
||||||
// "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/5e013195-fc79-4082-bf06-9aa79aea65ae.png";
|
// "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 =
|
const ringUrl =
|
||||||
"https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/b635164f-ecec-434a-a00b-69614a918f2f.png";
|
"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(
|
function roundRectGradient(
|
||||||
ctx: any,
|
ctx: any,
|
||||||
@@ -131,6 +135,57 @@ async function drawCoverImage(
|
|||||||
ctx.restore();
|
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(
|
function roundRect(
|
||||||
ctx: any,
|
ctx: any,
|
||||||
@@ -156,9 +211,47 @@ function roundRect(
|
|||||||
ctx.fill();
|
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) {
|
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 padding = 12;
|
||||||
const textWidth = ctx.measureText(text).width;
|
const textWidth = ctx.measureText(text).width;
|
||||||
roundRect(ctx, x, y, textWidth + padding * 2, 40, 20, "#fff");
|
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");
|
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);
|
const mainImg = await loadImage(canvas, data.mainCoursal);
|
||||||
await drawCoverImage(
|
await drawRotateCoverImage(
|
||||||
ctx,
|
ctx,
|
||||||
canvas,
|
canvas,
|
||||||
data.mainCoursal,
|
data.mainCoursal,
|
||||||
mainImg,
|
mainImg,
|
||||||
10,
|
75,
|
||||||
10,
|
105,
|
||||||
width - 20,
|
width - 150,
|
||||||
width - 20,
|
width - 150,
|
||||||
20
|
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);
|
let left = drawTag(ctx, data.playType, 20, 20);
|
||||||
drawTag(ctx, data.ntrp, left + 4, 18);
|
drawTag(ctx, data.ntrp, left + 8, 20);
|
||||||
|
|
||||||
let top = width - 10 + 16;
|
let top = width - 10 + 16;
|
||||||
left = 16;
|
left = 20;
|
||||||
|
|
||||||
// 用户头像
|
// 用户头像
|
||||||
const avatarImg = await loadImage(canvas, data.avatarUrl);
|
const avatarImg = await loadImage(canvas, data.avatarUrl);
|
||||||
@@ -246,7 +355,7 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
top += 40;
|
top += 40;
|
||||||
|
|
||||||
// 用户名 + 邀请
|
// 用户名 + 邀请
|
||||||
ctx.fillStyle = "#333";
|
ctx.fillStyle = "#000";
|
||||||
ctx.font = "bold 28px sans-serif";
|
ctx.font = "bold 28px sans-serif";
|
||||||
const nickNameText = `${data.nickname} 邀你加入`;
|
const nickNameText = `${data.nickname} 邀你加入`;
|
||||||
ctx.fillText(nickNameText, left, top);
|
ctx.fillText(nickNameText, left, top);
|
||||||
@@ -258,16 +367,16 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
const ringImg = await loadImage(canvas, ringUrl);
|
const ringImg = await loadImage(canvas, ringUrl);
|
||||||
ctx.drawImage(ringImg, left - 10, top - 30, 80, 36);
|
ctx.drawImage(ringImg, left - 10, top - 30, 80, 36);
|
||||||
|
|
||||||
left = 16;
|
left = 20;
|
||||||
top += 60;
|
top += 60;
|
||||||
|
|
||||||
// 活动标题
|
// 活动标题
|
||||||
ctx.fillStyle = "#333";
|
ctx.fillStyle = "#000";
|
||||||
ctx.font = "bold 34px sans-serif";
|
ctx.font = "bold 34px sans-serif";
|
||||||
let r = drawTextWrap(ctx, data.title, left, top, width - 32, 40);
|
let r = drawTextWrap(ctx, data.title, left, top, width - 32, 40);
|
||||||
|
|
||||||
top = r.top + 40;
|
top = r.top + 30;
|
||||||
left = 16;
|
left = 20;
|
||||||
|
|
||||||
const dateImg = await loadImage(canvas, dateIcon);
|
const dateImg = await loadImage(canvas, dateIcon);
|
||||||
await drawCoverImage(
|
await drawCoverImage(
|
||||||
@@ -279,34 +388,34 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
top,
|
top,
|
||||||
40,
|
40,
|
||||||
40,
|
40,
|
||||||
8
|
12
|
||||||
);
|
);
|
||||||
|
|
||||||
left += 40 + 8;
|
left += 40 + 16;
|
||||||
top += 30;
|
top += 30;
|
||||||
|
|
||||||
ctx.font = "26px sans-serif";
|
ctx.font = "500 26px sans-serif";
|
||||||
ctx.fillStyle = "#00B578";
|
ctx.fillStyle = "#00B578";
|
||||||
ctx.fillText(data.date, left, top);
|
ctx.fillText(data.date, left, top);
|
||||||
textW = ctx.measureText(data.date).width;
|
textW = ctx.measureText(data.date).width;
|
||||||
left += 8 + textW;
|
left += 8 + textW;
|
||||||
ctx.fillStyle = "#333";
|
ctx.fillStyle = "#000";
|
||||||
ctx.fillText(data.time, left, top);
|
ctx.fillText(data.time, left, top);
|
||||||
|
|
||||||
left = 16;
|
left = 20;
|
||||||
top += 24;
|
top += 24;
|
||||||
|
|
||||||
const mapImg = await loadImage(canvas, mapIcon);
|
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;
|
top += 30;
|
||||||
|
|
||||||
ctx.fillStyle = "#666";
|
ctx.fillStyle = "#000";
|
||||||
ctx.font = "26px sans-serif";
|
ctx.font = "500 26px sans-serif";
|
||||||
r = drawTextWrap(ctx, data.locationName, left, top, width - 32 - left, 34);
|
r = drawTextWrap(ctx, data.locationName, left, top, width - 32 - left, 34);
|
||||||
|
|
||||||
left = 16;
|
left = 20;
|
||||||
top = r.top + 60;
|
top = r.top + 60;
|
||||||
|
|
||||||
const logoWh = await getImageWh(logoText);
|
const logoWh = await getImageWh(logoText);
|
||||||
@@ -314,22 +423,23 @@ export async function generatePosterImage(data: any): Promise<string> {
|
|||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
logoTextImg,
|
logoTextImg,
|
||||||
left,
|
left,
|
||||||
top,
|
// top,
|
||||||
|
height - logoWh.height - 30 - 30,
|
||||||
400,
|
400,
|
||||||
400 / (logoWh.width / logoWh.height)
|
400 / (logoWh.width / logoWh.height)
|
||||||
);
|
);
|
||||||
|
|
||||||
const qrImg = await loadImage(canvas, data.qrCodeUrl);
|
const qrImg = await loadImage(canvas, data.qrCodeUrl);
|
||||||
|
|
||||||
roundRectGradient(ctx, width - 12 - 150, top - 50, 140, 140, 20, "#fff", "#fff")
|
// roundRectGradient(ctx, width - 12 - 150, height - 22 - 140, 140, 140, 20, "#fff", "#fff")
|
||||||
ctx.drawImage(qrImg, width - 12 - 145, top - 45, 130, 130);
|
ctx.drawImage(qrImg, width - 22 - 100, height - 22 - 100 - 2, 100, 100);
|
||||||
|
|
||||||
left = 16;
|
left = 20;
|
||||||
top += 400 / (logoWh.width / logoWh.height) + 30;
|
// top += 400 / (logoWh.width / logoWh.height) + 30;
|
||||||
|
|
||||||
ctx.fillStyle = "#333";
|
ctx.fillStyle = "rgba(0, 0, 0, 0.45)";
|
||||||
ctx.font = "20px sans-serif";
|
ctx.font = "400 20px sans-serif";
|
||||||
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, top);
|
ctx.fillText("长按识别二维码,快来加入,有你就有场!", left, height - 30/* top */);
|
||||||
|
|
||||||
// 导出图片
|
// 导出图片
|
||||||
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
const { tempFilePath } = await Taro.canvasToTempFilePath({
|
||||||
|
|||||||
Reference in New Issue
Block a user