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 && (
- )
+ );
}
// 场馆信息
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,
+ }));