diff --git a/src/components/refundPopup/index.module.scss b/src/components/refundPopup/index.module.scss index adbfc5a..b71d5de 100644 --- a/src/components/refundPopup/index.module.scss +++ b/src/components/refundPopup/index.module.scss @@ -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; } -} \ No newline at end of file +} diff --git a/src/components/refundPopup/index.tsx b/src/components/refundPopup/index.tsx index 5106799..616a376 100644 --- a/src/components/refundPopup/index.tsx +++ b/src/components/refundPopup/index.tsx @@ -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 ( @@ -69,8 +77,22 @@ function renderCancelContent(checkOrderInfo) { {/* 订单信息摘要 */} {policyList.map((item, index) => ( - - {item.time} + index && index !== 0 ? styles.pastItem : "", + targetIndex === index ? styles.currentItem : "" + )} + > + + {targetIndex === index && ( + + 当前时间段 + + )} + {item.time} + {item.rule} ))} diff --git a/src/game_pages/detail/components/OrganizerInfo/index.module.scss b/src/game_pages/detail/components/OrganizerInfo/index.module.scss index c10d8ed..9b775f8 100644 --- a/src/game_pages/detail/components/OrganizerInfo/index.module.scss +++ b/src/game_pages/detail/components/OrganizerInfo/index.module.scss @@ -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; diff --git a/src/game_pages/detail/components/OrganizerInfo/index.tsx b/src/game_pages/detail/components/OrganizerInfo/index.tsx index d837bbc..325cdfb 100644 --- a/src/game_pages/detail/components/OrganizerInfo/index.tsx +++ b/src/game_pages/detail/components/OrganizerInfo/index.tsx @@ -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"] } /> - NTRP {ntrp_level ? formatNtrpDisplay(ntrp_level) : "初学者"} + + NTRP {ntrp_level ? formatNtrpDisplay(ntrp_level) : "初学者"} + diff --git a/src/game_pages/detail/components/Participants/index.tsx b/src/game_pages/detail/components/Participants/index.tsx index 9252487..565d2d8 100644 --- a/src/game_pages/detail/components/Participants/index.tsx +++ b/src/game_pages/detail/components/Participants/index.tsx @@ -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 ? ( + <> + 0 + {/* .00 */} + + ) : ( + <> + {priceStrArr[0]} + .{priceStrArr[1]} + + ); + // 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: () => ( + <> + + {displayPrice} + 已加入 + + ), + action: () => toast("您已参与了本次活动"), + }; + } else if (is_substituting) { + return { + text: () => ( + <> + + {displayPrice} + 已加入候补 + + ), + action: () => toast("您已加入候补,候补失败会全额退款~"), + }; + } else if (can_pay) { + return { + text: () => ( + <> + + {displayPrice} + 继续支付 + + ), + 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: () => ( + <> + + {displayPrice} + 立即加入 + + ), + available: false, + action: () => + toast("您当前不符合此球局NTRP水平要求,去看看其他活动吧~"), + }; + } else if (can_substitute) { + return { + text: () => ( + <> + + {displayPrice} + 我要候补 + + ), + action: checkPhoneAndExecute(handleJoinGame), + }; + } else if (can_join) { + return { + text: () => { + return ( + <> + + {displayPrice} + 立即加入 + + ); + }, + action: checkPhoneAndExecute(handleJoinGame), + }; + } else if (can_assess) { + return { + text: () => ( + <> + + {displayPrice} + 立即加入 + + ), + action: checkPhoneAndExecute(handleSelfEvaluate), + }; + } + return { + text: "球局无法加入", + available: false, + }; + } + + const { action = () => {} } = generateTextAndAction(user_action_status)!; + 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, - 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 ( - - - - {nickname || "未知"} - - - {displayNtrp} - - - {role} - - - ); - })} - - + <> + + + 参与者 + · + {leftCount > 0 ? `剩余空位 ${leftCount}` : "已满员"} - ) : ( - "" - )} - + {participant_count > 0 || showApplicationEntry ? ( + + {/* application */} + {showApplicationEntry && ( + { + action?.(); + }} + > + + + 申请加入 + + + )} + {/* participants list */} + + + {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 ( + + + + {nickname || "未知"} + + + {displayNtrp} + + + {role} + + + ); + })} + + + + ) : ( + "" + )} + + + ); } diff --git a/src/order_pages/orderDetail/index.module.scss b/src/order_pages/orderDetail/index.module.scss index 5f13806..c5e68b7 100644 --- a/src/order_pages/orderDetail/index.module.scss +++ b/src/order_pages/orderDetail/index.module.scss @@ -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); } diff --git a/src/order_pages/orderDetail/index.tsx b/src/order_pages/orderDetail/index.tsx index 8c49f2a..3508f9d 100644 --- a/src/order_pages/orderDetail/index.tsx +++ b/src/order_pages/orderDetail/index.tsx @@ -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 && {gameNotice.content}} )} + {!orderDetail.order_id && ( + + {title} + + )} {/* Date and Weather */} @@ -322,41 +330,32 @@ function GameInfo(props) { {/* Action bar */} - - {orderDetail.order_id ? ( - <> - {generateOrderActions( - orderDetail, - { - handleDeleteOrder, - handleCancelOrder, - handleQuit, - handlePayNow: () => {}, - handleViewGame, - }, - "detail" - )?.map((obj) => ( - - {obj.text} - - - ))} - - - 客服 + {orderDetail.order_id && ( + + {generateOrderActions( + orderDetail, + { + handleDeleteOrder, + handleCancelOrder, + handleQuit, + handlePayNow: () => {}, + handleViewGame, + }, + "detail" + )?.map((obj) => ( + + {obj.text} + - - ) : ( - "" - )} - + ))} + + + 客服 + + + )} @@ -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 ( @@ -519,8 +521,22 @@ function RefundPolicy(props) { {/* 订单信息摘要 */} {policyList.map((item, index) => ( - - {item.time} + index && index !== 0 ? styles.pastItem : "", + targetIndex === index ? styles.currentItem : "" + )} + > + + {targetIndex === index && ( + + 当前时间段 + + )} + {item.time} + {item.rule} ))} @@ -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; diff --git a/src/order_pages/orderList/index.module.scss b/src/order_pages/orderList/index.module.scss index 417a694..7296ddb 100644 --- a/src/order_pages/orderList/index.module.scss +++ b/src/order_pages/orderList/index.module.scss @@ -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; + } + } + } } } diff --git a/src/order_pages/orderList/index.tsx b/src/order_pages/orderList/index.tsx index f555fb0..a5bb553 100644 --- a/src/order_pages/orderList/index.tsx +++ b/src/order_pages/orderList/index.tsx @@ -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 ( { ))} + {unPay && diffMs > 0 && ( + + + {current_players > 0 ? ( + {`已有${current_players}人参加, `} + ) : ( + "" + )} + 请在 + {timeLeft} + 分钟内完成支付 + + + )} ); })}