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.

500 lines
14 KiB

6 months ago
<template>
<view class="pay-bg" :class="{ 'wechat-browser': isWeixinBrowser }">
<view class="fixed-nav" v-if="!isWeixinBrowser">
6 months ago
<NavBar title="订单详情" />
</view>
<view class="pay-scroll">
<!-- 预约信息 -->
<view class="pay-section">
<view class="pay-title">预约信息</view>
<view class="pay-row"><text>预约日期</text><text>{{ formatChinaDate(item.batch && item.batch.created_at ? item.batch.created_at : item.created_at) }}</text></view>
<view class="pay-row"><text>预约批次</text><text>{{ item.batch && item.batch.name ? item.batch.name : '-' }}</text></view>
<view class="pay-row"><text>航行方向</text><text>{{ item.direction_name }}</text></view>
<view class="pay-row">
<text>预约状态</text>
<text class="pay-status" :class="item.status">{{ getStatusText(item.status) }}</text>
</view>
</view>
<!-- 船舶信息 -->
<view class="pay-section">
<view class="pay-title">船舶信息</view>
<view class="pay-row"><text>船舶编号</text><text>{{ item.ship.ship_number }}</text></view>
<view class="pay-row"><text>总长度</text><text>{{ item.ship.total_length }}</text></view>
<view class="pay-row"><text>型宽</text><text>{{ item.ship.total_width }}</text></view>
<view class="pay-row"><text>型深</text><text>{{ item.ship.molded_depth }}</text></view>
<view class="pay-row"><text>载重</text><text>{{ item.ship.total_tonnage }}</text></view>
<view class="pay-row"><text>类型</text><text>{{ item.ship && item.ship.ship_type ? getShipTypeName(item.ship.ship_type) : '' }}</text></view>
</view>
<!-- 票价信息 -->
<view class="pay-section" v-if="item.status === 'paid' || item.status === 'completed'">
<view class="pay-title">票价信息</view>
<view class="pay-row"><text>过闸费用</text><text>{{ item.price }}</text></view>
<view class="pay-row"><text>按吨位计费</text><text>{{ item.ship.total_tonnage }}</text></view>
<view class="pay-row pay-total">
<text>总计</text>
<text class="pay-total-num">{{ item.price }}</text>
</view>
</view>
<!-- 支付方式 -->
<view class="pay-section" v-if="item.status === 'unpaid' || item.status === 'approved'">
<view class="pay-title">扫码支付</view>
<view class="pay-qrcode-box">
<image :src="qrcodeUrl" class="pay-qrcode-img" mode="aspectFit" @longpress="handleLongPress" />
</view>
<view class="pay-qrcode-tip">请使用微信或支付宝扫码支付</view>
</view>
</view>
<view class="pay-bottom-bar">
<template v-if="item.status === 'unpaid' || item.status === 'approved'">
<view class="pay-bottom-tip">
<text class="pay-bottom-clock">🕒</text>
<text class="pay-bottom-tip-text">
请在 <text class="pay-bottom-time">14:06</text> 内完成支付
</text>
</view>
<view class="pay-bottom-btns">
<button class="pay-pay-btn" @click="handlePay"> {{ item.price }}</button>
</view>
</template>
<template v-else-if="item.status === 'pending'">
<view class="pay-bottom-btns">
<button class="pay-cancel-btn" @click="handleCancel"></button>
</view>
</template>
<template v-else-if="item.status === 'rejected'">
<view class="pay-bottom-btns">
<button class="pay-cancel-btn" @click="handleReReserve"></button>
</view>
</template>
</view>
</view>
</template>
<script>
import NavBar from '@/components/NavBar.vue'
import { API } from '@/config/index.js'
export default {
name: 'PayOrderDetailPage',
6 months ago
components: {
NavBar
},
data() {
return {
isWeixinBrowser: false,
6 months ago
qrcodeUrl: 'https://xukoushuniu.115.langye.net/assets/images/fake-qrcode.png',
payType: 'wechat',
item: {},
shipTypeEnum: [],
reservationStatusEnum: []
}
},
onLoad(options) {
if (options.item) {
try {
this.item = JSON.parse(decodeURIComponent(options.item));
} catch (e) {
console.error('Failed to parse item:', e);
this.item = {};
}
}
console.log(this.item);
// #ifdef H5
this.isWeixinBrowser = /MicroMessenger/i.test(navigator.userAgent)
// #endif
6 months ago
},
onShow() {
this.fetchShipTypeEnum().then(() => {
if (this.item.id) {
this.fetchQrcode(this.item.id);
}
});
this.fetchReservationStatusEnum();
},
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 fetchQrcode(id) {
const token = uni.getStorageSync('token');
if (!token || !id) return;
try {
const res = await new Promise((resolve, reject) => {
uni.request({
url: `${API.GET_PAYMENT_QRCODE}?id=${id}&token=${token}`,
method: 'GET',
success: resolve,
fail: reject
});
});
if (res.data && res.data.errcode === 0) {
this.qrcodeUrl = res.data.data.qrcode;
}
} catch (e) {}
},
async fetchShipTypeEnum() {
const token = uni.getStorageSync('token');
if (!token) return;
try {
const res = await new Promise((resolve, reject) => {
uni.request({
url: `${API.SHIP_PROPERTY_ENUM}?token=${token}`,
method: 'GET',
success: resolve,
fail: reject
});
});
if (res.data && res.data.errcode === 0) {
const shipTypeRaw = res.data.data.ship_type || {};
if (Array.isArray(shipTypeRaw)) {
this.shipTypeEnum = shipTypeRaw;
} else {
this.shipTypeEnum = Object.keys(shipTypeRaw).map(label => ({
label,
value: shipTypeRaw[label]
}));
}
}
} catch (e) {
console.error('Failed to fetch ship type enum:', e);
}
},
getShipTypeName(type) {
const found = this.shipTypeEnum.find(item => item.value === type || item.value == type);
return found ? found.label : type;
},
handleLongPress() {
// 获取当前图片信息
uni.getImageInfo({
src: this.qrcodeUrl,
success: (imageInfo) => {
// 使用图片路径进行扫码
uni.scanCode({
scanType: ['qrCode'],
onlyFromCamera: false,
path: imageInfo.path,
success: (res) => {
console.log('扫码结果:', res);
if (res.result) {
uni.showToast({
title: '扫码成功',
icon: 'success'
});
// 这里可以处理扫码结果,比如跳转到支付页面
// uni.navigateTo({
// url: `/pages/payment/index?url=${encodeURIComponent(res.result)}`
// });
}
},
fail: (err) => {
console.error('扫码失败:', err);
uni.showToast({
title: '扫码失败',
icon: 'none'
});
}
});
},
fail: (err) => {
console.error('获取图片信息失败:', err);
uni.showToast({
title: '获取图片失败',
icon: 'none'
});
}
});
},
handleCancel() {
uni.showModal({
title: '提示',
content: '确定要取消该预约吗?',
confirmText: '确定',
cancelText: '再想想',
success: (res) => {
if (res.confirm) {
// 这里写取消预约的逻辑
uni.showToast({ title: '已取消预约', icon: 'success' });
}
}
});
},
handleReReserve() {
uni.navigateTo({ url: '/pages/reservation/index' });
},
handlePay() {
//POST调用模拟支付接口
uni.request({
url: `${API.FAKE_PAY}`,
method: 'POST',
data: {
reservation_id: this.item.id,
token: uni.getStorageSync('token')
},
success: (res) => {
console.log('模拟支付结果:', res);
if (res.data && res.data.errcode === 0) {
uni.showToast({ title: '支付成功', icon: 'success' });
uni.navigateBack();
} else {
uni.showToast({ title: res.data.errmsg || '支付失败', icon: 'none' });
}
},
fail: (err) => {
console.error('模拟支付失败:', err);
}
});
},
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) {
console.error('Failed to fetch reservation status enum:', e);
}
},
getStatusText(status) {
if (this.reservationStatusEnum && this.reservationStatusEnum[status]) {
return this.reservationStatusEnum[status].label;
}
return status;
},
}
}
</script>
<style lang="scss" scoped>
@import '@/styles/common.scss';
.pay-bg {
min-height: 100vh;
background: linear-gradient(180deg, #cbe6ff 0%, #f6faff 100%);
padding-bottom: 32rpx;
}
.wechat-browser {
padding-top: 20rpx;
}
.wechat-browser .pay-scroll {
padding-top: 0;
}
6 months ago
.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;
}
.pay-scroll {
padding-top: 110px;
padding-bottom: 160rpx;
}
.pay-section {
background: #fff;
border-radius: 24rpx;
margin: 0 24rpx 32rpx 24rpx;
box-shadow: 0 4rpx 16rpx rgba(59,124,255,0.08);
padding: 32rpx 24rpx 8rpx 24rpx;
}
.pay-title {
@include font-primary;
font-weight: bold;
color: #222;
margin-bottom: 24rpx;
}
.pay-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18rpx 0;
border-bottom: 1rpx solid #f2f4f8;
text:first-child {
@include font-secondary;
color: $font-color-primary;
}
text:last-child {
@include font-secondary;
color: $font-color-secondary;
}
}
.pay-row:last-child {
border-bottom: none;
}
.pay-status {
background: #217aff;
color: #fff !important; // 强制纯白色
border-radius: 24rpx;
padding: 4rpx 24rpx;
font-size: 24rpx;
&.pending {
background: #ff9800;
}
&.rejected {
background: #ff4d4f;
}
&.unpaid, &.approved {
background: #217aff;
}
&.paid {
background: #22c58b;
}
}
.pay-total {
font-weight: bold;
}
.pay-total-num {
color: #217aff;
font-size: 32rpx;
}
.pay-paytype {
gap: 16rpx;
}
.pay-label {
display: flex;
align-items: center;
}
.pay-icon {
width: 36rpx;
height: 36rpx;
margin-right: 20rpx;
}
.pay-paytype text {
margin-left: 0; /* 确保文字紧跟图标 */
}
.pay-radio-group {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.pay-bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
box-shadow: 0 -2rpx 16rpx rgba(59,124,255,0.08);
padding: 24rpx 24rpx 32rpx 24rpx;
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
}
.pay-bottom-tip {
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
margin-bottom: 18rpx;
}
.pay-bottom-clock-box {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #217aff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
}
.pay-bottom-clock {
color: #217aff;
font-size: 28rpx;
}
.pay-bottom-tip-text {
color: #222;
}
.pay-bottom-time {
color: #217aff;
font-weight: bold;
}
.pay-bottom-btns {
display: flex;
gap: 24rpx;
justify-content: center;
width: 100%;
}
.pay-cancel-btn {
min-width: 240rpx;
height: 72rpx;
border-radius: 36rpx;
background: #f5f7fa;
color: #222;
font-size: 28rpx;
border: none;
outline: none;
&::after {
border: none;
}
}
.pay-pay-btn {
min-width: 320rpx;
height: 72rpx;
border-radius: 36rpx;
background: #217aff;
color: #fff;
font-size: 28rpx;
font-weight: 500;
border: none;
outline: none;
&::after {
border: none;
}
}
.pay-qrcode-box {
display: flex;
justify-content: center;
align-items: center;
margin: 32rpx 0;
}
.pay-qrcode-img {
width: 260rpx;
height: 260rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
}
.pay-qrcode-tip {
text-align: center;
color: #888;
font-size: 26rpx;
margin-top: 12rpx;
}
.pay-detail-btn {
min-width: 240rpx;
height: 72rpx;
border-radius: 36rpx;
background: #e4f3fe;
color: #217aff;
font-size: 28rpx;
border: none;
outline: none;
&::after {
border: none;
}
}
</style>