feat: 生成海报

This commit is contained in:
2025-10-03 09:19:05 +08:00
parent 40a043d2a0
commit 5fec10b342
18 changed files with 1032 additions and 200 deletions

View File

@@ -0,0 +1,9 @@
export const DayOfWeekMap = new Map([
[0, "周日"],
[1, "周一"],
[2, "周二"],
[3, "周三"],
[4, "周四"],
[5, "周五"],
[6, "周六"],
]);

View File

@@ -2,4 +2,5 @@ export default definePageConfig({
navigationBarTitleText: '球局详情',
navigationStyle: 'custom',
enableShareAppMessage: true,
enableShareTimeline: true,
})

View File

@@ -6,7 +6,6 @@ import React, {
forwardRef,
} from "react";
import { View, Text, Image, Map, ScrollView, Button } from "@tarojs/components";
// import { Avatar } from "@nutui/nutui-react-taro";
import Taro, {
useRouter,
useShareAppMessage,
@@ -25,6 +24,7 @@ import {
Comments,
Poster,
} from "@/components";
import { generateShareImage, generatePosterImage } from "@/utils";
import DetailService, {
MATCH_STATUS,
IsSubstituteSupported,
@@ -35,6 +35,12 @@ import { getCurrentLocation, calculateDistance } from "@/utils/locationUtils";
import { useUserInfo, useUserActions } from "@/store/userStore";
import { EvaluateCallback, EvaluateScene } from "@/store/evaluateStore";
import img from "@/config/images";
import DownloadIcon from "@/static/detail/download_icon.svg";
import WechatLogo from "@/static/detail/wechat_icon.svg";
import WechatTimeline from "@/static/detail/wechat_timeline.svg";
import LinkIcon from "@/static/detail/link.svg";
import CrossIcon from "@/static/detail/cross.svg";
import { DayOfWeekMap } from "./config";
import styles from "./style.module.scss";
import "./index.scss";
@@ -173,100 +179,281 @@ function Coursel(props) {
);
}
// 分享弹窗
const SharePopup = forwardRef(
({ id, from }: { id: string; from: string }, ref) => {
const [visible, setVisible] = useState(true);
useImperativeHandle(ref, () => ({
show: () => {
setVisible(true);
},
}));
useShareAppMessage((res) => {
console.log(res, "res");
return {
title: "分享",
imageUrl: "https://img.yzcdn.cn/vant/cat.jpeg",
path: `/game_pages/detail/index?id=${id}&from=share`,
};
});
// function handleShareToWechatMoments() {
// useShareTimeline(() => {
// return {
// title: '分享',
// path: `/game_pages/detail/index?id=${id}&from=share`,
// }
// })
// }
function handleSaveToLocal() {
Taro.showToast({ title: "not yet", icon: "error" });
return;
Taro.saveImageToPhotosAlbum({
filePath: "",
success: () => {
Taro.showToast({ title: "保存成功", icon: "success" });
},
fail: () => {
Taro.showToast({ title: "保存失败", icon: "none" });
},
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}`,
});
}
},
}));
return (
<>
{/* <CommonPopup
title="分享至"
visible={visible}
onClose={() => {
setVisible(false);
}}
hideFooter
style={{ minHeight: "100px" }}
>
<View className={styles.shareContainer}>
<View catchMove className={styles.title}>
分享至
</View>
<View className={styles.shareItems}>
<Button
className={classnames(styles.button, styles.share)}
openType="share"
>
微信好友
</Button>
<Button
className={classnames(styles.button, styles.save)}
onClick={handleSaveToLocal}
>
生成分享图
</Button>
</View>
</View>
</CommonPopup> */}
<CommonPopup
title="分享至"
visible={visible}
onClose={() => {
setVisible(false);
}}
showHeader={false}
position="center"
hideFooter
enableDragToClose={false}
style={{ minHeight: "100px" }}
>
<View className={styles.posterWrap}>
<Poster />
</View>
</CommonPopup>
</>
);
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`,
};
});
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 && (
<CommonPopup
title="分享至"
visible={visible}
onClose={onClose}
showHeader={false}
position="center"
hideFooter
enableDragToClose={false}
style={{ minHeight: "100px" }}
zIndex={2001}
>
<View className={styles.posterContainer}>
<View className={styles.posterWrap}>
{posterData && <Poster ref={posterRef} data={posterData} />}
</View>
<View className={styles.sharePoster}>
<Button className={styles.shareItem} plain={true}>
<View className={styles.icon}>
<Image className={styles.download} src={DownloadIcon} />
</View>
<Text></Text>
</Button>
<Button
className={styles.shareItem}
plain={true}
onClick={handleShare}
>
<View className={classnames(styles.icon, styles.wechatIcon)}>
<Image className={styles.wechat} src={WechatLogo} />
</View>
<Text></Text>
</Button>
<Button className={styles.shareItem} plain={true} openType="share">
<View className={styles.icon}>
<Image className={styles.timeline} src={WechatTimeline} />
</View>
<Text></Text>
</Button>
</View>
</View>
</CommonPopup>
)
);
});
// 分享弹窗
const SharePopup = forwardRef(({ id, from, detail, userInfo }, ref) => {
const [visible, setVisible] = useState(false);
const posterRef = useRef();
useEffect(() => {
changeMessageType();
}, []);
async function changeMessageType() {
try {
const res = await DetailService.getActivityId({
business_id: id,
business_type: "game",
is_private: false,
});
if (res.code === 0) {
Taro.updateShareMenu({
withShareTicket: false, // 是否需要返回 shareTicket
isUpdatableMessage: true, // 是否是动态消息(需要服务端配置过模版)
activityId: res.data.activity_id, // 动态消息的活动 id
});
}
} catch (e) {
Taro.showToast({ title: e.message, icon: "none" });
}
}
useImperativeHandle(ref, () => ({
show: () => {
setVisible(true);
},
}));
useShareAppMessage(async (res) => {
const {
play_type,
skill_level_max,
skill_level_min,
start_time,
end_time,
location_name,
venue_image_list,
} = detail || {};
const startTime = dayjs(start_time);
const endTime = dayjs(end_time);
const dayofWeek = DayOfWeekMap.get(startTime.day());
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
const url = await generateShareImage({
userAvatar: userInfo.avatar_url,
userNickname: userInfo.nickname,
gameType: play_type,
skillLevel: `NTRP ${genNTRPRequirementText(
skill_level_min,
skill_level_max
)}`,
gameDate: `${startTime.format("M月D日")} (${dayofWeek})`,
gameTime: `${startTime.format("ah")}${gameLength}`,
venueName: location_name,
venueImages: venue_image_list ? venue_image_list.map((c) => c.url) : [],
});
// console.log(res, "res");
return {
title: detail.title,
imageUrl: url || "https://img.yzcdn.cn/vant/cat.jpeg",
path: `/game_pages/detail/index?id=${id}&from=share`,
};
});
async function handlePost() {
const {
play_type,
skill_level_max,
skill_level_min,
start_time,
end_time,
location_name,
image_list,
title,
} = detail || {};
const { avatar_url, nickname } = userInfo;
const startTime = dayjs(start_time);
const endTime = dayjs(end_time);
const dayofWeek = DayOfWeekMap.get(startTime.day());
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
Taro.showLoading({ title: "生成中..." });
const url = await generatePosterImage({
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}`,
});
Taro.hideLoading();
setVisible(false);
Taro.showShareImageMenu({
path: url,
});
}
function onClose() {
setVisible(false);
}
return (
<>
<CommonPopup
title="分享至"
visible={visible}
onClose={onClose}
showHeader={false}
hideFooter
enableDragToClose={false}
style={{ minHeight: "100px" }}
zIndex={1000}
>
<View className={styles.shareContainer}>
<View catchMove className={styles.title}>
<Text></Text>
<View className={styles.closeIconWrap} onClick={onClose}>
<Image className={styles.closeIcon} src={CrossIcon} />
</View>
</View>
<View className={styles.shareItems}>
<Button className={styles.button} openType="share">
<View className={classnames(styles.icon, styles.wechatIcon)}>
<Image className={styles.wechat} src={WechatLogo} />
</View>
<Text></Text>
</Button>
<Button className={styles.button} onClick={handlePost}>
<View className={styles.icon}>
<Image className={styles.download} src={DownloadIcon} />
</View>
<Text></Text>
</Button>
<Button className={styles.button}>
<View className={styles.icon}>
<Image className={styles.linkIcon} src={LinkIcon} />
</View>
<Text></Text>
</Button>
</View>
</View>
</CommonPopup>
{/* <PosterPopup ref={posterRef} id={detail.id} /> */}
</>
);
});
function navto(url) {
Taro.navigateTo({
@@ -1346,6 +1533,8 @@ function Index() {
ref={sharePopupRef}
id={id as string}
from={from as string}
detail={detail}
userInfo={userInfo}
/>
</View>
</View>

View File

@@ -2,26 +2,173 @@
.title {
padding: 20px;
color: #000;
text-align: center;
// text-align: center;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 28px;
display: flex;
align-items: center;
justify-content: space-between;
.closeIconWrap {
display: flex;
width: 40px;
height: 40px;
justify-content: center;
align-items: center;
gap: 6px;
flex-shrink: 0;
border-radius: 999px;
border: 1px solid rgba(0, 0, 0, 0.06);
background: #fff;
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06);
.closeIcon {
width: 24px;
height: 24px;
}
}
}
.shareItems {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: space-around;
padding-bottom: 60px;
.button {
width: 140px;
height: 40px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
gap: 12px;
color: rgba(0, 0, 0, 0.85);
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: normal;
background-color: #fff;
border: none;
padding: 0;
margin: 0;
line-height: normal;
font-size: inherit;
color: inherit;
&:after {
border: none;
background: transparent;
}
.icon {
width: 64px;
height: 64px;
border-radius: 50%;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.1);
&.wechatIcon {
background-color: #07c160;
}
.download {
width: 28px;
height: 28px;
}
.wechat {
width: 36px;
height: 30px;
}
.linkIcon {
width: 28px;
height: 28px;
}
}
}
}
}
.posterWrap {
.posterContainer {
background: linear-gradient(180deg, #fff 0%, #fafafa 100%), #fff;
padding: 20px;
}
.posterWrap {
border-radius: 19.067px;
border: 1px solid rgba(0, 0, 0, 0.06);
background: linear-gradient(180deg, #bfffef 0%, #f2fffc 100%), #fff;
box-shadow: 0 6.933px 55.467px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
box-sizing: border-box;
}
.sharePoster {
margin-top: 40px;
display: flex;
align-items: center;
justify-content: space-around;
.shareItem {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
gap: 12px;
color: rgba(0, 0, 0, 0.85);
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: normal;
background-color: #fff;
border: none;
padding: 0;
margin: 0;
line-height: normal;
font-size: inherit;
color: inherit;
&:after {
border: none;
background: transparent;
}
.icon {
width: 64px;
height: 64px;
border-radius: 50%;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.1);
&.wechatIcon {
background-color: #07c160;
}
.download {
width: 28px;
height: 28px;
}
.wechat {
width: 36px;
height: 30px;
}
.timeline {
width: 32px;
height: 32px;
}
}
}
}