feat: 订单详情 & 问卷调查
This commit is contained in:
@@ -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",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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<P extends object>(WrappedComponent: React.ComponentType<P>) {
|
||||
export default function withAuth<P extends object>(
|
||||
WrappedComponent: React.ComponentType<P>,
|
||||
) {
|
||||
const ComponentWithAuth: React.FC<P> = (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 <View style={{ width: '100vh', height: '100vw', backgroundColor: 'white', position: 'fixed', top: 0, left: 0, zIndex: 999 }} /> // 空壳,避免 children 渲染出错
|
||||
// }
|
||||
|
||||
return <WrappedComponent {...props} />
|
||||
}
|
||||
return <WrappedComponent {...props} />;
|
||||
};
|
||||
|
||||
return ComponentWithAuth
|
||||
return ComponentWithAuth;
|
||||
}
|
||||
@@ -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 (
|
||||
<View className='guide-bar-container'>
|
||||
<View className='guide-bar'>
|
||||
<View className="guide-bar-container">
|
||||
<View className="guide-bar">
|
||||
{/* guide area on the left */}
|
||||
<View className='guide-bar-pages'>
|
||||
<View className="guide-bar-pages">
|
||||
{guideItems.map((item) => (
|
||||
<View
|
||||
className={`guide-bar-pages-item ${currentPage === item.code ? 'guide-bar-pages-item-active' : ''}`}
|
||||
className={`guide-bar-pages-item ${currentPage === item.code ? "guide-bar-pages-item-active" : ""}`}
|
||||
onClick={() => handlePageChange(item.code)}
|
||||
>
|
||||
<Text>{item.text}</Text>
|
||||
@@ -70,7 +73,7 @@ const GuideBar = (props) => {
|
||||
<PublishMenu />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default GuideBar
|
||||
export default GuideBar;
|
||||
|
||||
12
src/components/NTRPEvaluatePopup/index.module.scss
Normal file
12
src/components/NTRPEvaluatePopup/index.module.scss
Normal file
@@ -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;
|
||||
}
|
||||
99
src/components/NTRPEvaluatePopup/index.tsx
Normal file
99
src/components/NTRPEvaluatePopup/index.tsx
Normal file
@@ -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 (
|
||||
<>
|
||||
<CommonPopup
|
||||
title="NTRP评估"
|
||||
visible={visible}
|
||||
onClose={() => setVisible(false)}
|
||||
position="center"
|
||||
hideFooter
|
||||
enableDragToClose={false}
|
||||
>
|
||||
<View className={style.container}>
|
||||
{/* TODO: 直接修改NTRP水平 */}
|
||||
<Text>您还未测评。。。</Text>
|
||||
<Text>请先进行NTRP评估</Text>
|
||||
<Button onClick={handleEvaluate}>开始评估</Button>
|
||||
</View>
|
||||
</CommonPopup>
|
||||
{showEntry && props.children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(NTRPEvaluatePopup);
|
||||
@@ -1,21 +1,22 @@
|
||||
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,
|
||||
@@ -36,5 +37,6 @@ import { CustomPicker, PopupPicker } from './Picker'
|
||||
EditModal,
|
||||
withAuth,
|
||||
CustomPicker,
|
||||
PopupPicker
|
||||
}
|
||||
PopupPicker,
|
||||
NTRPEvaluatePopup,
|
||||
};
|
||||
|
||||
5
src/mod_user/ntrp-evaluate/index.config.ts
Normal file
5
src/mod_user/ntrp-evaluate/index.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: "NTRP 评测",
|
||||
// navigationBarBackgroundColor: '#FAFAFA',
|
||||
// navigationStyle: 'custom',
|
||||
});
|
||||
64
src/mod_user/ntrp-evaluate/index.module.scss
Normal file
64
src/mod_user/ntrp-evaluate/index.module.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/mod_user/ntrp-evaluate/index.tsx
Normal file
53
src/mod_user/ntrp-evaluate/index.tsx
Normal file
@@ -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 (
|
||||
<View className={styles.container}>
|
||||
<View className={styles.title}>NTRP评分</View>
|
||||
<View className={styles.content}>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src="https://img.yzcdn.cn/vant/cat.jpeg"
|
||||
/>
|
||||
<Text className={styles.description}>您的NTRP评分是 4.0 分。</Text>
|
||||
</View>
|
||||
<Button className={styles.button} onClick={handleUpdateNtrp}>
|
||||
评测通过
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withAuth(NtrpEvaluate);
|
||||
24
src/mod_user/orderDetail/config.ts
Normal file
24
src/mod_user/orderDetail/config.ts
Normal file
@@ -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!支持全额退款
|
||||
参与者爽约不通知,不可退款但鼓励用户评分机制中反馈,平台将限制其部分功能使用(如发起权限、报名权限等)。
|
||||
`;
|
||||
4
src/mod_user/orderDetail/index.config.ts
Normal file
4
src/mod_user/orderDetail/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: "订单详情",
|
||||
navigationBarBackgroundColor: "#FAFAFA",
|
||||
});
|
||||
342
src/mod_user/orderDetail/index.module.scss
Normal file
342
src/mod_user/orderDetail/index.module.scss
Normal file
@@ -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);
|
||||
}
|
||||
292
src/mod_user/orderDetail/index.tsx
Normal file
292
src/mod_user/orderDetail/index.tsx
Normal file
@@ -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 (
|
||||
<View className={styles.gameInfoContainer}>
|
||||
<View className={styles.gameInfo}>
|
||||
{/* Date and Weather */}
|
||||
<View className={styles.gameInfoDateWeather}>
|
||||
{/* Calendar and Date time */}
|
||||
<View className={styles.gameInfoDateWeatherCalendarDate}>
|
||||
{/* Calendar */}
|
||||
<View className={styles.gameInfoDateWeatherCalendarDateCalendar}>
|
||||
<View className={styles.month}>{startMonth}月</View>
|
||||
<View className={styles.day}>{startDay}</View>
|
||||
</View>
|
||||
{/* Date time */}
|
||||
<View className={styles.gameInfoDateWeatherCalendarDateDate}>
|
||||
<View className={styles.date}>{startDate}</View>
|
||||
<View className={styles.venueTime}>
|
||||
{gameRange} ({game_length}小时)
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Place */}
|
||||
<View className={styles.gameInfoPlace}>
|
||||
{/* venue location message */}
|
||||
<View className={styles.locationMessage}>
|
||||
{/* location icon */}
|
||||
<View className={styles.locationMessageIcon}>
|
||||
<Image
|
||||
className={styles.locationMessageIconImage}
|
||||
src="https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/43aab7e9-061e-4e3b-88c6-61c19b660b22.png"
|
||||
/>
|
||||
</View>
|
||||
{/* location message */}
|
||||
<View className={styles.locationMessageText}>
|
||||
{/* venue name and distance */}
|
||||
<View
|
||||
className={styles.locationMessageTextNameDistance}
|
||||
onClick={openMap}
|
||||
>
|
||||
<Text>{location_name || "-"}</Text>
|
||||
{distance ? (
|
||||
<>
|
||||
<Text>·</Text>
|
||||
<Text>{distance.toFixed(1)}km</Text>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Image
|
||||
className={styles.locationMessageTextNameDistanceArrow}
|
||||
src={img.ICON_DETAIL_ARROW_RIGHT}
|
||||
/>
|
||||
</View>
|
||||
{/* venue address */}
|
||||
<View className={styles.locationMessageTextAddress}>
|
||||
<Text>{location || "-"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Action bar */}
|
||||
<View className={styles.gameInfoActions}></View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<View className={styles.orderSummary}>
|
||||
<View className={styles.moduleTitle}>
|
||||
<Text>确认订单信息</Text>
|
||||
</View>
|
||||
{/* 订单信息摘要 */}
|
||||
<View className={styles.summaryList}>
|
||||
{
|
||||
summary.map((item, index) => (<View key={index} className={styles.summaryItem}>
|
||||
<Text className={styles.title}>{item.title}</Text>
|
||||
<Text className={styles.content}>{item.content}</Text>
|
||||
</View>))
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<View className={styles.refundPolicy}>
|
||||
<View className={styles.moduleTitle}>
|
||||
<Text>退款政策</Text>
|
||||
</View>
|
||||
{/* 订单信息摘要 */}
|
||||
<View className={styles.policyList}>
|
||||
{
|
||||
policyList.map((item, index) => (<View key={index} className={styles.policyItem}>
|
||||
<View className={styles.time}>{item.time}</View>
|
||||
<View className={styles.rule}>{item.rule}</View>
|
||||
</View>))
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function Disclaimer() {
|
||||
return (
|
||||
<View className={styles.declaimer}>
|
||||
<Text className={styles.title}>免责声明</Text>
|
||||
<Text className={styles.content}>{DECLAIMER}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const OrderCheck = () => {
|
||||
const { params } = useRouter();
|
||||
const { id, gameId } = params;
|
||||
const [detail, setDetail] = useState<GameDetail | {}>({});
|
||||
const [location, setLocation] = useState<number[]>([0, 0]);
|
||||
const [orderInfo, setOrderInfo] = useState<GameOrderRes | {}>({})
|
||||
|
||||
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 (
|
||||
<View className={styles.container}>
|
||||
{/* Game Date and Address */}
|
||||
<GameInfo detail={detail} currentLocation={location} />
|
||||
{/* Order message */}
|
||||
<OrderMsg detail={detail} orderInfo={orderInfo} />
|
||||
{/* Refund policy */}
|
||||
<RefundPolicy orderInfo={orderInfo} />
|
||||
{/* Disclaimer */}
|
||||
<Disclaimer />
|
||||
<Button className={styles.payButton} type="primary" onClick={handlePay}>支付</Button>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default withAuth(OrderCheck);
|
||||
4
src/mod_user/orderList/index.config.ts
Normal file
4
src/mod_user/orderList/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '订单确认',
|
||||
navigationBarBackgroundColor: '#FAFAFA'
|
||||
})
|
||||
1
src/mod_user/orderList/index.scss
Normal file
1
src/mod_user/orderList/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
71
src/mod_user/orderList/index.tsx
Normal file
71
src/mod_user/orderList/index.tsx
Normal file
@@ -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<GameDetail | {}>({})
|
||||
|
||||
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 (
|
||||
<View>
|
||||
<Text>OrderCheck</Text>
|
||||
<Text>球局名称:{detail?.title || '-'}</Text>
|
||||
<Text>价格:¥{detail?.price || '-'}</Text>
|
||||
<Button onClick={handlePay}>支付</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(OrderCheck)
|
||||
@@ -1,13 +0,0 @@
|
||||
import { View, } from '@tarojs/components';
|
||||
const OrderPage: React.FC = () => {
|
||||
|
||||
|
||||
return (
|
||||
<View className="myself_page">
|
||||
我的收藏
|
||||
</View>)
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default OrderPage;
|
||||
@@ -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<UserInfo>({
|
||||
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 (
|
||||
<View className="myself_page">
|
||||
{/* 主要内容 */}
|
||||
<View className='main_content'>
|
||||
<View className="main_content">
|
||||
{/* 用户信息区域 */}
|
||||
<View className="user_info_section">
|
||||
{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 = () => {
|
||||
<View className="action_content" onClick={handle_game_orders}>
|
||||
<Image
|
||||
className="action_icon"
|
||||
src={require('../../../static/userInfo/order_btn.svg')}
|
||||
src={require("../../../static/userInfo/order_btn.svg")}
|
||||
/>
|
||||
<Text className="action_text">我的订单</Text>
|
||||
</View>
|
||||
@@ -174,7 +173,7 @@ const MyselfPage: React.FC = () => {
|
||||
<View className="action_content" onClick={handle_favorites}>
|
||||
<Image
|
||||
className="action_icon"
|
||||
src={require('../../../static/userInfo/sc.svg')}
|
||||
src={require("../../../static/userInfo/sc.svg")}
|
||||
/>
|
||||
<Text className="action_text">收藏</Text>
|
||||
</View>
|
||||
@@ -185,10 +184,16 @@ const MyselfPage: React.FC = () => {
|
||||
{/* 球局类型标签页 */}
|
||||
<View className="game_tabs_section">
|
||||
<View className="tab_container">
|
||||
<View className={`tab_item ${active_tab === 'hosted' ? 'active' : ''}`} onClick={() => setActiveTab('hosted')}>
|
||||
<View
|
||||
className={`tab_item ${active_tab === "hosted" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("hosted")}
|
||||
>
|
||||
<Text className="tab_text">我主办的</Text>
|
||||
</View>
|
||||
<View className={`tab_item ${active_tab === 'participated' ? 'active' : ''}`} onClick={() => setActiveTab('participated')}>
|
||||
<View
|
||||
className={`tab_item ${active_tab === "participated" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("participated")}
|
||||
>
|
||||
<Text className="tab_text">我参与的</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -207,7 +212,7 @@ const MyselfPage: React.FC = () => {
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
<GuideBar currentPage='personal' />
|
||||
<GuideBar currentPage="personal" />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { View, } from '@tarojs/components';
|
||||
const OrderPage: React.FC = () => {
|
||||
|
||||
|
||||
return (
|
||||
<View className="myself_page">
|
||||
我的订单
|
||||
</View>)
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default OrderPage;
|
||||
@@ -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<UserInfo>({
|
||||
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,15 +98,14 @@ 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}`,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -138,7 +151,7 @@ const OtherUserPage: React.FC = () => {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<GuideBar currentPage='personal' />
|
||||
<GuideBar currentPage="personal" />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
73
src/services/evaluateService.ts
Normal file
73
src/services/evaluateService.ts
Normal file
@@ -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<AnswerResItem, "question_id" | "answer_index">;
|
||||
|
||||
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<ApiResponse<QuestionItem[]>> {
|
||||
return httpService.post("/ntrp/questions", {
|
||||
showLoading: true,
|
||||
});
|
||||
}
|
||||
|
||||
async submitEvaluateAnswers({
|
||||
answers,
|
||||
}: Answers): Promise<ApiResponse<SubmitAnswerRes>> {
|
||||
return httpService.post(
|
||||
"/ntrp/submit",
|
||||
{ answers },
|
||||
{
|
||||
showLoading: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async getHistoryNtrp(): Promise<ApiResponse<any>> {
|
||||
return httpService.post("/ntrp/history", {
|
||||
showLoading: true,
|
||||
});
|
||||
}
|
||||
|
||||
async getNtrpDetail(record_id: number): Promise<ApiResponse<any>> {
|
||||
return httpService.post(
|
||||
"/ntrp/detail",
|
||||
{ record_id },
|
||||
{
|
||||
showLoading: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出认证服务实例
|
||||
export default new EvaluateService();
|
||||
@@ -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<LoginResponse> => {
|
||||
export const wechat_auth_login = async (
|
||||
phone_code?: string,
|
||||
): Promise<LoginResponse> => {
|
||||
try {
|
||||
// 先进行微信登录获取code
|
||||
const login_result = await Taro.login();
|
||||
@@ -65,34 +80,34 @@ export const wechat_auth_login = async (phone_code?: string): Promise<LoginRespo
|
||||
if (!login_result.code) {
|
||||
return {
|
||||
success: false,
|
||||
message: '微信登录失败'
|
||||
message: "微信登录失败",
|
||||
};
|
||||
}
|
||||
|
||||
// 使用 httpService 调用微信授权接口,传递手机号code
|
||||
const auth_response = await httpService.post('user/wx_auth', {
|
||||
const auth_response = await httpService.post("user/wx_auth", {
|
||||
code: login_result.code,
|
||||
phone_code: phone_code // 传递手机号加密code
|
||||
phone_code: phone_code, // 传递手机号加密code
|
||||
});
|
||||
|
||||
if (auth_response.code === 0) {
|
||||
return {
|
||||
success: true,
|
||||
message: '微信登录成功',
|
||||
token: auth_response.data?.token || '',
|
||||
user_info: auth_response.data?.userInfo
|
||||
message: "微信登录成功",
|
||||
token: auth_response.data?.token || "",
|
||||
user_info: auth_response.data?.userInfo,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message: auth_response.message || '微信授权失败'
|
||||
message: auth_response.message || "微信授权失败",
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('微信授权登录失败:', error);
|
||||
console.error("微信授权登录失败:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: '微信授权失败,请重试'
|
||||
message: "微信授权失败,请重试",
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -101,38 +116,39 @@ export const wechat_auth_login = async (phone_code?: string): Promise<LoginRespo
|
||||
export interface PhoneLoginParams {
|
||||
phone: string;
|
||||
verification_code: string;
|
||||
user_code: string
|
||||
user_code: string;
|
||||
}
|
||||
|
||||
// 手机号验证码登录
|
||||
export const phone_auth_login = async (params: PhoneLoginParams): Promise<LoginResponse> => {
|
||||
export const phone_auth_login = async (
|
||||
params: PhoneLoginParams,
|
||||
): Promise<LoginResponse> => {
|
||||
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<LoginR
|
||||
// 发送短信验证码
|
||||
export const send_sms_code = async (phone: string): Promise<SmsResponse> => {
|
||||
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<VerifyCodeResponse> => {
|
||||
export const verify_sms_code = async (
|
||||
phone: string,
|
||||
code: string,
|
||||
): Promise<VerifyCodeResponse> => {
|
||||
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<boolean> => {
|
||||
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<boolean> => {
|
||||
// 检查本地存储的登录状态
|
||||
return check_login_status();
|
||||
} catch (error) {
|
||||
console.error('刷新登录状态失败:', error);
|
||||
console.error("刷新登录状态失败:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取用户详细信息
|
||||
export const fetchUserProfile = async (): Promise<ApiResponse<UserInfoType>> => {
|
||||
export const fetchUserProfile = async (): Promise<
|
||||
ApiResponse<UserInfoType>
|
||||
> => {
|
||||
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<ApiResponse<UserInfoType>> =>
|
||||
// 更新用户信息
|
||||
export const updateUserProfile = async (payload: Partial<UserInfoType>) => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
@@ -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<ApiResponse<GameOrderRes>> {
|
||||
return httpService.post('/payment/check_order', { game_id }, {
|
||||
showLoading: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 导出认证服务实例
|
||||
|
||||
@@ -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<void>
|
||||
updateUserInfo: (userInfo: Partial<UserInfoType>) => void
|
||||
user: UserInfoType | {};
|
||||
fetchUserInfo: () => Promise<void>;
|
||||
updateUserInfo: (userInfo: Partial<UserInfoType>) => void;
|
||||
}
|
||||
|
||||
export const useUser = create<UserState>()((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<UserInfoType>) => {
|
||||
const res = await updateUserProfile(userInfo)
|
||||
set({ user: res.data })
|
||||
}
|
||||
}))
|
||||
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) => ({
|
||||
export const useUserActions = () =>
|
||||
useUser((state) => ({
|
||||
fetchUserInfo: state.fetchUserInfo,
|
||||
updateUserInfo: state.updateUserInfo
|
||||
}))
|
||||
updateUserInfo: state.updateUserInfo,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user