Files
mini-programs/src/game_pages/detail/index.tsx
2026-02-07 20:19:04 +08:00

309 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useRef } from "react";
import { View, Text, Image, ScrollView } from "@tarojs/components";
import Taro, { useRouter, useDidShow } from "@tarojs/taro";
import classnames from "classnames";
import { throttle } from "@tarojs/runtime";
// 导入API服务
import { withAuth, Comments } from "@/components";
import DetailService from "@/services/detailService";
import * as LoginService from "@/services/loginService";
import { getCurrentLocation } from "@/utils/locationUtils";
import { useUserInfo, useUserActions } from "@/store/userStore";
import { useGlobalState } from "@/store/global";
import { waitForAuthInit } from "@/utils/authInit";
import { requireLoginWithPhone } from "@/utils/helper";
import GameTags from "./components/GameTags";
import Carousel from "./components/Carousel";
import StickyBottom from "./components/StickyBottom";
import GameInfo from "./components/GameInfo";
import VenueInfo from "./components/VenueInfo";
import GamePlayAndRequirement from "./components/GamePlayAndReq";
import Participants from "./components/Participants";
import SupplementalNotes from "./components/SupplementalNotes";
import OrganizerInfo from "./components/OrganizerInfo";
import SharePopup from "./components/SharePopup";
import { navto, toast } from "@/utils/helper";
import { delay } from "@/utils";
import ArrowLeft from "@/static/detail/icon-arrow-left.svg";
// import Logo from "@/static/detail/icon-logo-go.svg";
import styles from "./index.module.scss";
function Index() {
const [detail, setDetail] = useState<any>({});
const { params } = useRouter();
const [currentLocation, setCurrentLocation] = useState<[number, number]>([
0, 0,
]);
const { id, from, message_id } = params;
const [userInfo, setUserInfo] = useState({}); // 组织者的userInfo
const { fetchUserInfo } = useUserActions(); // 获取登录用户的userInfo
const myInfo = useUserInfo();
const { statusNavbarHeightInfo } = useGlobalState();
const { statusBarHeight, navBarHeight, totalHeight } = statusNavbarHeightInfo;
const isMyOwn = userInfo.id === myInfo.id;
const sharePopupRef = useRef<any>(null);
const commentRef = useRef();
useEffect(() => {
const init = async () => {
updateLocation();
// 先等待静默登录完成
await waitForAuthInit();
// 然后再获取用户信息
await fetchUserInfo();
// await delay(1000);
if (from === "publish") {
handleShare(true);
}
};
init();
}, []);
useDidShow(() => {
// await updateLocation();
// await fetchUserInfo();
if (id) {
// Taro.showLoading();
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),
});
// 位置更新后,重新获取详情页数据(因为距离等信息可能发生变化)
// 注意:这里不调用 fetchDetail避免与 useDidShow 中的调用重复
// 如果需要更新距离信息,可以在 fetchDetail 成功后根据当前位置重新计算
// if (from === "publish") {
// handleShare(true);
// }
} catch (error) {
console.error("用户位置更新失败", error);
}
};
const fetchDetail = async () => {
if (!id) return;
const res = await DetailService.getDetail(Number(id)).catch((e) => {
// 跳转到空状态页面
(Taro as any).redirectTo({
url: "/other_pages/emptyState/index",
});
return e;
});
if (res.code === 0) {
setDetail(res.data);
fetchUserInfoById(res.data.publisher_id);
}
// Taro.hideLoading();
};
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(flag = false) {
sharePopupRef.current.show(flag);
}
const handleJoinGame = async () => {
// 检查登录状态和手机号
if (!requireLoginWithPhone()) {
return; // 未登录或未绑定手机号,已跳转到登录页
}
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: "/main_pages/index",
});
} else {
Taro.navigateBack();
}
}
function handleViewUserInfo(userId) {
navto(
userId === myInfo.id
? "/user_pages/myself/index"
: `/user_pages/other/index?userid=${userId}`,
);
}
const backgroundImage = detail?.image_list?.[0]
? { opacity: 1, backgroundImage: `url(${detail?.image_list?.[0]})` }
: { opacity: 0 };
const [glass, setGlass] = useState(false);
const onScroll = throttle((e) => {
const top = e.detail.scrollTop;
setGlass(top > 20);
}, 16);
const [scrollToTarget, setScrollToTarget] = useState("");
return (
<ScrollView
className={styles["detail-page"]}
scrollY
onScroll={onScroll}
onScrollToUpper={() => {
setGlass(false);
}}
enhanced
showScrollbar={false}
scrollIntoView={scrollToTarget}
// scroll-with-animation
>
{/* custom navbar */}
<View
className={classnames(
styles["custom-navbar"],
glass ? styles.glass : "",
)}
style={{
height: `${totalHeight}px`,
paddingTop: `${statusBarHeight}px`,
}}
>
<View className={styles["detail-navigator"]}>
<View
className={styles["detail-navigator-back"]}
onClick={handleBack}
>
<Image
className={styles["detail-navigator-back-icon"]}
src={ArrowLeft}
/>
</View>
{/* <View className={styles["detail-navigator-icon"]}>
<Image
className={styles["detail-navigator-logo-icon"]}
src={Logo}
/>
</View> */}
</View>
</View>
<View className={styles["detail-page-bg"]} style={backgroundImage} />
<View style={{ paddingTop: `${totalHeight}px` }}> </View>
{/* swiper */}
<Carousel detail={detail} />
{/* content */}
<View className={styles["detail-page-content"]}>
{/* avatar and tags */}
<GameTags
detail={detail}
userInfo={userInfo}
handleViewUserInfo={handleViewUserInfo}
/>
{/* title */}
<View className={styles["detail-page-content-title"]}>
<Text className={styles["detail-page-content-title-text"]}>
{detail.title}
</Text>
</View>
{/* Date and Place and weather */}
<GameInfo detail={detail} currentLocation={currentLocation} />
{/* detail */}
<VenueInfo detail={detail} />
{/* gameplay requirements */}
<GamePlayAndRequirement detail={detail} />
{/* participants */}
<Participants
detail={detail}
handleJoinGame={handleJoinGame}
handleViewUserInfo={handleViewUserInfo}
/>
{/* supplemental notes */}
<SupplementalNotes detail={detail} />
{/* organizer and recommend games by organizer */}
<OrganizerInfo
detail={detail}
userInfo={userInfo}
currentLocation={currentLocation}
onUpdateUserInfo={onUpdateUserInfo}
handleViewUserInfo={handleViewUserInfo}
handleAddComment={() => {
commentRef.current && commentRef.current.addComment();
}}
/>
<Comments
ref={commentRef}
game_id={Number(detail.id)}
message_id={message_id ? Number(message_id) : undefined}
publisher_id={Number(detail.publisher_id)}
onScrollTo={setScrollToTarget}
/>
{/* sticky bottom action bar */}
<StickyBottom
handleShare={handleShare}
handleJoinGame={handleJoinGame}
detail={detail}
onStatusChange={onStatusChange}
handleAddComment={() => {
commentRef.current && commentRef.current.addComment();
}}
getCommentCount={
commentRef.current && commentRef.current.getCommentCount
}
currentUserInfo={myInfo}
/>
{/* share popup */}
<SharePopup
ref={sharePopupRef}
id={id as string}
from={from as string}
detail={detail}
userInfo={myInfo}
/>
</View>
</ScrollView>
);
}
export default Index;