完成12306火车票页面
|
|
@ -10,7 +10,9 @@
|
|||
<view class="nav-bar-left">
|
||||
<slot name="left">
|
||||
<view class="left-icon" @click.stop="onBack">
|
||||
<image class="nav-icon-back" src="/static/image/nav-bar/back-black.png" mode="">
|
||||
<image class="nav-icon-back"
|
||||
:src="`/static/image/nav-bar/back-${textColor == '#fff' || textColor == '#fffffff' ? 'white' : 'black'}.png`"
|
||||
mode="">
|
||||
</image>
|
||||
</view>
|
||||
</slot>
|
||||
|
|
@ -25,7 +27,9 @@
|
|||
<view class="nav-bar-right" @click.stop="onRightClick">
|
||||
<slot name="right">
|
||||
<view v-if="isRightIcon" class="right-icon">
|
||||
<image class="nav-icon-more" src="/static/image/nav-bar/more-black.png" mode="">
|
||||
<image class="nav-icon-more"
|
||||
:src="`/static/image/nav-bar/more-${textColor == '#fff' || textColor == '#fffffff' ? 'white' : 'black'}.png`"
|
||||
mode="">
|
||||
</image>
|
||||
</view>
|
||||
<view v-if="isRightButton" class="right-button">
|
||||
|
|
@ -74,18 +78,18 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import popup from '../popup/popup.vue'
|
||||
import {
|
||||
import popup from '../popup/popup.vue'
|
||||
import {
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs
|
||||
} from 'vue'
|
||||
} from 'vue'
|
||||
|
||||
const topPopup = ref()
|
||||
const topPopup = ref()
|
||||
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
|
|
@ -134,22 +138,22 @@
|
|||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['back', 'right-click', 'button-click', 'refresh'])
|
||||
// 定义事件
|
||||
const emit = defineEmits(['back', 'right-click', 'button-click', 'refresh'])
|
||||
|
||||
const data = reactive({
|
||||
const data = reactive({
|
||||
statusBarHeight: 0,
|
||||
showTipLayer: true,
|
||||
})
|
||||
})
|
||||
|
||||
let {
|
||||
let {
|
||||
showTipLayer
|
||||
} = toRefs(data)
|
||||
} = toRefs(data)
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
// 同步获取系统信息
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
data.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||||
|
|
@ -160,44 +164,44 @@
|
|||
}
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
const closeTipLayer = () => {
|
||||
const closeTipLayer = () => {
|
||||
showTipLayer.value = false
|
||||
uni.setStorageSync(props.tipLayerType, props.tipLayerType)
|
||||
emit("refresh")
|
||||
}
|
||||
}
|
||||
|
||||
const openPopup = () => {
|
||||
const openPopup = () => {
|
||||
if (props.buttonGroup.length > 0) {
|
||||
topPopup.value.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回按钮点击事件
|
||||
const onBack = () => {
|
||||
// 返回按钮点击事件
|
||||
const onBack = () => {
|
||||
emit('back')
|
||||
// 默认返回上一页
|
||||
if (props.noBack) return
|
||||
uni.navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧按钮点击事件
|
||||
const onRightClick = () => {
|
||||
// 右侧按钮点击事件
|
||||
const onRightClick = () => {
|
||||
emit('right-click')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const buttonClick = (button) => {
|
||||
const buttonClick = (button) => {
|
||||
topPopup.value.close()
|
||||
emit('button-click', button)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "/common/main.css";
|
||||
@import "/common/main.css";
|
||||
|
||||
.nav-bar-container {
|
||||
.nav-bar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed !important;
|
||||
|
|
@ -205,42 +209,42 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.status-placeholder {
|
||||
.status-placeholder {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .uni-navbar__content {
|
||||
::v-deep .uni-navbar__content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar-left {
|
||||
.nav-bar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon-back {
|
||||
.nav-icon-back {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon-more {
|
||||
.nav-icon-more {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar-title {
|
||||
.nav-bar-title {
|
||||
flex: 1;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
|
|
@ -248,15 +252,15 @@
|
|||
justify-content: center;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar-right {
|
||||
.nav-bar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.right-button {
|
||||
.right-button {
|
||||
font-size: 12px;
|
||||
border-radius: 16px;
|
||||
color: #fff;
|
||||
|
|
@ -265,23 +269,23 @@
|
|||
height: 30px;
|
||||
min-width: 60px;
|
||||
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.button-box {
|
||||
.button-box {
|
||||
width: calc(50% - 8rpx);
|
||||
text-align: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
.button {
|
||||
border: 1px solid #E4E4E4;
|
||||
border-radius: 8px;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tipLayer {
|
||||
.tipLayer {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px !important;
|
||||
height: 48px;
|
||||
|
|
@ -295,15 +299,15 @@
|
|||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.tipLayer-content {
|
||||
.tipLayer-content {
|
||||
position: relative;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
.title {
|
||||
font-weight: 450;
|
||||
font-size: 14px;
|
||||
color: #268FFF;
|
||||
|
|
@ -321,29 +325,29 @@
|
|||
font-weight: bold;
|
||||
color: #006ADD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.triangleImg {
|
||||
.triangleImg {
|
||||
width: 111px;
|
||||
height: 52px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: -23px;
|
||||
left: calc(50% - 111px);
|
||||
}
|
||||
}
|
||||
|
||||
.triangle {
|
||||
.triangle {
|
||||
position: absolute;
|
||||
top: -57px;
|
||||
left: calc(50% - 40px);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
.close {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
2
main.js
|
|
@ -27,7 +27,7 @@ export function createApp() {
|
|||
const systemInfo = uni.getStorageSync('systemInfo') || {}
|
||||
app.config.globalProperties.$system = systemInfo.platform == 'ios' ? 'iOS' : 'Android'
|
||||
app.config.globalProperties.$systemInfo = systemInfo
|
||||
uni.setStorageSync('version', '1.0.0.sp7')
|
||||
uni.setStorageSync('version', '1.0.0.sp9')
|
||||
app.config.globalProperties.$version = uni.getStorageSync('version')
|
||||
|
||||
app.use(globalMethods);
|
||||
|
|
|
|||
29
pages.json
|
|
@ -70,7 +70,8 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"root": "pages/finance-management",
|
||||
"pages": [{
|
||||
"path": "index",
|
||||
|
|
@ -111,6 +112,29 @@
|
|||
"navigationBarTitleText": "工资单",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "train-tickets/12306-tickets/12306-tickets",
|
||||
"style": {
|
||||
"navigationBarTitleText": "12306火车票",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "train-tickets/edit/edit",
|
||||
"style": {
|
||||
"navigationBarTitleText": "修改火车票信息",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "air-tickets/qunar-air-tickets/qunar-air-tickets",
|
||||
"style": {
|
||||
"navigationBarTitleText": "去哪儿",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -155,7 +179,8 @@
|
|||
"pages/bill",
|
||||
"pages/common",
|
||||
"pages/finance-management",
|
||||
"pages/ant-credit-pay"
|
||||
"pages/ant-credit-pay",
|
||||
"pages/other"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@
|
|||
</view>
|
||||
</view>
|
||||
<view class="overdue-box">
|
||||
<image class="bg-image" src="/static/image/ant-credit-pay/overdue-payment/overdue-bg.png" mode="widthFix">
|
||||
</image>
|
||||
<view class="overdue-info">
|
||||
<image class="icon" src="/static/image/ant-credit-pay/overdue-payment/warring-icon.png"></image>
|
||||
<view class="err-text">抱歉,您暂时无法使用该服务!服务机构将不定期评估您的使用资格,请保持良好信用行为并耐心等待通知</view>
|
||||
|
|
@ -258,7 +260,7 @@ const buttonGroup = [{
|
|||
openStyleDialog()
|
||||
}
|
||||
}, {
|
||||
name: "逾期后停用",
|
||||
name: "逾期停用",
|
||||
isSwitch: true,
|
||||
key: 'isOverdueDeactivate',
|
||||
click: () => {
|
||||
|
|
@ -471,7 +473,7 @@ const goBack = () => {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #F6F6F6;
|
||||
height: 100vh;
|
||||
// height: 100vh;
|
||||
// overflow: hidden;
|
||||
padding-bottom: 16rpx;
|
||||
|
||||
|
|
@ -646,26 +648,42 @@ const goBack = () => {
|
|||
}
|
||||
|
||||
.overdue-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 18rpx;
|
||||
background-image: url('/static/image/ant-credit-pay/overdue-payment/overdue-bg.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
// background-image: url('/static/image/ant-credit-pay/overdue-payment/overdue-bg.png');
|
||||
// background-size: contain;
|
||||
// background-repeat: no-repeat;
|
||||
margin-top: -1px;
|
||||
// height: 700rpx;
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.overdue-info {
|
||||
width: 654rpx;
|
||||
// height: 628rpx;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 48rpx;
|
||||
background: linear-gradient(180deg, #FFF5F4 0%, #FFFBF7 100%);
|
||||
border-radius: 20rpx 20rpx 20rpx 20rpx;
|
||||
padding: 100rpx 24rpx 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// flex-direction: column;
|
||||
// min-height: 628rpx;
|
||||
// max-height: 100%;
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
}
|
||||
|
|
@ -710,7 +728,7 @@ const goBack = () => {
|
|||
border-radius: 16rpx;
|
||||
margin: 16rpx 24rpx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 12rpx;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
</view>
|
||||
<view class="content-box" :style="{ height: windowHeight + 'px' }">
|
||||
<scroll-view scroll-y="true"
|
||||
<scroll-view scroll-y="true" class="scroll-view"
|
||||
:style="{ height: (windowHeight - statusBarHeight - 44) + 'px', marginTop: (statusBarHeight + 44) + 'px' }"
|
||||
@scroll="handleScroll">
|
||||
<view>
|
||||
|
|
@ -125,6 +125,8 @@
|
|||
|
||||
<view class="footer-box" :class="{ 'ios-padding-bottom': platform === 'ios' }">
|
||||
<text class="vision-text">版本:{{ vision }}</text>
|
||||
<text class="vision-text margin-l-6" v-if="qqgroup.enable">{{ qqgroup.text }}</text>
|
||||
<text class="vision-text" @click="copyNumber(qqgroup.number)">{{ qqgroup.number }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
|
@ -150,7 +152,9 @@ import {
|
|||
|
||||
import {
|
||||
onLoad,
|
||||
onShow
|
||||
onShow,
|
||||
onHide,
|
||||
onUnload
|
||||
} from '@dcloudio/uni-app';
|
||||
|
||||
// 内部埋点方法
|
||||
|
|
@ -222,7 +226,7 @@ const otherList = [{
|
|||
{
|
||||
icon: "/static/image/index/qita/huochepiao.png",
|
||||
name: "火车票",
|
||||
path: ""
|
||||
path: "/pages/other/train-tickets/12306-tickets/12306-tickets"
|
||||
},
|
||||
{
|
||||
icon: "/static/image/index/qita/gongzidan.png",
|
||||
|
|
@ -245,7 +249,8 @@ const data = reactive({
|
|||
videoHelpList: [],
|
||||
noticeInfo: {},
|
||||
vision: "",
|
||||
platform: '' // 添加平台信息
|
||||
platform: '', // 添加平台信息,
|
||||
qqgroup: {}
|
||||
})
|
||||
|
||||
const {
|
||||
|
|
@ -256,7 +261,8 @@ const {
|
|||
videoHelpList,
|
||||
noticeInfo,
|
||||
vision,
|
||||
platform
|
||||
platform,
|
||||
qqgroup
|
||||
} = toRefs(data);
|
||||
|
||||
/**
|
||||
|
|
@ -389,6 +395,7 @@ const setUserData = () => {
|
|||
// 启动走马灯
|
||||
startMarquee();
|
||||
data.videoHelpList = configData.config['client.uniapp.alipay.video_help'] || []
|
||||
data.qqgroup = configData.config['client.uniapp.qqgroup'] || { enable: false, number: "", text: "" }
|
||||
} else {
|
||||
data.noticeInfo = {
|
||||
text: '加载中...',
|
||||
|
|
@ -437,6 +444,30 @@ const clickNotice = () => {
|
|||
util.goPage(`/pages/common/webview/webview?url=${encodeURIComponent(url)}&title=${noticeInfo.value.title}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 终极兼容版复制函数
|
||||
*/
|
||||
const copyNumber = (number) => {
|
||||
if (!number) return;
|
||||
const text = String(number);
|
||||
uni.setClipboardData({
|
||||
data: text,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: '复制成功',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: '复制失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退出模拟器
|
||||
*/
|
||||
|
|
@ -566,12 +597,38 @@ const runMarqueeAnimation = (containerWidth, textWidth, myId) => {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止走马灯
|
||||
*/
|
||||
const stopMarquee = () => {
|
||||
// 清除定时器
|
||||
if (marqueeTimer) {
|
||||
clearTimeout(marqueeTimer);
|
||||
marqueeTimer = null;
|
||||
}
|
||||
// 增加ID使其失效
|
||||
currentMarqueeId.value++;
|
||||
}
|
||||
|
||||
onHide(() => {
|
||||
stopMarquee();
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
stopMarquee();
|
||||
})
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.container {
|
||||
background-color: #F0F4F9;
|
||||
}
|
||||
|
||||
.margin-l-6 {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.index-bg-img {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
|
@ -600,6 +657,13 @@ const runMarqueeAnimation = (containerWidth, textWidth, myId) => {
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
::v-deep .uni-scroll-view-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.status-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -846,6 +910,7 @@ const runMarqueeAnimation = (containerWidth, textWidth, myId) => {
|
|||
|
||||
.footer-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 40rpx;
|
||||
|
|
|
|||
|
|
@ -579,14 +579,14 @@ onReachBottom(() => {
|
|||
}
|
||||
|
||||
.btn-save-image {
|
||||
background-color: #07C160;
|
||||
background: linear-gradient(90deg, #187AFF 0%, #3295FC 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 18rpx 60rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 3rpx 10rpx rgba(7, 193, 96, 0.3);
|
||||
box-shadow: 0 3rpx 10rpx rgba(7, 66, 193, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
min-width: 160rpx;
|
||||
|
|
@ -594,11 +594,11 @@ onReachBottom(() => {
|
|||
|
||||
.btn-save-image:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 193, 96, 0.4);
|
||||
box-shadow: 0 4rpx 12rpx rgba(7, 72, 193, 0.4);
|
||||
}
|
||||
|
||||
.btn-save-image:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2rpx 6rpx rgba(7, 193, 96, 0.3);
|
||||
box-shadow: 0 2rpx 6rpx rgba(7, 140, 193, 0.3);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,930 @@
|
|||
<template>
|
||||
<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">
|
||||
<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">
|
||||
<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;">
|
||||
<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">
|
||||
<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
|
||||
} from 'vue';
|
||||
import {
|
||||
onLoad,
|
||||
onShow
|
||||
} from '@dcloudio/uni-app';
|
||||
|
||||
import { util } from '@/utils/common.js';
|
||||
|
||||
const buttonGroup = [{
|
||||
name: "编辑车票信息",
|
||||
click: () => {
|
||||
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;
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.date-label {
|
||||
font-size: 24rpx;
|
||||
color: #1A1A1A;
|
||||
margin: 0 10rpx;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
|
@ -0,0 +1,757 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<NavBar title="修改车票信息" bgColor="#F5F5F5" isRightButton @right-click="handleRightButtonClick"></NavBar>
|
||||
|
||||
<scroll-view scroll-y class="form-content">
|
||||
<!-- 订单信息 -->
|
||||
<view class="section-container">
|
||||
<view class="section-header" @click="toggleSection('orderInfo')">
|
||||
<text class="section-title">订单信息</text>
|
||||
<uni-icons :type="collapsed.orderInfo ? 'bottom' : 'top'" size="16" color="#666"></uni-icons>
|
||||
</view>
|
||||
<view class="card" v-show="!collapsed.orderInfo">
|
||||
<view class="form-item">
|
||||
<text class="label">订单号</text>
|
||||
<input class="input" v-model="ticketsInfo.orderInfo.orderNo" />
|
||||
</view>
|
||||
<picker mode="date" fields="day" :value="getPickerDate(ticketsInfo.orderInfo.orderTime)"
|
||||
@change="onOrderTimeChange">
|
||||
<view class="form-item">
|
||||
<text class="label">下单时间</text>
|
||||
<view class="input">{{ ticketsInfo.orderInfo.orderTime }}</view>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 车票信息 -->
|
||||
<view class="section-container">
|
||||
<view class="section-header" @click="toggleSection('ticketInfo')">
|
||||
<text class="section-title">车票信息</text>
|
||||
<uni-icons :type="collapsed.ticketInfo ? 'bottom' : 'top'" size="16" color="#666"></uni-icons>
|
||||
</view>
|
||||
<view class="card" v-show="!collapsed.ticketInfo">
|
||||
<view class="form-item">
|
||||
<text class="label">车次</text>
|
||||
<input class="input" v-model="ticketsInfo.ticketInfo.trainNo" />
|
||||
</view>
|
||||
<picker mode="date" fields="day" :value="getPickerDate(ticketsInfo.ticketInfo.date)"
|
||||
@change="onTicketDateChange">
|
||||
<view class="form-item" style="border-bottom: 1rpx solid #F5F5F5;">
|
||||
<text class="label">日期</text>
|
||||
<view class="input">{{ ticketsInfo.ticketInfo.date }}</view>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="form-item">
|
||||
<text class="label">检票口</text>
|
||||
<input class="input" v-model="ticketsInfo.ticketInfo.gate" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">出发站</text>
|
||||
<input class="input" v-model="ticketsInfo.ticketInfo.departureStation" />
|
||||
</view>
|
||||
|
||||
<picker mode="multiSelector" :range="departureTimeRange" :value="departureTimeIndex"
|
||||
@change="onDepartureTimeChange">
|
||||
<view class="form-item" style="border-bottom: 1rpx solid #F5F5F5;">
|
||||
<text class="label">出发时间</text>
|
||||
<view class="input">{{ ticketsInfo.ticketInfo.departureTime }}</view>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">到达站</text>
|
||||
<input class="input" v-model="ticketsInfo.ticketInfo.arrivalStation" />
|
||||
</view>
|
||||
|
||||
<!-- Arrival Time (Multi-Selector Picker) -->
|
||||
<picker mode="multiSelector" :range="arrivalRange" :value="arrivalIndex" @change="onArrivalChange">
|
||||
<view class="form-item">
|
||||
<text class="label">到达时间</text>
|
||||
<view class="input">{{ ticketsInfo.ticketInfo.arrivalTime }}</view>
|
||||
</view>
|
||||
</picker>
|
||||
<!-- <view class="form-item">
|
||||
<text class="label">历时</text>
|
||||
<input class="input" v-model="ticketsInfo.ticketInfo.duration" disabled />
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 乘客信息 -->
|
||||
<view class="section-container">
|
||||
<view class="section-header" @click="toggleSection('passengerList')">
|
||||
<text class="section-title">乘客信息 ({{ ticketsInfo.passengerList.length }}人)</text>
|
||||
<uni-icons :type="collapsed.passengerList ? 'bottom' : 'top'" size="16" color="#666"></uni-icons>
|
||||
</view>
|
||||
|
||||
<view v-show="!collapsed.passengerList">
|
||||
<view class="card" v-for="(passenger, index) in ticketsInfo.passengerList" :key="index">
|
||||
<view class="card-header-row">
|
||||
<text class="card-header">乘客 {{ index + 1 }}</text>
|
||||
<text class="delete-btn" @click="removePassenger(index)">删除</text>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">姓名</text>
|
||||
<input class="input" v-model="passenger.name" />
|
||||
</view>
|
||||
<picker :range="ticketType" range-key="label" @change="(e) => onTicketTypeChange(e, index)">
|
||||
<view class="form-item" style="border-bottom: 1rpx solid #F5F5F5;">
|
||||
<text class="label">票种</text>
|
||||
<view class="input">{{ passenger.type }}</view>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="form-item">
|
||||
<text class="label">席别</text>
|
||||
<input class="input" v-model="passenger.seatType" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">车厢</text>
|
||||
<input class="input" v-model="passenger.carriage" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">座位号</text>
|
||||
<input class="input" v-model="passenger.seatNo" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">票价</text>
|
||||
<input class="input" v-model="passenger.price" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">证件类型</text>
|
||||
<input class="input" v-model="passenger.idType" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">是否本人</text>
|
||||
<switch :checked="passenger.isMe" @change="(e) => passenger.isMe = e.detail.value" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="add-btn-box" @click="addPassenger">
|
||||
<uni-icons type="plusempty" size="20" color="#1677FF"></uni-icons>
|
||||
<text class="add-text">添加乘客</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 酒店广告 -->
|
||||
<view class="section-container">
|
||||
<view class="section-header" @click="toggleSection('hotelInfo')">
|
||||
<text class="section-title">酒店广告</text>
|
||||
<uni-icons :type="collapsed.hotelInfo ? 'bottom' : 'top'" size="16" color="#666"></uni-icons>
|
||||
</view>
|
||||
<view class="card" v-show="!collapsed.hotelInfo">
|
||||
<view class="form-item">
|
||||
<text class="label">城市</text>
|
||||
<input class="input" v-model="ticketsInfo.hotelInfo.city" />
|
||||
</view>
|
||||
<uni-datetime-picker type="daterange" v-model="hotelDateRange" :border="false">
|
||||
<view class="form-item">
|
||||
<text class="label">入住/离店日期</text>
|
||||
<view class="input">{{ ticketsInfo.hotelInfo.startDay }} 至 {{ ticketsInfo.hotelInfo.endDay
|
||||
}}</view>
|
||||
</view>
|
||||
</uni-datetime-picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="placeholder"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import NavBar from '@/components/nav-bar/nav-bar.vue'
|
||||
import { reactive, toRefs, onMounted, computed } from 'vue';
|
||||
|
||||
const defaultData = {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
// 车票类型
|
||||
const ticketType = [
|
||||
{
|
||||
label: '成人票',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
label: '儿童票',
|
||||
value: '2'
|
||||
},
|
||||
{
|
||||
label: '学生票',
|
||||
value: '3'
|
||||
},
|
||||
{
|
||||
label: '残疾军人票',
|
||||
value: '4'
|
||||
}
|
||||
]
|
||||
|
||||
const data = reactive({
|
||||
ticketsInfo: JSON.parse(JSON.stringify(defaultData)),
|
||||
collapsed: {
|
||||
orderInfo: true,
|
||||
ticketInfo: false, // Default open ticket info as it is most likely to be edited
|
||||
passengerList: false,
|
||||
hotelInfo: true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const { ticketsInfo, collapsed } = toRefs(data)
|
||||
|
||||
const ticketYear = computed(() => {
|
||||
const dateStr = data.ticketsInfo.ticketInfo.date;
|
||||
if (dateStr && dateStr.length >= 4) {
|
||||
return dateStr.substring(0, 4);
|
||||
}
|
||||
return new Date().getFullYear().toString();
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* 获取酒店日期范围
|
||||
*/
|
||||
const hotelDateRange = computed({
|
||||
get() {
|
||||
const year = ticketYear.value;
|
||||
const start = data.ticketsInfo.hotelInfo.startDay ? `${year}-${data.ticketsInfo.hotelInfo.startDay}` : '';
|
||||
const end = data.ticketsInfo.hotelInfo.endDay ? `${year}-${data.ticketsInfo.hotelInfo.endDay}` : '';
|
||||
if (start && end) {
|
||||
return [start, end];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
set(val) {
|
||||
if (Array.isArray(val) && val.length === 2) {
|
||||
data.ticketsInfo.hotelInfo.startDay = val[0].substring(5);
|
||||
data.ticketsInfo.hotelInfo.endDay = val[1].substring(5);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const stored = uni.getStorageSync('ticketsInfo')
|
||||
if (stored) {
|
||||
Object.assign(data.ticketsInfo, stored)
|
||||
}
|
||||
updateDuration();
|
||||
})
|
||||
/**
|
||||
* 确认
|
||||
*/
|
||||
const handleRightButtonClick = () => {
|
||||
console.log("handleRightButtonClick", data.ticketsInfo)
|
||||
const orderTimeStr = data.ticketsInfo.orderInfo.orderTime;
|
||||
const ticketDateStr = data.ticketsInfo.ticketInfo.date;
|
||||
|
||||
if (orderTimeStr && ticketDateStr) {
|
||||
if (orderTimeStr > ticketDateStr) {
|
||||
uni.showToast({
|
||||
title: '下单时间不能晚于出发日期',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.ticketsInfo.passengerList.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请至少添加一名乘客',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
const passengerList = data.ticketsInfo.passengerList.filter(item => item.isMe)
|
||||
if (passengerList.length > 1) {
|
||||
uni.showToast({
|
||||
title: '至多添加一名乘客为本人',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uni.setStorageSync('ticketsInfo', data.ticketsInfo)
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换折叠状态
|
||||
* @param {string} key
|
||||
*/
|
||||
const toggleSection = (key) => {
|
||||
data.collapsed[key] = !data.collapsed[key]
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除乘客
|
||||
* @param {number} index
|
||||
*/
|
||||
const removePassenger = (index) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该乘客吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
data.ticketsInfo.passengerList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加乘客
|
||||
*/
|
||||
const addPassenger = () => {
|
||||
const oldPassenger = data.ticketsInfo.passengerList[data.ticketsInfo.passengerList.length - 1]
|
||||
const newPassenger = {
|
||||
name: '新乘客',
|
||||
type: oldPassenger.type || '成人票',
|
||||
seatType: oldPassenger.seatType || '二等座',
|
||||
carriage: oldPassenger.carriage || '01',
|
||||
seatNo: '01A',
|
||||
idType: '中国居民身份证',
|
||||
price: oldPassenger.price || '0',
|
||||
status: '已支付',
|
||||
isMe: false
|
||||
}
|
||||
data.ticketsInfo.passengerList.push(newPassenger)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选择器日期
|
||||
* @param {string} dateStr
|
||||
* @returns {string}
|
||||
*/
|
||||
const getPickerDate = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
// Handle YYYY.MM.DD format
|
||||
if (dateStr.includes('.')) {
|
||||
return dateStr.replace(/\./g, '-');
|
||||
}
|
||||
// Handle MM-DD (prepend year) - logic from before, but mainly for Hotel.
|
||||
// Ticket date is full date YYYY.MM.DD
|
||||
if (dateStr.length <= 5) {
|
||||
const year = new Date().getFullYear();
|
||||
return `${year}-${dateStr}`;
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换下单时间
|
||||
* @param {*} e
|
||||
*/
|
||||
const onOrderTimeChange = (e) => {
|
||||
const val = e.detail.value;
|
||||
if (val) {
|
||||
data.ticketsInfo.orderInfo.orderTime = val.replace(/-/g, '.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换乘客类型
|
||||
* @param {*} e
|
||||
* @param {*} index
|
||||
*/
|
||||
const onTicketTypeChange = (e, index) => {
|
||||
const val = e.detail.value;
|
||||
data.ticketsInfo.passengerList[index].type = ticketType[val].label;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换出发日期
|
||||
* @param {*} e
|
||||
*/
|
||||
const onTicketDateChange = (e) => {
|
||||
const val = e.detail.value; // YYYY-MM-DD
|
||||
if (val) {
|
||||
// Update Date: YYYY.MM.DD
|
||||
data.ticketsInfo.ticketInfo.date = val.replace(/-/g, '.');
|
||||
|
||||
// Update WeekDay
|
||||
const dateObj = new Date(val.replace(/-/g, '/')); // Compatible
|
||||
const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
||||
data.ticketsInfo.ticketInfo.weekDay = days[dateObj.getDay()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间
|
||||
* @param {string} fullStr
|
||||
* @returns {string}
|
||||
*/
|
||||
const getTimeHHMM = (fullStr) => {
|
||||
if (!fullStr) return '';
|
||||
if (fullStr.length > 5) return fullStr.split(' ')[1];
|
||||
return fullStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出发时间
|
||||
*/
|
||||
const departureTimeHHMM = computed(() => {
|
||||
return getTimeHHMM(data.ticketsInfo.ticketInfo.departureTime);
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取出发时间选择器范围
|
||||
*/
|
||||
const departureTimeRange = computed(() => {
|
||||
const hours = Array.from({ length: 24 }, (_, i) => i < 10 ? '0' + i : '' + i);
|
||||
const minutes = Array.from({ length: 60 }, (_, i) => i < 10 ? '0' + i : '' + i);
|
||||
return [hours, minutes];
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取出发时间索引
|
||||
*/
|
||||
const departureTimeIndex = computed(() => {
|
||||
const timeStr = getTimeHHMM(data.ticketsInfo.ticketInfo.departureTime);
|
||||
if (!timeStr) return [0, 0];
|
||||
const [h, m] = timeStr.split(':');
|
||||
const hours = departureTimeRange.value[0];
|
||||
const minutes = departureTimeRange.value[1];
|
||||
let hIdx = hours.indexOf(h);
|
||||
if (hIdx === -1) hIdx = 0;
|
||||
let mIdx = minutes.indexOf(m);
|
||||
if (mIdx === -1) mIdx = 0;
|
||||
return [hIdx, mIdx];
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取到达时间日期范围
|
||||
*/
|
||||
const arrivalRange = computed(() => {
|
||||
const dateStr = data.ticketsInfo.ticketInfo.date; // YYYY.MM.DD
|
||||
const dates = [];
|
||||
if (dateStr) {
|
||||
const baseDate = new Date(dateStr.replace(/\./g, '-').replace(/-/g, '/') + ' 00:00:00');
|
||||
for (let i = 0; i < 4; i++) { // Ticket Date + 3 days
|
||||
const d = new Date(baseDate);
|
||||
d.setDate(d.getDate() + i);
|
||||
const pad = n => n < 10 ? '0' + n : n;
|
||||
dates.push(`${pad(d.getMonth() + 1)}-${pad(d.getDate())}`);
|
||||
}
|
||||
} else {
|
||||
dates.push('MM-DD');
|
||||
}
|
||||
const hours = Array.from({ length: 24 }, (_, i) => i < 10 ? '0' + i : '' + i);
|
||||
const minutes = Array.from({ length: 60 }, (_, i) => i < 10 ? '0' + i : '' + i);
|
||||
return [dates, hours, minutes];
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取到达时间索引
|
||||
*/
|
||||
const arrivalIndex = computed(() => {
|
||||
const arrTime = data.ticketsInfo.ticketInfo.arrivalTime;
|
||||
if (!arrTime || arrTime.length < 5) return [0, 0, 0];
|
||||
// Might be HH:mm or MM-DD HH:mm
|
||||
const parts = arrTime.split(' ');
|
||||
let datePart = parts[0];
|
||||
let timePart = parts[1];
|
||||
|
||||
// Handle case where only HH:mm (old data)
|
||||
if (!timePart && datePart.includes(':')) {
|
||||
timePart = datePart;
|
||||
datePart = arrivalRange.value[0][0];
|
||||
}
|
||||
|
||||
if (!timePart) return [0, 0, 0];
|
||||
const [h, m] = timePart.split(':');
|
||||
|
||||
const dates = arrivalRange.value[0];
|
||||
const hours = arrivalRange.value[1];
|
||||
const minutes = arrivalRange.value[2];
|
||||
|
||||
let dateIdx = dates.indexOf(datePart);
|
||||
if (dateIdx === -1) dateIdx = 0;
|
||||
|
||||
let hIdx = hours.indexOf(h);
|
||||
if (hIdx === -1) hIdx = 0;
|
||||
|
||||
let mIdx = minutes.indexOf(m);
|
||||
if (mIdx === -1) mIdx = 0;
|
||||
|
||||
return [dateIdx, hIdx, mIdx];
|
||||
})
|
||||
|
||||
/**
|
||||
* 切换到达时间
|
||||
* @param {*} e
|
||||
*/
|
||||
const onArrivalChange = (e) => {
|
||||
const idxs = e.detail.value;
|
||||
const range = arrivalRange.value;
|
||||
if (!range[0][idxs[0]]) return;
|
||||
|
||||
const dateStr = range[0][idxs[0]];
|
||||
const hStr = range[1][idxs[1]];
|
||||
const mStr = range[2][idxs[2]];
|
||||
|
||||
const newArrTime = `${dateStr} ${hStr}:${mStr}`;
|
||||
|
||||
// Validate: >= Departure
|
||||
const getTs = (str) => {
|
||||
if (!str || str.length <= 5) return 0;
|
||||
const year = new Date().getFullYear();
|
||||
// Robust format: YYYY/MM/DD HH:mm:00
|
||||
return new Date(`${year}/${str.replace(/-/g, '/')}:00`).getTime();
|
||||
}
|
||||
|
||||
const startTs = getTs(data.ticketsInfo.ticketInfo.departureTime);
|
||||
const endTs = getTs(newArrTime);
|
||||
|
||||
if (startTs > 0 && endTs < startTs) {
|
||||
uni.showToast({
|
||||
title: '到达时间不能早于出发时间',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
data.ticketsInfo.ticketInfo.arrivalTime = newArrTime;
|
||||
updateDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换出发时间
|
||||
* @param e
|
||||
*/
|
||||
/**
|
||||
* 切换出发时间
|
||||
* @param e
|
||||
*/
|
||||
const onDepartureTimeChange = (e) => {
|
||||
let val = e.detail.value; // Array [hIdx, mIdx]
|
||||
|
||||
// Convert array to HH:mm string
|
||||
if (Array.isArray(val)) {
|
||||
const h = departureTimeRange.value[0][val[0]];
|
||||
const m = departureTimeRange.value[1][val[1]];
|
||||
val = `${h}:${m}`;
|
||||
}
|
||||
|
||||
if (!val) return;
|
||||
|
||||
// Construct New Departure Timestamp
|
||||
// Departure uses Ticket Date
|
||||
const ticketDate = data.ticketsInfo.ticketInfo.date; // YYYY.MM.DD
|
||||
if (!ticketDate) return;
|
||||
|
||||
// Assuming format YYYY.MM.DD
|
||||
const depDateStr = ticketDate.replace(/\./g, '/'); // YYYY/MM/DD
|
||||
const newDepTs = new Date(`${depDateStr} ${val}:00`).getTime();
|
||||
|
||||
// Get Arrival Timestamp
|
||||
const arrStr = data.ticketsInfo.ticketInfo.arrivalTime; // MM-DD HH:mm
|
||||
if (arrStr && arrStr.length > 5) {
|
||||
// Use Ticket Year as base.
|
||||
const ticketYear = ticketDate.split('.')[0];
|
||||
const arrDatePart = arrStr.split(' ')[0]; // MM-DD
|
||||
const arrTimePart = arrStr.split(' ')[1]; // HH:mm
|
||||
|
||||
// Handle Cross Year if Ticket Date is Dec and Arrival is Jan
|
||||
let arrYear = parseInt(ticketYear);
|
||||
const ticketMonth = parseInt(ticketDate.split('.')[1]);
|
||||
const arrMonth = parseInt(arrDatePart.split('-')[0]);
|
||||
|
||||
if (ticketMonth === 12 && arrMonth === 1) {
|
||||
arrYear++;
|
||||
}
|
||||
|
||||
const arrTs = new Date(`${arrYear}/${arrDatePart.replace(/-/g, '/')} ${arrTimePart}:00`).getTime();
|
||||
|
||||
if (newDepTs > arrTs) {
|
||||
uni.showToast({
|
||||
title: '出发时间不能晚于到达时间',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const dateParts = ticketDate.split('.');
|
||||
let mmdd = `${dateParts[1]}-${dateParts[2]}`;
|
||||
data.ticketsInfo.ticketInfo.departureTime = `${mmdd} ${val}`;
|
||||
updateDuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新时长
|
||||
*/
|
||||
const updateDuration = () => {
|
||||
// Helper to parse MM-DD HH:mm to timestamp (using current year)
|
||||
// Safer to use "/" for cross-platform compatibility
|
||||
const getTs = (str) => {
|
||||
if (!str || str.length <= 5) return 0;
|
||||
const year = new Date().getFullYear();
|
||||
// Format: "YYYY/MM/DD HH:mm:00"
|
||||
return new Date(`${year}/${str.replace(/-/g, '/')}:00`).getTime();
|
||||
}
|
||||
|
||||
const start = getTs(data.ticketsInfo.ticketInfo.departureTime);
|
||||
const end = getTs(data.ticketsInfo.ticketInfo.arrivalTime);
|
||||
|
||||
if (start && end && end >= start) {
|
||||
const diffMs = end - start;
|
||||
const diffHrs = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffMins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||
data.ticketsInfo.ticketInfo.duration = `${diffHrs}时${diffMins}分`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "@/common/main.css";
|
||||
|
||||
page {
|
||||
background-color: #F8F8F8;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.section-container {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 12rpx 16rpx;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.card-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 0 12rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
font-size: 26rpx;
|
||||
color: #FF4D4F;
|
||||
padding: 4rpx 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
width: 240rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn-box {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 2rpx dashed #1677FF;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.add-text {
|
||||
color: #1677FF;
|
||||
font-size: 30rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
height: 60rpx;
|
||||
}
|
||||
</style>
|
||||
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 355 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.1 KiB |