Files
h5-meike/pages/ticket/orderdetail.vue
T
jacobxu666andClaude Opus 4.8 dd25c0ae63 修复门票订单详情页 tickets 为 null 时的白屏崩溃,补 .gitignore
- 已退款/已下架订单后端返回 tickets 为 null,orderdetail 直接取
  obj.tickets.name 导致页面白屏崩溃
- 新增 ticketName 计算属性,回退订单快照 orderGoods.ticketsName;
  重新购买跳转回退 orderGoods.ticketsId
- 补 .gitignore 忽略 unpackage/ 与 .DS_Store

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 17:38:55 +08:00

523 lines
16 KiB
Vue
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.
<template>
<view v-if="obj">
<view class="detail_h">
<view style="width: 654rpx;display: flex;justify-content: space-between;">
<text style="font-size: 48rpx;font-weight: bold;"
:style="{color:obj.order.orderStatus=='WAITPAY'?'#FF0000':'#000'}">{{orderStatus}}</text>
<view>
<text style="color: #000000;font-size: 20rpx;">¥</text>
<text
style="color: #000000;font-size: 48rpx;font-weight: bold;margin-right: 12rpx;">{{(obj.order.orderAmount/100).toFixed(2)}}</text>
<u-icon name="arrow-right" color="#808080" size="28"></u-icon>
</view>
</view>
<text style="color:#808080;margin-top: 24rpx;display: block;">{{orderTitle}}</text>
</view>
<view class="detail_rq">
<text style="color: #000;line-height: 88rpx;font-size: 36rpx;font-weight: bold;">商品信息</text>
<view style="width: 702rpx;display: flex;justify-content: space-between;">
<text style="color: #000;font-weight: bold;">{{ticketName}}</text>
<u-icon name="arrow-right" color="#808080" size="24"></u-icon>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="width: 120rpx;color: #808080;">票型类型</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">
【{{getSkuType(obj.sku.typeId)}}】&nbsp;{{obj.sku.skuName}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="width: 120rpx;color: #808080;">使用日期</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #FF0000;">{{obj.orderGoods.bookDate}} <text
v-if="obj.orderGoods.timeSlot">({{obj.orderGoods.timeSlot}})</text></view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="width: 120rpx;color: #808080;">生效时间</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">出票后立即生效</view>
</view>
<view class="rypz" v-if="obj.touristList.length!=0">
<view style="width: 654rpx;text-align: center;display: flex;flex-direction: column;">
<text style="color: #000000;font-size: 32rpx;font-weight: bold;">入园凭证</text>
<scroll-view scroll-x="true" class="xsms-scroll">
<view style="width: 404rpx;display: inline-block;"
:style="{marginRight:index==obj.touristList.length-1?'0rpx':'24rpx'}"
v-for="(item,index) in obj.touristList" :key="index">
<view class="rq_item">凭证码</view>
<view
style="width: 404rpx;background: #F8F9FC;display: flex;flex-direction: column;align-items: center;padding-bottom: 24rpx0;border-radius: 0rpx 0rpx 24rpx 24rpx;">
<view class="rq_num">{{item.qrCode}}</view>
<view class="rq_btn" @tap='coyp(item.qrCode)'>复制</view>
<image style="width: 240rpx;height: 240rpx;margin-top: 24rpx;" :src="item.qrCodeBase64">
</image>
</view>
</view>
</scroll-view>
</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="width: 120rpx;color: #808080;">入园方式</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">
{{obj.sku.enterChangeFlag?'凭('+getTyepStr(obj.sku.enterType)+')换票入园':'无需换票'}}
</view>
</view>
<view style="display: flex;margin-top: 24rpx;" v-if="obj.sku.enterChangeFlag">
<view style="width: 120rpx;color: #808080;">换票时间</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">
{{obj.sku.changeStart}}~{{obj.sku.changeEnd}}
</view>
</view>
<view style="display: flex;margin-top: 24rpx;" v-if="obj.sku.enterChangeFlag">
<view style="width: 120rpx;color: #808080;">换票地点</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">{{obj.sku.changePostion}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="width: 120rpx;color: #808080;">入园时间</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">{{obj.sku.enterStart}}~{{obj.sku.enterEnd}}
</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="width: 120rpx;color: #808080;">入园地点</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">{{obj.sku.enterPostion}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;border-bottom: 2rpx #E1E1E1 dashed;padding-bottom: 24rpx;">
<view style="width: 120rpx;color: #808080;">门票</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">
¥{{(obj.orderGoods.skuPrice/100).toFixed(2)}} x {{obj.order.buyNum}}</view>
</view>
<view style="position: relative;height: 96rpx;display: flex;align-items: center;" @click="show1=true">
<text style="color: black;font-weight: bold;display: block;width: 120rpx;">取消政策</text>
<image src="https://aliyunoss.hn12301.net/sd/ticket_dg.png"
style="width: 40rpx;height: 40rpx;margin-left: 12rpx;margin-right: 12rpx;"></image>
<text
style="color: rgba(0, 0, 0, 0.8);">{{obj.sku.refundType==1?'随时退':obj.sku.refundType==2?'有条件退':obj.sku.refundType==3?'不可退款':''}}</text>
<u-icon name="arrow-right" color="#808080" size="24" style="position: absolute;right: 0rpx;"></u-icon>
</view>
</view>
<view class="detail_rq" style="border-top: none;padding-bottom: 24rpx;">
<text style="color: #000;line-height: 88rpx;font-size: 36rpx;font-weight: bold;">订单信息</text>
<view v-if="obj.sku.enterDemand=='2'">
<view style="display: flex;margin-top: 24rpx;" v-for="(item,index) in obj.touristList">
<view style="width: 120rpx;color: #808080;">出行人</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;line-height: 48rpx;">{{item.name}} <br />
{{item.phone}}
<br />{{item.identityNo}}
</view>
</view>
</view>
<view v-if="obj.sku.enterDemand=='1'"
style="display: flex;margin-top: 24rpx;border-bottom: 2rpx #E1E1E1 dashed;padding-bottom: 24rpx;">
<view style="width: 120rpx;color: #808080;">联系人</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">{{obj.order.orderContactName}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;border-bottom: 2rpx #E1E1E1 dashed;padding-bottom: 24rpx;">
<view style="width: 120rpx;color: #808080;">手机号</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">{{obj.order.orderContactMobile}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;position: relative;">
<view style="width: 120rpx;color: #808080;">订单编号</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;">{{obj.order.id}}</view>
<text style="position: absolute;right: 0rpx;color: #808080;" @tap='coyp(obj.order.id)'>复制</text>
</view>
<view style="display: flex;margin-top: 24rpx;position: relative;" v-if="obj.order.orderStatus!='WAITPAY'">
<view style="width: 120rpx;color: #808080;">支付时间</view>
<view style="width: 566rpx;margin-left: 16rpx;color: #000;" v-if="obj.payment">
{{obj.payment.createdTime||''}}
</view>
</view>
</view>
<view style="width: 750rpx;height: 120rpx;"></view>
<view class="detail_btn">
<!-- <view class="tozf">再次购买</view> -->
<view class="tozf" v-if="obj.order.orderStatus=='WAITPAY'" @tap='toPay()'>去支付</view>
<view class="toqx" v-if="obj.order.orderStatus=='WAITPAY'" @tap='cancel()'>取消订单</view>
<view class="toqx" @tap='aginPlay()'
v-if="obj.order.orderStatus=='HAVESEND'||obj.order.orderStatus=='COMPLETED'||obj.order.orderStatus=='BACKPAYED'">
重新购买</view>
<view class="toqx" v-if="obj.order.orderStatus=='HAVESEND'" @tap='refund()'>申请退款</view>
</view>
<u-popup v-model="show1" border-radius='20' mode='bottom'>
<view style="padding:48rpx;display: flex;flex-direction: column;">
<text style="color: #000;font-size: 36rpx;font-weight: bold;">取消政策</text>
<!-- <view
style="height: 88rpx;display: flex;align-items: center;position: relative;border-top: 1px dashed #E1E1E1;margin-top: 24rpx;">
<image src="https://aliyunoss.hn12301.net/sd/ticket_dg.png"
style="width: 40rpx;height: 40rpx;margin-right: 12rpx;"></image>
<text style="color: rgba(0, 0, 0, 0.8);">随时退</text>
</view>
<view style="position: relative;color: rgba(0, 0, 0, 0.5);">
该商品支持随时退,经核实未使用后,可免费取消。
</view> -->
<u-parse style="margin-top: 24rpx;" :html="obj.sku.refundPolicy"></u-parse>
<view style="width: 654rpx;height: 120rpx;"></view>
<view
style="width: 654rpx;position: fixed;height: 88rpx;line-height: 88rpx;background: #fff;bottom: 0rpx;left: 0rpx;right: 0rpx;margin: 0 auto;border-bottom: constant(safe-area-inset-bottom) solid #fff;border-bottom: env(safe-area-inset-bottom) solid #fff;">
<u-button shape="circle" size="default"
style="background: linear-gradient(180deg, #FF9E49 0%, #ED3B1A 100%);color: #fff;"
@click="show1=false">确定</u-button>
</view>
</view>
</u-popup>
<u-popup v-model="show" mode='bottom' border-radius='20'>
<view style="padding: 48rpx;">
<view style="width: 654rpx;text-align: center;font-size: 30rpx;font-weight: bold;">请选择游客</view>
<u-checkbox-group :wrap='true'>
<u-checkbox v-model="item.checked" v-for="(item, index) in list" :key="index"
:name="item.name">{{item.name}}</u-checkbox>
</u-checkbox-group>
<u-button shape="circle" size="default"
style="background: linear-gradient(180deg, #FF9E49 0%, #ED3B1A 100%);color: #fff;margin-top: 24rpx;"
@click="sure()">确定</u-button>
</view>
</u-popup>
</view>
</template>
<script>
// xuchao: 订单详情页 —— 待支付订单的"去支付"统一走 common/pay.js 支付层
import pay from '@/common/pay.js'
var that = ''
import {
getSkuType
} from '../../unit/utils.js'
export default {
data() {
return {
enterTypeList: [{
value: "二维码",
label: '1'
},
{
value: "实体票",
label: '2'
},
{
value: "第三方凭证码",
label: '3'
},
{
value: "身份证",
label: '4'
},
{
value: "护照",
label: '5'
},
{
value: "港澳居民来往内地通行证",
label: '6'
},
{
value: "台湾居民来往大陆通行证",
label: '7'
}
],
list: [],
show1: false,
show: false,
id: '',
obj: '',
getSkuType: '',
orderList: [{
text: '待支付',
value: 'WAITPAY',
label: '逾期未支付订单将自动关闭'
}, {
text: '出票中',
value: 'WAITSEND',
label: '正在出票中,预计30分钟内出票,超时未出票系统将为您自动退款'
}, {
text: '已出票',
value: 'HAVESEND',
label: '已出票,如有疑问请及时联系客服'
}, {
text: '已完成',
value: 'COMPLETED',
label: '订单已完成'
}, {
text: '已取消',
value: 'CANCELLED',
label: '订单已取消'
}, {
text: '退款中',
value: 'REFUNDING',
label: '已确认退款,款项将在1-7个工作日内退回您的付款渠道,请耐心等待'
}, {
text: '已退款',
value: 'BACKPAYED',
label: '退款完成,款项已退回您的付款渠道'
}]
}
},
computed: {
// xuchao: 门票名称兜底 —— 已退款/下架订单 tickets 可能为 null,回退订单快照 orderGoods.ticketsName
ticketName() {
if (this.obj && this.obj.tickets) return this.obj.tickets.name
return this.obj && this.obj.orderGoods ? this.obj.orderGoods.ticketsName : ''
},
orderStatus() {
let str = ''
if (this.obj) {
str = this.orderList.filter(item => {
return item.value == this.obj.order.orderStatus
})[0].text
}
return str
},
orderTitle() {
let str = ''
if (this.obj) {
str = this.orderList.filter(item => {
return item.value == this.obj.order.orderStatus
})[0].label
}
return str
}
},
onLoad(e) {
that = this
this.id = e.id
this.getSkuType = getSkuType
this.getOrderDetail()
},
methods: {
cancel(e) {
uni.showModal({
title: '提示',
content: '确定要取消订单吗?',
success: function(res) {
if (res.confirm) {
that.$u.api.cancelTicketsOrder({
id: that.id,
}).then(res => {
that.getOrderDetail()
})
} else if (res.cancel) {
console.log('用户点击取消');
}
}
})
},
toPay() {
// xuchao: 支付层 —— 开发模式模拟成功,正式模式唤起微信支付
pay.pay(this, {
orderId: this.obj.order.id,
onSuccess: () => {
uni.showToast({
icon: 'success',
title: '支付成功'
})
this.getOrderDetail()
},
onFail: () => {
uni.showToast({
icon: 'none',
title: '支付未完成,可稍后再次支付'
})
}
})
},
refund() {
//
this.show = true
},
sure() {
console.log(this.list);
let arr = []
this.list.forEach(item => {
arr.push(item.id)
})
that.$u.api.refundTicketsOrderXgh({
orderId: this.id,
touristIdList: arr
}).then(res => {
if (res) {
this.show = false
uni.showToast({
icon: 'none',
title: res.data.message
})
setTimeout(() => {
this.getOrderDetail()
}, 1000)
}
})
},
aginPlay() {
// xuchao: tickets 可能为 null(已下架),回退订单快照 orderGoods.ticketsId
let ticketsId = this.obj.tickets ? this.obj.tickets.id : this.obj.orderGoods.ticketsId
uni.reLaunch({
url: 'scenicdetail?id=' + ticketsId
})
},
coyp(e) {
uni.setClipboardData({
data: e + ''
})
},
getTyepStr(e) {
let typeStr = ''
e.split(',').forEach((item) => {
this.enterTypeList.forEach((item1) => {
if (item == item1.label) {
typeStr = typeStr + item1.value + '、'
}
})
})
return typeStr.slice(0, -1)
},
getOrderDetail() {
that.$u.api.getTicketsOrderDetail({
id: this.id,
}).then(res => {
if (res) {
this.obj = res.data
this.list = []
res.data.touristList.forEach(item => {
this.$set(item, 'checked', false)
this.list.push(item)
})
}
})
},
}
}
</script>
<style>
page {
background: #F8F9FC;
}
</style>
<style scoped lang="less">
.xsms-scroll {
overflow: hidden;
white-space: nowrap;
margin-top: 24rpx;
width: 654rpx;
}
.rypz {
width: 702rpx;
border: 1px dashed #E1E1E1;
margin-top: 24rpx;
padding: 24rpx;
.rq_item {
width: 404rpx;
height: 64rpx;
border-radius: 20rpx 20rpx 0rpx 0rpx;
background: #EEEFF3;
color: #000;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.rq_num {
width: 356rpx;
margin: 24rpx;
color: #000;
font-weight: bold;
white-space: normal;
overflow-wrap: break-word;
}
.rq_btn {
color: rgba(0, 0, 0, 0.8);
width: 72rpx;
height: 36rpx;
font-size: 20rpx;
border-radius: 36rpx;
border: 1px solid #E1E1E1;
display: flex;
align-items: center;
justify-content: center;
}
}
.tozf {
height: 64rpx;
background: linear-gradient(180deg, #FF9E49 0%, #ED3B1A 100%);
border-radius: 64rpx;
color: #fff;
font-weight: bold;
padding: 0rpx 24rpx;
display: flex;
align-items: center;
}
.toqx {
height: 64rpx;
background: rgba(0, 0, 0, 0.1);
border-radius: 64rpx;
color: #000;
font-weight: bold;
padding: 0rpx 24rpx;
display: flex;
align-items: center;
}
.detail_btn {
width: 750rpx;
height: 88rpx;
display: flex;
align-items: center;
background-color: #fff;
position: fixed;
bottom: 0rpx;
// xuchao: 视口水平居中(宽屏下与页面列对齐)+ 全面屏底部安全区
left: 0rpx;
right: 0rpx;
margin-left: auto;
margin-right: auto;
border-bottom: constant(safe-area-inset-bottom) solid #fff;
border-bottom: env(safe-area-inset-bottom) solid #fff;
gap: 20rpx;
padding-right: 24rpx;
justify-content: flex-end;
}
.detail_h {
margin: 48rpx 24rpx 0rpx 24rpx;
background: #fff;
border-radius: 20rpx 20rpx 0rpx 0rpx;
padding: 24rpx;
width: 702rpx;
}
.detail_rq {
border-top: 1px #E1E1E1 solid;
background-color: #fff;
padding: 24rpx 24rpx 0rpx 24rpx;
}
</style>