Merge branch 'feat/liujie'

This commit is contained in:
2025-11-24 15:18:04 +08:00
9 changed files with 642 additions and 145 deletions

View File

@@ -20,9 +20,9 @@
.specTips {
padding-bottom: 20px;
color: rgba(60, 60, 67, 0.60);
color: rgba(60, 60, 67, 0.6);
text-align: center;
font-feature-settings: 'liga' off, 'clig' off;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
@@ -42,22 +42,55 @@
align-items: center;
color: #000;
text-align: center;
font-feature-settings:
"liga" off,
"clig" off;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
&.pastItem {
color: #3c3c43;
}
&.currentItem {
position: relative;
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 3px;
height: 100%;
background: #007aff;
z-index: 0;
}
&::after {
content: "";
position: absolute;
left: 3px;
top: 15px;
width: 0;
height: 0;
border-style: solid;
border-width: 3px 0 3px 6px;
border-color: transparent transparent transparent #007bff;
z-index: 0;
}
.currentTag {
color: #007aff;
font-size: 12px;
font-weight: 600;
line-height: 20px;
font-family: "pingfang SC";
text-align: left;
}
}
&:nth-child(1) {
color: #000;
text-align: center;
font-feature-settings:
"liga" off,
"clig" off;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
@@ -72,6 +105,11 @@
padding: 10px 12px;
}
.time {
text-align: left;
padding-left: 30px;
}
.rule {
border-left: 1px solid rgba(0, 0, 0, 0.06);
}
@@ -92,7 +130,7 @@
.title {
color: #000;
text-align: center;
font-feature-settings: 'liga' off, 'clig' off;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
@@ -117,11 +155,11 @@
height: 52px;
border-radius: 16px;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.10);
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.1);
backdrop-filter: blur(16px);
color: #fff;
background-color: #000;
font-feature-settings: 'liga' off, 'clig' off;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
@@ -129,4 +167,4 @@
line-height: 20px;
letter-spacing: -0.23px;
}
}
}

View File

@@ -6,6 +6,7 @@ import React, {
} from "react";
import { View, Text, Button, Image } from "@tarojs/components";
import Taro from "@tarojs/taro";
import classnames from "classnames";
import dayjs from "dayjs";
import { CommonPopup } from "@/components";
import orderService from "@/services/orderService";
@@ -47,18 +48,25 @@ function genRefundNotice(refund_policy) {
function renderCancelContent(checkOrderInfo) {
const { refund_policy = [] } = checkOrderInfo;
const current = dayjs();
const policyList = [
{
time: "申请退款时间",
rule: "退款规则",
},
...refund_policy.map((item) => {
...refund_policy.map((item, index) => {
const isLast = index === refund_policy.length - 1;
return {
time: item.application_time,
rule: item.refund_rule,
beforeCurrent: isLast
? true
: current.isBefore(dayjs(item.deadline_formatted)),
};
}),
];
console.log("policyList", policyList);
const targetIndex = policyList.findIndex((item) => item.beforeCurrent);
const { notice } = genRefundNotice(refund_policy);
return (
<View className={styles.refundPolicy}>
@@ -69,8 +77,22 @@ function renderCancelContent(checkOrderInfo) {
{/* 订单信息摘要 */}
<View className={styles.policyList}>
{policyList.map((item, index) => (
<View key={index} className={styles.policyItem}>
<View className={styles.time}>{item.time}</View>
<View
key={index}
className={classnames(
styles.policyItem,
targetIndex > index && index !== 0 ? styles.pastItem : "",
targetIndex === index ? styles.currentItem : ""
)}
>
<View className={styles.time}>
{targetIndex === index && (
<View className={styles.currentTag}>
<Text></Text>
</View>
)}
<Text>{item.time}</Text>
</View>
<View className={styles.rule}>{item.rule}</View>
</View>
))}

View File

@@ -29,6 +29,7 @@
height: 40px;
border-radius: 50%;
object-fit: cover;
flex: 0 0 40px;
}
&-message {
@@ -41,7 +42,11 @@
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 184.615% */
line-height: 24px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 180px;
}
&-stats {
@@ -65,6 +70,7 @@
.organizer-actions {
display: flex;
flex: 0 0 103px;
align-items: center;
gap: 8px;
margin-left: auto;

View File

@@ -44,7 +44,9 @@ function genRecommendGames(games, location, avatar) {
checkedApplications: current_players,
levelRequirements:
skill_level_max !== skill_level_min
? `${formatNtrpDisplay(skill_level_min) || "-"}-${formatNtrpDisplay(skill_level_max) || "-"}`
? `${formatNtrpDisplay(skill_level_min) || "-"}-${
formatNtrpDisplay(skill_level_max) || "-"
}`
: skill_level_min === "1"
? "无要求"
: `${formatNtrpDisplay(skill_level_min)}以上`,
@@ -125,7 +127,9 @@ export default function OrganizerInfo(props) {
styles["organizer-avatar-name-message-stats-separator"]
}
/>
<Text>NTRP {ntrp_level ? formatNtrpDisplay(ntrp_level) : "初学者"}</Text>
<Text>
NTRP {ntrp_level ? formatNtrpDisplay(ntrp_level) : "初学者"}
</Text>
</View>
</View>
<View className={styles["organizer-actions"]}>

View File

@@ -1,19 +1,91 @@
import React, { useRef } from "react";
import Taro from "@tarojs/taro";
import { View, Text, Image, ScrollView } from "@tarojs/components";
import dayjs from "dayjs";
import img from "@/config/images";
import { formatNtrpDisplay } from "@/utils/helper";
import { useUserInfo } from "@/store/userStore";
import { formatNtrpDisplay, toast, navto } from "@/utils/helper";
import RMB_ICON from "@/static/detail/rmb.svg";
import { MATCH_STATUS, IsSubstituteSupported } from "@/services/detailService";
import OrderService from "@/services/orderService";
import styles from "./index.module.scss";
import NTRPEvaluatePopup from "@/components/NTRPEvaluatePopup";
import { EvaluateCallback, EvaluateScene } from "@/store/evaluateStore";
function isFull(counts) {
const {
max_players,
current_players,
max_substitute_players,
current_substitute_count,
is_substitute_supported,
} = counts;
if (
max_players === current_players &&
is_substitute_supported === IsSubstituteSupported.NOTSUPPORT
) {
return true;
} else if (
max_players === current_players &&
is_substitute_supported === IsSubstituteSupported.SUPPORT
) {
return max_substitute_players === current_substitute_count;
}
return false;
}
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 Participants(props) {
const { detail = {}, handleJoinGame, handleViewUserInfo } = props;
const ntrpRef = useRef<{
show: (evaluateCallback: EvaluateCallback) => void;
}>({ show: () => {} });
const userInfo = useUserInfo();
const participants = detail.participants || [];
const {
participant_count,
max_participants,
user_action_status = {},
start_time,
} = detail;
price,
ntrp_level,
skill_level_min,
skill_level_max,
is_organizer,
match_status,
end_time,
id,
} = detail || {};
const { can_join, can_pay, can_substitute, is_substituting, waiting_start } =
user_action_status;
const showApplicationEntry =
@@ -22,91 +94,322 @@ export default function Participants(props) {
) &&
can_join &&
dayjs(start_time).isAfter(dayjs());
// 检查手机号绑定的包装函数
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
);
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();
},
});
}
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} />
{displayPrice}
<Text className={styles.btnText}></Text>
</>
),
action: () => toast("您已参与了本次活动"),
};
} else if (is_substituting) {
return {
text: () => (
<>
<Image className={styles.crrrencySymbol} src={RMB_ICON} />
{displayPrice}
<Text className={styles.btnText}></Text>
</>
),
action: () => toast("您已加入候补,候补失败会全额退款~"),
};
} else if (can_pay) {
return {
text: () => (
<>
<Image className={styles.crrrencySymbol} src={RMB_ICON} />
{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: () => (
<>
<Image className={styles.crrrencySymbol} src={RMB_ICON} />
{displayPrice}
<Text className={styles.btnText}></Text>
</>
),
available: false,
action: () =>
toast("您当前不符合此球局NTRP水平要求去看看其他活动吧"),
};
} else if (can_substitute) {
return {
text: () => (
<>
<Image className={styles.crrrencySymbol} src={RMB_ICON} />
{displayPrice}
<Text className={styles.btnText}></Text>
</>
),
action: checkPhoneAndExecute(handleJoinGame),
};
} else if (can_join) {
return {
text: () => {
return (
<>
<Image className={styles.crrrencySymbol} src={RMB_ICON} />
{displayPrice}
<Text className={styles.btnText}></Text>
</>
);
},
action: checkPhoneAndExecute(handleJoinGame),
};
} else if (can_assess) {
return {
text: () => (
<>
<Image className={styles.crrrencySymbol} src={RMB_ICON} />
{displayPrice}
<Text className={styles.btnText}></Text>
</>
),
action: checkPhoneAndExecute(handleSelfEvaluate),
};
}
return {
text: "球局无法加入",
available: false,
};
}
const { action = () => {} } = generateTextAndAction(user_action_status)!;
const leftCount = max_participants - participant_count;
return (
<View className={styles["detail-page-content-participants"]}>
<View className={styles["participants-title"]}>
<Text></Text>
<Text>·</Text>
<Text>{leftCount > 0 ? `剩余空位 ${leftCount}` : "已满员"}</Text>
</View>
{participant_count > 0 || showApplicationEntry ? (
<View className={styles["participants-list"]}>
{/* application */}
{showApplicationEntry && (
<View
className={styles["participants-list-application"]}
onClick={() => {
handleJoinGame();
}}
>
<Image
className={styles["participants-list-application-icon"]}
src={img.ICON_DETAIL_APPLICATION_ADD}
/>
<Text className={styles["participants-list-application-text"]}>
</Text>
</View>
)}
{/* participants list */}
<ScrollView className={styles["participants-list-scroll"]} scrollX>
<View
className={styles["participants-list-scroll-content"]}
style={{
width: `${
participants.length * 103 + (participants.length - 1) * 8
}px`,
}}
>
{participants.map((participant) => {
const {
is_organizer,
user: {
avatar_url,
nickname,
level,
ntrp_level,
id: participant_user_id,
},
} = participant;
const role = is_organizer ? "组织者" : "参与者";
// 优先使用 ntrp_level如果没有则使用 level
const ntrpValue = ntrp_level || level;
// 格式化显示 NTRP如果没有值则显示"初学者"
const displayNtrp = ntrpValue ? formatNtrpDisplay(ntrpValue) : "初学者";
return (
<View
key={participant.id}
className={styles["participants-list-item"]}
>
<Image
className={styles["participants-list-item-avatar"]}
mode="aspectFill"
src={avatar_url}
onClick={handleViewUserInfo.bind(
null,
participant_user_id
)}
/>
<Text className={styles["participants-list-item-name"]}>
{nickname || "未知"}
</Text>
<Text className={styles["participants-list-item-level"]}>
{displayNtrp}
</Text>
<Text className={styles["participants-list-item-role"]}>
{role}
</Text>
</View>
);
})}
</View>
</ScrollView>
<>
<View className={styles["detail-page-content-participants"]}>
<View className={styles["participants-title"]}>
<Text></Text>
<Text>·</Text>
<Text>{leftCount > 0 ? `剩余空位 ${leftCount}` : "已满员"}</Text>
</View>
) : (
""
)}
</View>
{participant_count > 0 || showApplicationEntry ? (
<View className={styles["participants-list"]}>
{/* application */}
{showApplicationEntry && (
<View
className={styles["participants-list-application"]}
onClick={() => {
action?.();
}}
>
<Image
className={styles["participants-list-application-icon"]}
src={img.ICON_DETAIL_APPLICATION_ADD}
/>
<Text className={styles["participants-list-application-text"]}>
</Text>
</View>
)}
{/* participants list */}
<ScrollView className={styles["participants-list-scroll"]} scrollX>
<View
className={styles["participants-list-scroll-content"]}
style={{
width: `${
participants.length * 103 + (participants.length - 1) * 8
}px`,
}}
>
{participants.map((participant) => {
const {
is_organizer,
user: {
avatar_url,
nickname,
level,
ntrp_level,
id: participant_user_id,
},
} = participant;
const role = is_organizer ? "组织者" : "参与者";
// 优先使用 ntrp_level如果没有则使用 level
const ntrpValue = ntrp_level || level;
// 格式化显示 NTRP如果没有值则显示"初学者"
const displayNtrp = ntrpValue
? formatNtrpDisplay(ntrpValue)
: "初学者";
return (
<View
key={participant.id}
className={styles["participants-list-item"]}
>
<Image
className={styles["participants-list-item-avatar"]}
mode="aspectFill"
src={avatar_url}
onClick={handleViewUserInfo.bind(
null,
participant_user_id
)}
/>
<Text className={styles["participants-list-item-name"]}>
{nickname || "未知"}
</Text>
<Text className={styles["participants-list-item-level"]}>
{displayNtrp}
</Text>
<Text className={styles["participants-list-item-role"]}>
{role}
</Text>
</View>
);
})}
</View>
</ScrollView>
</View>
) : (
""
)}
</View>
<NTRPEvaluatePopup type={EvaluateScene.detail} ref={ntrpRef} showGuide />
</>
);
}

View File

@@ -441,6 +441,44 @@
line-height: 20px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
&.pastItem {
color: #3c3c43;
}
&.currentItem {
position: relative;
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 3px;
height: 100%;
background: #007aff;
z-index: 0;
}
&::after {
content: "";
position: absolute;
left: 3px;
top: 15px;
width: 0;
height: 0;
border-style: solid;
border-width: 3px 0 3px 6px;
border-color: transparent transparent transparent #007bff;
z-index: 0;
}
.currentTag {
color: #007aff;
font-size: 12px;
font-weight: 600;
line-height: 20px;
font-family: "pingfang SC";
text-align: left;
}
}
&:nth-child(1) {
color: #000;
text-align: center;
@@ -459,6 +497,11 @@
padding: 10px 12px;
}
.time {
text-align: left;
padding-left: 30px;
}
.rule {
border-left: 1px solid rgba(0, 0, 0, 0.06);
}

View File

@@ -82,6 +82,7 @@ function GameInfo(props) {
start_time,
end_time,
weather,
title,
} = detail || {};
const [{ iconDay, tempMax, tempMin }] = weather || [{}];
@@ -106,7 +107,9 @@ function GameInfo(props) {
const startTime = dayjs(start_time);
const endTime = dayjs(end_time);
const game_length = endTime.diff(startTime, "minutes") / 60;
const game_length = Number(
(endTime.diff(startTime, "minutes") / 60).toFixed()
);
const startMonth = startTime.format("M");
const startDay = startTime.format("D");
@@ -248,6 +251,11 @@ function GameInfo(props) {
{gameNotice.content && <Text>{gameNotice.content}</Text>}
</View>
)}
{!orderDetail.order_id && (
<View className={styles.gameStatus}>
<Text className={styles.statusText}>{title}</Text>
</View>
)}
<View className={styles.gameInfo}>
{/* Date and Weather */}
<View className={styles.gameInfoDateWeather}>
@@ -322,41 +330,32 @@ function GameInfo(props) {
</View>
</View>
{/* Action bar */}
<View className={styles.gameInfoActions}>
{orderDetail.order_id ? (
<>
{generateOrderActions(
orderDetail,
{
handleDeleteOrder,
handleCancelOrder,
handleQuit,
handlePayNow: () => {},
handleViewGame,
},
"detail"
)?.map((obj) => (
<View
className={classnames(styles.button, styles[obj.className])}
>
<Text className={styles.buttonText}>{obj.text}</Text>
<Button
className={styles.transparentButton}
onClick={obj.action}
>
{obj.text}
</Button>
</View>
))}
<View className={styles.customer} onClick={handleCustomerService}>
<Image className={styles.customerIcon} src={CustomerIcon} />
<Text></Text>
{orderDetail.order_id && (
<View className={styles.gameInfoActions}>
{generateOrderActions(
orderDetail,
{
handleDeleteOrder,
handleCancelOrder,
handleQuit,
handlePayNow: () => {},
handleViewGame,
},
"detail"
)?.map((obj) => (
<View className={classnames(styles.button, styles[obj.className])}>
<Text className={styles.buttonText}>{obj.text}</Text>
<Button className={styles.transparentButton} onClick={obj.action}>
{obj.text}
</Button>
</View>
</>
) : (
""
)}
</View>
))}
<View className={styles.customer} onClick={handleCustomerService}>
<Image className={styles.customerIcon} src={CustomerIcon} />
<Text></Text>
</View>
</View>
)}
<Dialog id="detailCancelOrder" />
<RefundPopup ref={refundRef} />
</View>
@@ -489,6 +488,7 @@ function OrderMsg(props) {
function RefundPolicy(props) {
const { checkOrderInfo } = props;
const { refund_policy = [] } = checkOrderInfo;
const current = dayjs();
const policyList = [
{
time: "申请退款时间",
@@ -508,9 +508,11 @@ function RefundPolicy(props) {
return {
time: `${year}${month}${day}${time} ${isLast ? "后" : "前"}`,
rule: item.refund_rule,
beforeCurrent: isLast ? true : current.isBefore(theTimeObj),
};
}),
];
const targetIndex = policyList.findIndex((item) => item.beforeCurrent);
return (
<View className={styles.refundPolicy}>
<View className={styles.moduleTitle}>
@@ -519,8 +521,22 @@ function RefundPolicy(props) {
{/* 订单信息摘要 */}
<View className={styles.policyList}>
{policyList.map((item, index) => (
<View key={index} className={styles.policyItem}>
<View className={styles.time}>{item.time}</View>
<View
key={index}
className={classnames(
styles.policyItem,
targetIndex > index && index !== 0 ? styles.pastItem : "",
targetIndex === index ? styles.currentItem : ""
)}
>
<View className={styles.time}>
{targetIndex === index && (
<View className={styles.currentTag}>
<Text></Text>
</View>
)}
<Text>{item.time}</Text>
</View>
<View className={styles.rule}>{item.rule}</View>
</View>
))}
@@ -588,7 +604,7 @@ const OrderCheck = () => {
if (!requireLoginWithPhone()) {
throw new Error("请先绑定手机号");
}
const unPaidRes = await orderService.getUnpaidOrder(detail.id);
if (unPaidRes.code === 0 && unPaidRes.data.has_unpaid_order) {
return unPaidRes.data.payment_params;

View File

@@ -151,7 +151,7 @@
.gameTitle {
display: flex;
align-items: center;
align-items: flex-start;
justify-content: space-between;
padding: 12px 15px 0;
@@ -168,12 +168,14 @@
}
.payNum {
width: 90px;
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 18px;
line-height: 26px;
text-align: right;
&.paid {
color: #000;
}
@@ -359,6 +361,45 @@
}
}
}
.payTipContainer {
margin-top: -8px;
display: flex;
justify-content: flex-end;
align-items: flex-start;
padding: 0 12px 14px 12px;
.payTip {
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 20px;
// letter-spacing: -0.23px;
padding: 4px 12px;
background-color: #f7f7f7;
border-radius: 4px;
position: relative;
&::before {
content: "";
position: absolute;
top: -6px;
right: 30px;
width: 0;
height: 0;
border-style: solid;
border-width: 0 4px 6px 4px;
border-color: transparent transparent #f7f7f7 transparent;
}
.timeLeft {
display: inline-block;
color: #ff3b30;
font-weight: 600;
padding: 0 4px;
}
}
}
}
}

View File

@@ -17,7 +17,11 @@ import { withAuth, RefundPopup, GeneralNavbar } from "@/components";
import { payOrder, generateOrderActions } from "@/utils";
import emptyContent from "@/static/emptyStatus/publish-empty.png";
import CustomerIcon from "@/static/order/customer.svg";
import { insertDotInTags, genNTRPRequirementText, requireLoginWithPhone } from "@/utils/helper";
import {
insertDotInTags,
genNTRPRequirementText,
requireLoginWithPhone,
} from "@/utils/helper";
import styles from "./index.module.scss";
dayjs.locale("zh-cn");
@@ -115,7 +119,7 @@ const OrderList = () => {
if (!requireLoginWithPhone()) {
return; // 未登录或未绑定手机号,已跳转到登录页
}
try {
const unPaidRes = await orderService.getUnpaidOrder(item.game_info?.id);
if (unPaidRes.code === 0 && unPaidRes.data.has_unpaid_order) {
@@ -306,6 +310,12 @@ const OrderList = () => {
court_type,
} = game_info || {};
const diffMs = dayjs(item.expire_time).diff();
const mm = String(Math.floor(diffMs / 1000 / 60)).padStart(2, "0");
const ss = String(Math.floor((diffMs / 1000) % 60)).padStart(2, "0");
const timeLeft = `${mm}:${ss}`;
return (
<View key={item.id} className={styles.orderItem}>
<View
@@ -412,6 +422,20 @@ const OrderList = () => {
))}
</View>
</View>
{unPay && diffMs > 0 && (
<View className={styles.payTipContainer}>
<View className={styles.payTip}>
{current_players > 0 ? (
<Text>{`已有${current_players}人参加, `}</Text>
) : (
""
)}
<Text></Text>
<Text className={styles.timeLeft}>{timeLeft}</Text>
<Text></Text>
</View>
</View>
)}
</View>
);
})}