Merge branch 'feat/liujie'

This commit is contained in:
2025-09-15 23:21:24 +08:00
8 changed files with 663 additions and 208 deletions

View File

@@ -126,7 +126,7 @@ export default function UploadCover(props: UploadCoverProps) {
value.map((item) => { value.map((item) => {
return ( return (
<View className="cover-image-item" key={item.id}> <View className="cover-image-item" key={item.id}>
<Image className="cover-image-item-image" src={item.url} /> <Image className="cover-image-item-image" src={item.url} mode="aspectFill" />
<Image className="cover-image-item-delete" src={img.ICON_REMOVE} onClick={() => onDelete(item)} /> <Image className="cover-image-item-delete" src={img.ICON_REMOVE} onClick={() => onDelete(item)} />
</View> </View>
) )

View File

@@ -128,7 +128,7 @@ export default forwardRef(function UploadImage(props: UploadImageProps, ref) {
const isSelected = checkImageSelected(selectedImages, item) const isSelected = checkImageSelected(selectedImages, item)
return ( return (
<View className={`upload-popup-image-item ${outOfMax ? 'disabled' : ''} ${isSelected ? 'selected' : ''}`} onClick={() => handleImageClick(item)}> <View className={`upload-popup-image-item ${outOfMax ? 'disabled' : ''} ${isSelected ? 'selected' : ''}`} onClick={() => handleImageClick(item)}>
<Image className="upload-popup-image-item-image" src={item.url} /> <Image className="upload-popup-image-item-image" src={item.url} mode="aspectFill" />
<View className={`upload-popup-image-item-select ${isSelected ? 'selected' : ''}`}> <View className={`upload-popup-image-item-select ${isSelected ? 'selected' : ''}`}>
{isSelected ? ( {isSelected ? (
<Image className="select-image-icon" src={img.ICON_CIRCLE_SELECT_ARROW} /> <Image className="select-image-icon" src={img.ICON_CIRCLE_SELECT_ARROW} />

View File

@@ -162,7 +162,6 @@
&-image { &-image {
width: 28px; width: 28px;
height: 28px; height: 28px;
border-radius: 50%;
} }
} }
@@ -690,9 +689,11 @@
background: rgba(255, 255, 255, 0.16); background: rgba(255, 255, 255, 0.16);
flex: 0 0 auto; flex: 0 0 auto;
&-avatar { .participants-list-item-avatar {
width: 60px; width: 60px;
height: 60px; height: 60px;
border-radius: 50%;
overflow: hidden;
} }
&-name { &-name {
@@ -836,6 +837,8 @@
&-avatar { &-avatar {
width: 40px; width: 40px;
height: 40px; height: 40px;
border-radius: 50%;
object-fit: cover;
} }
&-message { &-message {
@@ -1012,6 +1015,8 @@
&-avatar { &-avatar {
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 50%;
object-fit: cover;
} }
&-message { &-message {

View File

@@ -38,8 +38,8 @@ function insertDotInTags(tags: string[]) {
} }
function GameTags(props) { function GameTags(props) {
const { userInfo } = props; const { userInfo, handleViewUserInfo } = props;
const { avatar_url } = userInfo; const { avatar_url, id } = userInfo;
const tags = [ const tags = [
{ {
name: "🕙 急招", name: "🕙 急招",
@@ -64,7 +64,9 @@ function GameTags(props) {
{/* network image mock */} {/* network image mock */}
<Image <Image
className="detail-page-content-avatar-tags-avatar-image" className="detail-page-content-avatar-tags-avatar-image"
mode="aspectFill"
src={avatar_url} src={avatar_url}
onClick={handleViewUserInfo.bind(null, id)}
/> />
</View> </View>
<View className="detail-page-content-avatar-tags-tags"> <View className="detail-page-content-avatar-tags-tags">
@@ -596,6 +598,7 @@ function VenueInfo(props) {
> >
<Image <Image
className="venue-screenshot-image-item-image" className="venue-screenshot-image-item-image"
mode="aspectFill"
src={item.url} src={item.url}
/> />
</View> </View>
@@ -661,7 +664,7 @@ function GamePlayAndRequirement(props) {
// 参与者 // 参与者
function Participants(props) { function Participants(props) {
const { detail = {}, handleJoinGame } = props; const { detail = {}, handleJoinGame, handleViewUserInfo } = props;
const participants = detail.participants || []; const participants = detail.participants || [];
const { const {
participant_count, participant_count,
@@ -724,9 +727,11 @@ function Participants(props) {
participant_user_id === organizer_id ? "组织者" : "参与者"; participant_user_id === organizer_id ? "组织者" : "参与者";
return ( return (
<View key={participant.id} className="participants-list-item"> <View key={participant.id} className="participants-list-item">
<Avatar <Image
className="participants-list-item-avatar" className="participants-list-item-avatar"
mode="aspectFill"
src={avatar_url} src={avatar_url}
onClick={handleViewUserInfo.bind(null, participant_user_id)}
/> />
<Text className="participants-list-item-name"> <Text className="participants-list-item-name">
{nickname || "未知"} {nickname || "未知"}
@@ -808,7 +813,7 @@ function genRecommendGames(games, location, avatar) {
avatar, avatar,
applications: max_players, applications: max_players,
checkedApplications: current_players, checkedApplications: current_players,
levelRequirements: `NTRP ${genNTRPRequirementText(skill_level_min, skill_level_max)}`, levelRequirements: skill_level_max !== skill_level_min ? `${skill_level_min || '-'}${skill_level_max || '-'}` : skill_level_min === 1 ? '无要求' : `${skill_level_min}以上`,
playType: play_type, playType: play_type,
}; };
}); });
@@ -819,6 +824,7 @@ function OrganizerInfo(props) {
userInfo, userInfo,
currentLocation: location, currentLocation: location,
onUpdateUserInfo = () => {}, onUpdateUserInfo = () => {},
handleViewUserInfo,
} = props; } = props;
const { const {
id, id,
@@ -855,11 +861,11 @@ function OrganizerInfo(props) {
} }
}; };
const viewOtherPage = () => { function handleViewGame(gameId) {
Taro.navigateTo({ Taro.navigateTo({
url: `/user_pages/other/index?userid=${id}`, url: `/game_pages/detail/index?id=${gameId}&from=current`
}); })
}; }
return ( return (
<View className="detail-page-content-organizer-recommend-games"> <View className="detail-page-content-organizer-recommend-games">
@@ -869,7 +875,7 @@ function OrganizerInfo(props) {
</View> </View>
{/* organizer avatar and name */} {/* organizer avatar and name */}
<View className="organizer-avatar-name"> <View className="organizer-avatar-name">
<Avatar className="organizer-avatar-name-avatar" src={avatar_url} onClick={viewOtherPage} /> <Image className="organizer-avatar-name-avatar" src={avatar_url} mode="aspectFill" onClick={handleViewUserInfo.bind(null, id)} />
<View className="organizer-avatar-name-message"> <View className="organizer-avatar-name-message">
<Text className="organizer-avatar-name-message-name">{nickname}</Text> <Text className="organizer-avatar-name-message-name">{nickname}</Text>
<View className="organizer-avatar-name-message-stats"> <View className="organizer-avatar-name-message-stats">
@@ -909,7 +915,7 @@ function OrganizerInfo(props) {
</View> </View>
{/* recommend games by organizer */} {/* recommend games by organizer */}
<View className="organizer-recommend-games"> <View className="organizer-recommend-games">
<View className="organizer-recommend-games-title" onClick={() => {}}> <View className="organizer-recommend-games-title" onClick={handleViewUserInfo.bind(null, id)}>
<Text>TA的更多活动</Text> <Text>TA的更多活动</Text>
<Image <Image
className="organizer-recommend-games-title-arrow" className="organizer-recommend-games-title-arrow"
@@ -919,7 +925,7 @@ function OrganizerInfo(props) {
<ScrollView className="recommend-games-list" scrollX> <ScrollView className="recommend-games-list" scrollX>
<View className="recommend-games-list-content"> <View className="recommend-games-list-content">
{recommendGames.map((game, index) => ( {recommendGames.map((game, index) => (
<View key={index} className="recommend-games-list-item"> <View key={index} className="recommend-games-list-item" onClick={handleViewGame.bind(null, game.id)}>
{/* game title */} {/* game title */}
<View className="recommend-games-list-item-title"> <View className="recommend-games-list-item-title">
<Text>{game.title}</Text> <Text>{game.title}</Text>
@@ -943,9 +949,11 @@ function OrganizerInfo(props) {
</View> </View>
{/* organizer avatar、applications、level requirements、play type */} {/* organizer avatar、applications、level requirements、play type */}
<View className="recommend-games-list-item-addon"> <View className="recommend-games-list-item-addon">
<Avatar <Image
className="recommend-games-list-item-addon-avatar" className="recommend-games-list-item-addon-avatar"
mode="aspectFill"
src={game.avatar} src={game.avatar}
onClick={(e) => { e.stopPropagation(); handleViewUserInfo(id) }}
/> />
<View className="recommend-games-list-item-addon-message"> <View className="recommend-games-list-item-addon-message">
<View className="recommend-games-list-item-addon-message-applications"> <View className="recommend-games-list-item-addon-message-applications">
@@ -1049,6 +1057,12 @@ function Index() {
} }
} }
function handleViewUserInfo(userId) {
Taro.navigateTo({
url: `/user_pages/other/index?userid=${userId}`
})
}
console.log("detail", detail); console.log("detail", detail);
const backgroundImage = detail?.image_list?.[0] const backgroundImage = detail?.image_list?.[0]
? { backgroundImage: `url(${detail?.image_list?.[0]})` } ? { backgroundImage: `url(${detail?.image_list?.[0]})` }
@@ -1079,7 +1093,7 @@ function Index() {
{/* content */} {/* content */}
<View className="detail-page-content"> <View className="detail-page-content">
{/* avatar and tags */} {/* avatar and tags */}
<GameTags detail={detail} userInfo={userInfo} /> <GameTags detail={detail} userInfo={userInfo} handleViewUserInfo={handleViewUserInfo} />
{/* title */} {/* title */}
<View className="detail-page-content-title"> <View className="detail-page-content-title">
<Text className="detail-page-content-title-text">{detail.title}</Text> <Text className="detail-page-content-title-text">{detail.title}</Text>
@@ -1091,7 +1105,7 @@ function Index() {
{/* gameplay requirements */} {/* gameplay requirements */}
<GamePlayAndRequirement detail={detail} /> <GamePlayAndRequirement detail={detail} />
{/* participants */} {/* participants */}
<Participants detail={detail} handleJoinGame={handleJoinGame} /> <Participants detail={detail} handleJoinGame={handleJoinGame} handleViewUserInfo={handleViewUserInfo} />
{/* supplemental notes */} {/* supplemental notes */}
<SupplementalNotes detail={detail} /> <SupplementalNotes detail={detail} />
{/* organizer and recommend games by organizer */} {/* organizer and recommend games by organizer */}
@@ -1100,6 +1114,7 @@ function Index() {
userInfo={userInfo} userInfo={userInfo}
currentLocation={currentLocation} currentLocation={currentLocation}
onUpdateUserInfo={onUpdateUserInfo} onUpdateUserInfo={onUpdateUserInfo}
handleViewUserInfo={handleViewUserInfo}
/> />
{/* sticky bottom action bar */} {/* sticky bottom action bar */}
<StickyButton <StickyButton

View File

@@ -137,7 +137,7 @@ function OrderMsg(props) {
wechat_contact, wechat_contact,
price, price,
} = detail; } = detail;
const { order_info: { registrant_nickname } = {} } = checkOrderInfo; const { order_info: { registrant_nickname, registrant_phone } = {} } = checkOrderInfo;
const startTime = dayjs(start_time); const startTime = dayjs(start_time);
const endTime = dayjs(end_time); const endTime = dayjs(end_time);
const startYear = startTime.format("YYYY"); const startYear = startTime.format("YYYY");
@@ -160,12 +160,12 @@ function OrderMsg(props) {
), ),
}, },
{ {
title: "组织者昵称", title: "报名人昵称",
content: registrant_nickname, content: registrant_nickname,
}, },
{ {
title: "组织者电话", title: "报名人电话",
content: wechat_contact, content: registrant_phone,
}, },
{ {
title: "费用", title: "费用",
@@ -199,14 +199,14 @@ function RefundPolicy(props) {
rule: "退款规则", rule: "退款规则",
}, },
...refund_policy.map((item, index) => { ...refund_policy.map((item, index) => {
const [, theTime] = item.application_time.split("undefined "); const isLast = index === refund_policy.length - 1
const theTimeObj = dayjs(theTime); const theTimeObj = dayjs(isLast ? refund_policy.at(-2).deadline_formatted : item.deadline_formatted);
const year = theTimeObj.format("YYYY"); const year = theTimeObj.format("YYYY");
const month = theTimeObj.format("M"); const month = theTimeObj.format("M");
const day = theTimeObj.format("D"); const day = theTimeObj.format("D");
const time = theTimeObj.format("HH:MM"); const time = theTimeObj.format("HH:mm");
return { return {
time: `${year}${month}${day}${time}${index === 0 ? "" : ""}`, time: `${year}${month}${day}${time} ${isLast ? "" : ""}`,
rule: item.refund_rule, rule: item.refund_rule,
}; };
}), }),

View File

@@ -1,81 +1,158 @@
@use "~@/scss/images.scss" as img; @use "~@/scss/images.scss" as img;
.container { .container {
padding: 12px; padding: 12px 12px 40px;
background-color: #fafafa; background-color: #fafafa;
min-height: 100vh; height: 100vh;
width: 100%;
box-sizing: border-box;
.list {
height: 100%;
width: 100%;
position: relative;
background-color: #fff;
// .bg {
// position: absolute;
// left: 0;
// top: 0;
// width: 100%;
// height: 100%;
// background-color: #fafafa;
// z-index: -1;
// }
.endTips {
height: 40px;
display: flex;
align-items: center;
justify-content: center;
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: 500;
line-height: 18px;
background-color: #f9f9f9;
}
}
.orderItem { .orderItem {
width: 100%; width: 100%;
height: 222px; // height: 222px;
background-color: #fff; background-color: #fff;
border-radius: 12px; border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.06); border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06); box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06);
margin-bottom: 12px; margin-bottom: 12px;
.orderTitle { // .orderTitle {
height: 18px; // height: 18px;
padding: 15px 15px 12px; // padding: 15px 15px 12px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06); // border-bottom: 1px solid rgba(0, 0, 0, 0.06);
display: flex; // display: flex;
align-items: center; // align-items: center;
justify-content: space-between; // justify-content: space-between;
.userInfo { // .userInfo {
// display: flex;
// align-items: center;
// justify-content: flex-start;
// gap: 6px;
// .avatar {
// width: 16px;
// height: 16px;
// }
// .nickName {
// display: contents;
// .nickNameText {
// color: #000;
// font-feature-settings:
// "liga" off,
// "clig" off;
// font-family: "PingFang SC";
// font-size: 12px;
// font-style: normal;
// font-weight: 500;
// line-height: 18px;
// }
// .arrowRight {
// width: 8px;
// height: 8px;
// }
// }
// }
// .paidInfo {
// display: flex;
// align-items: center;
// justify-content: flex-end;
// gap: 8px;
// .payTime {
// font-feature-settings:
// "liga" off,
// "clig" off;
// font-family: "PingFang SC";
// font-size: 12px;
// font-style: normal;
// font-weight: 400;
// line-height: 18px;
// &.paid {
// color: rgba(60, 60, 67, 0.6);
// }
// &.pending {
// color: #000;
// }
// }
// .payNum {
// font-feature-settings:
// "liga" off,
// "clig" off;
// font-family: "PingFang SC";
// font-size: 12px;
// font-style: normal;
// font-weight: 600;
// line-height: 18px;
// &.paid {
// color: #000;
// }
// &.pending {
// color: #ff3b30;
// }
// }
// }
// }
.gameInfo {
height: 122px;
.gameTitle {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: space-between;
gap: 6px; padding: 12px 15px 0;
.avatar { .title {
width: 16px; overflow: hidden;
height: 16px; color: #000;
} font-feature-settings: 'liga' off, 'clig' off;
text-overflow: ellipsis;
.nickName {
display: contents;
.nickNameText {
color: #000;
font-feature-settings:
"liga" off,
"clig" off;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px;
}
.arrowRight {
width: 8px;
height: 8px;
}
}
}
.paidInfo {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
.payTime {
font-feature-settings:
"liga" off,
"clig" off;
font-family: "PingFang SC"; font-family: "PingFang SC";
font-size: 12px; font-size: 16px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 600;
line-height: 18px; line-height: 24px; /* 150% */
&.paid {
color: rgba(60, 60, 67, 0.6);
}
&.pending {
color: #000;
}
} }
.payNum { .payNum {
@@ -96,14 +173,96 @@
} }
} }
} }
}
.gameInfo { .gameTime {
height: 122px; padding: 6px 0 0 15px;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
color: rgba(60, 60, 67, 0.60);
font-feature-settings: 'liga' off, 'clig' off;
text-overflow: ellipsis;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
}
.address {
padding: 6px 0 0 15px;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 4px;
color: rgba(60, 60, 67, 0.60);
font-feature-settings: 'liga' off, 'clig' off;
text-overflow: ellipsis;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
}
.gameOtherInfo {
padding: 8px 0 12px 15px;
height: 20px;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 4px;
.avatarCards {
display: flex;
align-items: center;
justify-content: flex-start;
height: 20px;
.avatar {
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.06);
&+.avatar {
margin-left: -10px;
}
}
}
.participantProgress, .levelReq, .playType {
display: flex;
height: 20px;
padding: 0px 8px;
align-items: center;
gap: 4px;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.16);
background: #FFF;
color: #000;
font-feature-settings: 'liga' off, 'clig' off;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 181.818% */
letter-spacing: -0.23px;
}
.participantProgress {
color: #c4c4c7;
.current {
color: #000;
}
}
}
} }
.orderActions { .orderActions {
height: 28px; min-height: 28px;
padding: 12px 12px 15px; padding: 12px 12px 15px;
border-top: 1px solid rgba(0, 0, 0, 0.06); border-top: 1px solid rgba(0, 0, 0, 0.06);
@@ -135,6 +294,9 @@
&:last-child { &:last-child {
background: #000; background: #000;
color: #fff; color: #fff;
&.payNow {
background-color: #ff3b30;
}
} }
} }
@@ -142,6 +304,7 @@
} }
.payNow { .payNow {
background-color: #ff3b30;
} }
} }
} }
@@ -215,3 +378,47 @@
} }
} }
} }
.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,5 +1,5 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { View, Text, Button, Image } from "@tarojs/components"; import { View, Text, Button, Image, ScrollView } from "@tarojs/components";
import Taro, { useDidShow } from "@tarojs/taro"; import Taro, { useDidShow } from "@tarojs/taro";
import { Avatar, Dialog } from "@nutui/nutui-react-taro"; import { Avatar, Dialog } from "@nutui/nutui-react-taro";
import dayjs from "dayjs"; import dayjs from "dayjs";
@@ -8,29 +8,98 @@ import orderService, { OrderStatus, CancelType } from "@/services/orderService";
import { withAuth } from "@/components"; import { withAuth } from "@/components";
import { payOrder } from "@/utils"; import { payOrder } from "@/utils";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import orderListArrowRight from "@/static/order/orderListArrowRight.svg"; // import orderListArrowRight from "@/static/order/orderListArrowRight.svg";
dayjs.locale("zh-cn");
const PAGESIZE = 20;
// 将·作为连接符插入到标签文本之间
function insertDotInTags(tags: string[]) {
return tags.join("-·-").split("-");
}
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
? diffDayMap.get(diffDay)
: startTime.format("YYYY-MM-DD")}
</Text>
<Text>({DayOfWeekMap.get(dayofWeek)})</Text>
<Text>{gameLength}</Text>
</>
);
}
const OrderList = () => { const OrderList = () => {
const [list, setList] = useState([]); const [list, setList] = useState<any[][]>([]);
const [total, setTotal] = useState(0);
useDidShow(() => { const end = list.length * PAGESIZE >= total;
getOrders();
});
async function getOrders() { useEffect(() => {
const res = await orderService.getOrderList(); getOrders(1);
console.log(res); }, []);
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) { if (res.code === 0) {
setList(res.data.rows); 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;
});
} }
} }
async function handlePayNow(gameId) { function handleFetchNext() {
console.log("scroll");
if (!end) {
getOrders(list.length + 1);
}
}
async function handlePayNow(item) {
try { try {
const unPaidRes = await orderService.getUnpaidOrder(gameId); const unPaidRes = await orderService.getUnpaidOrder(item.game_info.id);
if (unPaidRes.code === 0 && unPaidRes.data.has_unpaid_order) { if (unPaidRes.code === 0 && unPaidRes.data.has_unpaid_order) {
await payOrder(unPaidRes.data.payment_params); await payOrder(unPaidRes.data.payment_params);
getOrders(); getOrders(item.page, false);
} else { } else {
throw new Error("支付调用失败"); throw new Error("支付调用失败");
} }
@@ -43,6 +112,12 @@ const OrderList = () => {
} }
} }
function handleViewGame(gameId) {
Taro.navigateTo({
url: `/game_pages/detail/index?id=${gameId}&from=orderList`,
});
}
function renderCancelContent(checkOrderInfo) { function renderCancelContent(checkOrderInfo) {
const { refund_policy = [] } = checkOrderInfo; const { refund_policy = [] } = checkOrderInfo;
const policyList = [ const policyList = [
@@ -51,14 +126,18 @@ const OrderList = () => {
rule: "退款规则", rule: "退款规则",
}, },
...refund_policy.map((item, index) => { ...refund_policy.map((item, index) => {
const [, theTime] = item.application_time.split("undefined "); const isLast = index === refund_policy.length - 1;
const theTimeObj = dayjs(theTime); const theTimeObj = dayjs(
isLast
? refund_policy.at(-2).deadline_formatted
: item.deadline_formatted
);
const year = theTimeObj.format("YYYY"); const year = theTimeObj.format("YYYY");
const month = theTimeObj.format("M"); const month = theTimeObj.format("M");
const day = theTimeObj.format("D"); const day = theTimeObj.format("D");
const time = theTimeObj.format("HH:MM"); const time = theTimeObj.format("HH:MM");
return { return {
time: `${year}${month}${day}${time}${index === 0 ? "" : ""}`, time: `${year}${month}${day}${time} ${isLast ? "" : ""}`,
rule: item.refund_rule, rule: item.refund_rule,
}; };
}), }),
@@ -81,34 +160,104 @@ const 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: "删除订单后,您将无法恢复订单。请确认是否继续取消?",
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) { async function handleCancelOrder(item) {
const { order_no, order_status, game_info, amount } = item; const { order_no, order_status, game_info, amount } = item;
if (order_status === OrderStatus.PENDING) { if (order_status === OrderStatus.PENDING) {
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", { Dialog.open("cancelOrder", {
title: "确定取消订单吗?", title: "确定取消订单吗?",
content: "取消订单后,您将无法恢复订单。请确认是否继续取消?", content: "取消订单后,您将无法恢复订单。请确认是否继续取消?",
onConfirm: async () => { footer: (
try { <View className={styles.dialogFooter}>
const cancelRes = await orderService.cancelUnpaidOrder({ <Button className={styles.cancel} onClick={onCancel}>
order_no,
cancel_reason: "用户主动取消", </Button>
}); <Button
if (cancelRes.code !== 0) { className={styles.confirm}
throw new Error(cancelRes.message); type="primary"
} onClick={onConfirm}
getOrders(); >
} catch (e) {
Taro.showToast({ </Button>
title: e.message, </View>
icon: "error", ),
}); onConfirm,
} finally { onCancel,
Dialog.close("cancelOrder");
}
},
onCancel: () => {
Dialog.close("cancelOrder");
},
}); });
return; return;
} }
@@ -126,7 +275,11 @@ const OrderList = () => {
if (refundRes.code !== 0) { if (refundRes.code !== 0) {
throw new Error(refundRes.message); throw new Error(refundRes.message);
} }
getOrders(); getOrders(item.page, false);
Taro.showToast({
title: "退出成功",
icon: "none",
})
} catch (e) { } catch (e) {
Taro.showToast({ Taro.showToast({
title: e.message, title: e.message,
@@ -150,89 +303,149 @@ const OrderList = () => {
return ( return (
<View className={styles.container}> <View className={styles.container}>
{list.map((item) => { <ScrollView
const unPay = item.order_status === OrderStatus.PENDING; scrollY
const expired = scrollWithAnimation
item.order_status === OrderStatus.FINISHED || lowerThreshold={20}
[CancelType.TIMEOUT, CancelType.USER].includes(item.cancel_type); onScrollToLower={handleFetchNext}
const expiredTime = dayjs(item.expire_time).isSame(dayjs(), "day") enhanced
? dayjs(item.expire_time).format("HH:mm:ss") showScrollbar={false}
: dayjs(item.expire_time).format("YYYY-MM-DD HH:mm:ss"); className={styles.list}
const showCancel = >
item.order_status !== OrderStatus.FINISHED && {/* <View className={styles.bg} /> */}
item.cancel_type === CancelType.NONE; {list.flat().map((item) => {
const unPay = item.order_status === OrderStatus.PENDING;
const expired =
item.order_status === OrderStatus.FINISHED ||
[CancelType.TIMEOUT, CancelType.USER].includes(item.cancel_type);
// const expiredTime = dayjs(item.expire_time).isSame(dayjs(), "day")
// ? dayjs(item.expire_time).format("HH:mm:ss")
// : dayjs(item.expire_time).format("YYYY-MM-DD HH:mm:ss");
const showCancel =
item.order_status === OrderStatus.PENDING &&
item.cancel_type === CancelType.NONE;
const showQuit =
item.order_status === OrderStatus.PAID &&
item.cancel_type === CancelType.NONE;
return ( const {
<View key={item.id} className={styles.orderItem}> game_info: {
<View className={styles.orderTitle}> skill_level_max,
<View className={styles.userInfo}> skill_level_min,
<Avatar play_type,
className={styles.avatar} participants,
src="https://img.yzcdn.cn/vant/cat.jpeg" location_name,
/> current_players,
<View className={styles.nickName}> max_players,
<Text className={styles.nickNameText}>Light</Text> court_type,
<Image },
className={styles.arrowRight} } = item;
src={orderListArrowRight}
/> return (
</View> <View key={item.id} className={styles.orderItem}>
</View> <View
{expired ? ( className={styles.gameInfo}
"" onClick={handleViewOrderDetail.bind(null, item.id)}
) : ( >
<View className={styles.paidInfo}> <View className={styles.gameTitle}>
<Text <View className={styles.title}>{item?.game_info?.title}</View>
className={classnames( <View
styles.payTime,
styles[unPay ? "pending" : "paid"],
)}
>
{unPay
? `请在 ${expiredTime} 前支付`
: dayjs(item.pay_time).format("YYYY-MM-DD HH:mm:ss")}
</Text>
<Text
className={classnames( className={classnames(
styles.payNum, styles.payNum,
styles[unPay ? "pending" : "paid"], styles[unPay && !expired ? "pending" : "paid"]
)} )}
> >
{unPay ? "待支付" : "已支付"} ¥ {item.amount} <Text>{unPay && !expired ? "待支付" : "已支付"}</Text> ¥{" "}
</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 */[{ user: { avatar_url: 'https://img.yzcdn.cn/vant/cat.jpeg', id: 1 } }, { user: { avatar_url: 'https://img.yzcdn.cn/vant/cat.jpeg', id: 2 } }, { user: { avatar_url: 'https://img.yzcdn.cn/vant/cat.jpeg', id: 3 } }].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>
<View className={styles.mainActions}>
{expired && (
<Button
className={classnames(styles.button, styles.cancelOrder)}
onClick={handleDeleteOrder.bind(null, item)}
>
</Button>
)}
{showCancel && (
<Button
className={classnames(styles.button, styles.cancelOrder)}
onClick={handleCancelOrder.bind(null, item)}
>
</Button>
)}
{showQuit && (
<Button
className={classnames(styles.button, styles.cancelOrder)}
onClick={handleCancelOrder.bind(null, item)}
>
退
</Button>
)}
{unPay && !expired ? (
<Button
className={classnames(styles.button, styles.payNow)}
onClick={handlePayNow.bind(null, item)}
>
</Button>
) : (
<Button
className={classnames(styles.button, styles.gameDetail)}
onClick={handleViewGame.bind(null, item.game_info.id)}
>
</Button>
)}
</View> </View>
)}
</View>
<View
className={styles.gameInfo}
onClick={handleViewOrderDetail.bind(null, item.id)}
>
{item?.game_info?.title}
</View>
<View className={styles.orderActions}>
<View className={styles.extraActions}></View>
<View className={styles.mainActions}>
{showCancel && (
<Button
className={classnames(styles.button, styles.cancelOrder)}
onClick={handleCancelOrder.bind(null, item)}
>
</Button>
)}
{unPay && !expired && (
<Button
className={classnames(styles.button, styles.payNow)}
onClick={handlePayNow.bind(null, item.game_info.id)}
>
</Button>
)}
</View> </View>
</View> </View>
</View> );
); })}
})} {end && <View className={styles.endTips}></View>}
</ScrollView>
<Dialog id="cancelOrder" /> <Dialog id="cancelOrder" />
</View> </View>
); );

View File

@@ -78,8 +78,8 @@ export interface GameOrderRes {
// 发布球局类 // 发布球局类
class OrderService { class OrderService {
// 查询订单列表 // 查询订单列表
async getOrderList() { async getOrderList(pagination: { page: number, pageSize: number }) {
return httpService.post("/user/orders", {}, { showLoading: true }); return httpService.post("/user/orders", pagination, { showLoading: true });
} }
// 获取订单详情 // 获取订单详情
@@ -161,6 +161,21 @@ class OrderService {
}, },
); );
} }
// 删除订单
async deleteOrder({
order_id,
}: {
order_id: number;
}): Promise<ApiResponse<any>> {
return httpService.post(
"/payment/delete_order",
{ order_id },
{
showLoading: true,
},
);
}
} }
// 导出认证服务实例 // 导出认证服务实例