diff --git a/src/app.config.ts b/src/app.config.ts index 8f41f6d..def94a1 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,56 +1,51 @@ export default defineAppConfig({ pages: [ + "pages/home/index", //中转页 - 'pages/home/index', //中转页 - - 'pages/login/index/index', - 'pages/login/verification/index', - 'pages/login/terms/index', - - 'pages/list/index', // 列表页 - 'pages/search/index', // 搜索页 - 'pages/searchResult/index', // 搜索结果页面 - 'pages/publishBall/index', - 'pages/detail/index', - 'pages/message/index', - 'pages/orderCheck/index', + "pages/login/index/index", + "pages/login/verification/index", + "pages/login/terms/index", + "pages/list/index", // 列表页 + "pages/search/index", // 搜索页 + "pages/searchResult/index", // 搜索结果页面 + "pages/publishBall/index", + "pages/detail/index", + // 'pages/message/index', + // 'pages/orderCheck/index', // 'pages/mapDisplay/index', - ], subPackages: [ { root: "mod_user", pages: [ - 'pages/myself/index', // 个人中心 - 'pages/edit/index', // 个人中心 - 'pages/favorites/index', // 个人中心 - 'pages/orders/index', // 个人中心 - - ] - - } + "pages/myself/index", // 个人中心 + "pages/edit/index", // 个人中心 + "message/index", // 消息页 + "orderList/index", // 订单列表页 + "orderDetail/index", // 订单详情页 + "favorites/index", // 收藏页 + "ntrp-evaluate/index", // NTRP评估页 + ], + }, ], window: { - backgroundTextStyle: 'light', - navigationBarBackgroundColor: '#fff', - navigationBarTextStyle: 'black' + backgroundTextStyle: "light", + navigationBarBackgroundColor: "#fff", + navigationBarTextStyle: "black", }, permission: { - 'scope.userLocation': { - desc: '你的位置信息将用于小程序位置接口的效果展示' - } + "scope.userLocation": { + desc: "你的位置信息将用于小程序位置接口的效果展示", + }, }, - requiredPrivateInfos: [ - 'getLocation', - 'chooseLocation' - ], + requiredPrivateInfos: ["getLocation", "chooseLocation"], plugins: { chooseLocation: { version: "1.0.12", - provider: "wx76a9a06e5b4e693e" - } - } -}) + provider: "wx76a9a06e5b4e693e", + }, + }, +}); diff --git a/src/components/Auth/index.tsx b/src/components/Auth/index.tsx index 2ec4f42..595c3aa 100644 --- a/src/components/Auth/index.tsx +++ b/src/components/Auth/index.tsx @@ -1,51 +1,51 @@ -import React, { useEffect, useState } from 'react' -import Taro from '@tarojs/taro' -import { View } from '@tarojs/components' -import { check_login_status } from '@/services/loginService' - - +import React, { useEffect, useState } from "react"; +import Taro from "@tarojs/taro"; +import { View } from "@tarojs/components"; +import { check_login_status } from "@/services/loginService"; export function getCurrentFullPath(): string { - const pages = Taro.getCurrentPages() - const currentPage = pages.at(-1) + const pages = Taro.getCurrentPages(); + const currentPage = pages.at(-1); if (currentPage) { - console.log(currentPage, 'currentPage get') - const route = currentPage.route - const options = currentPage.options || {} + console.log(currentPage, "currentPage get"); + const route = currentPage.route; + const options = currentPage.options || {}; const query = Object.keys(options) - .map(key => `${key}=${options[key]}`) - .join('&') + .map((key) => `${key}=${options[key]}`) + .join("&"); - return query ? `/${route}?${query}` : `/${route}` + return query ? `/${route}?${query}` : `/${route}`; } - return '' + return ""; } -export default function withAuth

(WrappedComponent: React.ComponentType

) { +export default function withAuth

( + WrappedComponent: React.ComponentType

, +) { const ComponentWithAuth: React.FC

= (props: P) => { - const [authed, setAuthed] = useState(false) + const [authed, setAuthed] = useState(false); useEffect(() => { - const is_login = check_login_status() - setAuthed(is_login) + const is_login = check_login_status(); + setAuthed(is_login); if (!is_login) { - const currentPage = getCurrentFullPath() + const currentPage = getCurrentFullPath(); // Taro.redirectTo({ // url: `/pages/login/index/index${ // currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : '' // }`, // }) } - }, []) + }, []); // if (!authed) { // return // 空壳,避免 children 渲染出错 // } - return - } + return ; + }; - return ComponentWithAuth -} \ No newline at end of file + return ComponentWithAuth; +} diff --git a/src/components/GuideBar/index.tsx b/src/components/GuideBar/index.tsx index 17ca07c..f8183d7 100644 --- a/src/components/GuideBar/index.tsx +++ b/src/components/GuideBar/index.tsx @@ -1,43 +1,46 @@ -import React, { useState } from 'react' -import { View, Text, Image } from '@tarojs/components' -import Taro from '@tarojs/taro' -import img from '@/config/images' -import './index.scss' -import PublishMenu from '../PublishMenu' -export type currentPageType = 'games' | 'message' | 'personal' +import React, { useState } from "react"; +import { View, Text, Image } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import img from "@/config/images"; +import "./index.scss"; +import PublishMenu from "../PublishMenu"; +export type currentPageType = "games" | "message" | "personal"; const GuideBar = (props) => { - const { currentPage } = props + const { currentPage } = props; const guideItems = [ { - code: 'list', - text: '球局', + code: "list", + text: "球局", }, { - code: 'message', - text: '消息', + code: "message", + text: "消息", }, { - code: 'personal', - text: '我的', + code: "personal", + text: "我的", }, - ] + ]; const handlePublish = () => { Taro.navigateTo({ - url: '/pages/publishBall/index', - }) - } + url: "/pages/publishBall/index", + }); + }; const handlePageChange = (code: string) => { if (code === currentPage) { - return + return; } - let url = `/pages/${code}/index` - if (code === 'personal') { - url = '/mod_user/pages/myself/index' + let url = `/pages/${code}/index`; + if (code === "personal") { + url = "/mod_user/pages/myself/index"; + } + if (code === "message") { + url = "/mod_user/message/index"; } Taro.redirectTo({ url: url, @@ -45,18 +48,18 @@ const GuideBar = (props) => { Taro.pageScrollTo({ scrollTop: 0, duration: 300, - }) - }) - } + }); + }); + }; return ( - - + + {/* guide area on the left */} - + {guideItems.map((item) => ( handlePageChange(item.code)} > {item.text} @@ -70,7 +73,7 @@ const GuideBar = (props) => { - ) -} + ); +}; -export default GuideBar \ No newline at end of file +export default GuideBar; diff --git a/src/components/NTRPEvaluatePopup/index.module.scss b/src/components/NTRPEvaluatePopup/index.module.scss new file mode 100644 index 0000000..5b618c0 --- /dev/null +++ b/src/components/NTRPEvaluatePopup/index.module.scss @@ -0,0 +1,12 @@ +@use "~@/scss/images.scss" as img; + +.container { + width: calc(100vw - 40px); + height: 400px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding: 20px; + box-sizing: border-box; +} diff --git a/src/components/NTRPEvaluatePopup/index.tsx b/src/components/NTRPEvaluatePopup/index.tsx new file mode 100644 index 0000000..aec7ede --- /dev/null +++ b/src/components/NTRPEvaluatePopup/index.tsx @@ -0,0 +1,99 @@ +import React, { + useState, + useImperativeHandle, + useEffect, + forwardRef, +} from "react"; +import { Button, Input, View, Text } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import CommonPopup from "../CommonPopup"; +import { getCurrentFullPath } from "@/components/Auth"; +import { useUserInfo, useUserActions } from "@/store/userStore"; +import style from "./index.module.scss"; + +export enum EvaluateType { + EDIT = "edit", + EVALUATE = "evaluate", +} + +export enum DisplayConditionType { + AUTO = "auto", + ALWAYS = "always", +} + +export enum SceneType { + LIST = "list", + PUBLISH = "publish", + PERSONAL = "personal", + DETAIL = "detail", +} + +interface NTRPEvaluatePopupProps { + types: EvaluateType[]; + displayCondition: DisplayConditionType; + scene: SceneType; + children: React.ReactNode; +} + +function showCondition(scene, ntrp) { + if (scene === "list") { + // TODO: 显示频率 + return Math.random() < 0.1 && [0, undefined].includes(ntrp); + } + return [0, undefined].includes(ntrp); +} + +const NTRPEvaluatePopup = (props: NTRPEvaluatePopupProps, ref) => { + const { + types = ["edit", "evaluate"], + displayCondition = "auto", + scene = "list", + } = props; + const [visible, setVisible] = useState(false); + const { ntrp } = useUserInfo(); + const { fetchUserInfo } = useUserActions(); + + useImperativeHandle(ref, () => ({ + show: () => setVisible(true), + })); + + function handleEvaluate() { + setVisible(false); + // TODO: 实现NTRP评估逻辑 + Taro.navigateTo({ + url: `/mod_user/ntrp-evaluate/index?redirect=${encodeURIComponent(getCurrentFullPath())}`, + }); + } + + useEffect(() => { + // fetchUserInfo(); + }, []); + + const showEntry = + displayCondition === "auto" + ? showCondition(scene, ntrp) + : displayCondition === "always"; + + return ( + <> + setVisible(false)} + position="center" + hideFooter + enableDragToClose={false} + > + + {/* TODO: 直接修改NTRP水平 */} + 您还未测评。。。 + 请先进行NTRP评估 + + + + {showEntry && props.children} + + ); +}; + +export default forwardRef(NTRPEvaluatePopup); diff --git a/src/components/index.ts b/src/components/index.ts index abf5a73..4d84493 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,40 +1,42 @@ -import ActivityTypeSwitch from './ActivityTypeSwitch' -import TextareaTag from './TextareaTag' -import FormSwitch from './FormSwitch' -import ImageUpload from './ImageUpload' -import Range from './Range' -import NumberInterval from './NumberInterval' +import ActivityTypeSwitch from "./ActivityTypeSwitch"; +import TextareaTag from "./TextareaTag"; +import FormSwitch from "./FormSwitch"; +import ImageUpload from "./ImageUpload"; +import Range from "./Range"; +import NumberInterval from "./NumberInterval"; -import TimeSelector from './TimeSelector' -import TitleTextarea from './TitleTextarea' -import CommonPopup from './CommonPopup' -import TimePicker from './TimePicker/TimePicker' -import { CalendarUI, DialogCalendarCard } from './Picker' -import CommonDialog from './CommonDialog' -import PublishMenu from './PublishMenu/PublishMenu' -import UploadCover from './UploadCover' -import EditModal from './EditModal/index' -import withAuth from './Auth' -import { CustomPicker, PopupPicker } from './Picker' +import TimeSelector from "./TimeSelector"; +import TitleTextarea from "./TitleTextarea"; +import CommonPopup from "./CommonPopup"; +import TimePicker from "./TimePicker/TimePicker"; +import { CalendarUI, DialogCalendarCard } from "./Picker"; +import CommonDialog from "./CommonDialog"; +import PublishMenu from "./PublishMenu/PublishMenu"; +import UploadCover from "./UploadCover"; +import EditModal from "./EditModal/index"; +import withAuth from "./Auth"; +import { CustomPicker, PopupPicker } from "./Picker"; +import NTRPEvaluatePopup from "./NTRPEvaluatePopup"; - export { - ActivityTypeSwitch, - TextareaTag, - FormSwitch, - ImageUpload, - Range, - NumberInterval, - TimeSelector, - TitleTextarea, - CommonPopup, - TimePicker, - DialogCalendarCard, - CalendarUI, - CommonDialog, - PublishMenu, - UploadCover, - EditModal, - withAuth, - CustomPicker, - PopupPicker - } +export { + ActivityTypeSwitch, + TextareaTag, + FormSwitch, + ImageUpload, + Range, + NumberInterval, + TimeSelector, + TitleTextarea, + CommonPopup, + TimePicker, + DialogCalendarCard, + CalendarUI, + CommonDialog, + PublishMenu, + UploadCover, + EditModal, + withAuth, + CustomPicker, + PopupPicker, + NTRPEvaluatePopup, +}; diff --git a/src/pages/orderCheck/index.config.ts b/src/mod_user/favorites/index.config.ts similarity index 100% rename from src/pages/orderCheck/index.config.ts rename to src/mod_user/favorites/index.config.ts diff --git a/src/pages/orderCheck/index.scss b/src/mod_user/favorites/index.scss similarity index 100% rename from src/pages/orderCheck/index.scss rename to src/mod_user/favorites/index.scss diff --git a/src/pages/orderCheck/index.tsx b/src/mod_user/favorites/index.tsx similarity index 100% rename from src/pages/orderCheck/index.tsx rename to src/mod_user/favorites/index.tsx diff --git a/src/pages/message/index.config.ts b/src/mod_user/message/index.config.ts similarity index 100% rename from src/pages/message/index.config.ts rename to src/mod_user/message/index.config.ts diff --git a/src/pages/message/index.scss b/src/mod_user/message/index.scss similarity index 100% rename from src/pages/message/index.scss rename to src/mod_user/message/index.scss diff --git a/src/pages/message/index.tsx b/src/mod_user/message/index.tsx similarity index 100% rename from src/pages/message/index.tsx rename to src/mod_user/message/index.tsx diff --git a/src/mod_user/ntrp-evaluate/index.config.ts b/src/mod_user/ntrp-evaluate/index.config.ts new file mode 100644 index 0000000..99e9ac1 --- /dev/null +++ b/src/mod_user/ntrp-evaluate/index.config.ts @@ -0,0 +1,5 @@ +export default definePageConfig({ + navigationBarTitleText: "NTRP 评测", + // navigationBarBackgroundColor: '#FAFAFA', + // navigationStyle: 'custom', +}); diff --git a/src/mod_user/ntrp-evaluate/index.module.scss b/src/mod_user/ntrp-evaluate/index.module.scss new file mode 100644 index 0000000..f66c2da --- /dev/null +++ b/src/mod_user/ntrp-evaluate/index.module.scss @@ -0,0 +1,64 @@ +@use "~@/scss/images.scss" as img; + +.container { + width: 100%; + padding: 20px; + box-sizing: border-box; + + .title { + font-size: 24px; + font-weight: bold; + font-family: Arial, sans-serif; + text-align: center; + margin-bottom: 20px; + color: #333; + } + + .content { + width: 100%; + height: 300px; + background-color: #f9f9f9; + border-radius: 8px; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .image { + /* width: 200px; */ + /* height: 200px; */ + /* object-fit: cover; */ + } + + .description { + padding: 10px; + box-sizing: border-box; + font-size: 16px; + color: #666; + } + } + .button { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + width: 200px; + height: 50px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 16px; + font-weight: bold; + font-family: Arial, sans-serif; + text-align: center; + line-height: 50px; + transition: background-color 0.3s ease; + + &:hover { + background-color: #0056b3; + } + } +} diff --git a/src/mod_user/ntrp-evaluate/index.tsx b/src/mod_user/ntrp-evaluate/index.tsx new file mode 100644 index 0000000..fcf5348 --- /dev/null +++ b/src/mod_user/ntrp-evaluate/index.tsx @@ -0,0 +1,53 @@ +import { useState, useEffect } from "react"; +import { View, Text, Image, Button } from "@tarojs/components"; +import Taro, { useRouter } from "@tarojs/taro"; +import { withAuth } from "@/components"; +import evaluateService from "@/services/evaluateService"; +import { useUserActions } from "@/store/userStore"; +import { delay } from "@/utils"; +import styles from "./index.module.scss"; + +function NtrpEvaluate() { + const { updateUserInfo } = useUserActions(); + const { params } = useRouter(); + const { redirect } = params; + + useEffect(() => { + evaluateService.getEvaluateQuestions().then((data) => { + console.log(data); + }); + }, []); + + async function handleUpdateNtrp() { + await updateUserInfo({ + ntrp_level: "4.0", + }); + Taro.showToast({ + title: "更新成功", + icon: "success", + duration: 2000, + }); + await delay(2000); + if (redirect) { + Taro.redirectTo({ url: decodeURIComponent(redirect) }); + } + } + + return ( + + NTRP评分 + + + 您的NTRP评分是 4.0 分。 + + + + ); +} + +export default withAuth(NtrpEvaluate); diff --git a/src/mod_user/orderDetail/config.ts b/src/mod_user/orderDetail/config.ts new file mode 100644 index 0000000..0d29f77 --- /dev/null +++ b/src/mod_user/orderDetail/config.ts @@ -0,0 +1,24 @@ +export const DECLAIMER = ` + GO!仅提供活动发布平台,如在约球活动中发生任何纠纷,请直接与相关方联系,GO!不承担任何责任,但根据需要会依法提供必要的协助。 + + 发起约球规则 + 1、需至少提前24小时发起活动; + 2、招募的人数为选择区间,达到最低人数,活动即发起成功; + 3、活动场地可选择输入(需要在地图上标记位置),选择已订场地的球局如因发起人原因未达成,场地不会自动退订。为避免不必要的损失,如需退订请提前2小时手动申请; + 4、使用GO!发起活动无须付费预订; + 5、约球活动结束4天后,队员的报名费自动转入发起人的[微信钱包-余额]中; + 6、活动自动取消:距约球活动开始时间2小时(时间待讨论),未达到最低招募人数,系统自动取消活动,活动的报名费用全额原路退回; + 7、活动手动取消:距约球活动开始2小时前可手动取消活动,请至「我的-我的球局」进行取消操作; + 8、发起人可距约球活动开始2小时前,对该活动进行编辑。 + ① 若无人报名时,可以对约球活动详情进行编辑。「我的球局-约球-点击发起的约球」在最下方点击编辑,可对约球活动名称,招募人数,发起方人数,级别要求,报名费用,联系方式,事项备注进行修改; + ② 取消约球:可于「我的-我的球局-我发起的」进行取消约球操作; + ③ 对报名队员进行编辑:在「我的-我的球局-我发起的-点击约球活动-球友」对加入的队员进行管理,若操作不约的队员自动退款并不可再报名加入这次活动。 + + 参与约球规则 + 1、活动开始前均可报名;若发起人允许,活动已开始未满员也可替补报名 + 2、参与者不提供取消报名方式,可于约球活动开始2小时前联系发起者删除报名; + + 异常处理场景说明 + 发起人临时失联/爽约;发起人恶意删除队员,GO!支持全额退款 + 参与者爽约不通知,不可退款但鼓励用户评分机制中反馈,平台将限制其部分功能使用(如发起权限、报名权限等)。 +`; diff --git a/src/mod_user/orderDetail/index.config.ts b/src/mod_user/orderDetail/index.config.ts new file mode 100644 index 0000000..baf5dd1 --- /dev/null +++ b/src/mod_user/orderDetail/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: "订单详情", + navigationBarBackgroundColor: "#FAFAFA", +}); diff --git a/src/mod_user/orderDetail/index.module.scss b/src/mod_user/orderDetail/index.module.scss new file mode 100644 index 0000000..8c4893e --- /dev/null +++ b/src/mod_user/orderDetail/index.module.scss @@ -0,0 +1,342 @@ +@use "~@/scss/images.scss" as img; + +.container { + padding: 20px; + box-sizing: border-box; + background-color: #fafafa; +} + +.gameInfoContainer { + .gameInfo { + border-top-left-radius: 12px; + border-top-right-radius: 12px; + background-color: #fff; + padding: 12px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 12px; + + &DateWeather { + display: flex; + align-items: center; + justify-content: space-between; + // gap: 12px; + + &CalendarDate { + /* width: 60%; */ + display: flex; + align-items: center; + gap: 16px; + + &Calendar { + display: flex; + width: 48px; + height: 48px; + box-sizing: border-box; + flex-direction: column; + align-items: center; + gap: 4px; + border-radius: 12px; + border: 0.5px solid rgba(0, 0, 0, 0.08); + overflow: hidden; + color: #fff; + background: #fff; + + .month { + width: 100%; + height: 18px; + font-size: 10px; + display: flex; + padding: 1px auto; + box-sizing: border-box; + justify-content: center; + align-items: center; + // border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: #ff3b30; + } + + .day { + display: flex; + width: 48px; + height: 30px; + color: #000; + // padding-bottom: 6px; + box-sizing: border-box; + flex-direction: column; + align-items: center; + // border: 0.5px solid rgba(255, 255, 255, 0.08); + // background: rgba(255, 255, 255, 0.25); + // background-color: #536272; + } + } + + &Date { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-evenly; + gap: 4px; + align-self: stretch; + color: #fff; + + .date { + color: #000; + font-feature-settings: + "liga" off, + "clig" off; + font-family: "PingFang SC"; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 24px; /* 150% */ + } + + .venueTime { + color: rgba(0, 0, 0, 0.8); + font-feature-settings: + "liga" off, + "clig" off; + font-family: "PingFang SC"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 166.667% */ + } + } + } + } + + &Place { + .locationMessage { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 12px; + + &Icon { + width: 48px; + height: 48px; + padding: 1px; + box-sizing: border-box; + + &Image { + width: 46px; + height: 46px; + border-radius: 12px; + } + } + + &Text { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-evenly; + gap: 4px; + align-self: stretch; + + &NameDistance { + display: flex; + align-items: center; + gap: 4px; + color: #000; + text-align: center; + font-feature-settings: + "liga" off, + "clig" off; + font-family: "PingFang SC"; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 24px; /* 150% */ + + &Arrow { + width: 16px; + height: 16px; + } + } + + &Address { + color: rgba(0, 0, 0, 0.8); + text-align: center; + font-feature-settings: + "liga" off, + "clig" off; + font-family: "PingFang SC"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 166.667% */ + } + } + } + } + } + + .gameInfoActions { + min-height: 12px; + /* height: 12px; */ + background-color: #fff; + border-top: 0.5px solid rgba(0, 0, 0, 0.06); + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; + } +} + +.orderSummary { + margin-top: 20px; + + .moduleTitle { + display: flex; + padding: 15px 0 8px; + justify-content: space-between; + align-items: center; + align-self: stretch; + color: #000; + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; + letter-spacing: -0.23px; + } + + .summaryList { + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.06); + background: #FFF; + box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06); + + .summaryItem { + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: 8px 12px; + // height: 24px; + + .title { + width: 120px; + display: inline-block; + color: rgba(60, 60, 67, 0.60); + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 24px; + } + + .content { + color: #000; + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 24px; + } + } + } +} + +.refundPolicy { + .moduleTitle { + display: flex; + padding: 15px 0 8px; + justify-content: space-between; + align-items: center; + align-self: stretch; + color: #000; + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; + letter-spacing: -0.23px; + } + + .policyList { + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.06); + background: #FFF; + box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06); + + .policyItem { + display: flex; + justify-content: space-around; + align-items: center; + color: #000; + text-align: center; + 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); + + &:nth-child(1) { + color: #000; + text-align: center; + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; + border: none; + } + + .time, .rule { + width: 50%; + padding: 10px 12px; + } + + .rule { + border-left: 1px solid rgba(0, 0, 0, 0.06); + } + } + } +} + +.declaimer { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + padding-bottom: 100px; + .title { + display: flex; + padding: 15px 0 0; + justify-content: space-between; + align-items: center; + align-self: stretch; + color: #000; + font-feature-settings: 'liga' off, 'clig' off; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; + letter-spacing: -0.23px; + } + + .content { + color: rgba(22, 24, 35, 0.60); + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 24px; /* 171.429% */ + letter-spacing: 0.28px; + } +} + +.payButton { + position: fixed; + bottom: 40px; + left: 12px; + width: calc(100vw - 24px); +} diff --git a/src/mod_user/orderDetail/index.tsx b/src/mod_user/orderDetail/index.tsx new file mode 100644 index 0000000..cd467fd --- /dev/null +++ b/src/mod_user/orderDetail/index.tsx @@ -0,0 +1,292 @@ +import React, { useState } from "react"; +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 { withAuth } from "@/components"; +import { calculateDistance, getCurrentLocation } from "@/utils"; +import img from "@/config/images"; +import { DECLAIMER } from "./config"; +import styles from "./index.module.scss"; + +dayjs.locale("zh-cn"); + +function GameInfo(props) { + const { detail, currentLocation } = props; + const { latitude, longitude, location, location_name, start_time, end_time } = + detail || {}; + + const openMap = () => { + Taro.openLocation({ + latitude, // 纬度(必填) + longitude, // 经度(必填) + name: location_name, // 位置名(可选) + address: location, // 地址详情(可选) + scale: 15, // 地图缩放级别(1-28) + }); + }; + + const [c_latitude, c_longitude] = currentLocation; + const distance = + c_latitude + c_longitude === 0 + ? 0 + : calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000; + + const startTime = dayjs(start_time); + const endTime = dayjs(end_time); + const game_length = endTime.diff(startTime, "minutes") / 60; + + const startMonth = startTime.format("M"); + const startDay = startTime.format("D"); + const theDayOfWeek = startTime.format("dddd"); + const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}`; + const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`; + + return ( + + + {/* Date and Weather */} + + {/* Calendar and Date time */} + + {/* Calendar */} + + {startMonth}月 + {startDay} + + {/* Date time */} + + {startDate} + + {gameRange} ({game_length}小时) + + + + + {/* Place */} + + {/* venue location message */} + + {/* location icon */} + + + + {/* location message */} + + {/* venue name and distance */} + + {location_name || "-"} + {distance ? ( + <> + · + {distance.toFixed(1)}km + + ) : null} + + + + {/* venue address */} + + {location || "-"} + + + + + + {/* Action bar */} + + + ); +} + +function OrderMsg(props) { + const { detail, orderInfo } = props; + const { + start_time, + end_time, + location, + location_name, + wechat_contact, + price, + } = detail; + const { order_info: { registrant_nickname } = {} } = orderInfo + const startTime = dayjs(start_time); + const endTime = dayjs(end_time); + const startYear = startTime.format('YYYY') + const startMonth = startTime.format("M"); + const startDay = startTime.format("D"); + const startDate = `${startYear}年${startMonth}月${startDay}日`; + const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`; + const summary = [ + { + title: "时间", + content: `${startDate} ${gameRange}`, + }, + { + title: "地址", + content: `${location} ${location_name}`, + }, + { + title: "组织者昵称", + content: registrant_nickname, + }, + { + title: "组织者电话", + content: wechat_contact, + }, + { + title: "费用", + content: `${price} 元`, + }, + ]; + return ( + + + 确认订单信息 + + {/* 订单信息摘要 */} + + { + 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 } + })] + return ( + + + 退款政策 + + {/* 订单信息摘要 */} + + { + policyList.map((item, index) => ( + {item.time} + {item.rule} + )) + } + + + ); +} + +function Disclaimer() { + return ( + + 免责声明 + {DECLAIMER} + + ); +} + +const OrderCheck = () => { + const { params } = useRouter(); + const { id, gameId } = params; + const [detail, setDetail] = useState({}); + const [location, setLocation] = useState([0, 0]); + const [orderInfo, setOrderInfo] = 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); + } + const location = await getCurrentLocation(); + setLocation([location.latitude, location.longitude]); + }); + + //TODO: get order msg from id + const handlePay = async () => { + Taro.showLoading({ + 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", + }); + }, + }); + } + } + }; + return ( + + {/* Game Date and Address */} + + {/* Order message */} + + {/* Refund policy */} + + {/* Disclaimer */} + + + + ); +}; + +export default withAuth(OrderCheck); diff --git a/src/mod_user/orderList/index.config.ts b/src/mod_user/orderList/index.config.ts new file mode 100644 index 0000000..e8419ed --- /dev/null +++ b/src/mod_user/orderList/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '订单确认', + navigationBarBackgroundColor: '#FAFAFA' +}) \ No newline at end of file diff --git a/src/mod_user/orderList/index.scss b/src/mod_user/orderList/index.scss new file mode 100644 index 0000000..e9e7c6b --- /dev/null +++ b/src/mod_user/orderList/index.scss @@ -0,0 +1 @@ +@use '~@/scss/images.scss' as img; \ No newline at end of file diff --git a/src/mod_user/orderList/index.tsx b/src/mod_user/orderList/index.tsx new file mode 100644 index 0000000..94c8903 --- /dev/null +++ b/src/mod_user/orderList/index.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react' +import { View, Text, Button } from '@tarojs/components' +import Taro, { useDidShow, useRouter } from '@tarojs/taro' +import { delay } from '@/utils' +import orderService from '@/services/orderService' +import detailService, { GameDetail } from '@/services/detailService' +import { withAuth } from '@/components' + +const OrderCheck = () => { + const { params } = useRouter() + const { id, gameId } = params + const [detail ,setDetail] = useState({}) + + useDidShow(async () => { + const res = await detailService.getDetail(Number(gameId)) + console.log(res) + if (res.code === 0) { + setDetail(res.data) + } + }) + + //TODO: get order msg from id + const handlePay = async () => { + Taro.showLoading({ + 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' + }) + } + }) + } + } + } + return ( + + OrderCheck + 球局名称:{detail?.title || '-'} + 价格:¥{detail?.price || '-'} + + + ) +} + +export default withAuth(OrderCheck) \ No newline at end of file diff --git a/src/mod_user/pages/favorites/index.tsx b/src/mod_user/pages/favorites/index.tsx deleted file mode 100644 index 1963b85..0000000 --- a/src/mod_user/pages/favorites/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { View, } from '@tarojs/components'; -const OrderPage: React.FC = () => { - - - return ( - - 我的收藏 - ) - - -} - -export default OrderPage; \ No newline at end of file diff --git a/src/mod_user/pages/myself/index.tsx b/src/mod_user/pages/myself/index.tsx index 97a2ca2..e038315 100644 --- a/src/mod_user/pages/myself/index.tsx +++ b/src/mod_user/pages/myself/index.tsx @@ -1,38 +1,38 @@ -import React, { useState, useEffect } from 'react'; -import { View, Text, Image, ScrollView } from '@tarojs/components'; -import Taro from '@tarojs/taro'; -import './index.scss'; -import GuideBar from '@/components/GuideBar' -import { UserInfoCard, UserInfo } from '@/components/UserInfo/index' -import { UserService } from '@/services/userService' -import ListContainer from '@/container/listContainer' -import { TennisMatch } from '../../../../types/list/types' -import { withAuth } from '@/components'; +import React, { useState, useEffect } from "react"; +import { View, Text, Image, ScrollView } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import "./index.scss"; +import GuideBar from "@/components/GuideBar"; +import { UserInfoCard, UserInfo } from "@/components/UserInfo/index"; +import { UserService } from "@/services/userService"; +import ListContainer from "@/container/listContainer"; +import { TennisMatch } from "../../../../types/list/types"; +import { withAuth } from "@/components"; const MyselfPage: React.FC = () => { // 获取页面参数 const instance = Taro.getCurrentInstance(); - const user_id = instance.router?.params?.userid || ''; + const user_id = instance.router?.params?.userid || ""; // 判断是否为当前用户 const is_current_user = !user_id; // 用户信息状态 const [user_info, set_user_info] = useState({ - id: '1', - nickname: '加载中...', - avatar: require('../../../static/userInfo/default_avatar.svg'), - join_date: '加载中...', + id: "1", + nickname: "加载中...", + avatar: require("../../../static/userInfo/default_avatar.svg"), + join_date: "加载中...", stats: { following: 0, friends: 0, hosted: 0, - participated: 0 + participated: 0, }, - bio: '加载中...', - location: '加载中...', - occupation: '加载中...', - ntrp_level: 'NTRP 3.0' + bio: "加载中...", + location: "加载中...", + occupation: "加载中...", + ntrp_level: "NTRP 3.0", }); // 球局记录状态 @@ -43,7 +43,9 @@ const MyselfPage: React.FC = () => { const [is_following, setIsFollowing] = useState(false); // 当前激活的标签页 - const [active_tab, setActiveTab] = useState<'hosted' | 'participated'>('hosted'); + const [active_tab, setActiveTab] = useState<"hosted" | "participated">( + "hosted", + ); // 加载用户数据 const load_user_data = async () => { @@ -56,19 +58,18 @@ const MyselfPage: React.FC = () => { // 获取球局记录 let games_data; - if (active_tab === 'hosted') { + if (active_tab === "hosted") { games_data = await UserService.get_hosted_games(user_id); } else { games_data = await UserService.get_participated_games(user_id); } set_game_records(games_data); - } catch (error) { - console.error('加载用户数据失败:', error); + console.error("加载用户数据失败:", error); Taro.showToast({ - title: '加载失败,请重试', - icon: 'error', - duration: 2000 + title: "加载失败,请重试", + icon: "error", + duration: 2000, }); } finally { set_loading(false); @@ -91,60 +92,59 @@ const MyselfPage: React.FC = () => { const load_game_data = async () => { try { let games_data; - if (active_tab === 'hosted') { + if (active_tab === "hosted") { games_data = await UserService.get_hosted_games(user_id); } else { games_data = await UserService.get_participated_games(user_id); } set_game_records(games_data); } catch (error) { - console.error('加载球局数据失败:', error); + console.error("加载球局数据失败:", error); } }; // 处理关注/取消关注 const handle_follow = async () => { try { - const new_following_state = await UserService.toggle_follow(user_id, is_following); + const new_following_state = await UserService.toggle_follow( + user_id, + is_following, + ); setIsFollowing(new_following_state); Taro.showToast({ - title: new_following_state ? '关注成功' : '已取消关注', - icon: 'success', - duration: 1500 + title: new_following_state ? "关注成功" : "已取消关注", + icon: "success", + duration: 1500, }); } catch (error) { - console.error('关注操作失败:', error); + console.error("关注操作失败:", error); Taro.showToast({ - title: '操作失败,请重试', - icon: 'error', - duration: 2000 + title: "操作失败,请重试", + icon: "error", + duration: 2000, }); } }; - - // 处理球局订单 const handle_game_orders = () => { Taro.navigateTo({ - url: '/mod_user/pages/orders/index' + url: "/mod_user/orderList/index", }); }; // 处理收藏 const handle_favorites = () => { Taro.navigateTo({ - url: '/mod_user/pages/favorites/index' + url: "/mod_user/favorites/index", }); }; - - return ( {/* 主要内容 */} - + {/* 用户信息区域 */} {loading ? ( @@ -157,7 +157,6 @@ const MyselfPage: React.FC = () => { is_current_user={is_current_user} is_following={is_following} on_follow={handle_follow} - /> )} {/* 球局订单和收藏功能 */} @@ -166,7 +165,7 @@ const MyselfPage: React.FC = () => { 我的订单 @@ -174,7 +173,7 @@ const MyselfPage: React.FC = () => { 收藏 @@ -185,10 +184,16 @@ const MyselfPage: React.FC = () => { {/* 球局类型标签页 */} - setActiveTab('hosted')}> + setActiveTab("hosted")} + > 我主办的 - setActiveTab('participated')}> + setActiveTab("participated")} + > 我参与的 @@ -207,7 +212,7 @@ const MyselfPage: React.FC = () => { - + ); }; diff --git a/src/mod_user/pages/orders/index.tsx b/src/mod_user/pages/orders/index.tsx deleted file mode 100644 index c09b0c4..0000000 --- a/src/mod_user/pages/orders/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { View, } from '@tarojs/components'; -const OrderPage: React.FC = () => { - - - return ( - - 我的订单 - ) - - -} - -export default OrderPage; \ No newline at end of file diff --git a/src/mod_user/pages/other/index.tsx b/src/mod_user/pages/other/index.tsx index bd3ecc1..dd4b774 100644 --- a/src/mod_user/pages/other/index.tsx +++ b/src/mod_user/pages/other/index.tsx @@ -1,10 +1,16 @@ -import React, { useState, useEffect } from 'react'; -import { View, Text, ScrollView } from '@tarojs/components'; -import Taro from '@tarojs/taro'; -import './index.scss'; -import GuideBar from '@/components/GuideBar'; -import { UserInfoCard, GameCard, GameTabs, UserInfo, GameRecord } from '@/components/UserInfo'; -import { UserService } from '@/services/userService'; +import React, { useState, useEffect } from "react"; +import { View, Text, ScrollView } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import "./index.scss"; +import GuideBar from "@/components/GuideBar"; +import { + UserInfoCard, + GameCard, + GameTabs, + UserInfo, + GameRecord, +} from "@/components/UserInfo"; +import { UserService } from "@/services/userService"; const OtherUserPage: React.FC = () => { // 获取页面参数 @@ -13,21 +19,21 @@ const OtherUserPage: React.FC = () => { // 模拟用户数据 const [user_info, setUserInfo] = useState({ - id: user_id || '1', - nickname: '网球爱好者', - avatar: require('../../../static/userInfo/default_avatar.svg'), - join_date: '2024年3月加入', + id: user_id || "1", + nickname: "网球爱好者", + avatar: require("../../../static/userInfo/default_avatar.svg"), + join_date: "2024年3月加入", stats: { following: 89, friends: 15, hosted: 12, - participated: 35 + participated: 35, }, - tags: ['北京朝阳', '金融从业者', 'NTRP 3.5'], - bio: '热爱网球的金融从业者,周末喜欢约球\n技术还在提升中,欢迎一起切磋\n平时在朝阳公园附近活动', - location: '北京朝阳', - occupation: '金融从业者', - ntrp_level: 'NTRP 3.5' + tags: ["北京朝阳", "金融从业者", "NTRP 3.5"], + bio: "热爱网球的金融从业者,周末喜欢约球\n技术还在提升中,欢迎一起切磋\n平时在朝阳公园附近活动", + location: "北京朝阳", + occupation: "金融从业者", + ntrp_level: "NTRP 3.5", }); // 模拟球局数据 @@ -37,7 +43,9 @@ const OtherUserPage: React.FC = () => { const [is_following, setIsFollowing] = useState(false); // 当前激活的标签页 - const [active_tab, setActiveTab] = useState<'hosted' | 'participated'>('hosted'); + const [active_tab, setActiveTab] = useState<"hosted" | "participated">( + "hosted", + ); // 页面加载时获取用户信息 useEffect(() => { @@ -47,13 +55,16 @@ const OtherUserPage: React.FC = () => { const user_data = await UserService.get_user_info(user_id); setUserInfo(user_data); - const games_data = await UserService.get_user_games(user_id, active_tab); + const games_data = await UserService.get_user_games( + user_id, + active_tab, + ); setGameRecords(games_data); } catch (error) { - console.error('加载用户数据失败:', error); + console.error("加载用户数据失败:", error); Taro.showToast({ - title: '加载失败', - icon: 'none' + title: "加载失败", + icon: "none", }); } } @@ -65,18 +76,21 @@ const OtherUserPage: React.FC = () => { // 处理关注/取消关注 const handle_follow = async () => { try { - const new_follow_status = await UserService.toggle_follow(user_info.id, is_following); + const new_follow_status = await UserService.toggle_follow( + user_info.id, + is_following, + ); setIsFollowing(new_follow_status); Taro.showToast({ - title: new_follow_status ? '关注成功' : '已取消关注', - icon: 'success', - duration: 1500 + title: new_follow_status ? "关注成功" : "已取消关注", + icon: "success", + duration: 1500, }); } catch (error) { - console.error('关注操作失败:', error); + console.error("关注操作失败:", error); Taro.showToast({ - title: '操作失败', - icon: 'none' + title: "操作失败", + icon: "none", }); } }; @@ -84,22 +98,21 @@ const OtherUserPage: React.FC = () => { // 处理发送消息 const handle_send_message = () => { Taro.navigateTo({ - url: `/pages/message/chat/index?user_id=${user_info.id}&nickname=${user_info.nickname}` + url: `/mode_user/message/chat/index?user_id=${user_info.id}&nickname=${user_info.nickname}`, }); }; - // 处理球局详情 const handle_game_detail = (game_id: string) => { Taro.navigateTo({ - url: `/pages/detail/index?id=${game_id}` + url: `/pages/detail/index?id=${game_id}`, }); }; return ( {/* 主要内容 */} - + {/* 用户信息区域 */} { - + ); }; -export default OtherUserPage; \ No newline at end of file +export default OtherUserPage; diff --git a/src/pages/detail/index.tsx b/src/pages/detail/index.tsx index 53c0f33..21c9851 100644 --- a/src/pages/detail/index.tsx +++ b/src/pages/detail/index.tsx @@ -1,366 +1,543 @@ -import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react' -import { View, Text, Image, Map, ScrollView } from '@tarojs/components' -import { Avatar, Popover, ImagePreview } from '@nutui/nutui-react-taro' -import Taro, { useRouter, useShareAppMessage, useShareTimeline, useDidShow } from '@tarojs/taro' -import dayjs from 'dayjs' -import 'dayjs/locale/zh-cn' +import React, { + useState, + useEffect, + useRef, + useImperativeHandle, + forwardRef, +} from "react"; +import { View, Text, Image, Map, ScrollView } from "@tarojs/components"; +import { Avatar } from "@nutui/nutui-react-taro"; +import Taro, { + useRouter, + useShareAppMessage, + useShareTimeline, + useDidShow, +} from "@tarojs/taro"; +import dayjs from "dayjs"; +import "dayjs/locale/zh-cn"; // 导入API服务 -import { CommonPopup, withAuth } from '@/components' -import DetailService, { MATCH_STATUS} from '@/services/detailService' -import { getCurrentLocation, calculateDistance } from '@/utils/locationUtils' +import { CommonPopup, withAuth, NTRPEvaluatePopup } from "@/components"; import { - useUserInfo, - useUserActions, -} from '@/store/userStore' -import img from '@/config/images' -// import { getTextColorOnImage } from '../../utils' -import './index.scss' + EvaluateType, + SceneType, + DisplayConditionType, +} from "@/components/NTRPEvaluatePopup"; +import DetailService, { MATCH_STATUS } from "@/services/detailService"; +import { getCurrentLocation, calculateDistance } from "@/utils/locationUtils"; +import { useUserInfo, useUserActions } from "@/store/userStore"; +import img from "@/config/images"; +import "./index.scss"; -dayjs.locale('zh-cn') +dayjs.locale("zh-cn"); // 将·作为连接符插入到标签文本之间 function insertDotInTags(tags: string[]) { - return tags.join('-·-').split('-') + return tags.join("-·-").split("-"); } function GameTags(props) { - const { detail } = props - const tags = [{ - name: '🕙 急招', - icon: '', - }, { - name: '🔥 本周热门', - icon: '', - }, { - name: '🎉 新活动', - icon: '', - }, { - name: '官方组织', - icon: '', - }] + const { detail } = props; + const tags = [ + { + name: "🕙 急招", + icon: "", + }, + { + name: "🔥 本周热门", + icon: "", + }, + { + name: "🎉 新活动", + icon: "", + }, + { + name: "官方组织", + icon: "", + }, + ]; return ( - - + + {/* network image mock */} - + - + {tags.map((tag, index) => ( - + {tag.icon && } {tag.name} ))} - ) + ); } type CourselItemType = { - url: string - width: number - height: number -} + url: string; + width: number; + height: number; +}; function Coursel(props) { - const { detail } = props - const [list, setList] = useState([]) - const [listWidth, setListWidth] = useState(0) - const { image_list } = detail + const { detail } = props; + const [list, setList] = useState([]); + const [listWidth, setListWidth] = useState(0); + const { image_list } = detail; - async function getImagesMsg (imageList) { - const latest_list: CourselItemType[] = [] - const sys_info = await Taro.getSystemInfo() - console.log(sys_info, 'info') - const max_width = sys_info.screenWidth - 30 - const max_height = 240 - const current_aspect_ratio = max_width / max_height - let container_width = 0 + async function getImagesMsg(imageList) { + const latest_list: CourselItemType[] = []; + const sys_info = await Taro.getSystemInfo(); + console.log(sys_info, "info"); + const max_width = sys_info.screenWidth - 30; + const max_height = 240; + const current_aspect_ratio = max_width / max_height; + let container_width = 0; for (const imageUrl of imageList) { - const { width, height } = await Taro.getImageInfo({ src: imageUrl }) + const { width, height } = await Taro.getImageInfo({ src: imageUrl }); if (width && height) { - const aspect_ratio = width / height - const latest_w_h = { width, height } + const aspect_ratio = width / height; + const latest_w_h = { width, height }; if (aspect_ratio < current_aspect_ratio) { - latest_w_h.width = max_height * aspect_ratio - latest_w_h.height = max_height + latest_w_h.width = max_height * aspect_ratio; + latest_w_h.height = max_height; } else { - latest_w_h.width = max_width - latest_w_h.height = max_width / aspect_ratio + latest_w_h.width = max_width; + latest_w_h.height = max_width / aspect_ratio; } - container_width += latest_w_h.width + 12 + container_width += latest_w_h.width + 12; latest_list.push({ url: imageUrl, width: latest_w_h.width, height: latest_w_h.height, - }) + }); } } - setList(latest_list) - setListWidth(container_width) + setList(latest_list); + setListWidth(container_width); } - useEffect(() => { getImagesMsg(image_list || []) }, [image_list]) + useEffect(() => { + getImagesMsg(image_list || []); + }, [image_list]); return ( - - { - list.map((item, index) => { - return ( - - - - ) - }) - } + + {list.map((item, index) => { + return ( + + + + ); + })} - ) + ); } // 分享弹窗 -const SharePopup = forwardRef(({ id, from }: { id: string, from: string }, ref) => { - const [visible, setVisible] = useState(false) +const SharePopup = forwardRef( + ({ id, from }: { id: string; from: string }, ref) => { + const [visible, setVisible] = useState(false); - useImperativeHandle(ref, () => ({ - show: () => { - setVisible(true) - } - })) + useImperativeHandle(ref, () => ({ + show: () => { + setVisible(true); + }, + })); - // function handleShareToWechat() { - // useShareAppMessage(() => { - // return { - // title: '分享', - // path: `/pages/detail/index?id=${id}&from=share`, - // } - // }) - // } + // function handleShareToWechat() { + // useShareAppMessage(() => { + // return { + // title: '分享', + // path: `/pages/detail/index?id=${id}&from=share`, + // } + // }) + // } - // function handleShareToWechatMoments() { - // useShareTimeline(() => { - // return { - // title: '分享', - // path: `/pages/detail/index?id=${id}&from=share`, - // } - // }) - // } + // function handleShareToWechatMoments() { + // useShareTimeline(() => { + // return { + // title: '分享', + // path: `/pages/detail/index?id=${id}&from=share`, + // } + // }) + // } - // function handleSaveToLocal() { - // Taro.saveImageToPhotosAlbum({ - // filePath: images[0], - // success: () => { - // Taro.showToast({ title: '保存成功', icon: 'success' }) - // }, - // fail: () => { - // Taro.showToast({ title: '保存失败', icon: 'none' }) - // }, - // }) - // } + // function handleSaveToLocal() { + // Taro.saveImageToPhotosAlbum({ + // filePath: images[0], + // success: () => { + // Taro.showToast({ title: '保存成功', icon: 'success' }) + // }, + // fail: () => { + // Taro.showToast({ title: '保存失败', icon: 'none' }) + // }, + // }) + // } - return ( - { setVisible(false) }} - hideFooter - style={{ minHeight: '100px' }} - > - - 分享卡片 - - - ) -}) + return ( + { + setVisible(false); + }} + hideFooter + style={{ minHeight: "100px" }} + > + + 分享卡片 + + + ); + }, +); + +function navto(url) { + Taro.navigateTo({ + url: url, + }); +} // 底部操作栏 function StickyButton(props) { - const { handleShare, handleJoinGame, detail } = props - const userInfo = useUserInfo() - const { id } = userInfo - const { publisher_id, match_status, price } = detail || {} + 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 || {}; + + function handleSelfEvaluate() { + // TODO: 打开自评弹窗 + ntrpRef?.current?.show(); + } + + function generateTextAndAction( + user_action_status: null | { [key: string]: boolean }, + ): undefined | { text: string | React.FC; action: () => void } { + if (!user_action_status) { + return; + } + // user_action_status.can_assess = true; + user_action_status.can_join = true; + const { + can_assess, + can_join, + can_substitute, + can_pay, + is_substituting, + waiting_start, + } = user_action_status || {}; + if ( + Object.values(user_action_status).every((value) => !value) && + dayjs(end_time).isBefore(dayjs()) + ) { + return { + text: "球局已结束,查看其他球局", + action: navto.bind(null, "/pages/list/index"), + }; + } + if (waiting_start) { + return { + text: "等待开始, 查看更多球局", + action: navto.bind(null, "/pages/list/index"), + }; + } else if (is_substituting) { + return { + text: "候补中,查看其他球局", + action: navto.bind(null, "/pages/list/index"), + }; + } else if (can_pay) { + return { + text: "继续支付", + action: handleJoinGame, + }; + } else if (can_substitute) { + return { + text: "立即候补", + action: handleJoinGame, + }; + } else if (can_join) { + return { + text: () => { + return ( + <> + 🎾 + 立即加入 + + ¥ {price} + + + ); + }, + action: handleJoinGame, + }; + } else if (can_assess) { + return { + text: () => ( + + NTRP自评 + + ), + action: handleSelfEvaluate, + }; + } + return { + text: "球局无法加入", + action: () => {}, + }; + } + + if (!user_action_status) { + return ""; + } + + const { text, action } = generateTextAndAction(user_action_status)!; + + let ActionText: React.FC | string = text; + + if (typeof ActionText === "string") { + ActionText = () => { + return {text as string}; + }; + } + + const role = Number(publisher_id) === id ? "ownner" : "visitor"; - const role = Number(publisher_id) === id ? 'ownner' : 'visitor' return ( - - - 分享 + + + 分享 - - { Taro.showToast({ title: 'To be continued', icon: 'none' }) }}> - - 32 + + { + Taro.showToast({ title: "To be continued", icon: "none" }); + }} + > + + 32 - - 🎾 - 立即加入 - - ¥ {price} - + + - ) + ); } // 球局信息 function GameInfo(props) { - const { detail, currentLocation } = props - const { latitude, longitude, location, location_name, start_time, end_time } = detail || {} + const { detail, currentLocation } = props; + const { latitude, longitude, location, location_name, start_time, end_time } = + detail || {}; const openMap = () => { Taro.openLocation({ - latitude, // 纬度(必填) + latitude, // 纬度(必填) longitude, // 经度(必填) - name: location_name, // 位置名(可选) + name: location_name, // 位置名(可选) address: location, // 地址详情(可选) - scale: 15, // 地图缩放级别(1-28) - }) - } + scale: 15, // 地图缩放级别(1-28) + }); + }; - const [c_latitude, c_longitude] = currentLocation - const distance = calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000 + const [c_latitude, c_longitude] = currentLocation; + const distance = + calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000; - const startTime = dayjs(start_time) - const endTime = dayjs(end_time) - const game_length = endTime.diff(startTime, 'minutes') / 60 - - const startMonth = startTime.format('M') - const startDay = startTime.format('D') - const theDayOfWeek = startTime.format('dddd') - const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}` - const gameRange = `${startTime.format('HH:mm')} - ${endTime.format('HH:mm')}` + const startTime = dayjs(start_time); + const endTime = dayjs(end_time); + const game_length = endTime.diff(startTime, "minutes") / 60; + const startMonth = startTime.format("M"); + const startDay = startTime.format("D"); + const theDayOfWeek = startTime.format("dddd"); + const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}`; + const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`; return ( - + {/* Date and Weather */} - + {/* Calendar and Date time */} - + {/* Calendar */} - + {startMonth}月 {startDay} {/* Date time */} - + {startDate} - {gameRange} ({game_length}小时) + + {gameRange} ({game_length}小时) + {/* Weather */} - + {/* Weather icon */} - + {/* Weather text and temperature */} - + 28℃ - 32℃ {/* Place */} - + {/* venue location message */} - + {/* location icon */} - - + + {/* location message */} - + {/* venue name and distance */} - - {location_name || '-'} + + {location_name || "-"} · {distance.toFixed(1)}km - + {/* venue address */} - - {location || '-'} + + {location || "-"} {/* venue map */} - + {longitude && latitude && ( {}} // hide business msg showLocation - theme='dark' + theme="dark" /> )} - ) + ); } // 场馆信息 function VenueInfo(props) { - const { detail } = props - const [visible, setVisible] = useState(false) - const { venue_description, venue_description_tag = [], venue_image_list = [] } = detail + const { detail } = props; + const [visible, setVisible] = useState(false); + const { + venue_description, + venue_description_tag = [], + venue_image_list = [], + } = detail; function showScreenShot() { - setVisible(true) + setVisible(true); } function onClose() { - setVisible(false) + setVisible(false); } function previewImage(current_url) { Taro.previewImage({ current: current_url, - urls: venue_image_list.map(c => c.url), - }) + urls: venue_image_list.map((c) => c.url), + }); } return ( - + {/* venue detail title and venue ordered status */} - + 场馆详情 - {venue_image_list?.length > 0 ? + {venue_image_list?.length > 0 ? ( <> · 已订场 - + - : - '' - } + ) : ( + "" + )} {/* venue detail content */} - + {/* venue detail tags */} - + {insertDotInTags(venue_description_tag).map((tag, index) => ( - + {tag} ))} {/* venue remarks */} - + {venue_description} @@ -369,230 +546,277 @@ function VenueInfo(props) { onClose={onClose} round hideFooter - position='bottom' + position="bottom" zIndex={1001} > 预定截图 - + - {venue_image_list.map(item => { + {venue_image_list.map((item) => { return ( - - + + - ) + ); })} - ) + ); } function genNTRPRequirementText(min, max) { if (min && max) { - return `${min} - ${max} 之间` + return `${min} - ${max} 之间`; } else if (max) { - return `${max} 以上` + return `${max} 以上`; } - return '没有要求' + return "没有要求"; } // 玩法要求 function GamePlayAndRequirement(props) { - const { detail: { skill_level_min, skill_level_max, play_type, game_type } } = props + const { + detail: { skill_level_min, skill_level_max, play_type, game_type }, + } = props; const requirements = [ { - title: 'NTRP水平要求', + title: "NTRP水平要求", desc: genNTRPRequirementText(skill_level_min, skill_level_max), }, { - title: '活动玩法', - desc: play_type || '-', + title: "活动玩法", + desc: play_type || "-", }, { - title: '人员构成', - desc: game_type || '-', - } - ] + title: "人员构成", + desc: game_type || "-", + }, + ]; return ( - + {/* title */} 玩法要求 {/* requirements */} - + {requirements.map((item, index) => ( - - {item.title} - {item.desc} + + + {item.title} + + {item.desc} ))} - ) + ); } // 参与者 function Participants(props) { - const { detail = {} } = props - const participants = detail.participants || [] - const organizer_id = Number(detail.publisher_id) + const { detail = {} } = props; + const participants = detail.participants || []; + const organizer_id = Number(detail.publisher_id); return ( - - + + 参与者 · 剩余空位 3 - + {/* application */} - { Taro.showToast({ title: 'To be continued', icon: 'none' }) }}> - - 申请加入 + { + 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 ? '组织者' : '参与者' + const { + user: { avatar_url, nickname, level, id: participant_user_id }, + } = participant; + const role = + participant_user_id === organizer_id ? "组织者" : "参与者"; return ( - - - {nickname || '未知'} - {level || '未知'} - {role} + + + + {nickname || "未知"} + + + {level || "未知"} + + {role} - ) + ); })} - ) + ); } function SupplementalNotes(props) { - const { detail: { description, description_tag = [] } } = props + const { + detail: { description, description_tag = [] }, + } = props; return ( - - + + 补充说明 - + {/* supplemental notes tags */} - + {insertDotInTags(description_tag).map((tag, index) => ( - + {tag} ))} {/* supplemental notes content */} - + {description} - ) + ); } 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', + 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: '双打', + 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', + 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: '双打', + 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', + 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: '双打', + levelRequirements: "NTRP 3.5", + playType: "双打", }, - ] + ]; return ( - + {/* orgnizer title */} - + 组织者 {/* organizer avatar and name */} - - - - Light - + + + + Light + 已组织 8 次 - + NTRP 3.5 - - 关注 + + 关注 - + {/* recommend games by organizer */} - - + + TA的更多活动 - + - - + + {recommendGames.map((game, index) => ( - + {/* game title */} - + {game.title} - + {/* game time and range */} - + {game.time} {game.timeLength} {/* game location、vunue、distance */} - + {game.venue} · {game.veuneType} @@ -600,16 +824,21 @@ function OrganizerInfo(props) { {game.distance} {/* organizer avatar、applications、level requirements、play type */} - - - - - 报名人数 {game.checkedApplications}/{game.applications} + + + + + + 报名人数 {game.checkedApplications}/{game.applications} + - + {game.levelRequirements} - + {game.playType} @@ -620,89 +849,102 @@ function OrganizerInfo(props) { - ) + ); } function Index() { - const [detail, setDetail] = useState({}) - const { params } = useRouter() - const [currentLocation, setCurrentLocation] = useState<[number, number]>([0, 0]) - const { id, from } = params - const { fetchUserInfo, updateUserInfo } = useUserActions() + const [detail, setDetail] = useState({}); + const { params } = useRouter(); + const [currentLocation, setCurrentLocation] = useState<[number, number]>([ + 0, 0, + ]); + const { id, from } = params; + const { fetchUserInfo, updateUserInfo } = useUserActions(); - const sharePopupRef = useRef(null) + const sharePopupRef = useRef(null); useDidShow(async () => { - await updateLocation() - await fetchUserInfo() - await fetchDetail() - }) + await updateLocation(); + await fetchUserInfo(); + await fetchDetail(); + }); const updateLocation = async () => { try { - const location = await getCurrentLocation() - setCurrentLocation([location.latitude, location.longitude]) - await updateUserInfo({ latitude: location.latitude, longitude: location.longitude }) + const location = await getCurrentLocation(); + setCurrentLocation([location.latitude, location.longitude]); + await updateUserInfo({ + latitude: location.latitude, + longitude: location.longitude, + }); } catch (error) { - console.error('用户位置更新失败', error) + console.error("用户位置更新失败", error); } - } + }; const fetchDetail = async () => { - const res = await DetailService.getDetail(Number(id)) + if (!id) return; + const res = await DetailService.getDetail(Number(id)); if (res.code === 0) { - setDetail(res.data) + setDetail(res.data); } - } + }; function handleShare() { - sharePopupRef.current.show() + sharePopupRef.current.show(); } const handleJoinGame = () => { Taro.navigateTo({ - url: `/pages/orderCheck/index?gameId=${id}`, - }) - } + url: `/mod_user/orderDetail/index?gameId=${id}`, + }); + }; function handleBack() { - const pages = Taro.getCurrentPages() + const pages = Taro.getCurrentPages(); if (pages.length <= 1) { Taro.redirectTo({ - url: '/pages/list/index', - }) + url: "/pages/list/index", + }); } else { - Taro.navigateBack() + Taro.navigateBack(); } } - - console.log('detail', detail) - const backgroundImage = detail?.image_list?.[0] ? { backgroundImage: `url(${detail?.image_list?.[0]})` } : {} + console.log("detail", detail); + const backgroundImage = detail?.image_list?.[0] + ? { backgroundImage: `url(${detail?.image_list?.[0]})` } + : {}; return ( - + {/* custom navbar */} - - - + + + - - + + - + {/* swiper */} {/* content */} - + {/* avatar and tags */} {/* title */} - - {detail.title} + + {detail.title} {/* Date and Place and weather */} @@ -717,12 +959,20 @@ function Index() { {/* organizer and recommend games by organizer */} {/* sticky bottom action bar */} - + {/* share popup */} - + - ) + ); } -export default withAuth(Index) \ No newline at end of file +export default withAuth(Index); diff --git a/src/services/evaluateService.ts b/src/services/evaluateService.ts new file mode 100644 index 0000000..b732212 --- /dev/null +++ b/src/services/evaluateService.ts @@ -0,0 +1,73 @@ +import httpService from "./httpService"; +import type { ApiResponse } from "./httpService"; + +export interface AnswerResItem { + question_id: number; + answer_index: number; + question_title: string; + selected_option: string; + score: number; +} + +export type AnswerItem = Pick; + +export interface Answers { + answers: AnswerItem[]; + test_duration: number; +} + +export interface QuestionItem { + id: number; + question_title: string; + question_content: string; + options: string[]; + scores: number[]; +} + +export interface SubmitAnswerRes { + record_id: number; + total_score: number; + ntrp_level: string; + level_description: string; + answers: AnswerResItem[]; +} + +// 发布球局类 +class EvaluateService { + async getEvaluateQuestions(): Promise> { + return httpService.post("/ntrp/questions", { + showLoading: true, + }); + } + + async submitEvaluateAnswers({ + answers, + }: Answers): Promise> { + return httpService.post( + "/ntrp/submit", + { answers }, + { + showLoading: true, + }, + ); + } + + async getHistoryNtrp(): Promise> { + return httpService.post("/ntrp/history", { + showLoading: true, + }); + } + + async getNtrpDetail(record_id: number): Promise> { + return httpService.post( + "/ntrp/detail", + { record_id }, + { + showLoading: true, + }, + ); + } +} + +// 导出认证服务实例 +export default new EvaluateService(); diff --git a/src/services/loginService.ts b/src/services/loginService.ts index 25c7d3f..a59f6f6 100644 --- a/src/services/loginService.ts +++ b/src/services/loginService.ts @@ -1,6 +1,6 @@ -import Taro from '@tarojs/taro'; -import httpService, { ApiResponse } from './httpService'; -import tokenManager from '../utils/tokenManager'; +import Taro from "@tarojs/taro"; +import httpService, { ApiResponse } from "./httpService"; +import tokenManager from "../utils/tokenManager"; // 微信用户信息接口 export interface WechatUserInfo { @@ -35,29 +35,44 @@ export interface VerifyCodeResponse { user_info?: WechatUserInfo; } // 用户详细信息 -export interface UserInfoType { - id: number - openid: string - unionid: string - session_key: string - nickname: string - avatar_url: string - gender: string - country: string - province: string - city: string - language: string - phone: string - is_subscribed: string - latitude: number - longitude: number - subscribe_time: string - last_login_time: string +export interface UserStats { + followers_count: number; + following_count: number; + hosted_games_count: number; + participated_games_count: number; } +export interface UserInfoType { + subscribe_time: string; + last_login_time: string; + create_time: string; + last_modify_time: string; + id: number; + openid: string; + user_code: string | null; + unionid: string; + session_key: string; + nickname: string; + ntrp_level: string; + occupation: string | null; + avatar_url: string; + gender: string; + country: string; + province: string; + city: string; + language: string; + phone: string; + personal_profile: string | null; + is_subscribed: string; // 如果只会是 "0" | "1",也可以写成字面量联合类型 + latitude: number; + longitude: number; + stats: UserStats; +} // 微信授权登录 -export const wechat_auth_login = async (phone_code?: string): Promise => { +export const wechat_auth_login = async ( + phone_code?: string, +): Promise => { try { // 先进行微信登录获取code const login_result = await Taro.login(); @@ -65,34 +80,34 @@ export const wechat_auth_login = async (phone_code?: string): Promise => { +export const phone_auth_login = async ( + params: PhoneLoginParams, +): Promise => { try { // 使用 httpService 调用验证验证码接口 - const verify_response = await httpService.post('user/sms/verify', { + const verify_response = await httpService.post("user/sms/verify", { phone: params.phone, code: params.verification_code, - user_code: params.user_code + user_code: params.user_code, }); - if (verify_response.code === 0) { return { success: true, - message: '登录成功', + message: "登录成功", token: verify_response.data?.token, - user_info: verify_response.data?.userInfo + user_info: verify_response.data?.userInfo, }; } else { return { success: false, - message: verify_response.message || '验证码错误' + message: verify_response.message || "验证码错误", }; } } catch (error) { - console.error('手机号登录失败:', error); + console.error("手机号登录失败:", error); return { success: false, - message: error.message + message: error.message, }; } }; @@ -140,55 +156,57 @@ export const phone_auth_login = async (params: PhoneLoginParams): Promise => { try { - const response = await httpService.post('user/sms/send', { - phone: phone + const response = await httpService.post("user/sms/send", { + phone: phone, }); // 修复响应检查逻辑:检查 code === 0 或 success === true if (response.code === 0 || response.success === true) { return { success: true, - message: '验证码发送成功' + message: "验证码发送成功", }; } else { return { success: false, - message: response.message || '验证码发送失败' + message: response.message || "验证码发送失败", }; } } catch (error) { - console.error('发送短信失败:', error); + console.error("发送短信失败:", error); return { success: false, - message: error.message + message: error.message, }; } }; // 验证短信验证码 -export const verify_sms_code = async (phone: string, code: string): Promise => { +export const verify_sms_code = async ( + phone: string, + code: string, +): Promise => { try { - const response = await httpService.post('user/sms/verify', { + const response = await httpService.post("user/sms/verify", { phone: phone, - code: code + code: code, }); return { success: response.success, - message: response.message || '验证失败', + message: response.message || "验证失败", token: response.data?.token, - user_info: response.data?.userInfo + user_info: response.data?.userInfo, }; } catch (error) { - console.error('验证验证码失败:', error); + console.error("验证验证码失败:", error); return { success: false, - message: error.message + message: error.message, }; } }; - // 保存用户登录状态 export const save_login_state = (token: string, user_info: WechatUserInfo) => { try { @@ -196,16 +214,15 @@ export const save_login_state = (token: string, user_info: WechatUserInfo) => { const expires_at = Date.now() + 24 * 60 * 60 * 1000; // 24小时后过期 tokenManager.setToken({ accessToken: token, - expiresAt: expires_at + expiresAt: expires_at, }); - // 保存用户信息 - Taro.setStorageSync('user_info', JSON.stringify(user_info)); - Taro.setStorageSync('is_logged_in', true); - Taro.setStorageSync('login_time', Date.now()); + Taro.setStorageSync("user_info", JSON.stringify(user_info)); + Taro.setStorageSync("is_logged_in", true); + Taro.setStorageSync("login_time", Date.now()); } catch (error) { - console.error('保存登录状态失败:', error); + console.error("保存登录状态失败:", error); } }; @@ -216,11 +233,11 @@ export const clear_login_state = () => { tokenManager.clearTokens(); // 清除其他登录状态 - Taro.removeStorageSync('user_info'); - Taro.removeStorageSync('is_logged_in'); - Taro.removeStorageSync('login_time'); + Taro.removeStorageSync("user_info"); + Taro.removeStorageSync("is_logged_in"); + Taro.removeStorageSync("login_time"); } catch (error) { - console.error('清除登录状态失败:', error); + console.error("清除登录状态失败:", error); } }; @@ -229,13 +246,12 @@ export const check_login_status = (): boolean => { try { // 使用 tokenManager 检查令牌有效性 - if (!tokenManager.hasValidToken()) { clear_login_state(); return false; } - const is_logged_in = Taro.getStorageSync('is_logged_in'); + const is_logged_in = Taro.getStorageSync("is_logged_in"); return !!is_logged_in; } catch (error) { return false; @@ -264,14 +280,14 @@ export const get_token_status = () => { is_valid, remaining_time, is_expired, - expires_in_minutes: Math.floor(remaining_time / (60 * 1000)) + expires_in_minutes: Math.floor(remaining_time / (60 * 1000)), }; } catch (error) { return { is_valid: false, remaining_time: 0, is_expired: true, - expires_in_minutes: 0 + expires_in_minutes: 0, }; } }; @@ -279,9 +295,9 @@ export const get_token_status = () => { // 获取用户信息 export const get_user_info = (): WechatUserInfo | null => { try { - let userinfo = Taro.getStorageSync('user_info') + let userinfo = Taro.getStorageSync("user_info"); if (userinfo) { - return JSON.parse(userinfo) + return JSON.parse(userinfo); } return null; } catch (error) { @@ -304,7 +320,7 @@ export const check_wechat_login = async (): Promise => { try { const check_result = await Taro.checkSession(); // Taro.checkSession 返回的是 { errMsg: string } - return check_result.errMsg === 'checkSession:ok'; + return check_result.errMsg === "checkSession:ok"; } catch (error) { return false; } @@ -325,18 +341,20 @@ export const refresh_login_status = async (): Promise => { // 检查本地存储的登录状态 return check_login_status(); } catch (error) { - console.error('刷新登录状态失败:', error); + console.error("刷新登录状态失败:", error); return false; } }; // 获取用户详细信息 -export const fetchUserProfile = async (): Promise> => { +export const fetchUserProfile = async (): Promise< + ApiResponse +> => { try { - const response = await httpService.post('user/detail'); + const response = await httpService.post("user/detail"); return response; } catch (error) { - console.error('获取用户信息失败:', error); + console.error("获取用户信息失败:", error); throw error; } }; @@ -344,10 +362,10 @@ export const fetchUserProfile = async (): Promise> => // 更新用户信息 export const updateUserProfile = async (payload: Partial) => { try { - const response = await httpService.post('/user/update', payload); + const response = await httpService.post("/user/update", payload); return response; } catch (error) { - console.error('更新用户信息失败:', error); + console.error("更新用户信息失败:", error); throw error; } -}; \ No newline at end of file +}; diff --git a/src/services/orderService.ts b/src/services/orderService.ts index d51b269..196e88f 100644 --- a/src/services/orderService.ts +++ b/src/services/orderService.ts @@ -30,6 +30,42 @@ export interface OrderResponse { payment_params: PayMentParams } +export interface OrderInfo { + time: string + address: string + registrant_nickname: string + registrant_phone: string + cost: string +} + +export interface RefundPolicy { + application_time: string + refund_rule: string +} + +export interface GameStatus { + current_players: number + max_players: number + is_full: boolean + can_join: boolean +} + +export interface GameDetails { + 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 +} + + + // 发布球局类 class OrderService { // 用户登录 @@ -39,7 +75,11 @@ class OrderService { }) } - // async getOrderInfo() + async getOrderInfo(game_id: number): Promise> { + return httpService.post('/payment/check_order', { game_id }, { + showLoading: true, + }) + } } // 导出认证服务实例 diff --git a/src/store/userStore.ts b/src/store/userStore.ts index 16831ab..7ace708 100644 --- a/src/store/userStore.ts +++ b/src/store/userStore.ts @@ -1,46 +1,33 @@ -import { create } from 'zustand' -import { fetchUserProfile, updateUserProfile, UserInfoType } from '@/services/loginService' +import { create } from "zustand"; +import { + fetchUserProfile, + updateUserProfile, + UserInfoType, +} from "@/services/loginService"; export interface UserState { - user: UserInfoType - fetchUserInfo: () => Promise - updateUserInfo: (userInfo: Partial) => void + user: UserInfoType | {}; + fetchUserInfo: () => Promise; + updateUserInfo: (userInfo: Partial) => void; } export const useUser = create()((set) => ({ - user: { - id: 0, - "openid": "", - "unionid": "", - "session_key": "", - "nickname": "张三", - "avatar_url": "https://example.com/avatar.jpg", - "gender": "", - "country": "", - "province": "", - "city": "", - "language": "", - "phone": "13800138000", - "is_subscribed": "0", - "latitude": 0, - "longitude": 0, - "subscribe_time": "2024-06-15 14:00:00", - "last_login_time": "2024-06-15 14:00:00" - }, + user: {}, fetchUserInfo: async () => { - const res = await fetchUserProfile() - console.log(res) - set({ user: res.data }) + const res = await fetchUserProfile(); + console.log(res); + set({ user: res.data }); }, - updateUserInfo: async(userInfo: Partial) => { - const res = await updateUserProfile(userInfo) - set({ user: res.data }) - } -})) + updateUserInfo: async (userInfo: Partial) => { + const res = await updateUserProfile(userInfo); + set({ user: res.data }); + }, +})); -export const useUserInfo = () => useUser((state) => state.user) +export const useUserInfo = () => useUser((state) => state.user); -export const useUserActions = () => useUser((state) => ({ - fetchUserInfo: state.fetchUserInfo, - updateUserInfo: state.updateUserInfo -})) +export const useUserActions = () => + useUser((state) => ({ + fetchUserInfo: state.fetchUserInfo, + updateUserInfo: state.updateUserInfo, + }));