Files
mini-programs/src/order_pages/orderList/index.tsx
2025-11-14 22:21:42 +08:00

434 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useRef } from "react";
import { View, Text, Button, Image, ScrollView } from "@tarojs/components";
import Taro, { useDidShow } from "@tarojs/taro";
import { Avatar, Dialog } from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import "dayjs/locale/zh-cn";
import classnames from "classnames";
import orderService, {
OrderStatus,
CancelType,
refundTextMap,
} from "@/services/orderService";
import { getStorage, removeStorage, setStorage } from "@/store/storage";
import { useGlobalStore } from "@/store/global";
import { handleCustomerService } from "@/services/userService";
import { withAuth, RefundPopup, GeneralNavbar } from "@/components";
import { payOrder, generateOrderActions } from "@/utils";
import emptyContent from "@/static/emptyStatus/publish-empty.png";
import CustomerIcon from "@/static/order/customer.svg";
import { insertDotInTags } from "@/game_pages/detail/utils/helper";
import styles from "./index.module.scss";
dayjs.locale("zh-cn");
const PAGESIZE = 100;
const diffDayMap = new Map([
[0, "今天"],
[1, "明天"],
[2, "后天"],
]);
const DayOfWeekMap = new Map([
[0, "周日"],
[1, "周一"],
[2, "周二"],
[3, "周三"],
[4, "周四"],
[5, "周五"],
[6, "周六"],
]);
function generateTimeMsg(game_info) {
const { start_time, end_time } = game_info;
const startTime = dayjs(start_time);
const endTime = dayjs(end_time);
const diffDay = startTime.startOf("day").diff(dayjs().startOf("day"), "day");
const dayofWeek = startTime.day();
const gameLength = `${endTime.diff(startTime, "hour")}小时`;
return (
<>
<Text>
{diffDay <= 2 && diffDay >= 0
? diffDayMap.get(diffDay)
: startTime.format("YYYY-MM-DD")}
</Text>
<Text>({DayOfWeekMap.get(dayofWeek)})</Text>
<Text>{startTime.format("ah")}</Text>
<Text>{gameLength}</Text>
</>
);
}
const OrderList = () => {
const [list, setList] = useState<any[][]>([]);
const [total, setTotal] = useState(0);
const refundRef = useRef(null);
const end = list.length * PAGESIZE >= total;
useEffect(() => {
getOrders(1);
}, []);
useDidShow(() => {
const targetPage = getStorage("list_reload_page_number");
if (targetPage) {
removeStorage("list_reload_page_number");
getOrders(Number(targetPage));
}
});
function addPageInfo(arr, page) {
return arr.map((item) => ({ ...item, page }));
}
// clear 是否清除当前页后面的数据(如果有的话,没有也不影响)
async function getOrders(page, clear = true) {
const res = await orderService.getOrderList({ page, pageSize: PAGESIZE });
if (res.code === 0) {
setTotal(res.data.count);
setList((prev) => {
const newList = [...prev];
const index = page - 1;
newList.splice(
index,
clear ? newList.length - index : 1,
addPageInfo(res.data.rows, page)
);
return newList;
});
}
}
function handleFetchNext() {
console.log("scroll");
if (!end) {
getOrders(list.length + 1);
}
}
async function handlePayNow(item) {
try {
const unPaidRes = await orderService.getUnpaidOrder(item.game_info?.id);
if (unPaidRes.code === 0 && unPaidRes.data.has_unpaid_order) {
await payOrder(unPaidRes.data.payment_params);
getOrders(item.page, false);
} else {
throw new Error("支付调用失败");
}
} catch (e) {
console.log(e, 1111);
Taro.showToast({
title: e.message,
icon: "none",
});
}
}
function handleViewGame(gameId) {
if (!gameId) {
Taro.showToast({ title: "球局未找到", icon: "error" });
return;
}
Taro.navigateTo({
url: `/game_pages/detail/index?id=${gameId}&from=orderList`,
});
}
async function handleDeleteOrder(item) {
const { id: order_id } = item;
// TODO删除订单,刷新这一页,然后后面的全清除掉
const onCancel = () => {
Dialog.close("cancelOrder");
};
const onConfirm = async () => {
try {
const deleteRes = await orderService.deleteOrder({
order_id,
});
if (deleteRes.code !== 0) {
throw new Error(deleteRes.message);
}
getOrders(item.page);
Taro.showToast({
title: "删除成功",
icon: "none",
});
} catch (e) {
Taro.showToast({
title: e.message,
icon: "error",
});
} finally {
Dialog.close("cancelOrder");
}
};
Dialog.open("cancelOrder", {
title: "确定删除订单吗?",
content: (
<View className={styles.cancelTip}>
<Text></Text>
</View>
),
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("cancelOrder");
};
const onConfirm = async () => {
try {
const cancelRes = await orderService.cancelUnpaidOrder({
order_no,
cancel_reason: "用户主动取消",
});
if (cancelRes.code !== 0) {
throw new Error(cancelRes.message);
}
getOrders(item.page, false);
Taro.showToast({
title: "取消成功",
icon: "none",
});
} catch (e) {
Taro.showToast({
title: e.message,
icon: "error",
});
} finally {
Dialog.close("cancelOrder");
}
};
Dialog.open("cancelOrder", {
title: "确定取消订单吗?",
content: (
<View className={styles.cancelTip}>
<Text></Text>
</View>
),
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) {
getOrders(item.page);
}
});
}
}
function handleViewOrderDetail({ page, id: orderId }) {
setStorage("list_reload_page_number", page);
Taro.navigateTo({
url: `/order_pages/orderDetail/index?id=${orderId}`,
});
}
const flatList = list.flat();
const { statusNavbarHeightInfo } = useGlobalStore();
const { totalHeight } = statusNavbarHeightInfo;
return (
<View
className={styles.container}
style={{ paddingTop: `${totalHeight + 8}px` }}
>
<GeneralNavbar
title="球局订单"
backgroundColor="transparent"
titleClassName={styles.titleClassName}
className={styles.navbar}
/>
<ScrollView
scrollY
scrollWithAnimation
lowerThreshold={20}
onScrollToLower={handleFetchNext}
enhanced
showScrollbar={false}
className={styles.list}
>
{/* <View className={styles.bg} /> */}
{flatList.map((item) => {
const unPay =
item.order_status === OrderStatus.PENDING &&
item.cancel_type === CancelType.NONE;
const canceled = [CancelType.USER, CancelType.TIMEOUT].includes(
item.cancel_type
);
const { game_info } = item;
const {
skill_level_max,
skill_level_min,
play_type,
participants,
location_name,
current_players,
max_players,
court_type,
} = game_info || {};
return (
<View key={item.id} className={styles.orderItem}>
<View
className={styles.gameInfo}
onClick={() => handleViewOrderDetail(item)}
>
<View className={styles.gameTitle}>
<View className={styles.title}>{item?.game_info?.title}</View>
{!canceled && (
<View
className={classnames(
styles.payNum,
styles[unPay ? "pending" : "paid"]
)}
>
<Text>
{unPay
? "待支付"
: refundTextMap.get(item.refund_status)}
</Text>{" "}
¥ <Text>{item.amount}</Text>
</View>
)}
</View>
<View className={styles.gameTime}>
{generateTimeMsg(item.game_info || {})}
</View>
<View className={styles.address}>
{insertDotInTags([location_name, court_type, "3.5km"]).map(
(text, index) => (
<Text key={index}>{text}</Text>
)
)}
</View>
<View className={styles.gameOtherInfo}>
{participants?.length >= 0 ? (
<View className={styles.avatarCards}>
{participants.map((participant) => {
const {
user: { avatar_url, id },
} = participant;
return (
<Image
className={styles.avatar}
mode="aspectFill"
key={id}
src={avatar_url}
/>
);
})}
</View>
) : (
""
)}
<View className={styles.participantProgress}>
<Text className={styles.current}>
{current_players}
</Text>
<Text>/</Text>
<Text>{max_players}</Text>
</View>
<View className={styles.levelReq}>
{skill_level_max !== skill_level_min
? `${skill_level_min || "-"}${skill_level_max || "-"}`
: skill_level_min === 1
? "无要求"
: `${skill_level_min} 以上`}
</View>
<View className={styles.playType}>{play_type}</View>
</View>
</View>
<View className={styles.orderActions}>
<View className={styles.extraActions}>
<View
className={styles.customer}
onClick={handleCustomerService}
>
<Image className={styles.customerIcon} src={CustomerIcon} />
<Text></Text>
</View>
</View>
<View className={styles.mainActions}>
{generateOrderActions(
item,
{
handleDeleteOrder,
handleCancelOrder,
handleQuit,
handlePayNow,
handleViewGame,
},
"list"
)?.map((obj) => (
<View
className={classnames(
styles.button,
styles[obj.className]
)}
>
<Text className={styles.buttonText}>{obj.text}</Text>
<Button
className={styles.transparentButton}
onClick={obj.action}
>
{obj.text}
</Button>
</View>
))}
</View>
</View>
</View>
);
})}
{flatList.length > 0 && end && (
<View className={styles.endTips}></View>
)}
{flatList.length === 0 && (
<View className={styles.emptyNotice}>
<Image mode="widthFix" src={emptyContent} />
<Text className={styles.emptyTip}></Text>
</View>
)}
</ScrollView>
<Dialog id="cancelOrder" />
<RefundPopup ref={refundRef} />
</View>
);
};
export default withAuth(OrderList);