import React, {
useState,
useEffect,
useRef,
useImperativeHandle,
forwardRef,
} from "react";
import { View, Text, Image, Map, ScrollView, Button } from "@tarojs/components";
// import { Avatar } from "@nutui/nutui-react-taro";
import Taro, {
useRouter,
useShareAppMessage,
useShareTimeline,
useDidShow,
} from "@tarojs/taro";
import classnames from "classnames";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
// 导入API服务
import {
CommonPopup,
withAuth,
NTRPEvaluatePopup,
GameManagePopup,
Comments,
} from "@/components";
// import {
// EvaluateType,
// SceneType,
// DisplayConditionType,
// } from "@/components/NTRPEvaluatePopup";
import DetailService, {
MATCH_STATUS,
IsSubstituteSupported,
} from "@/services/detailService";
import * as LoginService from "@/services/loginService";
import OrderService from "@/services/orderService";
import { getCurrentLocation, calculateDistance } from "@/utils/locationUtils";
// import { getCurrentFullPath } from "@/utils";
import { useUserInfo, useUserActions } from "@/store/userStore";
import { EvaluateCallback, EvaluateScene } from "@/store/evaluateStore";
import img from "@/config/images";
import styles from "./style.module.scss";
import "./index.scss";
dayjs.locale("zh-cn");
// 将·作为连接符插入到标签文本之间
function insertDotInTags(tags: string[]) {
if (!tags) return [];
return tags.join("-·-").split("-");
}
function GameTags(props) {
const { userInfo, handleViewUserInfo } = props;
const { avatar_url, id } = userInfo;
const tags = [
{
name: "🕙 急招",
icon: "",
},
{
name: "🔥 本周热门",
icon: "",
},
{
name: "🎉 新活动",
icon: "",
},
{
name: "官方组织",
icon: "",
},
];
return (
{/* network image mock */}
{tags.map((tag, index) => (
{tag.icon && }
{tag.name}
))}
);
}
type CourselItemType = {
url: string;
width: number;
height: number;
};
function Coursel(props) {
const { detail } = props;
const [list, setList] = useState([]);
const [listWidth, setListWidth] = useState(0);
const { image_list } = detail;
async function getImagesMsg(imageList) {
const latest_list: CourselItemType[] = [];
const sys_info = await Taro.getSystemInfo();
const max_width = sys_info.screenWidth - 30;
const max_height = 240;
const current_aspect_ratio = max_width / max_height;
let container_width = 0;
for (const imageUrl of imageList) {
const { width, height } = await Taro.getImageInfo({ src: imageUrl });
if (width && height) {
const aspect_ratio = width / height;
const latest_w_h = { width, height };
if (aspect_ratio < current_aspect_ratio) {
latest_w_h.width = max_height * aspect_ratio;
latest_w_h.height = max_height;
} else {
latest_w_h.width = max_width;
latest_w_h.height = max_width / aspect_ratio;
}
container_width += latest_w_h.width + 12;
latest_list.push({
url: imageUrl,
width: latest_w_h.width,
height: latest_w_h.height,
});
}
}
setList(latest_list);
setListWidth(container_width);
}
useEffect(() => {
getImagesMsg(image_list || []);
}, [image_list]);
return (
{list.map((item, index) => {
return (
);
})}
);
}
// 分享弹窗
const SharePopup = forwardRef(
({ id, from }: { id: string; from: string }, ref) => {
const [visible, setVisible] = useState(false);
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" });
},
});
}
return (
{
setVisible(false);
}}
hideFooter
style={{ minHeight: "100px" }}
>
分享卡片
);
}
);
function navto(url) {
Taro.navigateTo({
url: url,
});
}
function toast(message) {
Taro.showToast({ title: message, icon: "none" });
}
function isFull(counts) {
const {
max_players,
current_players,
max_substitute_players,
current_substitute_count,
is_substitute_supported,
} = counts;
if (max_players === current_players) {
return true;
} else if (is_substitute_supported === IsSubstituteSupported.SUPPORT) {
return max_substitute_players === current_substitute_count;
}
return false;
}
// 底部操作栏
function StickyButton(props) {
const {
handleShare,
handleJoinGame,
detail,
onStatusChange,
handleAddComment,
getCommentCount,
} = props;
const [commentCount, setCommentCount] = useState(0);
const ntrpRef = useRef<{
show: (evaluateCallback: EvaluateCallback) => void;
}>({ show: () => {} });
const {
id,
price,
user_action_status,
match_status,
start_time,
end_time,
is_organizer,
} = detail || {};
const gameManageRef = useRef();
function handleSelfEvaluate() {
ntrpRef?.current?.show({
type: EvaluateScene.detail,
next: (flag) => {
if (flag) {
Taro.navigateTo({
url: `/order_pages/orderDetail/index?gameId=${id}`,
});
return;
}
Taro.redirectTo({ url: `/order_pages/orderDetail/index?gameId=${id}` });
},
onCancel: () => {
// Taro.redirectTo({ url: `/game_pages/detail/index?id=${id}` });
Taro.navigateBack();
},
});
}
useEffect(() => {
getCommentCount?.((count) => {
setCommentCount(count);
});
}, [getCommentCount]);
function generateTextAndAction(
user_action_status: null | { [key: string]: boolean }
):
| undefined
| { text: string | React.FC; action?: () => void; available?: boolean } {
if (!user_action_status) {
return;
}
const displayPrice = is_organizer ? 0 : price;
// user_action_status.can_assess = true;
// user_action_status.can_join = false;
// console.log(user_action_status, "user_action");
const {
can_assess,
can_join,
can_substitute,
can_pay,
is_substituting,
waiting_start,
} = user_action_status || {};
if (MATCH_STATUS.CANCELED === match_status) {
return {
text: "活动已取消",
available: false,
// action: () => toast("活动已取消"),
};
} else if (dayjs(end_time).isBefore(dayjs())) {
return {
text: "活动已结束",
available: false,
// action: () => toast("活动已结束"),
};
} else if (dayjs(start_time).isBefore(dayjs())) {
return {
text: "活动已开始",
available: false,
// action: () => toast("活动已开始"),
};
} else if (isFull(detail)) {
return {
text: "活动已满员",
available: false,
// action: () => toast("活动已满员"),
};
}
if (waiting_start) {
return {
text: () => ¥{displayPrice} 已加入,
action: () => toast("已加入"),
};
} else if (is_substituting) {
return {
text: () => ¥{displayPrice} 已加入候补,
action: () => toast("已加入候补"),
};
} else if (can_pay) {
return {
text: () => ¥{price} 继续支付,
action: async () => {
const res = await OrderService.getUnpaidOrder(id);
if (res.code === 0) {
navto(
`/order_pages/orderDetail/index?id=${res.data.order_info.order_id}`
);
}
},
};
} else if (can_substitute) {
return {
text: () => ¥{displayPrice} 我要候补,
action: handleJoinGame,
};
} else if (can_join) {
return {
text: () => {
return ¥{displayPrice} 立即加入;
},
action: handleJoinGame,
};
} else if (can_assess) {
return {
text: () => ¥{displayPrice} 立即加入,
action: handleSelfEvaluate,
};
}
return {
text: "球局无法加入",
available: false,
};
}
if (!user_action_status) {
return "";
}
const {
text,
available = true,
action = () => {},
} = generateTextAndAction(user_action_status)!;
let ActionText: React.FC | string = text;
if (typeof ActionText === "string") {
ActionText = () => {
return {text as string};
};
}
return (
<>
分享
{
// Taro.showToast({ title: "To be continued", icon: "none" });
handleAddComment();
}}
>
{commentCount > 0 ? commentCount : "评论"}
{is_organizer && (
{
gameManageRef.current.show(detail, onStatusChange);
}}
>
管理
)}
>
);
}
// 球局信息
function GameInfo(props) {
const { detail, currentLocation } = props;
const {
latitude,
longitude,
location,
location_name,
start_time,
end_time,
weather,
} = detail || {};
const [{ iconDay, tempMax, tempMin }] = weather || [{}];
const openMap = () => {
Taro.openLocation({
latitude, // 纬度(必填)
longitude, // 经度(必填)
name: location_name, // 位置名(可选)
address: location, // 地址详情(可选)
scale: 15, // 地图缩放级别(1-28)
});
};
const [c_latitude, c_longitude] = currentLocation;
const distance =
latitude && longitude
? calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000
: 0;
const startTime = dayjs(start_time);
const endTime = dayjs(end_time);
const game_length = endTime.diff(startTime, "minutes") / 60;
const startMonth = startTime.format("M");
const startDay = startTime.format("D");
const theDayOfWeek = startTime.format("dddd");
const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}`;
const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`;
return (
{/* Date and Weather */}
{/* Calendar and Date time */}
{/* Calendar */}
{startMonth}月
{startDay}
{/* Date time */}
{startDate}
{gameRange} ({game_length}小时)
{/* Weather */}
{/* Weather icon */}
{/**/}
{/* Weather text and temperature */}
{tempMin && tempMax && (
{tempMin}℃ - {tempMax}℃
)}
{/* Place */}
{/* venue location message */}
{/* location icon */}
{/* location message */}
{/* venue name and distance */}
{distance ? (
{location_name || "-"}
·
{distance.toFixed(1)}km
) : (
""
)}
{/* venue address */}
{location || "-"}
{/* venue map */}
{longitude && latitude && (
);
}
// 场馆信息
function VenueInfo(props) {
const { detail } = props;
const [visible, setVisible] = useState(false);
const {
venue_description,
venue_description_tag = [],
venue_image_list = [],
} = detail;
function showScreenShot() {
setVisible(true);
}
function onClose() {
setVisible(false);
}
function previewImage(current_url) {
Taro.previewImage({
current: current_url,
urls:
venue_image_list?.length > 0 ? venue_image_list.map((c) => c.url) : [],
});
}
return (
{/* venue detail title and venue ordered status */}
场馆详情
{venue_image_list?.length > 0 ? (
<>
·
已订场
>
) : (
""
)}
{/* venue detail content */}
{/* venue detail tags */}
{insertDotInTags(venue_description_tag).map((tag, index) => (
{tag}
))}
{/* venue remarks */}
{venue_description}
预定截图
{venue_image_list?.length > 0 &&
venue_image_list.map((item) => {
return (
);
})}
);
}
function genNTRPRequirementText(min, max) {
if (min && max && min !== max) {
return `${min} - ${max} 之间`;
} else if (max === "1") {
return "无要求";
} else if (max) {
return `${max} 以上`;
}
return "-";
}
// 玩法要求
function GamePlayAndRequirement(props) {
const {
detail: { skill_level_min, skill_level_max, play_type, game_type },
} = props;
const requirements = [
{
title: "NTRP水平要求",
desc: genNTRPRequirementText(skill_level_min, skill_level_max),
},
{
title: "活动玩法",
desc: play_type || "-",
},
{
title: "人员构成",
desc: game_type || "-",
},
];
return (
{/* title */}
玩法要求
{/* requirements */}
{requirements.map((item, index) => (
{item.title}
{item.desc}
))}
);
}
// 参与者
function Participants(props) {
const { detail = {}, handleJoinGame, handleViewUserInfo } = props;
const participants = detail.participants || [];
const {
participant_count,
max_participants,
user_action_status = {},
} = detail;
const { can_join, can_pay, can_substitute, is_substituting, waiting_start } =
user_action_status;
const showApplicationEntry =
[can_pay, can_substitute, is_substituting, waiting_start].every(
(item) => !item
) && can_join;
const leftCount = max_participants - participant_count;
return (
参与者
·
{leftCount > 0 ? `剩余空位 ${leftCount}` : "已满员"}
{participant_count > 0 || showApplicationEntry ? (
{/* application */}
{showApplicationEntry && (
{
handleJoinGame();
}}
>
申请加入
)}
{/* participants list */}
{participants.map((participant) => {
const {
is_organizer,
user: {
avatar_url,
nickname,
level,
id: participant_user_id,
},
} = participant;
const role = is_organizer ? "组织者" : "参与者";
return (
{nickname || "未知"}
{level || "未知"}
{role}
);
})}
) : (
""
)}
);
}
function SupplementalNotes(props) {
const {
detail: { description, description_tag },
} = props;
return (
补充说明
{/* supplemental notes tags */}
{insertDotInTags(description_tag || []).map((tag, index) => (
{tag}
))}
{/* supplemental notes content */}
{description}
);
}
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,
};
});
}
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 (
{/* orgnizer title */}
组织者
{/* organizer avatar and name */}
{nickname}
已组织 {hosted_games_count} 次
NTRP {ntrp_level || "初学者"}
{my_id === id ? (
""
) : (
{is_following ? (
取消关注
) : (
<>
关注
>
)}
)}
handleAddComment()}
>
{/* recommend games by organizer */}
{recommendGames.length > 0 && (
TA的更多活动
{recommendGames.map((game, index) => (
{/* game title */}
{game.title}
{/* game time and range */}
{game.time}
{game.timeLength}
{/* game location、vunue、distance */}
{game.venue}
·
{game.venueType}
·
{game.distance}
{/* organizer avatar、applications、level requirements、play type */}
{
e.stopPropagation();
handleViewUserInfo(id);
}}
/>
报名人数 {game.checkedApplications}/
{game.applications}
{game.levelRequirements}
{game.playType}
))}
)}
);
}
function Index() {
const [detail, setDetail] = useState({});
const { params } = useRouter();
const [currentLocation, setCurrentLocation] = useState<[number, number]>([
0, 0,
]);
const { id, from } = params;
const [userInfo, setUserInfo] = useState({}); // 组织者的userInfo
const { fetchUserInfo } = useUserActions(); // 获取登录用户的userInfo
const myInfo = useUserInfo();
const isMyOwn = userInfo.id === myInfo.id;
const sharePopupRef = useRef(null);
const commentRef = useRef();
useDidShow(async () => {
await updateLocation();
await fetchUserInfo();
// await fetchDetail();
});
const updateLocation = async () => {
try {
const { address, ...location } = await getCurrentLocation();
setCurrentLocation([location.latitude, location.longitude]);
// 使用 userStore 中的统一位置更新方法
// await updateUserInfo({ latitude: location.latitude, longitude: location.longitude })
await DetailService.updateLocation({
latitude: Number(location.latitude),
longitude: Number(location.longitude),
});
// 位置更新后,重新获取详情页数据(因为距离等信息可能发生变化)
await fetchDetail();
} catch (error) {
console.error("用户位置更新失败", error);
}
};
const fetchDetail = async () => {
if (!id) return;
try {
const res = await DetailService.getDetail(Number(id));
if (res.code === 0) {
setDetail(res.data);
fetchUserInfoById(res.data.publisher_id);
}
} catch (e) {
if (e.message === "球局不存在") {
handleBack();
}
}
};
const onUpdateUserInfo = () => {
fetchUserInfoById(detail.publisher_id);
};
async function fetchUserInfoById(user_id) {
const userDetailInfo = await LoginService.getUserInfoById(user_id);
if (userDetailInfo.code === 0) {
setUserInfo(userDetailInfo.data);
}
}
function handleShare() {
sharePopupRef.current.show();
}
const handleJoinGame = async () => {
if (isMyOwn) {
const res = await DetailService.organizerJoin(Number(id));
if (res.code === 0) {
toast("加入成功");
fetchDetail();
}
return;
}
navto(`/order_pages/orderDetail/index?gameId=${id}`);
};
function onStatusChange(result) {
if (result) {
fetchDetail();
}
}
function handleBack() {
const pages = Taro.getCurrentPages();
if (pages.length <= 1) {
Taro.redirectTo({
url: "/game_pages/list/index",
});
} else {
Taro.navigateBack();
}
}
function handleViewUserInfo(userId) {
navto(`/user_pages/other/index?userid=${userId}`);
}
const backgroundImage = detail?.image_list?.[0]
? { backgroundImage: `url(${detail?.image_list?.[0]})` }
: {};
return (
{/* custom navbar */}
{/* swiper */}
{/* content */}
{/* avatar and tags */}
{/* title */}
{detail.title}
{/* Date and Place and weather */}
{/* detail */}
{/* gameplay requirements */}
{/* participants */}
{/* supplemental notes */}
{/* organizer and recommend games by organizer */}
{
commentRef.current && commentRef.current.addComment();
}}
/>
{/* sticky bottom action bar */}
{
commentRef.current && commentRef.current.addComment();
}}
getCommentCount={
commentRef.current && commentRef.current.getCommentCount
}
/>
{/* share popup */}
);
}
export default withAuth(Index);