diff --git a/package.json b/package.json index 4b34573..71d29f8 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@tarojs/shared": "4.1.5", "@tarojs/taro": "4.1.5", "dayjs": "^1.11.13", + "qweather-icons": "^1.8.0", "react": "^18.0.0", "react-dom": "^18.0.0", "zustand": "^4.4.7" diff --git a/src/app.ts b/src/app.ts index 89d3e97..93ee401 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,50 +1,49 @@ -import { Component, ReactNode } from 'react' -import './nutui-theme.scss' -import './app.scss' -import { useDictionaryStore } from './store/dictionaryStore' -import { useGlobalStore } from './store/global' +import { Component, ReactNode } from "react"; +import "./nutui-theme.scss"; +import "./app.scss"; +import "qweather-icons/font/qweather-icons.css"; +import { useDictionaryStore } from "./store/dictionaryStore"; +import { useGlobalStore } from "./store/global"; // import { getNavbarHeight } from "@/utils/getNavbarHeight"; interface AppProps { - children: ReactNode + children: ReactNode; } class App extends Component { - - onLaunch() { - console.log('小程序启动,初始化逻辑写这里') + console.log("小程序启动,初始化逻辑写这里"); } componentDidMount() { // 初始化字典数据 - this.initDictionaryData() - this.getNavBarHeight() + this.initDictionaryData(); + this.getNavBarHeight(); // this.getLocation() } - componentDidShow() { } + componentDidShow() {} - componentDidHide() { } + componentDidHide() {} // 初始化字典数据 private async initDictionaryData() { try { - const { fetchDictionary } = useDictionaryStore.getState() - await fetchDictionary() + const { fetchDictionary } = useDictionaryStore.getState(); + await fetchDictionary(); } catch (error) { - console.error('初始化字典数据失败:', error) + console.error("初始化字典数据失败:", error); } } // 获取导航高度 getNavBarHeight = () => { - const { getNavbarHeightInfo } = useGlobalStore.getState() - getNavbarHeightInfo() - } + const { getNavbarHeightInfo } = useGlobalStore.getState(); + getNavbarHeightInfo(); + }; - // 获取位置信息 + // 获取位置信息 // getLocation = () => { // const { getCurrentLocationInfo } = useGlobalStore.getState() // getCurrentLocationInfo() @@ -52,8 +51,8 @@ class App extends Component { render() { // this.props.children 是将要会渲染的页面 - return this.props.children + return this.props.children; } } -export default App +export default App; diff --git a/src/components/Auth/index.tsx b/src/components/Auth/index.tsx index e2d75ba..c7e08e4 100644 --- a/src/components/Auth/index.tsx +++ b/src/components/Auth/index.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react' -import Taro from '@tarojs/taro' -import { check_login_status } from '@/services/loginService' - - +import React, { useEffect, useState } from "react"; +import { View } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import { check_login_status } from "@/services/loginService"; export function getCurrentFullPath(): string { const pages = Taro.getCurrentPages(); @@ -31,18 +30,30 @@ export default function withAuth

( const is_login = check_login_status(); setAuthed(is_login); - if (!is_login) { - const currentPage = getCurrentFullPath(); - // Taro.redirectTo({ - // url: `/pages/login/index/index${ - // currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : '' - // }`, - // }) - } + // if (!is_login) { + // const currentPage = getCurrentFullPath(); + // Taro.redirectTo({ + // url: `/pages/login/index/index${ + // currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : "" + // }`, + // }); + // } }, []); // if (!authed) { - // return // 空壳,避免 children 渲染出错 + // return ( + // + // ); // 空壳,避免 children 渲染出错 // } return ; diff --git a/src/config/env.ts b/src/config/env.ts index c9592f4..98f161d 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -17,8 +17,8 @@ const envConfigs: Record = { // 开发环境 development: { name: '开发环境', - // apiBaseURL: 'https://sit.light120.com', - apiBaseURL: 'http://localhost:9098', + apiBaseURL: 'https://sit.light120.com', + // apiBaseURL: 'http://localhost:9098', timeout: 15000, enableLog: true, enableMock: true @@ -92,4 +92,4 @@ export const getEnvInfo = () => { } // 导出当前环境配置(方便直接使用) -export default getCurrentConfig() \ No newline at end of file +export default getCurrentConfig() \ No newline at end of file diff --git a/src/mod_user/orderDetail/index.module.scss b/src/mod_user/orderDetail/index.module.scss index 8c4893e..96eaaf7 100644 --- a/src/mod_user/orderDetail/index.module.scss +++ b/src/mod_user/orderDetail/index.module.scss @@ -1,5 +1,16 @@ @use "~@/scss/images.scss" as img; +.errorTip { + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: space-around; + flex-direction: column; + font-weight: 600; + font-size: 16px; +} + .container { padding: 20px; box-sizing: border-box; @@ -193,7 +204,9 @@ align-items: center; align-self: stretch; color: #000; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -205,7 +218,7 @@ .summaryList { border-radius: 12px; border: 1px solid rgba(0, 0, 0, 0.06); - background: #FFF; + background: #fff; box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06); .summaryItem { @@ -218,8 +231,10 @@ .title { width: 120px; display: inline-block; - color: rgba(60, 60, 67, 0.60); - font-feature-settings: 'liga' off, 'clig' off; + color: rgba(60, 60, 67, 0.6); + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -229,7 +244,9 @@ .content { color: #000; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -248,7 +265,9 @@ align-items: center; align-self: stretch; color: #000; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -260,7 +279,7 @@ .policyList { border-radius: 12px; border: 1px solid rgba(0, 0, 0, 0.06); - background: #FFF; + background: #fff; box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06); .policyItem { @@ -269,7 +288,9 @@ 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; @@ -280,7 +301,9 @@ &: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; @@ -289,7 +312,8 @@ border: none; } - .time, .rule { + .time, + .rule { width: 50%; padding: 10px 12px; } @@ -314,7 +338,9 @@ align-items: center; align-self: stretch; color: #000; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -324,7 +350,7 @@ } .content { - color: rgba(22, 24, 35, 0.60); + color: rgba(22, 24, 35, 0.6); font-family: "PingFang SC"; font-size: 14px; font-style: normal; diff --git a/src/mod_user/orderDetail/index.tsx b/src/mod_user/orderDetail/index.tsx index cd467fd..a327085 100644 --- a/src/mod_user/orderDetail/index.tsx +++ b/src/mod_user/orderDetail/index.tsx @@ -3,8 +3,11 @@ import { View, Text, Button, Image } from "@tarojs/components"; import Taro, { useDidShow, useRouter } from "@tarojs/taro"; import dayjs from "dayjs"; import { delay } from "@/utils"; -import orderService, { GameOrderRes } from "@/services/orderService"; -import detailService, { GameDetail } from "@/services/detailService"; +import orderService, { + GameOrderRes, + OrderStatus, +} from "@/services/orderService"; +import detailService, { GameData } from "@/services/detailService"; import { withAuth } from "@/components"; import { calculateDistance, getCurrentLocation } from "@/utils"; import img from "@/config/images"; @@ -111,7 +114,7 @@ function GameInfo(props) { } function OrderMsg(props) { - const { detail, orderInfo } = props; + const { detail, checkOrderInfo } = props; const { start_time, end_time, @@ -120,10 +123,10 @@ function OrderMsg(props) { wechat_contact, price, } = detail; - const { order_info: { registrant_nickname } = {} } = orderInfo + const { order_info: { registrant_nickname } = {} } = checkOrderInfo; const startTime = dayjs(start_time); const endTime = dayjs(end_time); - const startYear = startTime.format('YYYY') + const startYear = startTime.format("YYYY"); const startMonth = startTime.format("M"); const startDay = startTime.format("D"); const startDate = `${startYear}年${startMonth}月${startDay}日`; @@ -157,32 +160,38 @@ function OrderMsg(props) { {/* 订单信息摘要 */} - { - summary.map((item, index) => ( + {summary.map((item, index) => ( + {item.title} {item.content} - )) - } + + ))} ); } function RefundPolicy(props) { - const { orderInfo } = props - const { refund_policy = [] } = orderInfo - const policyList = [{ - time: '申请退款时间', - rule: '退款规则', - }, ...refund_policy.map((item, index) => { - const [, theTime] = item.application_time.split('undefined ') - const theTimeObj = dayjs(theTime) - const year = theTimeObj.format('YYYY') - const month = theTimeObj.format('M') - const day = theTimeObj.format('D') - const time = theTimeObj.format('HH:MM') - return { time: `${year}年${month}月${day}日${time}${index === 0 ? '前' : '后'}`, rule: item.refund_rule } - })] + const { checkOrderInfo } = props; + const { refund_policy = [] } = checkOrderInfo; + const policyList = [ + { + time: "申请退款时间", + rule: "退款规则", + }, + ...refund_policy.map((item, index) => { + const [, theTime] = item.application_time.split("undefined "); + const theTimeObj = dayjs(theTime); + const year = theTimeObj.format("YYYY"); + const month = theTimeObj.format("M"); + const day = theTimeObj.format("D"); + const time = theTimeObj.format("HH:MM"); + return { + time: `${year}年${month}月${day}日${time}${index === 0 ? "前" : "后"}`, + rule: item.refund_rule, + }; + }), + ]; return ( @@ -190,12 +199,12 @@ function RefundPolicy(props) { {/* 订单信息摘要 */} - { - policyList.map((item, index) => ( + {policyList.map((item, index) => ( + {item.time} {item.rule} - )) - } + + ))} ); @@ -212,22 +221,43 @@ function Disclaimer() { const OrderCheck = () => { const { params } = useRouter(); - const { id, gameId } = params; - const [detail, setDetail] = useState({}); + const { id: stringId, gameId: stringGameId } = params; + const [id, gameId] = [Number(stringId), Number(stringGameId)]; + const [detail, setDetail] = useState({}); const [location, setLocation] = useState([0, 0]); - const [orderInfo, setOrderInfo] = useState({}) + const [checkOrderInfo, setCheckOrderInfo] = useState({}); + const [orderDetail, setOrderDetail] = useState({}); useDidShow(async () => { - const res = await detailService.getDetail(Number(gameId)); - const orderRes = await orderService.getOrderInfo(Number(gameId)) - setOrderInfo(orderRes.data) - console.log(res); - if (res.code === 0) { - setDetail(res.data); + let gameDetail = {}; + if (id) { + const res = await orderService.getOrderDetail(id); + if (res.code === 0) { + setOrderDetail(res.data); + gameDetail = res.data.game_info; + } + } else if (gameId) { + const res = await detailService.getDetail(gameId); + if (res.code === 0) { + gameDetail = res.data; + } } + if (gameDetail.id) { + setDetail(gameDetail); + onInit(gameDetail.id); + } + }); + + async function checkOrder(gid) { + const orderRes = await orderService.getCheckOrderInfo(gid); + setCheckOrderInfo(orderRes.data); + } + + async function onInit(gid) { + checkOrder(gid); const location = await getCurrentLocation(); setLocation([location.latitude, location.longitude]); - }); + } //TODO: get order msg from id const handlePay = async () => { @@ -235,56 +265,102 @@ const OrderCheck = () => { title: "支付中...", mask: true, }); - const res = await orderService.createOrder(Number(gameId)); - if (res.code === 0) { - const { payment_required, payment_params } = res.data; - if (payment_required) { - const { - timeStamp, - nonceStr, - package: package_, - signType, - paySign, - } = payment_params; - await Taro.requestPayment({ - timeStamp, - nonceStr, - package: package_, - signType, - paySign, - success: async () => { - Taro.hideLoading(); - Taro.showToast({ - title: "支付成功", - icon: "success", - }); - await delay(1000); - Taro.navigateBack({ - delta: 1, - }); - }, - fail: () => { - Taro.hideLoading(); - Taro.showToast({ - title: "支付失败", - icon: "none", - }); - }, - }); + let wxPayRes: any = {}; + try { + if (orderDetail.game_info?.id) { + const res = await orderService.getUnpaidOrder(orderDetail.game_info.id); + if (res.code === 0) { + wxPayRes = { + ...res.data, + payment_required: res.data.has_unpaid_order, + }; + } + } else { + const res = await orderService.createOrder(detail.id); + if (res.code === 0) { + wxPayRes = res.data; + } } + } catch (error) { + Taro.hideLoading(); + Taro.showToast({ + title: "支付调用失败", + icon: "none", + }); + return; + } + + const { payment_required, payment_params } = wxPayRes; + if (payment_required) { + const { + timeStamp, + nonceStr, + package: package_, + signType, + paySign, + } = payment_params; + await Taro.requestPayment({ + timeStamp, + nonceStr, + package: package_, + signType, + paySign, + success: async () => { + Taro.hideLoading(); + Taro.showToast({ + title: "支付成功", + icon: "success", + }); + await delay(1000); + Taro.navigateBack({ + delta: 1, + }); + }, + fail: () => { + Taro.hideLoading(); + Taro.showToast({ + title: "支付失败", + icon: "none", + }); + }, + }); } }; + if (!id && !gameId) { + return ( + + 异常订单 + + + ); + } return ( {/* Game Date and Address */} {/* Order message */} - + {/* Refund policy */} - + {/* Disclaimer */} - + {!id || + (orderDetail.order_status === OrderStatus.PENDING && ( + + ))} ); }; diff --git a/src/pages/detail/index.scss b/src/pages/detail/index.scss index 76fe0de..c8996da 100644 --- a/src/pages/detail/index.scss +++ b/src/pages/detail/index.scss @@ -1,4 +1,4 @@ -@use '~@/scss/images.scss' as img; +@use "~@/scss/images.scss" as img; .detail-page { width: 100%; @@ -31,13 +31,14 @@ color: #fff; display: flex; align-items: center; - background: rgba(0, 0, 0, 0.10); + background: rgba(0, 0, 0, 0.1); .detail-navigator-back { border-right: 1px solid #444; } - .detail-navigator-back, .detail-navigator-icon { + .detail-navigator-back, + .detail-navigator-icon { height: 20px; width: 50%; @@ -75,14 +76,18 @@ height: 100vh; &::after { - content: ''; + content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; - background: linear-gradient(180deg, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0.40) 100%); + background: linear-gradient( + 180deg, + rgba(0, 0, 0, 0.8) 0%, + rgba(0, 0, 0, 0.4) 100% + ); backdrop-filter: blur(100px); } } @@ -141,7 +146,6 @@ } .detail-page-content { - &-avatar-tags { padding: 20px 20px 0; box-sizing: border-box; @@ -194,8 +198,10 @@ &-text { overflow: hidden; - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 22px; @@ -232,7 +238,7 @@ // border: 0.5px solid rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.25); overflow: hidden; - color: #FFF; + color: #fff; background: #536272; .month { @@ -245,7 +251,7 @@ justify-content: center; align-items: center; // border-bottom: 1px solid rgba(255, 255, 255, 0.08); - background: #7B828B; + background: #7b828b; } .day { @@ -269,11 +275,13 @@ justify-content: space-evenly; gap: 4px; align-self: stretch; - color: #FFF; + color: #fff; .date { - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 16px; font-style: normal; @@ -282,8 +290,10 @@ } .venue-time { - color: rgba(255, 255, 255, 0.80); - font-feature-settings: 'liga' off, 'clig' off; + color: rgba(255, 255, 255, 0.8); + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 12px; font-style: normal; @@ -302,18 +312,16 @@ &-icon { width: 20px; height: 20px; - - .weather-icon { - width: 20px; - height: 20px; - } + color: rgba(255, 255, 255, 0.8); } &-text-temperature { display: flex; align-items: center; gap: 12px; - color: rgba(255, 255, 255, 0.80); - font-feature-settings: 'liga' off, 'clig' off; + color: rgba(255, 255, 255, 0.8); + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 12px; font-style: normal; @@ -338,7 +346,7 @@ border-radius: 12px; padding: 14px; box-sizing: border-box; - background: #4D5865; + background: #4d5865; display: flex; justify-content: center; align-items: center; @@ -364,9 +372,11 @@ display: flex; align-items: center; gap: 4px; - color: #FFF; + color: #fff; 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; @@ -380,9 +390,11 @@ } &-address { - color: rgba(255, 255, 255, 0.80); + color: rgba(255, 255, 255, 0.8); 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; @@ -422,7 +434,7 @@ justify-content: flex-start; gap: 8px; padding-bottom: 6px; - color: #FFF; + color: #fff; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 16px; @@ -455,8 +467,10 @@ &-tag { overflow: hidden; - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 15px; @@ -520,6 +534,7 @@ height: 100%; border-radius: 9px; margin: 0; + object-fit: cover; } } } @@ -532,10 +547,12 @@ .gameplay-requirements-title { overflow: hidden; - color: #FFF; + color: #fff; height: 24px; padding-bottom: 6px; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 16px; @@ -559,8 +576,10 @@ align-self: stretch; &-title { - color: rgba(255, 255, 255, 0.80); - font-feature-settings: 'liga' off, 'clig' off; + color: rgba(255, 255, 255, 0.8); + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 14px; font-style: normal; @@ -569,8 +588,10 @@ } &-desc { - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 15px; font-style: normal; @@ -590,8 +611,10 @@ padding-bottom: 6px; align-items: center; overflow: hidden; - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 16px; @@ -620,7 +643,7 @@ gap: 8px; align-self: stretch; border-radius: 20px; - border: 1px dashed rgba(255, 255, 255, 0.20); + border: 1px dashed rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.16); flex: 0 0 auto; @@ -630,8 +653,10 @@ } &-text { - color: rgba(255, 255, 255, 0.60); - font-feature-settings: 'liga' off, 'clig' off; + color: rgba(255, 255, 255, 0.6); + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 12px; font-style: normal; @@ -661,7 +686,7 @@ align-items: center; gap: 4px; border-radius: 20px; - border: 0.5px solid rgba(255, 255, 255, 0.20); + border: 0.5px solid rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.16); flex: 0 0 auto; @@ -674,7 +699,9 @@ width: 100%; color: rgba(255, 255, 255, 0.85); text-align: center; - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 13px; font-style: normal; @@ -688,7 +715,9 @@ &-level { color: rgba(255, 255, 255, 0.45); 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; @@ -697,9 +726,11 @@ } &-role { - color: #FFF; + color: #fff; 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; @@ -719,8 +750,10 @@ overflow: hidden; padding-bottom: 7px; height: 24px; - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 16px; @@ -744,8 +777,10 @@ &-tag { overflow: hidden; - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 15px; @@ -758,7 +793,9 @@ &-text { overflow: hidden; color: rgba(255, 255, 255, 0.65); - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; font-family: "PingFang SC"; font-size: 15px; font-style: normal; @@ -775,8 +812,10 @@ overflow: hidden; padding-bottom: 6px; height: 24px; - color: #FFF; - font-feature-settings: 'liga' off, 'clig' off; + color: #fff; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 16px; @@ -816,7 +855,7 @@ display: flex; align-items: center; gap: 5px; - color: rgba(255, 255, 255, 0.60); + color: rgba(255, 255, 255, 0.6); font-size: 12px; font-style: normal; font-weight: 400; @@ -826,7 +865,7 @@ &-separator { width: 1px; height: 10px; - color: rgba(255, 255, 255, 0.20); + color: rgba(255, 255, 255, 0.2); } } } @@ -837,7 +876,8 @@ gap: 8px; margin-left: auto; - .organizer-actions-follow, .organizer-actions-comment { + .organizer-actions-follow, + .organizer-actions-comment { display: flex; height: 32px; box-sizing: border-box; @@ -857,7 +897,7 @@ .organizer-actions-follow { padding: 8px 12px 8px; &-text { - color: #FFF; + color: #fff; font-size: 13px; font-style: normal; font-weight: 500; @@ -913,7 +953,7 @@ gap: 6px; flex: 0 0 auto; border-radius: 20px; - border: 1px solid rgba(33, 178, 0, 0.20); + border: 1px solid rgba(33, 178, 0, 0.2); background: rgba(255, 255, 255, 0.16); padding: 12px 0 12px 15px; box-sizing: border-box; @@ -953,7 +993,9 @@ gap: 2px; overflow: hidden; color: rgba(255, 255, 255, 0.45); - font-feature-settings: 'liga' off, 'clig' off; + font-feature-settings: + "liga" off, + "clig" off; text-overflow: ellipsis; font-family: "PingFang SC"; font-size: 12px; @@ -976,7 +1018,9 @@ display: flex; gap: 4px; - &-applications, &-level-requirements, &-play-type { + &-applications, + &-level-requirements, + &-play-type { color: rgba(255, 255, 255, 0.85); font-size: 11px; font-style: normal; @@ -1024,7 +1068,7 @@ gap: 16px; border-radius: 16px; border: 1px solid rgba(255, 255, 255, 0.06); - background: #FFF; + background: #fff; .sticky-bottom-bar-share { display: flex; @@ -1049,7 +1093,7 @@ &-separator { width: 1px; height: 24px; - background: rgba(0, 0, 0, 0.10); + background: rgba(0, 0, 0, 0.1); } .sticky-bottom-bar-comment { @@ -1085,7 +1129,7 @@ flex: 1 0 0; border-radius: 16px; border: 1px solid rgba(0, 0, 0, 0.06); - background: #FFF; + background: #fff; &-price { font-family: "PoetsenOne"; @@ -1130,4 +1174,3 @@ } } } - diff --git a/src/pages/detail/index.tsx b/src/pages/detail/index.tsx index ea2820a..4122817 100644 --- a/src/pages/detail/index.tsx +++ b/src/pages/detail/index.tsx @@ -23,6 +23,8 @@ import { DisplayConditionType, } from "@/components/NTRPEvaluatePopup"; import DetailService, { MATCH_STATUS } from "@/services/detailService"; +import * as LoginService from "@/services/loginService"; +import OrderService from "@/services/orderService"; import { getCurrentLocation, calculateDistance } from "@/utils/locationUtils"; import { useUserInfo, useUserActions } from "@/store/userStore"; import img from "@/config/images"; @@ -218,10 +220,16 @@ function navto(url) { function StickyButton(props) { const { handleShare, handleJoinGame, detail } = props; const ntrpRef = useRef(null); - const userInfo = useUserInfo(); - const { id } = userInfo; - const { publisher_id, match_status, price, user_action_status, end_time } = - detail || {}; + // const userInfo = useUserInfo(); + // const { id } = userInfo; + const { + id, + publisher_id, + match_status, + price, + user_action_status, + end_time, + } = detail || {}; function handleSelfEvaluate() { // TODO: 打开自评弹窗 @@ -266,7 +274,14 @@ function StickyButton(props) { } else if (can_pay) { return { text: "继续支付", - action: handleJoinGame, + action: async () => { + const res = await OrderService.getUnpaidOrder(id); + if (res.code === 0) { + Taro.navigateTo({ + url: `/mod_user/orderDetail/index?id=${res.data.order_info.order_id}`, + }); + } + }, }; } else if (can_substitute) { return { @@ -323,7 +338,7 @@ function StickyButton(props) { }; } - const role = Number(publisher_id) === id ? "ownner" : "visitor"; + // const role = Number(publisher_id) === id ? "ownner" : "visitor"; return ( @@ -359,8 +374,17 @@ function StickyButton(props) { // 球局信息 function GameInfo(props) { const { detail, currentLocation } = props; - const { latitude, longitude, location, location_name, start_time, end_time } = - detail || {}; + const { + latitude, + longitude, + location, + location_name, + start_time, + end_time, + weather = [{}], + } = detail || {}; + + const [{ iconDay, tempMax, tempMin }] = weather; const openMap = () => { Taro.openLocation({ @@ -374,7 +398,9 @@ function GameInfo(props) { const [c_latitude, c_longitude] = currentLocation; const distance = - calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000; + latitude && longitude + ? calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000 + : 0; const startTime = dayjs(start_time); const endTime = dayjs(end_time); @@ -409,11 +435,16 @@ function GameInfo(props) { {/* Weather icon */} - + {/**/} + {/* Weather text and temperature */} - 28℃ - 32℃ + {tempMin && tempMax && ( + + {tempMin}℃ - {tempMax}℃ + + )} @@ -431,18 +462,22 @@ function GameInfo(props) { {/* location message */} {/* venue name and distance */} - - {location_name || "-"} - · - {distance.toFixed(1)}km - - + {distance ? ( + + {location_name || "-"} + · + {distance.toFixed(1)}km + + + ) : ( + "" + )} {/* venue address */} {location || "-"} @@ -454,8 +489,8 @@ function GameInfo(props) { {longitude && latitude && ( {}} @@ -580,7 +615,7 @@ function genNTRPRequirementText(min, max) { } else if (max) { return `${max} 以上`; } - return '-' + return "-"; } // 玩法要求 function GamePlayAndRequirement(props) { @@ -627,65 +662,87 @@ function GamePlayAndRequirement(props) { function Participants(props) { const { detail = {}, handleJoinGame } = props; const participants = detail.participants || []; - const { participant_count, max_participants, user_action_status = {} } = detail - const { can_join } = user_action_status - const leftCount = max_participants - participant_count + 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; const organizer_id = Number(detail.publisher_id); return ( 参与者 · - {leftCount > 0 ? `剩余空位 ${leftCount}` : '已满员'} - - - {/* application */} - {can_join && { - handleJoinGame() - // Taro.showToast({ title: "To be continued", icon: "none" }); - }} - > - - 申请加入 - } - {/* participants list */} - - - {participants.map((participant) => { - const { - user: { avatar_url, nickname, level, id: participant_user_id }, - } = participant; - const role = - participant_user_id === organizer_id ? "组织者" : "参与者"; - return ( - - - - {nickname || "未知"} - - - {level || "未知"} - - {role} - - ); - })} - - + {leftCount > 0 ? `剩余空位 ${leftCount}` : "已满员"} + {participant_count > 0 || showApplicationEntry ? ( + + {/* application */} + {showApplicationEntry && ( + { + handleJoinGame(); + // Taro.showToast({ title: "To be continued", icon: "none" }); + }} + > + + + 申请加入 + + + )} + {/* participants list */} + + + {participants.map((participant) => { + const { + user: { + avatar_url, + nickname, + level, + id: participant_user_id, + }, + } = participant; + const role = + participant_user_id === organizer_id ? "组织者" : "参与者"; + return ( + + + + {nickname || "未知"} + + + {level || "未知"} + + {role} + + ); + })} + + + + ) : ( + "" + )} ); } @@ -717,48 +774,86 @@ function SupplementalNotes(props) { ); } +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: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`, + playType: play_type, + }; + }); +} + function OrganizerInfo(props) { - const recommendGames = [ - { - title: "黄浦日场对拉", - time: "2025-08-25 9:00", - timeLength: "2小时", - venue: "上海体育场", - veuneType: "室外", - distance: "1.2km", - avatar: "https://img.yzcdn.cn/vant/cat.jpeg", - applications: 10, - checkedApplications: 3, - levelRequirements: "NTRP 3.5", - playType: "双打", - }, - { - title: "黄浦夜场对拉", - time: "2025-08-25 19:00", - timeLength: "2小时", - venue: "上海体育场", - veuneType: "室外", - distance: "1.2km", - avatar: "https://img.yzcdn.cn/vant/cat.jpeg", - applications: 10, - checkedApplications: 3, - levelRequirements: "NTRP 3.5", - playType: "双打", - }, - { - title: "黄浦全天对拉", - time: "2025-08-25 9:00", - timeLength: "12小时", - venue: "上海体育场", - veuneType: "室外", - distance: "1.2km", - avatar: "https://img.yzcdn.cn/vant/cat.jpeg", - applications: 10, - checkedApplications: 3, - levelRequirements: "NTRP 3.5", - playType: "双打", - }, - ]; + const { + userInfo, + currentLocation: location, + onUpdateUserInfo = () => {}, + } = 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", + }); + } + }; + return ( {/* orgnizer title */} @@ -767,26 +862,36 @@ function OrganizerInfo(props) { {/* organizer avatar and name */} - + - Light + {nickname} - 已组织 8 次 + 已组织 {hosted_games_count} 次 - NTRP 3.5 + NTRP {ntrp_level || "初学者"} - - - 关注 - + {my_id === id ? ( + "" + ) : ( + + {is_following ? ( + 取消关注 + ) : ( + <> + + 关注 + + )} + + )} {/* recommend games by organizer */} - + {}}> TA的更多活动 {game.venue} · - {game.veuneType} + {game.venueType} · {game.distance} @@ -865,26 +970,31 @@ function Index() { 0, 0, ]); const { id, from } = params; - const { fetchUserInfo, updateUserInfo } = useUserActions(); + const [userInfo, setUserInfo] = useState({}); // 组织者的userInfo + const { fetchUserInfo } = useUserActions(); // 获取登录用户的userInfo const sharePopupRef = useRef(null); useDidShow(async () => { await updateLocation(); await fetchUserInfo(); - await fetchDetail(); + // await fetchDetail(); }); const updateLocation = async () => { try { - const location = await getCurrentLocation() - setCurrentLocation([location.latitude, location.longitude]) - + const { address, ...location } = await getCurrentLocation(); + setCurrentLocation([location.latitude, location.longitude]); + // 使用 userStore 中的统一位置更新方法 - await updateUserInfo({ latitude: location.latitude, longitude: location.longitude }) - + // await updateUserInfo({ latitude: location.latitude, longitude: location.longitude }) + await DetailService.updateLocation({ + latitude: Number(location.latitude), + longitude: Number(location.longitude), + }); + // 位置更新后,重新获取详情页数据(因为距离等信息可能发生变化) - await fetchDetail() + await fetchDetail(); } catch (error) { console.error("用户位置更新失败", error); } @@ -895,9 +1005,22 @@ function Index() { const res = await DetailService.getDetail(Number(id)); if (res.code === 0) { setDetail(res.data); + fetchUserInfoById(res.data.publisher_id); } }; + const onUpdateUserInfo = () => { + fetchUserInfoById(detail.publisher_id); + }; + + async function fetchUserInfoById(user_id) { + const userDetailInfo = await LoginService.getUserInfoById(Number(user_id)); + if (userDetailInfo.code === 0) { + // console.log(userDetailInfo.data); + setUserInfo(userDetailInfo.data); + } + } + function handleShare() { sharePopupRef.current.show(); } @@ -965,7 +1088,12 @@ function Index() { {/* supplemental notes */} {/* organizer and recommend games by organizer */} - + {/* sticky bottom action bar */} > { - return httpService.post('/games/detail', { id }, { + async getDetail(id: number): Promise> { + return httpService.post( + "/games/detail", + { id }, + { + showLoading: true, + }, + ); + } + + async updateLocation(location: { + latitude: number; + longitude: number; + }): Promise> { + return httpService.post("/user/update_location", location, { showLoading: true, - }) + }); } } // 导出认证服务实例 -export default new GameDetailService() \ No newline at end of file +export default new GameDetailService(); diff --git a/src/services/loginService.ts b/src/services/loginService.ts index 96398b3..a0027f6 100644 --- a/src/services/loginService.ts +++ b/src/services/loginService.ts @@ -1,7 +1,7 @@ import Taro from "@tarojs/taro"; import httpService, { ApiResponse } from "./httpService"; -import tokenManager from '../utils/tokenManager'; -import { useUser } from '@/store/userStore'; +import tokenManager from "../utils/tokenManager"; +import { useUser } from "@/store/userStore"; // 微信用户信息接口 export interface WechatUserInfo { @@ -49,7 +49,7 @@ export interface UserStats { export interface PhoneLoginParams { phone: string; verification_code: string; - user_code: string + user_code: string; } export interface UserInfoType { @@ -105,7 +105,7 @@ export const wechat_auth_login = async ( try { await useUser.getState().fetchUserInfo(); } catch (error) { - console.error('更新用户信息到 store 失败:', error); + console.error("更新用户信息到 store 失败:", error); } return { @@ -158,7 +158,7 @@ export const phone_auth_login = async ( try { await useUser.getState().fetchUserInfo(); } catch (error) { - console.error('更新用户信息到 store 失败:', error); + console.error("更新用户信息到 store 失败:", error); } return { @@ -410,4 +410,40 @@ export const updateUserPhone = async (payload: ChangePhoneParams) => { console.error("更新用户手机号失败:", error); throw error; } +} +// 获取指定用户信息 +export const getUserInfoById = async (id) => { + try { + const response = await httpService.post("/user/detail_by_id", { id }); + return response; + } catch (error) { + console.error("获取用户信息失败:", error); + throw error; + } +}; + +// 关注用户 +export const followUser = async (following_id) => { + try { + const response = await httpService.post("/wch_users/follow", { + following_id, + }); + return response; + } catch (error) { + console.error("关注失败:", error); + throw error; + } +}; + +// 取消关注用户 +export const unFollowUser = async (following_id) => { + try { + const response = await httpService.post("/wch_users/unfollow", { + following_id, + }); + return response; + } catch (error) { + console.error("取消关注失败:", error); + throw error; + } }; diff --git a/src/services/orderService.ts b/src/services/orderService.ts index 76f3394..7272cd0 100644 --- a/src/services/orderService.ts +++ b/src/services/orderService.ts @@ -1,90 +1,125 @@ -import httpService from './httpService' -import type { ApiResponse } from './httpService' -import { requestPayment } from '@tarojs/taro' +import httpService from "./httpService"; +import type { ApiResponse } from "./httpService"; export interface SignType { /** 仅在微信支付 v2 版本接口适用 */ - MD5 + MD5; /** 仅在微信支付 v2 版本接口适用 */ - 'HMAC-SHA256' + "HMAC-SHA256"; /** 仅在微信支付 v3 版本接口适用 */ - RSA + RSA; +} + +export enum OrderStatus { + PENDING = 0, + PAID, + FINISHED, } export interface PayMentParams { - order_id: number, - order_no: string, - status: number, - appId: string, - timeStamp: string, - nonceStr: string, - package: string, - signType: keyof SignType, - paySign: string + order_id: number; + order_no: string; + status: number; + appId: string; + timeStamp: string; + nonceStr: string; + package: string; + signType: keyof SignType; + paySign: string; } // 用户接口 export interface OrderResponse { - participant_id: number, - payment_required: boolean, - payment_params: PayMentParams + participant_id: number; + payment_required: boolean; + payment_params: PayMentParams; } export interface OrderInfo { - time: string - address: string - registrant_nickname: string - registrant_phone: string - cost: string + time: string; + address: string; + registrant_nickname: string; + registrant_phone: string; + cost: string; } export interface RefundPolicy { - application_time: string - refund_rule: string + application_time: string; + refund_rule: string; } export interface GameStatus { - current_players: number - max_players: number - is_full: boolean - can_join: boolean + current_players: number; + max_players: number; + is_full: boolean; + can_join: boolean; } export interface GameDetails { - game_id: number - game_title: string - game_description: string + game_id: number; + game_title: string; + game_description: string; } export interface GameOrderRes { - order_info: OrderInfo - refund_policy: RefundPolicy[] - notice: string - game_status: GameStatus - game_details: GameDetails + order_info: OrderInfo; + refund_policy: RefundPolicy[]; + notice: string; + game_status: GameStatus; + game_details: GameDetails; } - - // 发布球局类 class OrderService { // 查询订单列表 async getOrderList() { - return httpService.post('/user/orders', {}, { showLoading: true }) - } - // 创建订单 - async createOrder(game_id: number): Promise> { - return httpService.post('/payment/create_order', { game_id }, { - showLoading: true, - }) + return httpService.post("/user/orders", {}, { showLoading: true }); } - async getOrderInfo(game_id: number): Promise> { - return httpService.post('/payment/check_order', { game_id }, { - showLoading: true, - }) + // 获取订单详情 + async getOrderDetail(order_id: number): Promise> { + return httpService.post( + "/payment/order_details", + { order_id }, + { + showLoading: true, + }, + ); + } + + // 创建订单 + async createOrder(game_id: number): Promise> { + return httpService.post( + "/payment/create_order", + { game_id }, + { + showLoading: true, + }, + ); + } + + // 检查订单信息 + async getCheckOrderInfo(game_id: number): Promise> { + return httpService.post( + "/payment/check_order", + { game_id }, + { + showLoading: true, + }, + ); + } + + // 获取未支付订单 + async getUnpaidOrder(game_id: number): Promise> { + return httpService.post( + "/payment/get_unpaid_order", + { game_id }, + { + showLoading: true, + }, + ); } } // 导出认证服务实例 -export default new OrderService() \ No newline at end of file +export default new OrderService(); diff --git a/yarn.lock b/yarn.lock index b558aa5..42e13a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8333,6 +8333,11 @@ quick-lru@^4.0.1: resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +qweather-icons@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/qweather-icons/-/qweather-icons-1.8.0.tgz#25eb2714d68daf13c06032c082f720e6734c4ecb" + integrity sha512-Ti7KGrWXSV7e5HMpjPhDYua8hwC3t0nScNOSQ3kLPehsOja5cioZw8bcKi7jeAGBlVLrgAkLla5xVEYlH1s5Jw== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"