feat: 订单详情 & 问卷调查

This commit is contained in:
2025-09-10 16:56:24 +08:00
parent 8a1a2af1e9
commit d60445b850
31 changed files with 2054 additions and 723 deletions

View 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);