alipay-emulator/pages/other/train-tickets/12306-tickets/12306-tickets.vue

958 lines
20 KiB
Vue
Raw Permalink 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="$isVip()">
<watermark dark="light" />
<liu-drag-button :canDocking="false" @clickBtn="$goRechargePage('watermark')">
<c-lottie ref="cLottieRef" :src='$watermark()' width="94px" height='74px' :loop="true"></c-lottie>
</liu-drag-button>
</view>
<view class="container">
<!-- 顶部背景 -->
<view class="header-bg"></view>
<!-- 自定义导航栏 -->
<NavBar class="nav-bar" isBack bgColor="#3C99FB" textColor="#fff" isRightIcon tipLayerType="train-tickets-tip"
isTipLayer tipLayerText="修改车票信息" :buttonGroup="buttonGroup" @button-click="util.clickTitlePopupButton">
<template v-slot:center>
<text class="center-text">订单详情</text>
</template>
</NavBar>
<!-- 页面内容 -->
<scroll-view scroll-y class="content-scroll">
<!-- 订单信息 -->
<view class="order-info-box">
<view class="order-number-row">
<text class="order-label">订单号:{{ ticketsInfo.orderInfo.orderNo }}</text>
<image class="copy-btn" @click="copyOrderNo"
src="/static/image/other/train-tickets/12306-tickets/copy-button.png"></image>
</view>
<text class="order-time">下单时间:{{ ticketsInfo.orderInfo.orderTime }}</text>
</view>
<view class="main-card">
<!-- 车票卡片 -->
<view class="ticket-card">
<view class="ticket-main-info">
<view class="station-time-box" @click="goEdit">
<text class="time-text">{{ departureTimeDisplay }}</text>
<view class="station-text">{{ ticketsInfo.ticketInfo.departureStation }}
<uni-icons type="forward" size="10" color="#767676"></uni-icons>
</view>
</view>
<view class="train-info-box" @click="goEdit">
<view class="train-no">
<text style="line-height: 26rpx;">{{ ticketsInfo.ticketInfo.trainNo }}</text>
<uni-icons type="arrowright" size="10" color="#979797"></uni-icons>
</view>
<view class="stopover-box">
<image style="width: 100%;height: 100%;"
src="/static/image/other/train-tickets/12306-tickets/stopover-bg.png"></image>
</view>
<text class="duration-text">历时{{ ticketsInfo.ticketInfo.duration }}</text>
</view>
<view class="station-time-box" style="align-items: flex-start;" @click="goEdit">
<view class="time-row">
<text class="time-text">{{ arrivalTimeDisplay }}</text>
<text v-if="dayDiff > 0" class="day-badge">+{{ dayDiff }}</text>
</view>
<view class="station-text">{{ ticketsInfo.ticketInfo.arrivalStation }}
<uni-icons type="forward" size="10" color="#767676"></uni-icons>
</view>
</view>
</view>
<view class="date-row">
<text class="date-text">发车时间:{{ ticketsInfo.ticketInfo.date }} {{ ticketsInfo.ticketInfo.weekDay
}}</text>
<text class="valid-text">车票当日当次有效</text>
</view>
<view class="gate-info-box">
<text>检票口{{ ticketsInfo.ticketInfo.gate }}</text>
<text class="gate-text">(如有变更,请以现场公告为准)</text>
</view>
</view>
<!-- 操作栏 -->
<view class="action-bar">
<view class="action-item" @click="handleAction('变更到站')">
<text class="action-text">变更到站</text>
</view>
<view class="divider"></view>
<view class="action-item" @click="handleAction('改签')">
<text class="action-text">改签</text>
</view>
<view class="divider"></view>
<view class="action-item" @click="handleAction('退票')">
<text class="action-text">退票</text>
</view>
</view>
<!-- 乘客卡片 -->
<view class="passenger-card-box" v-for="(passenger, index) in ticketsInfo.passengerList" :key="index">
<view class="passenger-card" @click="goEdit">
<view class="passenger-header">
<view class="name-box">
<text class="passenger-name">{{ passenger.name }}</text>
<text class="tag-text">{{ passenger.type }}</text>
</view>
<view class="seat-info">
<text v-if="computeSeatNo(passenger.seatNo)" class="tag-text-grey">{{
computeSeatNo(passenger.seatNo) }}</text>
<text class="seat-text">{{ passenger.seatType }} {{ passenger.carriage }}车 {{
passenger.seatNo
}}号</text>
<uni-icons style="margin-left: 4rpx;margin-top: 4rpx;" type="forward" size="10"
color="#979797"></uni-icons>
</view>
</view>
<view class="passenger-details">
<text class="id-card-text">{{ passenger.idType }}</text>
<text class="price-text">¥{{ passenger.price }}</text>
</view>
<view class="status-row">
<text class="status-text">已支付</text>
<text class="refund-rule-text">退改说明</text>
</view>
</view>
<view class="passenger-actions">
<view class="svc-btn outline">
<text class="svc-text">订餐</text>
</view>
<view class="svc-btn outline">
<text class="svc-text">购乘意险</text>
</view>
<view class="svc-btn primary" v-if="passenger.isMe">
<text class="svc-text-white">
<image style="width: 20rpx;height: 20rpx;"
src="/static/image/other/train-tickets/12306-tickets/qr-code.png"></image>
<text style="margin-left: 8rpx;">二维码验票</text>
</text>
</view>
</view>
</view>
</view>
<view style="margin: 0 16rpx;">
<!-- 分享区 -->
<view class="share-section">
<text class="share-tips">美好旅途,快与朋友一起分享吧~</text>
<image style="width: 158rpx;height: 50rpx;"
src="/static/image/other/train-tickets/12306-tickets/share.png">
</image>
</view>
<!-- 提示 -->
<view class="notice-row">
<uni-icons type="notification" size="14" color="#AAAAAA"></uni-icons>
<text class="notice-text">订单信息查询有效期限为30日</text>
</view>
<!-- 广告Banner -->
<view class="ad-banner">
<image class="bg-image" src="/static/image/other/train-tickets/12306-tickets/hotel-bg.png"
mode="widthFix">
</image>
<image class="bg-image" style="opacity: 0;position: relative;"
src="/static/image/other/train-tickets/12306-tickets/hotel-bg.png" mode="widthFix">
</image>
<view class="hotel-ad">
<view class="hotel-search-row">
<view class="city-box">
<uni-icons class="search-icon" style="margin-right: 8rpx;height: 16px;" type="search"
size="16" color="#C8C8C8"></uni-icons>
<text class="city-text">{{ ticketsInfo.hotelInfo.city }}</text>
</view>
<view class="date-range">
<text class="date-val">{{ ticketsInfo.hotelInfo.startDay }}</text>
<text class="date-label">入住</text>
<view class="night-count">
<text class="night-num">{{ data.nightCount }}晚</text>
</view>
<text class="date-val">{{ ticketsInfo.hotelInfo.endDay }}</text>
<text class="date-label">离店</text>
</view>
</view>
</view>
</view>
<!-- 更多服务 -->
<view class="bottom-image">
<image style="width: 100%;height: 100%;"
src="/static/image/other/train-tickets/12306-tickets/bottom-bg.png" mode="widthFix">
</image>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import NavBar from '@/components/nav-bar/nav-bar.vue';
import {
ref,
reactive,
toRefs,
computed,
getCurrentInstance
} from 'vue';
import {
onLoad,
onShow
} from '@dcloudio/uni-app';
import { util } from '@/utils/common.js';
const {
appContext,
proxy
} = getCurrentInstance();
const buttonGroup = [{
name: "编辑车票信息",
click: () => {
goEdit()
}
}]
function goEdit() {
util.goPage('/pages/other/train-tickets/edit/edit')
}
const ticketType = [
{
label: '成人票',
value: '1'
},
{
label: '儿童票',
value: '2'
},
{
label: '学生票',
value: '3'
},
{
label: '残疾军人票',
value: '4'
}
]
const statusBarHeight = ref(20);
const data = reactive({
ticketsInfo: {
"orderInfo": {
"orderNo": "EJ66223536",
"orderTime": "2026.01.01"
},
"ticketInfo": {
"departureTime": "01-01 09:19",
"departureStation": "北京南",
"arrivalTime": "01-01 14:04",
"arrivalStation": "上海虹桥",
"trainNo": "G905",
"duration": "4时45分",
"date": "2026.01.01",
"weekDay": "星期四",
"gate": "6B"
},
"passengerList": [
{
"name": "张元英",
"type": "成人票",
"seatType": "商务座",
"carriage": "01",
"seatNo": "03C",
"idType": "外国护照KR",
"price": "2110",
"status": "已支付",
"isMe": true
}
],
"hotelInfo": {
"city": "上海",
"startDay": "01-01",
"endDay": "01-02"
}
},
nightCount: 1
})
let { ticketsInfo } = toRefs(data)
// Computed Helpers for Display
const getDisplayTime = (str) => {
if (!str) return '';
// If format is "MM-DD HH:mm", split and take time
if (str.length > 5) {
return str.split(' ')[1];
}
return str;
}
const departureTimeDisplay = computed(() => {
return getDisplayTime(data.ticketsInfo.ticketInfo.departureTime);
})
const arrivalTimeDisplay = computed(() => {
return getDisplayTime(data.ticketsInfo.ticketInfo.arrivalTime);
})
const dayDiff = computed(() => {
const dep = data.ticketsInfo.ticketInfo.departureTime;
const arr = data.ticketsInfo.ticketInfo.arrivalTime;
if (!dep || !arr) return 0;
// Format is "MM-DD HH:mm"
const depDate = dep.split(' ')[0];
const arrDate = arr.split(' ')[0];
if (depDate === arrDate) return 0;
const year = new Date().getFullYear();
// Use "/" for compatibility
const d1 = new Date(`${year}/${depDate.replace(/-/g, '/')} 00:00:00`).getTime();
const d2 = new Date(`${year}/${arrDate.replace(/-/g, '/')} 00:00:00`).getTime();
const diffTime = d2 - d1;
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays > 0 ? diffDays : 0;
})
const computeSeatNo = (seatNo) => {
if (seatNo.includes('C') || seatNo.includes('F')) {
return '靠窗';
} else if (seatNo.includes('A') || seatNo.includes('D')) {
return '靠过道';
}
return '';
}
/**
* 计算两个日期之间的天数差
*/
const calculateNightCount = () => {
const currentYear = new Date().getFullYear();
// 补全年份格式YYYY-MM-DD
const startStr = `${currentYear}-${data.ticketsInfo.hotelInfo.startDay}`;
const endStr = `${currentYear}-${data.ticketsInfo.hotelInfo.endDay}`;
// 解析时间戳 (兼容 iOS用 / 替换 -)
const startTime = new Date(startStr.replace(/-/g, '/')).getTime();
const endTime = new Date(endStr.replace(/-/g, '/')).getTime();
if (!isNaN(startTime) && !isNaN(endTime)) {
const diff = endTime - startTime;
// 向上取整,不足一天按一天算
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
data.nightCount = days > 0 ? days : 1;
} else {
data.nightCount = 1;
}
}
onLoad(() => {
const sys = uni.getSystemInfoSync();
statusBarHeight.value = sys.statusBarHeight || 20;
// 进入高铁票页面埋点
proxy.$apiUserEvent('all', {
type: 'event',
key: 'train_ticket',
prefix: '.uni.other.',
value: "高铁票"
})
});
onShow(() => {
if (uni.getStorageSync('ticketsInfo')) {
Object.assign(data.ticketsInfo, uni.getStorageSync('ticketsInfo'))
calculateNightCount()
}
// #ifdef APP-PLUS
util.setAndroidSystemBarColor('#ffffff')
setTimeout(() => {
plus.navigator.setStatusBarStyle("light");
}, 500)
// #endif
const stored = uni.getStorageSync('ticketsInfo')
if (stored) {
Object.assign(data.ticketsInfo, stored)
calculateNightCount()
}
});
const goBack = () => {
uni.navigateBack();
};
const copyOrderNo = () => {
uni.setClipboardData({
data: orderInfo.value.orderNo,
success: () => {
uni.showToast({
title: '复制成功',
icon: 'none'
});
}
});
};
const handleAction = (action) => {
uni.showToast({
title: `点击了${action}`,
icon: 'none'
})
}
</script>
<style lang="less" scoped>
.container {
width: 100%;
min-height: 100vh;
background-color: #ffffff;
position: relative;
}
/* 顶部渐变背景 */
.header-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 540rpx;
background: linear-gradient(180deg, #3C99FB 0%, #3C99FB 45.73%, rgba(60, 153, 251, 0.6007) 60.23%, rgba(122, 122, 122, 0) 98.34%);
z-index: 0;
}
/* 导航栏 */
.nav-bar {
.center-text {
font-size: 40rpx;
font-weight: 400;
color: #fff;
}
}
::v-deep.right-icon {
margin-right: 50rpx;
}
/* 内容区域 */
.content-scroll {
position: relative;
z-index: 1;
// padding: 0 16rpx;
box-sizing: border-box;
width: 100%;
}
/* 订单源信息 */
.order-info-box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin: 0 16rpx;
margin-bottom: 12rpx;
margin-top: 14rpx;
}
.order-number-row {
display: flex;
flex-direction: row;
align-items: center;
}
.order-label {
font-size: 26rpx;
color: #FFFFFF;
margin-right: 5px;
line-height: 52rpx;
}
.copy-btn {
height: 28rpx;
width: 50rpx;
}
.order-time {
font-size: 24rpx;
color: #FFFFFF;
}
.main-card {
margin: 0 16rpx;
position: relative;
/* 四周均匀扩散阴影 */
box-shadow: 0rpx 0rpx 32rpx 4rpx rgba(0, 0, 0, 0.1);
background-color: #fff;
border-radius: 16rpx;
}
/* 票卡片 */
.ticket-card {
position: relative;
background-color: #FFFFFF;
border-radius: 12rpx;
padding: 30rpx 28rpx;
box-shadow: 0rpx 8rpx 30rpx 0rpx rgba(0, 0, 0, 0.05);
z-index: 9;
}
.ticket-main-info {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30rpx;
}
.station-time-box {
display: flex;
flex-direction: column;
}
.time-text {
font-size: 48rpx;
font-weight: 500;
color: #1a1a1a;
line-height: 52rpx;
margin: 0 10rpx;
}
.station-text {
font-size: 30rpx;
color: #1a1a1a;
margin: 12rpx 12rpx 0;
display: flex;
align-items: center;
}
.train-info-box {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 8rpx;
}
.train-no {
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #1A1A1A;
}
.stopover-box {
width: 90rpx;
height: 24rpx;
position: relative;
margin-bottom: 8rpx;
margin-top: 14rpx;
}
.stopover-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 18rpx;
text-align: center;
white-space: nowrap;
}
.duration-line {
width: 60px;
height: 1px;
background-color: #E0E0E0;
margin: 2px 0;
}
.duration-text {
font-size: 24rpx;
color: #999999;
}
.date-row {
display: flex;
flex-direction: row;
justify-content: space-between;
font-size: 26rpx;
color: #767676;
padding: 0 12rpx;
line-height: 30rpx;
margin-bottom: 22rpx;
}
.gate-info-box {
background-color: #FFF9F0;
border-radius: 12rpx;
padding: 12rpx 16rpx 14rpx;
font-size: 26rpx;
color: #664A37;
display: flex;
flex-direction: row;
align-items: flex-end;
line-height: 30rpx;
}
.gate-text {
font-size: 20rpx;
color: #767676;
}
.action-bar {
position: relative;
margin-top: -24rpx;
background-color: #fff;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
border-top: 1px solid #F0F0F0;
padding-top: 48rpx;
padding-bottom: 30rpx;
box-shadow: 0rpx 8rpx 30rpx 0rpx rgba(0, 0, 0, 0.05);
/* REMOVED box-shadow from child */
z-index: 2;
}
.action-item {
flex: 1;
text-align: center;
}
.action-text {
font-size: 30rpx;
color: #4C9BEC;
font-weight: 500;
}
.divider {
width: 1px;
height: 16px;
background-color: #EFEFEF;
}
/* 乘客卡片 */
.passenger-card-box {
position: relative;
border-bottom: 1px solid #F0F0F0;
z-index: 1;
}
.passenger-card-box:last-child {
border-bottom: none;
}
.passenger-card {
position: relative;
background-color: #FFFFFF;
padding: 32rpx 42rpx 38rpx;
box-shadow: 0rpx 8rpx 50rpx 0rpx rgba(0, 0, 0, 0.04);
}
.passenger-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 22rpx;
}
.name-box {
display: flex;
flex-direction: row;
align-items: center;
}
.passenger-name {
font-size: 36rpx;
font-weight: 500;
color: #3D3D3D;
line-height: 30rpx;
margin-right: 8px;
}
.tag-text {
border-radius: 4rpx;
padding: 4rpx 2rpx;
border: 1rpx solid #B2CDE8;
font-size: 20rpx;
line-height: 20rpx;
color: #4A94D2;
}
.seat-info {
display: flex;
align-items: center;
.tag-text-grey {
border-radius: 4rpx;
padding: 2rpx 4rpx;
border: 1rpx solid #EDEDED;
font-size: 18rpx;
line-height: 18rpx;
color: #979797;
margin-right: 8rpx;
}
.seat-text {
font-size: 13px;
color: #1A1A1A;
font-weight: 400;
}
}
.passenger-details {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 28rpx;
.id-card-text {
font-size: 28rpx;
color: #979797;
line-height: 30rpx;
}
.price-text {
font-size: 28rpx;
color: #F28127;
line-height: 28rpx;
}
}
.status-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.status-text {
font-size: 28rpx;
color: #767676;
line-height: 28rpx;
}
.refund-rule-text {
font-size: 28rpx;
color: #4A94D2;
text-decoration: underline;
line-height: 28rpx;
}
}
.passenger-actions {
padding: 14rpx 28rpx;
padding-right: 18rpx;
background-color: #FFFFFF;
display: flex;
flex-direction: row;
justify-content: flex-end;
// gap: 20rpx;
.svc-btn {
height: 54rpx;
padding: 0 16rpx;
border-radius: 4rpx;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 10rpx;
}
.svc-btn.outline {
border: 1px solid #EDEDED;
background-color: #FFFFFF;
}
.svc-btn.primary {
background-color: #3C99FB;
border: 1px solid #C3EFFF;
}
.svc-text {
color: #767676;
}
.svc-text-white {
color: #FFFFFF;
}
}
/* 分享区 */
.share-section {
position: relative;
margin-top: 20rpx;
background-color: #FFFFFF;
border-radius: 8rpx;
padding: 20rpx 30rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 28rpx;
box-shadow: 0rpx 0rpx 4rpx 0rpx rgba(0, 0, 0, 0.05);
.share-tips {
font-size: 24rpx;
color: #3D3D3D;
}
}
/* 提示 */
.notice-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
line-height: 24rpx;
margin-bottom: 28rpx;
}
.notice-text {
font-size: 24rpx;
color: #AAAAAA;
margin-left: 4rpx;
}
/* 广告Banner */
.ad-banner {
position: relative;
width: 100%;
.bg-image {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
}
.hotel-ad {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
padding: 0 20rpx 12rpx;
margin-bottom: 12rpx;
}
.hotel-search-row {
background-color: #FFFFFF;
border-radius: 20px;
padding: 14rpx 44rpx;
display: flex;
flex-direction: row;
align-items: center;
height: 60rpx;
.city-box {
display: flex;
align-items: center;
.search-icon {
margin-right: 18rpx;
height: 16px;
display: flex;
align-items: center;
}
}
.city-text {
font-size: 30rpx;
color: #4495F0;
margin-right: 44rpx;
font-weight: 500;
white-space: nowrap;
}
.date-range {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
padding: 0 32rpx;
border-left: 1rpx solid #D8D8D8;
padding-right: 0;
}
.date-val {
font-size: 30rpx;
font-weight: 500;
color: #4495F0;
border-bottom: 1px solid #4495F0;
white-space: nowrap;
}
.date-label {
font-size: 24rpx;
color: #1A1A1A;
margin: 0 10rpx;
white-space: nowrap;
}
.night-count {
font-size: 24rpx;
border: 1px solid #E0E0E0;
border-radius: 16rpx;
height: 34rpx;
width: 80rpx;
line-height: 34rpx;
text-align: center;
margin: 0 10rpx;
margin-right: 18rpx;
}
.night-num {
font-size: 24rpx;
line-height: 24rpx;
color: #333333;
}
}
}
.time-row {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.day-badge {
font-size: 22rpx;
color: rgb(60, 153, 251);
margin-left: 2rpx;
line-height: 30rpx;
position: relative;
top: 0rpx;
}
</style>