You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

465 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="order-page">
<view class="fixed-nav">
<NavBar :title="title" />
</view>
<!-- 订单卡片列表 -->
<view class="order-list">
<template v-if="reservationList.length > 0">
<view v-for="item in reservationList" :key="item.id">
<!-- 已付款 -->
<view v-if="item.status === 'paid'" class="order-card purchased">
<view class="order-card-header">
<view class="status purchased"><span class="status-text">已付款</span></view>
<view class="date">{{ formatChinaDate(item.created_at) }}</view>
</view>
<view class="order-info">
<view class="flight">{{ item.ship ? item.ship.ship_number : '' }}</view>
<view class="desc"> {{ item.direction_name }} | {{ item.batch && item.batch.name ? item.batch.name : '-' }}</view>
</view>
<view class="order-actions single-btn">
<button class="detail-btn" @click="onShowDetail(item)">查看详情</button>
</view>
</view>
<!-- 待确认 -->
<view v-else-if="item.status === 'pending'" class="order-card pending">
<view class="order-card-header">
<view class="status pending"><span class="status-text">待确认</span></view>
<view class="date">{{ formatChinaDate(item.created_at) }}</view>
</view>
<view class="order-info">
<view class="flight">{{ item.ship ? item.ship.ship_number : '' }}</view>
<view class="desc"> {{ item.direction_name }} | {{ item.batch && item.batch.name ? item.batch.name : '-' }}</view>
</view>
<view class="order-actions">
<button class="cancel-btn" :disabled="false" @click="onCancelOrder(item)">取消预约</button>
<button class="detail-btn" @click="onShowDetail(item)">查看详情</button>
</view>
</view>
<!-- 已拒绝 -->
<view v-else-if="item.status === 'rejected'" class="order-card pending">
<view class="order-card-header">
<view class="status pending"><span class="status-text">已拒绝</span></view>
<view class="date">{{ formatChinaDate(item.created_at) }}</view>
</view>
<view class="order-info">
<view class="flight">{{ item.ship ? item.ship.ship_number : '' }}</view>
<view class="desc"> {{ item.direction_name }} | {{ item.batch && item.batch.name ? item.batch.name : '-' }}</view>
</view>
<view class="order-actions">
<button class="cancel-btn" :disabled="false" @click="goReservation(item)">重新预约</button>
<button class="detail-btn" @click="onShowDetail(item)">查看详情</button>
</view>
</view>
<!-- 待支付 -->
<view v-else-if="item.status === 'unpaid' || item.status === 'approved'" class="order-card confirmed">
<view class="order-card-header">
<view class="status confirmed"><span class="status-text">待支付</span></view>
<view class="date">{{ formatChinaDate(item.created_at) }}</view>
</view>
<view class="order-info">
<view class="flight">{{ item.ship ? item.ship.ship_number : '' }}</view>
<view class="desc"> {{ item.direction_name }} | {{ item.batch && item.batch.name ? item.batch.name : '-' }}</view>
</view>
<view class="order-actions">
<button class="detail-btn" @click="onShowDetail(item)">查看详情</button>
<button class="buy-btn" @click="goPayOrder(item)">去支付</button>
</view>
</view>
</view>
</template>
<template v-else>
<view class="empty-box">
<image src="/static/empty.png" class="empty-img" mode="aspectFit" />
<view class="empty-text"></view>
</view>
</template>
</view>
</view>
</template>
<script>
import { API } from '@/config/index.js'
import NavBar from '@/components/NavBar.vue'
export default {
name: 'OrderPage',
components: {
NavBar
},
data() {
return {
status: 'unpaid', // 0: 待支付, 1: 排队过闸
list: [],
page: 1,
title: '在线付款',
hasMore: true,
loading: false,
reservationStatusEnum: [],
reservationList: []
}
},
onLoad(options) {
if (options.status) {
if (options.status === 'paid') {
this.status = 'paid';
this.title = '排队过闸';
} else {
this.status = 'unpaid';
this.title = '在线付款';
}
}
},
onShow() {
this.fetchReservationStatusEnum().then(() => {
this.fetchReservationList();
});
},
methods: {
formatChinaDate(dateStr) {
if (!dateStr) return '';
// 解析 ISO 字符串为 Date 对象
const date = new Date(dateStr);
// 转为北京时间(中国标准时间,东八区)
// Date对象自动以本地时区显示前提是 dateStr 是标准ISO格式
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
},
async fetchReservationStatusEnum() {
const token = uni.getStorageSync('token');
if (!token) return;
try {
const res = await new Promise((resolve, reject) => {
uni.request({
url: `${API.RESERVATION_STATUS_ENUM}?token=${token}`,
method: 'GET',
success: resolve,
fail: reject
});
});
if (res.data && res.data.errcode === 0) {
this.reservationStatusEnum = res.data.data;
}
} catch (e) {
// 可选:错误处理
}
},
async fetchReservationList() {
const token = uni.getStorageSync('token');
if (!token) return;
try {
const res = await new Promise((resolve, reject) => {
uni.request({
url: `${API.RESERVATION_LIST}?token=${token}&status=${this.status}`,
method: 'GET',
success: resolve,
fail: reject
});
});
if (res.data && res.data.errcode === 0) {
this.reservationList = res.data.data.data;
console.log(this.reservationList);
}
} catch (e) {
// 可选:错误处理
}
},
onCancelOrder(item) {
uni.showModal({
title: '提示',
content: '确定要取消该预约吗?',
confirmText: '确定',
cancelText: '再想想',
success: (res) => {
if (res.confirm) {
// 这里写取消预约的逻辑
uni.showToast({ title: '已取消预约', icon: 'success' });
}
}
});
},
onShowDetail(item) {
const itemString = JSON.stringify(item);
uni.navigateTo({ url: `/pages/order/pay_order_detail?item=${encodeURIComponent(itemString)}` });
},
goPayOrder(item) {
const itemString = JSON.stringify(item);
uni.navigateTo({ url: `/pages/order/pay_order_detail?item=${encodeURIComponent(itemString)}` });
},
goReservation(item) {
uni.navigateTo({ url: '/pages/reservation/index' });
}
}
}
</script>
<style scoped>
.order-page {
background: linear-gradient(180deg, #cbe6ff 0%, #f6faff 100%);
min-height: 100vh;
padding-bottom: 20px;
font-family: 'SourceHanSansCN', 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
.header-title {
text-align: center;
font-size: 36rpx;
font-weight: bold;
padding-top: 7vh;
letter-spacing: 2rpx;
}
.fixed-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: linear-gradient(180deg, #cbe6ff 0%, #f6faff 100%);
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
height: 90px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 16px 10px 16px;
background: linear-gradient(180deg, #cbe6ff 0%, #f6faff 100%);
}
.back-btn, .more-btn {
font-size: 24px;
color: #333;
}
.title {
font-size: 22px;
font-weight: bold;
color: #222;
}
.order-list {
padding: 10px 0 0 0;
margin-top: 110px;
padding-bottom: 160rpx;
}
.order-card {
background: #fff;
border-radius: 10px;
margin: 0 16px 16px 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
padding: 18px 18px 12px 18px;
height: 340rpx;
}
.order-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.status {
font-size: 12px;
border-radius: 8rpx;
padding: 2px 8px;
color: #fff;
font-weight: 500;
display: inline-block;
transform: skewX(-20deg);
border: none;
}
.status-text {
display: inline-block;
transform: skewX(20deg);
}
.status.purchased { background: #22c58b; }
.status.pending { background: #ff9800; }
.status.confirmed { background: #217aff; }
.status.cancelled { background: #bdbdbd; }
.date { color: #173766; font-size: 15px; }
.order-info {
margin-bottom: 24px;
}
.flight {
font-size: 16px;
font-weight: 500;
margin-top: 12px;
}
.desc {
color: #888;
font-size: 14px;
margin-top: 10px;
}
.order-actions {
display: flex;
gap: 12px;
}
button {
flex: 1;
border-radius: 4px;
padding: 8px 0;
font-size: 16px;
margin: 0;
}
.detail-btn {
background: #e4f3fe;
color: #217aff;
height: 69rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
margin-left: auto;
}
.order-actions.single-btn {
justify-content: flex-end;
}
.order-actions.single-btn .detail-btn {
flex: 0 0 auto;
width: 153px;
}
.buy-btn, .rebook-btn {
background: linear-gradient(90deg, #3b7cff 0%, #5bb6ff 100%);
color: #fff;
height: 69rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.cancel-btn {
background: #ededed;
color: #bdbdbd;
height: 69rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.cancel-btn[disabled] {
opacity: 1;
}
.tabbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: 60px;
background: #fff;
display: flex;
border-top: 1px solid #eaeaea;
z-index: 10;
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #888;
font-size: 14px;
}
.tab-item.active {
color: #217aff;
}
.icon {
font-size: 22px;
margin-bottom: 2px;
}
.detail-modal-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.detail-modal {
background: #fff;
border-radius: 20px;
padding: 64rpx 56rpx 48rpx 56rpx;
width: 90vw;
max-width: 500px;
min-height: 420px;
margin: 0 auto;
position: relative;
display: flex;
flex-direction: column;
}
.detail-modal-content {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
.modal-close {
position: absolute;
right: 24rpx;
top: 24rpx;
font-size: 44rpx;
color: #222;
z-index: 2;
cursor: pointer;
}
.modal-title {
font-size: 38rpx;
font-weight: bold;
margin-bottom: 40rpx;
text-align: left;
}
.modal-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 34rpx;
color: #222;
margin-bottom: 36rpx;
}
.modal-label {
color: #3b4a6b;
min-width: 180rpx;
font-size: 34rpx;
}
.modal-amount {
color: #217aff;
font-size: 32rpx;
font-weight: bold;
}
.modal-confirm-btn {
width: 160px !important;
height: 44px !important;
line-height: 44px !important;
border-radius: 12px !important;
background: linear-gradient(90deg, #3b7cff 0%, #5bb6ff 100%) !important;
color: #fff !important;
font-size: 18px !important;
font-weight: 500 !important;
margin: 40px auto 0 auto !important;
border: none !important;
outline: none !important;
display: block !important;
padding: 0 !important;
box-sizing: border-box !important;
text-align: center !important;
align-self: center !important;
flex-shrink: 0 !important;
flex-grow: 0 !important;
}
.empty-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 120rpx;
}
.empty-img {
width: 320rpx;
height: 320rpx;
margin-bottom: 32rpx;
}
.empty-text {
color: #888;
font-size: 28rpx;
}
</style>