feat: 生成分享图
This commit is contained in:
239
src/game_pages/detail/components/OrganizerInfo/index.module.scss
Normal file
239
src/game_pages/detail/components/OrganizerInfo/index.module.scss
Normal file
@@ -0,0 +1,239 @@
|
||||
.detail-page-content-organizer-recommend-games {
|
||||
padding: 24px 15px 10px;
|
||||
|
||||
.organizer-title {
|
||||
overflow: hidden;
|
||||
padding-bottom: 6px;
|
||||
height: 24px;
|
||||
color: #fff;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
text-overflow: ellipsis;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 24px; /* 150% */
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.organizer-avatar-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
justify-content: flex-start;
|
||||
|
||||
&-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&-name {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 184.615% */
|
||||
}
|
||||
|
||||
&-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px; /* 133.333% */
|
||||
letter-spacing: 0.06px;
|
||||
|
||||
&-separator {
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.organizer-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
|
||||
.organizer-actions-follow,
|
||||
.organizer-actions-comment {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 999px;
|
||||
// border: 0.5px solid rgba(255, 255, 255, 0.10);
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
box-shadow: 0 4px 48px 0 rgba(0, 0, 0, 0.08);
|
||||
|
||||
& > image {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.organizer-actions-follow {
|
||||
padding: 8px 12px 8px;
|
||||
&-text {
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 153.846% */
|
||||
letter-spacing: -0.23px;
|
||||
}
|
||||
}
|
||||
|
||||
.organizer-actions-comment {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.organizer-recommend-games {
|
||||
padding-top: 20px;
|
||||
|
||||
.organizer-recommend-games-title {
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
font-family: "PingFang SC";
|
||||
font-size: 15px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 160% */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
align-self: stretch;
|
||||
|
||||
&-arrow {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.recommend-games-list {
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.recommend-games-list-item {
|
||||
width: 246px;
|
||||
height: 122px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(33, 178, 0, 0.2);
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
padding: 12px 0 12px 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
text-overflow: ellipsis;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 24px; /* 150% */
|
||||
|
||||
&-arrow {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-time-range {
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
text-overflow: ellipsis;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 150% */
|
||||
}
|
||||
|
||||
&-location-venue-distance {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
text-overflow: ellipsis;
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 150% */
|
||||
}
|
||||
|
||||
&-addon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&-message {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
&-applications,
|
||||
&-level-requirements,
|
||||
&-play-type {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 181.818% */
|
||||
letter-spacing: -0.23px;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
padding: 6px 8px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 999px;
|
||||
// border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
276
src/game_pages/detail/components/OrganizerInfo/index.tsx
Normal file
276
src/game_pages/detail/components/OrganizerInfo/index.tsx
Normal file
@@ -0,0 +1,276 @@
|
||||
import Taro from "@tarojs/taro";
|
||||
import dayjs from "dayjs";
|
||||
import { View, Text, Image, ScrollView } from "@tarojs/components";
|
||||
import { calculateDistance } from "@/utils";
|
||||
import { useUserInfo } from "@/store/userStore";
|
||||
import * as LoginService from "@/services/loginService";
|
||||
import img from "@/config/images";
|
||||
import { navto } from "../../utils/helper";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
function genRecommendGames(games, location, avatar) {
|
||||
return games.map((item) => {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
start_time,
|
||||
end_time,
|
||||
court_type,
|
||||
location_name,
|
||||
current_players,
|
||||
max_players,
|
||||
latitude,
|
||||
longitude,
|
||||
skill_level_max,
|
||||
skill_level_min,
|
||||
play_type,
|
||||
} = item;
|
||||
const [c_latitude, c_longitude] = location;
|
||||
const distance =
|
||||
calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000;
|
||||
const startTime = dayjs(start_time);
|
||||
const endTime = dayjs(end_time);
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
time: startTime.format("YYYY-MM-DD HH:MM"),
|
||||
timeLength: endTime.diff(startTime, "hour"),
|
||||
venue: location_name,
|
||||
venueType: court_type,
|
||||
distance: `${distance.toFixed(2)}km`,
|
||||
avatar,
|
||||
applications: max_players,
|
||||
checkedApplications: current_players,
|
||||
levelRequirements:
|
||||
skill_level_max !== skill_level_min
|
||||
? `${skill_level_min || "-"}至${skill_level_max || "-"}`
|
||||
: skill_level_min === "1"
|
||||
? "无要求"
|
||||
: `${skill_level_min}以上`,
|
||||
playType: play_type,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default function OrganizerInfo(props) {
|
||||
const {
|
||||
userInfo,
|
||||
currentLocation: location,
|
||||
onUpdateUserInfo = () => {},
|
||||
handleViewUserInfo,
|
||||
handleAddComment,
|
||||
} = props;
|
||||
const {
|
||||
id,
|
||||
nickname,
|
||||
avatar_url,
|
||||
is_following,
|
||||
ntrp_level,
|
||||
stats: { hosted_games_count } = {},
|
||||
ongoing_games = [],
|
||||
} = userInfo;
|
||||
|
||||
const myInfo = useUserInfo();
|
||||
const { id: my_id } = myInfo as LoginService.UserInfoType;
|
||||
|
||||
const recommendGames = genRecommendGames(ongoing_games, location, avatar_url);
|
||||
|
||||
const toggleFollow = async (follow) => {
|
||||
try {
|
||||
if (follow) {
|
||||
await LoginService.unFollowUser(id);
|
||||
} else {
|
||||
await LoginService.followUser(id);
|
||||
}
|
||||
onUpdateUserInfo();
|
||||
Taro.showToast({
|
||||
title: `${nickname} ${follow ? "已取消关注" : "已关注"}`,
|
||||
icon: "success",
|
||||
});
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
title: `${nickname} ${follow ? "取消关注失败" : "关注失败"}`,
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function handleViewGame(gameId) {
|
||||
navto(`/game_pages/detail/index?id=${gameId}&from=current`);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={styles["detail-page-content-organizer-recommend-games"]}>
|
||||
{/* orgnizer title */}
|
||||
<View className={styles["organizer-title"]}>
|
||||
<Text>组织者</Text>
|
||||
</View>
|
||||
{/* organizer avatar and name */}
|
||||
<View className={styles["organizer-avatar-name"]}>
|
||||
<Image
|
||||
className={styles["organizer-avatar-name-avatar"]}
|
||||
src={avatar_url}
|
||||
mode="aspectFill"
|
||||
onClick={handleViewUserInfo.bind(null, id)}
|
||||
/>
|
||||
<View className={styles["organizer-avatar-name-message"]}>
|
||||
<Text className={styles["organizer-avatar-name-message-name"]}>
|
||||
{nickname}
|
||||
</Text>
|
||||
<View className={styles["organizer-avatar-name-message-stats"]}>
|
||||
<Text>已组织 {hosted_games_count} 次</Text>
|
||||
<View
|
||||
className={
|
||||
styles["organizer-avatar-name-message-stats-separator"]
|
||||
}
|
||||
/>
|
||||
<Text>NTRP {ntrp_level || "初学者"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className={styles["organizer-actions"]}>
|
||||
{my_id === id ? (
|
||||
""
|
||||
) : (
|
||||
<View
|
||||
className={styles["organizer-actions-follow"]}
|
||||
onClick={toggleFollow.bind(null, is_following)}
|
||||
>
|
||||
{is_following ? (
|
||||
<Text className={styles["organizer-actions-follow-text"]}>
|
||||
取消关注
|
||||
</Text>
|
||||
) : (
|
||||
<>
|
||||
<Image
|
||||
className={styles["organizer-actions-follow-icon"]}
|
||||
src={img.ICON_DETAIL_APPLICATION_ADD}
|
||||
/>
|
||||
<Text className={styles["organizer-actions-follow-text"]}>
|
||||
关注
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
<View
|
||||
className={styles["organizer-actions-comment"]}
|
||||
onClick={() => handleAddComment()}
|
||||
>
|
||||
<Image
|
||||
className={styles["organizer-actions-comment-icon"]}
|
||||
src={img.ICON_DETAIL_COMMENT}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* recommend games by organizer */}
|
||||
{recommendGames.length > 0 && (
|
||||
<View className={styles["organizer-recommend-games"]}>
|
||||
<View
|
||||
className={styles["organizer-recommend-games-title"]}
|
||||
onClick={handleViewUserInfo.bind(null, id)}
|
||||
>
|
||||
<Text>TA的更多活动</Text>
|
||||
<Image
|
||||
className={styles["organizer-recommend-games-title-arrow"]}
|
||||
src={img.ICON_DETAIL_ARROW_RIGHT}
|
||||
/>
|
||||
</View>
|
||||
<ScrollView className={styles["recommend-games-list"]} scrollX>
|
||||
<View className={styles["recommend-games-list-content"]}>
|
||||
{recommendGames.map((game, index) => (
|
||||
<View
|
||||
key={index}
|
||||
className={styles["recommend-games-list-item"]}
|
||||
onClick={handleViewGame.bind(null, game.id)}
|
||||
>
|
||||
{/* game title */}
|
||||
<View className={styles["recommend-games-list-item-title"]}>
|
||||
<Text>{game.title}</Text>
|
||||
<Image
|
||||
className={
|
||||
styles["recommend-games-list-item-title-arrow"]
|
||||
}
|
||||
src={img.ICON_DETAIL_ARROW_RIGHT}
|
||||
/>
|
||||
</View>
|
||||
{/* game time and range */}
|
||||
<View
|
||||
className={styles["recommend-games-list-item-time-range"]}
|
||||
>
|
||||
<Text>{game.time}</Text>
|
||||
<Text>{game.timeLength}</Text>
|
||||
</View>
|
||||
{/* game location、vunue、distance */}
|
||||
<View
|
||||
className={
|
||||
styles[
|
||||
"recommend-games-list-item-location-venue-distance"
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text>{game.venue}</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{game.venueType}</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{game.distance}</Text>
|
||||
</View>
|
||||
{/* organizer avatar、applications、level requirements、play type */}
|
||||
<View className={styles["recommend-games-list-item-addon"]}>
|
||||
<Image
|
||||
className={
|
||||
styles["recommend-games-list-item-addon-avatar"]
|
||||
}
|
||||
mode="aspectFill"
|
||||
src={game.avatar}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleViewUserInfo(id);
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
className={
|
||||
styles["recommend-games-list-item-addon-message"]
|
||||
}
|
||||
>
|
||||
<View
|
||||
className={
|
||||
styles[
|
||||
"recommend-games-list-item-addon-message-applications"
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
报名人数 {game.checkedApplications}/
|
||||
{game.applications}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
className={
|
||||
styles[
|
||||
"recommend-games-list-item-addon-message-level-requirements"
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text>{game.levelRequirements}</Text>
|
||||
</View>
|
||||
<View
|
||||
className={
|
||||
styles[
|
||||
"recommend-games-list-item-addon-message-play-type"
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text>{game.playType}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user