feat: 订单模块基本完成

This commit is contained in:
2025-09-16 14:34:11 +08:00
parent 4a00c7f1d8
commit a045b39580
13 changed files with 799 additions and 288 deletions

View File

@@ -229,7 +229,34 @@
.gameInfoActions {
min-height: 12px;
padding: 0 12px;
border-top: 0.5px solid rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10px;
& > .button {
margin: 12px 0;
padding: 4px 10px;
height: 28px;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
color: #000;
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 20px;
letter-spacing: -0.23px;
&:first-child {
background: #000;
color: #fff;
&.payNow {
background-color: #ff3b30;
}
}
}
}
}
@@ -432,3 +459,47 @@
font-weight: 600;
line-height: normal;
}
.dialogFooter {
// width: 100%;
width: calc(100% + 1px);
height: 44px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: flex-end;
position: absolute;
// margin: 0 -24px -24px;
bottom: 0;
left: 0;
border-top: 1px solid rgba(0, 0, 0, 0.06);
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
overflow: hidden;
& > .cancel, & > .confirm {
padding: 12px 10px;
height: 44px;
width: 50%;
text-align: center;
// border: 0.5px solid rgba(0, 0, 0, 0.06);
color: #000;
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 20px;
&:last-child {
background: #000;
color: #fff;
}
}
& > .cancel {
border-radius: 0;
}
& > .confirm {
border-radius: 0;
}
}

View File

@@ -1,31 +1,80 @@
import React, { useState } from "react";
import React, { useState, useRef } from "react";
import { View, Text, Button, Image } from "@tarojs/components";
import { Dialog } from "@nutui/nutui-react-taro";
import Taro, { useDidShow, useRouter } from "@tarojs/taro";
import dayjs from "dayjs";
import classnames from "classnames";
import orderService, {
CancelType,
GameOrderRes,
OrderStatus,
RefundStatus,
} from "@/services/orderService";
import {
payOrder,
delay,
calculateDistance,
getCurrentLocation,
getOrderStatus,
generateOrderActions,
reloadPage,
} from "@/utils";
import detailService, { GameData } from "@/services/detailService";
import { withAuth } from "@/components";
import { withAuth, RefundPopup } from "@/components";
import img from "@/config/images";
import { DECLAIMER } from "./config";
import styles from "./index.module.scss";
dayjs.locale("zh-cn");
const refundTextMap = new Map([
[RefundStatus.NONE, "已支付"],
[RefundStatus.PENDING, "退款中"],
[RefundStatus.SUCCESS, "已退款"],
]);
const gameNoticeMap = new Map([
[
"pending",
{ title: "球局暂未开始", content: "球局开始前2小时我们将通过短信通知你" },
],
[
"pendinging",
{
title: "球局即将开始,请按时抵达球局",
content: "球局开始前2小时我们将通过短信通知你",
},
],
["progress", { title: "球局已开始", content: "友谊第一,比赛第二" }],
["finish", { title: "球局已结束", content: "" }],
]);
function genGameNotice(order_status, start_time) {
const startTime = dayjs(start_time);
let key = "";
if (order_status === OrderStatus.FINISHED) {
key = "finish";
}
const leftHour = startTime.diff(dayjs(), "hour");
const start = startTime.isBefore(dayjs());
if (start) {
key = "progress";
} else if (leftHour > 2) {
key = "pending";
} else if (leftHour < 2) {
key = "pendinging";
}
return gameNoticeMap.get(key) || {};
}
function GameInfo(props) {
const { detail, currentLocation, orderDetail } = props;
const { order_status } = orderDetail;
const { order_status, refund_status } = orderDetail;
const { latitude, longitude, location, location_name, start_time, end_time } =
detail || {};
const refundRef = useRef(null);
const openMap = () => {
Taro.openLocation({
latitude, // 纬度(必填)
@@ -52,14 +101,129 @@ function GameInfo(props) {
const startDate = `${startMonth}${startDay}${theDayOfWeek}`;
const gameRange = `${startTime.format("HH:mm")} - ${endTime.format("HH:mm")}`;
const orderStatus = getOrderStatus(orderDetail);
const gameNotice = genGameNotice(order_status, start_time);
function handleViewGame(gameId) {
Taro.navigateTo({
url: `/game_pages/detail/index?id=${gameId}&from=orderList`,
});
}
async function handleDeleteOrder(item) {
const { order_id } = item;
// TODO删除订单,刷新这一页,然后后面的全清除掉
const onCancel = () => {
Dialog.close("detailCancelOrder");
};
const onConfirm = async () => {
try {
const deleteRes = await orderService.deleteOrder({
order_id,
});
if (deleteRes.code !== 0) {
throw new Error(deleteRes.message);
}
Taro.showToast({
title: "删除成功",
icon: "none",
});
delay(2000);
Taro.redirectTo({ url: "/order_pages/orderList/index" });
} catch (e) {
Taro.showToast({
title: e.message,
icon: "error",
});
} finally {
Dialog.close("detailCancelOrder");
}
};
Dialog.open("detailCancelOrder", {
title: "确定删除订单吗?",
content: "删除订单后,您将无法恢复订单。请确认是否继续取消?",
footer: (
<View className={styles.dialogFooter}>
<Button className={styles.cancel} onClick={onCancel}>
</Button>
<Button className={styles.confirm} type="primary" onClick={onConfirm}>
</Button>
</View>
),
onConfirm,
onCancel,
});
}
async function handleCancelOrder(item) {
const { order_no } = item;
const onCancel = () => {
Dialog.close("detailCancelOrder");
};
const onConfirm = async () => {
try {
const cancelRes = await orderService.cancelUnpaidOrder({
order_no,
cancel_reason: "用户主动取消",
});
if (cancelRes.code !== 0) {
throw new Error(cancelRes.message);
}
reloadPage();
Taro.showToast({
title: "取消成功",
icon: "none",
});
} catch (e) {
Taro.showToast({
title: e.message,
icon: "error",
});
} finally {
Dialog.close("detailCancelOrder");
}
};
Dialog.open("detailCancelOrder", {
title: "确定取消订单吗?",
content: "取消订单后,您将无法恢复订单。请确认是否继续取消?",
footer: (
<View className={styles.dialogFooter}>
<Button className={styles.cancel} onClick={onCancel}>
</Button>
<Button className={styles.confirm} type="primary" onClick={onConfirm}>
</Button>
</View>
),
onConfirm,
onCancel,
});
}
function handleQuit(item) {
if (refundRef.current) {
refundRef.current.show(item, (result) => {
if (result) {
reloadPage();
}
});
}
}
return (
<View className={styles.gameInfoContainer}>
{Boolean(order_status) && order_status !== OrderStatus.PENDING && (
{["progress", "expired"].includes(orderStatus) && (
<>
<View className={styles.paidInfo}> ¥ 90</View>
<View className={styles.paidInfo}>
{refundTextMap.get(refund_status)} ¥ 90
</View>
<View className={styles.gameStatus}>
<Text className={styles.statusText}></Text>
<Text>2</Text>
<Text className={styles.statusText}>{gameNotice.title}</Text>
{gameNotice.content && <Text>{gameNotice.content}</Text>}
</View>
</>
)}
@@ -122,13 +286,36 @@ function GameInfo(props) {
</View>
</View>
{/* Action bar */}
<View className={styles.gameInfoActions}></View>
<View className={styles.gameInfoActions}>
{orderDetail.order_id
? generateOrderActions(
orderDetail,
{
handleDeleteOrder,
handleCancelOrder,
handleQuit,
handlePayNow: () => {},
handleViewGame,
},
"detail"
)?.map((obj) => (
<Button
className={classnames(styles.button, styles[obj.className])}
onClick={obj.action}
>
{obj.text}
</Button>
))
: ""}
</View>
<Dialog id="detailCancelOrder" />
<RefundPopup ref={refundRef} />
</View>
);
}
function OrderMsg(props) {
const { detail, checkOrderInfo } = props;
const { detail, orderDetail, checkOrderInfo } = props;
const {
start_time,
end_time,
@@ -137,7 +324,8 @@ function OrderMsg(props) {
wechat_contact,
price,
} = detail;
const { order_info: { registrant_nickname, registrant_phone } = {} } = checkOrderInfo;
const { order_no } = orderDetail;
const { order_info: { registrant_phone } = {} } = checkOrderInfo;
const startTime = dayjs(start_time);
const endTime = dayjs(end_time);
const startYear = startTime.format("YYYY");
@@ -159,18 +347,30 @@ function OrderMsg(props) {
</View>
),
},
{
title: "报名人昵称",
content: registrant_nickname,
},
{
title: "报名人电话",
content: registrant_phone,
},
{
title: "组织人微信号",
content: wechat_contact,
},
{
title: "组织人电话",
content: wechat_contact,
},
{
title: "费用",
content: `${price} 元 / 人`,
},
...(order_no
? [
{
title: "订单号",
content: order_no,
},
]
: []),
];
return (
<View className={styles.orderSummary}>
@@ -199,8 +399,12 @@ function RefundPolicy(props) {
rule: "退款规则",
},
...refund_policy.map((item, index) => {
const isLast = index === refund_policy.length - 1
const theTimeObj = dayjs(isLast ? refund_policy.at(-2).deadline_formatted : item.deadline_formatted);
const isLast = index === refund_policy.length - 1;
const theTimeObj = dayjs(
isLast
? refund_policy.at(-2).deadline_formatted
: item.deadline_formatted
);
const year = theTimeObj.format("YYYY");
const month = theTimeObj.format("M");
const day = theTimeObj.format("D");
@@ -332,6 +536,9 @@ const OrderCheck = () => {
</View>
);
}
const { order_status, cancel_type } = orderDetail;
return (
<View className={styles.container}>
{/* Game Date and Address */}
@@ -341,14 +548,20 @@ const OrderCheck = () => {
currentLocation={location}
/>
{/* Order message */}
<OrderMsg detail={detail} checkOrderInfo={checkOrderInfo} />
<OrderMsg
detail={detail}
orderDetail={orderDetail}
checkOrderInfo={checkOrderInfo}
/>
{/* Refund policy */}
<RefundPolicy checkOrderInfo={checkOrderInfo} />
{/* Disclaimer */}
<Disclaimer />
{(!id || orderDetail.order_status === OrderStatus.PENDING) && (
{(!id ||
(order_status === OrderStatus.PENDING &&
cancel_type === CancelType.NONE)) && (
<Button className={styles.payButton} onClick={handlePay}>
{orderDetail.order_status === OrderStatus.PENDING ? "继续" : "确认"}
{order_status === OrderStatus.PENDING ? "继续" : "确认"}
</Button>
)}