Files
h5-meike/pages/ticket/orderdetail.vue
T
jacobxu666andClaude Opus 4.8 77e5cb9510 feat: 全站 UI 按 Minimalist Modern 重设计,新增返回悬浮钮
- 设计令牌层:mm-* CSS 变量与工具类、uView 换肤、优设标题黑字体
- 九个页面全部重写为新风格(列表/详情/下单/出行人/订单/支付成功/登录)
- 补齐 H5 运行时缺失的基础样式(uni-view/scroll-view/toast/swiper 等,App.vue 全局)
- 修复:详情头图轮播、预订须知弹层滚动、出行人数量按购买数限制、订单详情去箭头
- 新增 gmy-back-touch 返回悬浮钮,除首页外全页接入,点击返回上一页

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 12:48:45 +08:00

695 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" class="page mm-page">
<gmy-back-touch></gmy-back-touch>
<!-- xuchao: 状态英雄卡 —— 待支付用主题蓝强调 -->
<view class="hero mm-card mm-fade-up">
<view class="hero__row">
<text :class="['hero__status mm-display', isWaitPay ? 'hero__status--accent' : '']">{{orderStatus}}</text>
<view class="hero__price">
<text class="hero__cur">¥</text>
<text class="hero__num mm-display">{{(obj.order.orderAmount/100).toFixed(2)}}</text>
</view>
</view>
<view class="hero__tip">{{orderTitle}}</view>
</view>
<!-- xuchao: 商品信息卡 -->
<view class="sec mm-card mm-fade-up">
<view class="mm-sec-title">商品信息</view>
<view class="goods">
<text class="goods__name">{{ticketName}}</text>
</view>
<view class="kv">
<view class="kv__label">票型类型</view>
<view class="kv__val">【{{getSkuType(obj.sku.typeId)}}】 {{obj.sku.skuName}}</view>
</view>
<view class="kv">
<view class="kv__label">使用日期</view>
<view class="kv__val kv__val--accent">{{obj.orderGoods.bookDate}} <text
v-if="obj.orderGoods.timeSlot">({{obj.orderGoods.timeSlot}})</text></view>
</view>
<view class="kv">
<view class="kv__label">生效时间</view>
<view class="kv__val">出票后立即生效</view>
</view>
<!-- xuchao: 入园凭证(横向滑动,每位游客一张凭证卡) -->
<view class="vch" v-if="obj.touristList.length!=0">
<view class="vch__title">入园凭证</view>
<scroll-view scroll-x="true" class="vch__scroll">
<view class="vch__item" :style="{marginRight:index==obj.touristList.length-1?'0rpx':'24rpx'}"
v-for="(item,index) in obj.touristList" :key="index">
<view class="vch__tag">凭证码</view>
<view class="vch__body">
<view class="vch__num">{{item.qrCode}}</view>
<view class="vch__copy" @tap="coyp(item.qrCode)">复制</view>
<view class="vch__qr">
<image :src="item.qrCodeBase64"></image>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="kv">
<view class="kv__label">入园方式</view>
<view class="kv__val">{{obj.sku.enterChangeFlag?'凭('+getTyepStr(obj.sku.enterType)+')换票入园':'无需换票'}}</view>
</view>
<view class="kv" v-if="obj.sku.enterChangeFlag">
<view class="kv__label">换票时间</view>
<view class="kv__val">{{obj.sku.changeStart}}~{{obj.sku.changeEnd}}</view>
</view>
<view class="kv" v-if="obj.sku.enterChangeFlag">
<view class="kv__label">换票地点</view>
<view class="kv__val">{{obj.sku.changePostion}}</view>
</view>
<view class="kv">
<view class="kv__label">入园时间</view>
<view class="kv__val">{{obj.sku.enterStart}}~{{obj.sku.enterEnd}}</view>
</view>
<view class="kv">
<view class="kv__label">入园地点</view>
<view class="kv__val">{{obj.sku.enterPostion}}</view>
</view>
<view class="kv kv--dash">
<view class="kv__label">门票</view>
<view class="kv__val">¥{{(obj.orderGoods.skuPrice/100).toFixed(2)}} x {{obj.order.buyNum}}</view>
</view>
<!-- xuchao: 取消政策 —— 点击弹层查看条款 -->
<view class="policy" @click="show1=true">
<text class="policy__label">取消政策</text>
<u-icon name="question-circle" color="#0052FF" size="28"></u-icon>
<text
class="policy__val">{{obj.sku.refundType==1?'随时退':obj.sku.refundType==2?'有条件退':obj.sku.refundType==3?'不可退款':''}}</text>
<u-icon name="arrow-right" color="#CBD5E1" size="24" class="policy__arrow"></u-icon>
</view>
</view>
<!-- xuchao: 订单信息卡 -->
<view class="sec mm-card mm-fade-up">
<view class="mm-sec-title">订单信息</view>
<template v-if="obj.sku.enterDemand=='2'">
<view class="kv" v-for="(item,index) in obj.touristList" :key="index">
<view class="kv__label">出行人</view>
<view class="kv__val">{{item.name}} {{item.phone}} {{item.identityNo}}</view>
</view>
</template>
<view class="kv kv--dash" v-if="obj.sku.enterDemand=='1'">
<view class="kv__label">联系人</view>
<view class="kv__val">{{obj.order.orderContactName}}</view>
</view>
<view class="kv kv--dash">
<view class="kv__label">手机号</view>
<view class="kv__val">{{obj.order.orderContactMobile}}</view>
</view>
<view class="kv">
<view class="kv__label">订单编号</view>
<view class="kv__val">{{obj.order.id}}</view>
<text class="kv__copy" @tap="coyp(obj.order.id)">复制</text>
</view>
<view class="kv" v-if="obj.order.orderStatus!='WAITPAY'">
<view class="kv__label">支付时间</view>
<view class="kv__val" v-if="obj.payment">{{obj.payment.createdTime||''}}</view>
</view>
</view>
<!-- xuchao: 底部按钮区预留(有操作按钮时才占位) -->
<view class="safe" v-if="hasActs"></view>
<!-- xuchao: 底部操作栏 -->
<view class="bar" v-if="hasActs">
<view class="bar__ghost" v-if="obj.order.orderStatus=='HAVESEND'" @tap="refund()">申请退款</view>
<view class="bar__ghost" @tap="aginPlay()"
v-if="obj.order.orderStatus=='HAVESEND'||obj.order.orderStatus=='COMPLETED'||obj.order.orderStatus=='BACKPAYED'">重新购买</view>
<view class="bar__ghost" v-if="obj.order.orderStatus=='WAITPAY'" @tap="cancel()">取消订单</view>
<view class="bar__main mm-grad" v-if="obj.order.orderStatus=='WAITPAY'" @tap="toPay()">去支付</view>
</view>
<!-- xuchao: 取消政策弹层 -->
<u-popup v-model="show1" border-radius="20" mode="bottom">
<view class="pp mm-safe-bottom">
<view class="mm-sec-title">取消政策</view>
<u-parse class="pp__parse" :html="obj.sku.refundPolicy"></u-parse>
<view class="mm-btn pp__btn" @click="show1=false">确定</view>
</view>
</u-popup>
<!-- xuchao: 申请退款 —— 选择游客弹层 -->
<u-popup v-model="show" mode="bottom" border-radius="20">
<view class="pp mm-safe-bottom">
<view class="pp__center-title">请选择游客</view>
<u-checkbox-group :wrap="true">
<u-checkbox v-model="item.checked" v-for="(item, index) in list" :key="index" :name="item.name"
active-color="#0052FF">{{item.name}}</u-checkbox>
</u-checkbox-group>
<view class="mm-btn pp__btn" @click="sure()">确定</view>
</view>
</u-popup>
</view>
</template>
<script>
// xuchao: 订单详情页 —— 待支付订单的"去支付"统一走 common/pay.js 支付层
import pay from '@/common/pay.js'
var that = ''
import {
getSkuType
} from '../../unit/utils.js'
import gmyBackTouch from "@/components/gmy-back-touch/gmy-back-touch.vue"
export default {
components: {
'gmy-back-touch': gmyBackTouch
},
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
},
// xuchao: 待支付状态用主题蓝强调
isWaitPay() {
return this.obj && this.obj.order.orderStatus == 'WAITPAY'
},
// xuchao: 是否存在底部操作按钮(决定操作栏与占位是否渲染)
hasActs() {
if (!this.obj) return false
const s = this.obj.order.orderStatus
return s == 'WAITPAY' || s == 'HAVESEND' || s == 'COMPLETED' || s == 'BACKPAYED'
}
},
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 scoped lang="less">
.page {
padding: 32rpx 32rpx 40rpx;
}
/* ---------- 状态英雄卡 ---------- */
.hero {
padding: 32rpx;
}
.hero__row {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.hero__status {
font-size: 44rpx;
line-height: 1.1;
color: var(--mm-fg);
}
.hero__status--accent {
color: var(--mm-accent);
}
.hero__price {
display: flex;
align-items: baseline;
}
.hero__cur {
font-size: 24rpx;
color: var(--mm-fg);
margin-right: 2rpx;
}
.hero__num {
font-size: 48rpx;
line-height: 1;
color: var(--mm-fg);
}
.hero__tip {
margin-top: 16rpx;
font-size: 24rpx;
line-height: 1.6;
color: var(--mm-muted-fg);
}
/* ---------- 内容卡片 ---------- */
.sec {
margin-top: 24rpx;
padding: 32rpx 24rpx;
}
.goods {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
}
.goods__name {
flex: 1;
min-width: 0;
font-size: 30rpx;
font-weight: 600;
color: var(--mm-fg);
}
/* xuchao: 键值行 —— 标签 + 取值,kv--dash 带虚线分隔 */
.kv {
display: flex;
margin-top: 24rpx;
}
.kv--dash {
border-bottom: 2rpx dashed var(--mm-border);
padding-bottom: 24rpx;
}
.kv__label {
flex: none;
width: 120rpx;
font-size: 26rpx;
color: var(--mm-muted-fg);
}
.kv__val {
flex: 1;
min-width: 0;
margin-left: 16rpx;
font-size: 26rpx;
line-height: 1.6;
color: var(--mm-fg);
}
.kv__val--accent {
color: var(--mm-accent);
font-weight: 500;
}
.kv__copy {
flex: none;
margin-left: 16rpx;
font-size: 24rpx;
color: var(--mm-accent);
}
/* ---------- 入园凭证 ---------- */
.vch {
margin-top: 28rpx;
border: 2rpx dashed var(--mm-border);
border-radius: 20rpx;
padding: 24rpx;
}
.vch__title {
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: var(--mm-fg);
}
.vch__scroll {
overflow: hidden;
white-space: nowrap;
margin-top: 24rpx;
width: 100%;
}
.vch__item {
width: 404rpx;
display: inline-block;
}
.vch__tag {
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 82, 255, 0.08);
color: var(--mm-accent);
font-size: 26rpx;
font-weight: 600;
letter-spacing: 0.1em;
border-radius: 20rpx 20rpx 0 0;
}
.vch__body {
display: flex;
flex-direction: column;
align-items: center;
background: var(--mm-muted);
border-radius: 0 0 20rpx 20rpx;
padding-bottom: 32rpx;
}
.vch__num {
width: 356rpx;
margin-top: 24rpx;
font-size: 28rpx;
font-weight: 600;
letter-spacing: 0.04em;
text-align: center;
white-space: normal;
overflow-wrap: break-word;
color: var(--mm-fg);
}
.vch__copy {
margin-top: 16rpx;
width: 88rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid rgba(0, 82, 255, 0.4);
border-radius: 999rpx;
font-size: 22rpx;
color: var(--mm-accent);
}
.vch__qr {
margin-top: 24rpx;
padding: 12rpx;
background: #fff;
border-radius: 12rpx;
}
.vch__qr image {
width: 240rpx;
height: 240rpx;
display: block;
}
/* ---------- 取消政策入口 ---------- */
.policy {
display: flex;
align-items: center;
height: 96rpx;
position: relative;
}
.policy__label {
font-size: 28rpx;
font-weight: 600;
color: var(--mm-fg);
margin-right: 12rpx;
}
.policy__val {
margin-left: 12rpx;
font-size: 26rpx;
color: var(--mm-muted-fg);
}
.policy__arrow {
position: absolute;
right: 0;
}
/* ---------- 底部操作栏 ---------- */
.safe {
height: 160rpx;
}
.bar {
position: fixed;
left: 0;
right: 0;
margin: 0 auto;
width: 750rpx;
bottom: 0;
z-index: 99;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 20rpx;
background: var(--mm-card);
border-top: 2rpx solid var(--mm-border);
padding: 20rpx 24rpx;
/* xuchao: 全面屏底部安全区 */
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}
.bar__main {
height: 72rpx;
padding: 0 40rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999rpx;
color: #fff;
font-size: 28rpx;
font-weight: 600;
box-shadow: var(--mm-shadow-accent);
transition: transform 0.15s ease;
}
.bar__main:active {
transform: scale(0.96);
}
.bar__ghost {
height: 72rpx;
padding: 0 32rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999rpx;
border: 2rpx solid var(--mm-border);
color: var(--mm-fg);
font-size: 28rpx;
transition: background 0.15s ease;
}
.bar__ghost:active {
background: var(--mm-muted);
}
/* ---------- 弹层 ---------- */
.pp {
padding: 48rpx;
display: flex;
flex-direction: column;
}
.pp__parse {
margin-top: 24rpx;
font-size: 26rpx;
line-height: 1.7;
color: var(--mm-fg);
}
.pp__btn {
margin-top: 48rpx;
}
.pp__center-title {
text-align: center;
font-size: 30rpx;
font-weight: 600;
color: var(--mm-fg);
margin-bottom: 24rpx;
}
</style>