|
|
<template>
|
|
|
<view class="pay-bg" :class="{ 'wechat-browser': isWeixinBrowser }">
|
|
|
<view class="fixed-nav" v-if="!isWeixinBrowser">
|
|
|
<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 class="pay-row"
|
|
|
><text>支付金额</text><text>¥{{ item.price }}</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">
|
|
|
<!-- 二维码组件:text 为内容,size 为尺寸(单位 px) -->
|
|
|
<a :href="qrContent">
|
|
|
<s-qrcode
|
|
|
ref="qrcode"
|
|
|
v-if="qrContent"
|
|
|
canvas-id="qrcode"
|
|
|
:value="qrContent"
|
|
|
:sizeUnit="'rpx'"
|
|
|
:size="260"
|
|
|
color="#333333"
|
|
|
bgColor="#ffffff"
|
|
|
margin="10"
|
|
|
@complete="handleComplete"
|
|
|
></s-qrcode>
|
|
|
</a>
|
|
|
</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="saveImage">保存图片</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 { base } from "@/common/util.js";
|
|
|
import { API } from "@/config/index.js";
|
|
|
import sQrcode from "@/uni_modules/Sansnn-uQRCode/components/u-qrcode/u-qrcode.vue";
|
|
|
|
|
|
export default {
|
|
|
name: "PayOrderDetailPage",
|
|
|
components: {
|
|
|
NavBar,
|
|
|
sQrcode,
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
isWeixinBrowser: false,
|
|
|
qrcodeUrl: "",
|
|
|
payType: "wechat",
|
|
|
item: {},
|
|
|
shipTypeEnum: [],
|
|
|
reservationStatusEnum: [],
|
|
|
qrContent: "",
|
|
|
};
|
|
|
},
|
|
|
onLoad(options) {
|
|
|
if (options.item) {
|
|
|
try {
|
|
|
this.item = JSON.parse(decodeURIComponent(options.item));
|
|
|
console.log("this.item", this.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
|
|
|
},
|
|
|
onShow() {
|
|
|
this.fetchShipTypeEnum().then(() => {
|
|
|
if (this.item.id && this.item.status === "unpaid") {
|
|
|
this.fetchQrcode(this.item.id);
|
|
|
}
|
|
|
});
|
|
|
this.fetchReservationStatusEnum();
|
|
|
},
|
|
|
methods: {
|
|
|
formatChinaDate: base.formatChinaDate,
|
|
|
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}`,
|
|
|
data: {
|
|
|
reservation_id: id,
|
|
|
token: token,
|
|
|
},
|
|
|
method: "POST",
|
|
|
success: resolve,
|
|
|
fail: reject,
|
|
|
});
|
|
|
});
|
|
|
|
|
|
if (res.data && res.data.errcode === 0) {
|
|
|
const payLink = res.data.data.payment_qrcode;
|
|
|
// 如果返回的是链接,则生成二维码
|
|
|
console.log("payLink", payLink);
|
|
|
// this.generateQRCode(payLink);
|
|
|
this.qrContent = payLink;
|
|
|
} else {
|
|
|
uni.showToast({
|
|
|
title: res.data.errmsg || "获取二维码失败",
|
|
|
icon: "none",
|
|
|
});
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
},
|
|
|
saveImage() {
|
|
|
//e 图片地址
|
|
|
// 非H5环境中
|
|
|
// #ifndef H5
|
|
|
uni.saveImageToPhotosAlbum({
|
|
|
filePath: this.qrcodeUrl, //图片url
|
|
|
success: () => {
|
|
|
uni.showToast({
|
|
|
title: "保存成功",
|
|
|
icon: "success",
|
|
|
});
|
|
|
},
|
|
|
});
|
|
|
// #endif
|
|
|
//H5环境中,也可以提示长按屏幕保存,让用户操作,不执行以下事件
|
|
|
// #ifdef H5
|
|
|
if (typeof plus !== "undefined") {
|
|
|
// 当前运行在H5+环境中
|
|
|
var down = plus.downloader.createDownload(this.qrcodeUrl, {}, function (e, a) {
|
|
|
plus.gallery.save(
|
|
|
e.filename,
|
|
|
function (e) {
|
|
|
uni.showToast({
|
|
|
title: "保存成功",
|
|
|
mask: true,
|
|
|
});
|
|
|
console.log("下载成功");
|
|
|
},
|
|
|
function (e) {
|
|
|
console.log("下载失败,请重试");
|
|
|
}
|
|
|
);
|
|
|
});
|
|
|
down.start();
|
|
|
} else {
|
|
|
var oA = document.createElement("a");
|
|
|
oA.download = "pay_code.png"; // 设置下载的文件名,默认是'下载'
|
|
|
oA.href = this.qrcodeUrl; //图片url
|
|
|
document.body.appendChild(oA);
|
|
|
oA.click();
|
|
|
oA.remove(); // 下载之后把创建的元素删除
|
|
|
}
|
|
|
// #endif
|
|
|
},
|
|
|
handleComplete(e) {
|
|
|
console.log("handleComplete", e);
|
|
|
if (e && e.success) {
|
|
|
this.$refs.qrcode.toTempFilePath({
|
|
|
success: (res) => {
|
|
|
console.log(res);
|
|
|
this.qrcodeUrl = res.tempFilePath;
|
|
|
},
|
|
|
fail: (err) => {
|
|
|
console.log(err);
|
|
|
},
|
|
|
complete: () => {
|
|
|
console.log("complete");
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
.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>
|