Files
h5-meike/pages/ticket/scenicdetail.vue
T
xuchaoandClaude Opus 4.8 4190565eb7 feat: 质惠游 H5 一期 —— 门票模块全链路(微信公众号版)
从 xghmall(云闪付版湘工游)迁移民票模块,改造为微信公众号 H5:

- 技术栈沿用 uni-app + Vue2 + uView(含定制版日历组件)
- 登录/支付独立成层(common/auth.js、common/pay.js):
  一期假登录(测试手机号)+ 模拟支付,后端微信接口就绪后只换这两处
- 登录页重写:短信验证码 + 公众号网页授权(code 换用户信息)预留
- 门票列表页为入口页(进页必须登录),加订单悬浮按钮
- 支付成功页接通:查看订单/继续逛逛 + 订单状态轮询
- 详情页定位导航改用 uni.openLocation + 高德网页兜底
- 出行人页:身份证 18 位格式校验,删除死的删除按钮,修正证件类型回显笔误
- 清理云闪付 SDK、支付宝授权、死页面引用等残留

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 00:43:59 +08:00

399 lines
12 KiB
Vue

<template>
<view v-if="obj">
<view class="wrap">
<u-swiper :list="list" height='750' mode='number' indicator-pos='bottomRight'
marginBottom='30rpx'></u-swiper>
</view>
<view style="position: absolute;top: 700rpx;">
<view class="title">
<view class="t_name">{{obj.tickets.name}} <text style="color: #FF8D28;">{{obj.shop.level}}</text></view>
<view class="t_info">{{obj.tickets.remark1}}</view>
<view class="t_opentime">
<view style="display: flex;gap: 8rpx;color: #FF8D28;">
<image src="https://aliyunoss.hn12301.net/xgh/ticket_opentime.png"
style="width: 36rpx;height: 36rpx;"></image>开园时间
</view>
<text style="color: #8E8E93;font-size: 24rpx;">{{obj.tickets.openDate}}</text>
</view>
<view class="t_address">
<view style="width: 548rpx;">{{obj.shop.address}}</view>
<image src="https://aliyunoss.hn12301.net/xgh/ticket_dh.png" @click="handleOpen()"></image>
<image style="margin-left: 24rpx;" src="https://aliyunoss.hn12301.net/xgh/ticket_dhua.png"
@click="playPhone()"></image>
</view>
<!-- <view style="color: #FF0000;font-size: 32rpx;margin-top: 24rpx;"><text
style="font-size: 20rpx;">¥</text>{{(obj.minPrice/100).toFixed(0)}} ~ {{(obj.maxPrice/100).toFixed(0)}}</view>
<view style="display: flex;margin-top: 24rpx;">
<text class="tag" v-for="item in obj.tickets.tags.split(',')" :key="item">{{item}}</text>
</view> -->
</view>
<view class="ticket">
<!-- <text style="color: #000;font-size: 36rpx;line-height: 88rpx;font-weight: 700;">成人票</text> -->
<view class="ticket_item" style="margin-top: 0rpx;" v-for="(item,index) in obj.skuList" :key="index">
<view style="width: 338rpx;display: flex;flex-direction: column;">
<text
style="color: #000;font-size: 28rpx;font-weight: 700;">【{{getSkuType(item.typeId)}}】{{item.skuName}}</text>
<view
style="color: #7C7C7E;display: flex;align-items: center;margin-top: 16rpx;font-size: 24rpx;">
{{item.bookTimeLimit}}之前{{item.bookType?'可订当天':'可订明日'}}<text
style="color: #DFE0E2;font-size: 48rpx;padding: 0rpx 12rpx;">·</text>
</view>
<view style="color: #0066F6;margin-top: 16rpx;" @click="seeSkuDetail(item.id)">预订须知 ></view>
</view>
<view
style="width: 292rpx;display: flex;flex-direction: column;margin-left: 24rpx;align-items: flex-end;">
<view
style="color: #FF0000;font-size: 28rpx;margin-top: 0rpx;display: flex;justify-content: flex-end;">
原售价<text
style="font-size: 28rpx;font-weight: normal;text-decoration: line-through;">¥{{item.calendar.listPrice?(item.calendar.listPrice/100).toFixed(2):0}}</text>
</view>
<view style="color: #FF0000;font-size: 36rpx;font-weight: bold;line-height: 88rpx;">
<text
style="font-size: 20rpx;font-weight: normal;">工会会员补贴价¥</text>{{item.calendar.salePrice?(item.calendar.salePrice/100).toFixed(2):0}}
</view>
<view class="btn" @tap="toOrderSure(item.id)">购买</view>
</view>
</view>
</view>
<view class="ticket">
<text style="color: #000;font-size: 36rpx;line-height: 88rpx;font-weight: 700;">产品介绍</text>
<u-parse :html="obj.tickets.introduction"></u-parse>
</view>
</view>
<u-popup v-model="show" border-radius='20' mode='bottom' height='1000'>
<view style="padding:0rpx 48rpx;" v-if="skuObj">
<view
style="width: 654rpx;height: 142rpx;line-height: 142rpx;text-align: center;color: #000;font-size: 32rpx;font-weight: bold;border-bottom: 2rpx #E1E1E1 dashed;">
{{skuObj.sku.skuName}}
</view>
<view style="width: 654rpx;padding: 48rpx 0rpx">
<text style="color: #000;font-size: 32rpx;font-weight: bold;">预订规则</text>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">预订时间</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">
{{skuObj.sku.bookTimeLimit}}之前{{skuObj.sku.bookType?'可订当天':'可订明日'}}
</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">生效时间</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">出票后立即使用</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">有效期</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">指定出行日期1天内有效</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">适用人群</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">{{skuObj.sku.typeInfo}}</view>
</view>
</view>
<view style="width: 654rpx;padding: 48rpx 0rpx">
<text style="color: #000;font-size: 32rpx;font-weight: bold;">费用说明</text>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">费用包含</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">{{skuObj.sku.contentWith}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">费用不含</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">{{skuObj.sku.contentWithout}}</view>
</view>
</view>
<view style="width: 654rpx;padding: 48rpx 0rpx">
<text style="color: #000;font-size: 32rpx;font-weight: bold;">使用说明</text>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">入园方式</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">
{{skuObj.sku.enterChangeFlag?'凭('+getTyepStr(skuObj.sku.enterType)+')换票入园':'无需换票'}}
</view>
</view>
<view style="display: flex;margin-top: 24rpx;" v-if="skuObj.sku.enterChangeFlag">
<view style="color: #808080;width: 120rpx;">换票时间</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">
{{skuObj.sku.changeStart}}~{{skuObj.sku.changeEnd}}
</view>
</view>
<view style="display: flex;margin-top: 24rpx;" v-if="skuObj.sku.enterChangeFlag">
<view style="color: #808080;width: 120rpx;">换票地点</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">{{skuObj.sku.changePostion}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">入园时间</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">
{{skuObj.sku.enterStart}}~{{skuObj.sku.enterEnd}}
</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">入园地址</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">{{skuObj.sku.enterPostion}}</view>
</view>
</view>
<view style="width: 654rpx;padding: 48rpx 0rpx">
<text style="color: #000;font-size: 32rpx;font-weight: bold;">取消政策</text>
<u-parse style="margin-top: 24rpx;" :html="skuObj.sku.refundPolicy"></u-parse>
</view>
<view style="width: 654rpx;padding: 48rpx 0rpx">
<text style="color: #000;font-size: 32rpx;font-weight: bold;">其他说明</text>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">补充说明</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">{{skuObj.sku.remark}}</view>
</view>
<view style="display: flex;margin-top: 24rpx;">
<view style="color: #808080;width: 120rpx;">发票说明</view>
<view style="width: 494rpx;margin-left: 48rpx;color: #000;">{{skuObj.sku.receiptPolicy}}</view>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
var that = ''
import {
getSkuType
} from '../../unit/utils.js'
export default {
data() {
return {
show: false,
list: [],
obj: '',
skuObj: '',
typeStr: '',
getSkuType: '',
enterTypeList: [{
value: "二维码",
label: 1
},
{
value: "实体票",
label: 2
},
{
value: "第三方凭证码",
label: 3
},
{
value: "身份证",
label: 4
},
{
value: "护照",
label: 5
},
{
value: "港澳居民来往内地通行证",
label: 6
},
{
value: "台湾居民来往大陆通行证",
label: 7
}
]
};
},
onLoad(e) {
that = this
this.getSkuType = getSkuType
this.getTicketsDetailMethod(e.id)
},
methods: {
playPhone() {
uni.makePhoneCall({
phoneNumber: that.obj.shop.mobile
})
},
// xuchao: 到景区去 —— 微信/浏览器环境用 uni.openLocation 打开地图,失败兜底跳高德网页
handleOpen() {
const lat = parseFloat(this.obj.shop.lat)
const lng = parseFloat(this.obj.shop.lng)
if (!lat || !lng) {
uni.showToast({
icon: 'none',
title: '暂无该景区位置信息'
})
return
}
uni.openLocation({
latitude: lat,
longitude: lng,
name: this.obj.shop.name,
address: this.obj.shop.address || '',
fail: () => {
// 备用方案:跳转高德地图网页
window.location.href =
`https://uri.amap.com/marker?position=${lng},${lat}&name=${this.obj.shop.name}`
}
})
},
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)
},
seeSkuDetail(e) {
this.$u.api.getSkuDetail({
id: e
}).then(res => {
if (res) {
this.skuObj = res.data
this.show = true
}
})
},
getTicketsDetailMethod(e) {
this.list = []
this.$u.api.getTicketsDetail({
id: e
}).then(res => {
if (res) {
if (res.data.tickets.propagatePic) {
res.data.tickets.propagatePic.split(',').forEach((item, index) => {
this.list.push({
image: item
})
})
}
this.obj = res.data
}
})
},
toOrderSure(e) {
this.$u.api.getSkuDetail({
id: e
}).then(res => {
if (res) {
this.skuObj = res.data
// this.$store.commit('setScenicDetail', res.data)
uni.setStorageSync('scenicDetail',res.data)
uni.navigateTo({
url: 'ordersure'
})
}
})
}
},
};
</script>
<style>
page {
background: #F8F9FC;
}
</style>
<style lang="less" scoped>
.wrap {
width: 750rpx;
height: 420rpx;
}
.ticket {
width: 750rpx;
background: #fff;
padding: 24rpx;
margin-top: 24rpx;
.ticket_item {
margin-top: 24rpx;
background: #F8F9FC;
border-radius: 8rpx;
padding: 24rpx;
display: flex;
}
.btn {
width: 152rpx;
height: 56rpx;
border-radius: 200rpx;
background: linear-gradient(180deg, #FF9E49 0%, #ED3B1A 100%);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20rpx;
}
}
.title {
width: 750rpx;
background-color: #fff;
margin-top: 50rpx;
padding: 24rpx;
z-index: 10000;
.t_info {
color: #FF383C;
line-height: 48rpx;
margin-top: 12rpx;
font-weight: bold;
}
.t_opentime {
background-color: #F8F9FD;
padding: 8rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
margin-top: 24rpx;
}
.t_address {
width: 702rpx;
height: 100rpx;
margin-top: 24rpx;
background: url('https://aliyunoss.hn12301.net/xgh/t_address_bj.png');
background-size: 100% 100%;
color: #000;
display: flex;
align-items: center;
padding-left: 24rpx;
image {
width: 40rpx;
height: 70rpx;
}
}
.t_name {
width: 702rpx;
color: #000;
font-size: 36rpx;
font-weight: 700;
line-height: 48rpx;
}
.tag {
color: #7C7C7E;
font-size: 20rpx;
padding: 5rpx 10rpx;
background: #F8F9FC;
border-radius: 4rpx;
display: block;
margin-right: 12rpx;
}
}
</style>