417 lines
12 KiB
TypeScript
417 lines
12 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
||
import Taro from "@tarojs/taro";
|
||
import classnames from "classnames";
|
||
import dayjs from "dayjs";
|
||
import { debounce } from "@tarojs/runtime";
|
||
import { Text, View, Image } from "@tarojs/components";
|
||
import OrderService from "@/services/orderService";
|
||
import { EvaluateCallback, EvaluateScene } from "@/store/evaluateStore";
|
||
import { MATCH_STATUS, IsSubstituteSupported } from "@/services/detailService";
|
||
import { GameManagePopup, NTRPEvaluatePopup } from "@/components";
|
||
import { useUserInfo } from "@/store/userStore";
|
||
import img from "@/config/images";
|
||
// import RMB_ICON from "@/static/detail/rmb.svg";
|
||
import { toast, navto } from "@/utils/helper";
|
||
import styles from "./index.module.scss";
|
||
|
||
function isFull(counts) {
|
||
const {
|
||
max_players,
|
||
current_players,
|
||
max_substitute_players,
|
||
current_substitute_count,
|
||
is_substitute_supported,
|
||
} = counts;
|
||
|
||
if (
|
||
current_players >= max_players &&
|
||
is_substitute_supported === IsSubstituteSupported.NOTSUPPORT
|
||
) {
|
||
return true;
|
||
} else if (
|
||
current_players >= max_players &&
|
||
is_substitute_supported === IsSubstituteSupported.SUPPORT
|
||
) {
|
||
return max_substitute_players === current_substitute_count;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
function RmbIcon() {
|
||
return <Text className={styles.rmbSymbol}>¥</Text>;
|
||
}
|
||
|
||
function matchNtrpRequestment(
|
||
target?: string,
|
||
min?: string,
|
||
max?: string,
|
||
): boolean {
|
||
// 目标值为空或 undefined
|
||
if (!target?.trim()) return true;
|
||
|
||
// 提取目标值中的第一个数字
|
||
const match = target.match(/-?\d+(\.\d+)?/);
|
||
if (!match) return true;
|
||
|
||
const value = parseFloat(match[0]);
|
||
const minNum = min !== undefined ? parseFloat(min) : undefined;
|
||
const maxNum = max !== undefined ? parseFloat(max) : undefined;
|
||
|
||
// min 和 max 都未定义 → 直接通过
|
||
if (minNum === undefined && maxNum === undefined) return true;
|
||
|
||
// min = max 或只有一边 undefined → 参考值判断,包含端点
|
||
if (minNum === undefined || maxNum === undefined || minNum === maxNum) {
|
||
return value >= (minNum ?? maxNum!);
|
||
}
|
||
|
||
// 正常区间判断,包含端点
|
||
return value >= minNum && value <= maxNum;
|
||
}
|
||
|
||
// 底部操作栏
|
||
export default function StickyButton(props) {
|
||
const {
|
||
handleShare,
|
||
handleJoinGame,
|
||
detail,
|
||
onStatusChange,
|
||
handleAddComment,
|
||
getCommentCount,
|
||
currentUserInfo,
|
||
} = props;
|
||
const [commentCount, setCommentCount] = useState(0);
|
||
const userInfo = useUserInfo();
|
||
const ntrpRef = useRef<{
|
||
show: (evaluateCallback: EvaluateCallback) => void;
|
||
}>({ show: () => {} });
|
||
const {
|
||
id,
|
||
price,
|
||
user_action_status,
|
||
match_status,
|
||
start_time,
|
||
end_time,
|
||
is_organizer,
|
||
skill_level_max,
|
||
skill_level_min,
|
||
} = detail || {};
|
||
|
||
const { ntrp_level } = currentUserInfo || {};
|
||
|
||
// 检查手机号绑定的包装函数
|
||
const checkPhoneAndExecute = (action: () => void) => {
|
||
return () => {
|
||
if (!userInfo?.phone) {
|
||
Taro.showModal({
|
||
title: "提示",
|
||
content: "该功能需要绑定手机号",
|
||
confirmText: "去绑定",
|
||
cancelText: "取消",
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
const currentPath = Taro.getCurrentInstance().router?.path || "";
|
||
const currentParams =
|
||
Taro.getCurrentInstance().router?.params || {};
|
||
const queryString = Object.keys(currentParams)
|
||
.map((key) => `${key}=${currentParams[key]}`)
|
||
.join("&");
|
||
const fullPath = queryString
|
||
? `${currentPath}?${queryString}`
|
||
: currentPath;
|
||
|
||
Taro.navigateTo({
|
||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||
fullPath,
|
||
)}`,
|
||
});
|
||
}
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
action();
|
||
};
|
||
};
|
||
|
||
const matchNtrpReq = matchNtrpRequestment(
|
||
ntrp_level,
|
||
skill_level_min,
|
||
skill_level_max,
|
||
);
|
||
|
||
const gameManageRef = useRef();
|
||
|
||
function handleSelfEvaluate() {
|
||
ntrpRef?.current?.show({
|
||
type: EvaluateScene.detail,
|
||
next: ({ flag, score }) => {
|
||
if (!matchNtrpRequestment(score, skill_level_min, skill_level_max)) {
|
||
toast("您当前不符合此球局NTRP水平要求,去看看其他活动吧~");
|
||
return;
|
||
}
|
||
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 priceStrArr = price.toString().split(".");
|
||
const displayPrice = is_organizer ? (
|
||
<>
|
||
<Text className={styles.integer}>0</Text>
|
||
{/* <Text className={styles.decimalPart}>.00</Text> */}
|
||
</>
|
||
) : (
|
||
<>
|
||
<Text className={styles.integer}>{priceStrArr[0]}</Text>
|
||
<Text className={styles.decimalPart}>.{priceStrArr[1]}</Text>
|
||
</>
|
||
);
|
||
// 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 (MATCH_STATUS.FINISHED === 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: () => (
|
||
<>
|
||
{/* <Image className={styles.crrrencySymbol} src={RMB_ICON} /> */}
|
||
<RmbIcon />
|
||
{displayPrice}
|
||
<Text className={styles.btnText}>已加入</Text>
|
||
</>
|
||
),
|
||
action: () => toast("您已参与了本次活动"),
|
||
};
|
||
} else if (is_substituting) {
|
||
return {
|
||
text: () => (
|
||
<>
|
||
<RmbIcon />
|
||
{displayPrice}
|
||
<Text className={styles.btnText}>已加入候补</Text>
|
||
</>
|
||
),
|
||
action: () => toast("您已加入候补,候补失败会全额退款~"),
|
||
};
|
||
} else if (can_pay) {
|
||
return {
|
||
text: () => (
|
||
<>
|
||
<RmbIcon />
|
||
{displayPrice}
|
||
<Text className={styles.btnText}>继续支付</Text>
|
||
</>
|
||
),
|
||
action: checkPhoneAndExecute(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 (!matchNtrpReq) {
|
||
return {
|
||
text: () => (
|
||
<>
|
||
<RmbIcon />
|
||
{displayPrice}
|
||
<Text className={styles.btnText}>立即加入</Text>
|
||
</>
|
||
),
|
||
available: false,
|
||
action: () =>
|
||
toast("您当前不符合此球局NTRP水平要求,去看看其他活动吧~"),
|
||
};
|
||
} else if (can_substitute) {
|
||
return {
|
||
text: () => (
|
||
<>
|
||
<RmbIcon />
|
||
{displayPrice}
|
||
<Text className={styles.btnText}>我要候补</Text>
|
||
</>
|
||
),
|
||
action: checkPhoneAndExecute(handleJoinGame),
|
||
};
|
||
} else if (can_join) {
|
||
return {
|
||
text: () => {
|
||
return (
|
||
<>
|
||
<RmbIcon />
|
||
{displayPrice}
|
||
<Text className={styles.btnText}>立即加入</Text>
|
||
</>
|
||
);
|
||
},
|
||
action: checkPhoneAndExecute(handleJoinGame),
|
||
};
|
||
} else if (can_assess) {
|
||
return {
|
||
text: () => (
|
||
<>
|
||
<RmbIcon />
|
||
{displayPrice}
|
||
<Text className={styles.btnText}>立即加入</Text>
|
||
</>
|
||
),
|
||
action: checkPhoneAndExecute(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 className={styles.btnText}>{text as string}</Text>;
|
||
};
|
||
}
|
||
|
||
const debounceAction = debounce(action, 300);
|
||
|
||
return (
|
||
<>
|
||
<View className={styles["sticky-bottom-bar"]}>
|
||
<View className={styles["sticky-bottom-bar-share-and-comment"]}>
|
||
<View
|
||
className={styles["sticky-bottom-bar-share"]}
|
||
onClick={() => handleShare()}
|
||
>
|
||
<Image
|
||
className={styles["sticky-bottom-bar-share-icon"]}
|
||
src={img.ICON_DETAIL_SHARE}
|
||
/>
|
||
<Text className={styles["sticky-bottom-bar-share-text"]}>分享</Text>
|
||
</View>
|
||
<View
|
||
className={styles["sticky-bottom-bar-share-and-comment-separator"]}
|
||
/>
|
||
<View
|
||
className={styles["sticky-bottom-bar-comment"]}
|
||
onClick={() => {
|
||
// Taro.showToast({ title: "To be continued", icon: "none" });
|
||
handleAddComment();
|
||
}}
|
||
>
|
||
<Image
|
||
className={styles["sticky-bottom-bar-comment-icon"]}
|
||
src={img.ICON_DETAIL_COMMENT_LIGHT}
|
||
/>
|
||
<Text className={styles["sticky-bottom-bar-comment-text"]}>
|
||
{commentCount > 0 ? commentCount : "评论"}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
<View
|
||
className={classnames(
|
||
styles["detail-main-action"],
|
||
available ? "" : styles.disabled,
|
||
)}
|
||
>
|
||
<View
|
||
style={is_organizer ? {} : { margin: "auto" }}
|
||
className={styles["sticky-bottom-bar-join-game"]}
|
||
onClick={debounceAction}
|
||
>
|
||
<ActionText />
|
||
</View>
|
||
{is_organizer && (
|
||
<View
|
||
className={styles.game_manage}
|
||
onClick={() => {
|
||
gameManageRef.current.show(detail, onStatusChange);
|
||
}}
|
||
>
|
||
管理
|
||
</View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
<GameManagePopup ref={gameManageRef} />
|
||
<NTRPEvaluatePopup type={EvaluateScene.detail} ref={ntrpRef} showGuide />
|
||
</>
|
||
);
|
||
}
|