Files
h5-meike/pages/ticket/scenicdetail.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

594 lines
14 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: 头图轮播(保留原有数字指示器) -->
<u-swiper :list="list" height="750" mode="number" indicator-pos="bottomRight"
marginBottom="30rpx"></u-swiper>
<!-- xuchao: 信息面板 —— 上移盖住头图底部的圆角 sheet,承载景区核心信息 -->
<view class="sheet mm-fade-up">
<view class="t_head">
<view class="t_name mm-display">{{obj.tickets.name}}</view>
<view class="t_level mm-grad" v-if="obj.shop.level">{{obj.shop.level}}</view>
</view>
<view class="t_info" v-if="obj.tickets.remark1">{{obj.tickets.remark1}}</view>
<!-- xuchao: 开园时间 —— 蓝色浅底提示条 -->
<view class="t_open" v-if="obj.tickets.openDate">
<u-icon name="clock-fill" color="#0052FF" size="28"></u-icon>
<text class="t_open__label">开园时间</text>
<text class="t_open__val">{{obj.tickets.openDate}}</text>
</view>
<!-- xuchao: 地址 + 导航/电话快捷圆钮 -->
<view class="t_addr">
<view class="t_addr__text">{{obj.shop.address}}</view>
<view class="t_addr__acts">
<view class="t_addr__btn mm-grad" @click="handleOpen()">
<u-icon name="map-fill" color="#ffffff" size="32"></u-icon>
</view>
<view class="t_addr__btn mm-grad" @click="playPhone()">
<u-icon name="phone-fill" color="#ffffff" size="32"></u-icon>
</view>
</view>
</view>
</view>
<!-- xuchao: 门票规格列表 -->
<view class="sec mm-fade-up">
<view class="sec__title mm-sec-title">门票规格</view>
<view class="sku" v-for="(item,index) in obj.skuList" :key="index">
<!-- xuchao: 名称提为独立整行,填充卡片宽度,不再被右侧价格列挤压换行 -->
<view class="sku__name">【{{getSkuType(item.typeId)}}】{{item.skuName}}</view>
<view class="sku__body">
<view class="sku__left">
<view class="sku__rule">{{item.bookTimeLimit}}之前{{item.bookType?'可订当天':'可订次日'}}</view>
<view class="sku__know" @click="seeSkuDetail(item.id)">
<text>预订须知</text>
<u-icon name="arrow-right" color="#0052FF" size="20"></u-icon>
</view>
</view>
<view class="sku__right">
<view class="sku__origin">原售价 <text
class="sku__origin-num">¥{{item.calendar.listPrice?(item.calendar.listPrice/100).toFixed(2):0}}</text></view>
<view class="sku__price">
<text class="sku__price-label">¥</text>
<text
class="sku__price-num mm-display mm-grad-text">{{item.calendar.salePrice?(item.calendar.salePrice/100).toFixed(2):0}}</text>
</view>
<view class="sku__buy mm-grad" @tap="toOrderSure(item.id)">购买</view>
</view>
</view>
</view>
</view>
<!-- xuchao: 产品介绍(后端富文本) -->
<view class="sec mm-fade-up">
<view class="sec__title mm-sec-title">产品介绍</view>
<view class="intro">
<u-parse :html="obj.tickets.introduction"></u-parse>
</view>
</view>
<!-- xuchao: 预订须知弹层 -->
<u-popup v-model="show" border-radius="20" mode="bottom" height="1000">
<view class="pp" v-if="skuObj">
<view class="pp__head">{{skuObj.sku.skuName}}</view>
<view class="pp__sec">
<view class="mm-sec-title">预订规则</view>
<view class="pp__row">
<view class="pp__label">预订时间</view>
<view class="pp__val">{{skuObj.sku.bookTimeLimit}}之前{{skuObj.sku.bookType?'可订当天':'可订次日'}}</view>
</view>
<view class="pp__row">
<view class="pp__label">生效时间</view>
<view class="pp__val">出票后立即使用</view>
</view>
<view class="pp__row">
<view class="pp__label">有效期</view>
<view class="pp__val">指定出行日期1天内有效</view>
</view>
<view class="pp__row">
<view class="pp__label">适用人群</view>
<view class="pp__val">{{skuObj.sku.typeInfo}}</view>
</view>
</view>
<view class="pp__sec">
<view class="mm-sec-title">费用说明</view>
<view class="pp__row">
<view class="pp__label">费用包含</view>
<view class="pp__val">{{skuObj.sku.contentWith}}</view>
</view>
<view class="pp__row">
<view class="pp__label">费用不含</view>
<view class="pp__val">{{skuObj.sku.contentWithout}}</view>
</view>
</view>
<view class="pp__sec">
<view class="mm-sec-title">使用说明</view>
<view class="pp__row">
<view class="pp__label">入园方式</view>
<view class="pp__val">{{skuObj.sku.enterChangeFlag?'凭('+getTyepStr(skuObj.sku.enterType)+')换票入园':'无需换票'}}</view>
</view>
<view class="pp__row" v-if="skuObj.sku.enterChangeFlag">
<view class="pp__label">换票时间</view>
<view class="pp__val">{{skuObj.sku.changeStart}}~{{skuObj.sku.changeEnd}}</view>
</view>
<view class="pp__row" v-if="skuObj.sku.enterChangeFlag">
<view class="pp__label">换票地点</view>
<view class="pp__val">{{skuObj.sku.changePostion}}</view>
</view>
<view class="pp__row">
<view class="pp__label">入园时间</view>
<view class="pp__val">{{skuObj.sku.enterStart}}~{{skuObj.sku.enterEnd}}</view>
</view>
<view class="pp__row">
<view class="pp__label">入园地址</view>
<view class="pp__val">{{skuObj.sku.enterPostion}}</view>
</view>
</view>
<view class="pp__sec">
<view class="mm-sec-title">取消政策</view>
<u-parse class="pp__parse" :html="skuObj.sku.refundPolicy"></u-parse>
</view>
<view class="pp__sec">
<view class="mm-sec-title">其他说明</view>
<view class="pp__row">
<view class="pp__label">补充说明</view>
<view class="pp__val">{{skuObj.sku.remark}}</view>
</view>
<view class="pp__row">
<view class="pp__label">发票说明</view>
<view class="pp__val">{{skuObj.sku.receiptPolicy}}</view>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
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 {
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) {
// xuchao: 头图首张放单票主图(与列表卡片一致),再拼宣传图,跳过与主图重复项
const mainPic = res.data.tickets.mainPic
if (mainPic) {
this.list.push({
image: mainPic
})
}
if (res.data.tickets.propagatePic) {
res.data.tickets.propagatePic.split(',').forEach((item, index) => {
if (item && item !== mainPic) {
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 lang="less" scoped>
.page {
padding-bottom: 80rpx;
}
/* xuchao: 头图固定 298px 高(u-swiper 的 height 属性只认 rpx,故在此覆写其内联高度),
图片按比例缩放居中裁切(object-fit: cover) */
.u-swiper-wrap /deep/ uni-swiper {
height: 298px !important;
}
.u-swiper-wrap /deep/ img {
object-fit: cover;
}
/* ---------- 信息 sheet(盖住头图底部) ---------- */
.sheet {
position: relative;
z-index: 10;
margin-top: -56rpx;
background: var(--mm-card);
border-radius: 40rpx 40rpx 0 0;
padding: 40rpx 32rpx 28rpx;
box-shadow: 0 -12rpx 32rpx rgba(15, 23, 42, 0.06);
}
.t_head {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.t_name {
flex: 1;
min-width: 0;
font-size: 44rpx;
line-height: 1.2;
color: var(--mm-fg);
}
/* xuchao: 景区等级徽标 —— 渐变胶囊,与景区名右对齐 */
.t_level {
flex: none;
margin: 8rpx 0 0 16rpx;
padding: 6rpx 20rpx;
border-radius: 999rpx;
color: #fff;
font-size: 22rpx;
box-shadow: var(--mm-shadow-accent);
}
.t_info {
margin-top: 12rpx;
font-size: 26rpx;
line-height: 1.6;
color: var(--mm-muted-fg);
}
.t_open {
margin-top: 24rpx;
display: flex;
align-items: center;
background: rgba(0, 82, 255, 0.05);
border-radius: 16rpx;
padding: 18rpx 24rpx;
}
.t_open__label {
flex: none;
margin: 0 16rpx 0 12rpx;
font-size: 24rpx;
font-weight: 500;
color: var(--mm-accent);
}
.t_open__val {
font-size: 24rpx;
color: var(--mm-fg);
}
.t_addr {
margin-top: 24rpx;
display: flex;
align-items: center;
background: var(--mm-muted);
border-radius: 20rpx;
padding: 24rpx;
}
.t_addr__text {
flex: 1;
min-width: 0;
font-size: 26rpx;
line-height: 1.5;
color: var(--mm-fg);
}
.t_addr__acts {
flex: none;
display: flex;
gap: 16rpx;
margin-left: 20rpx;
}
.t_addr__btn {
width: 76rpx;
height: 76rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--mm-shadow-accent);
transition: transform 0.15s ease;
}
.t_addr__btn:active {
transform: scale(0.9);
}
/* ---------- 内容分区(门票规格 / 产品介绍) ---------- */
.sec {
margin: 24rpx 32rpx 0;
background: var(--mm-muted);
border-radius: var(--mm-radius);
padding: 28rpx 24rpx;
}
.sec__title {
margin-bottom: 4rpx;
}
/* ---------- 规格卡片 ---------- */
.sku {
margin-top: 20rpx;
background: var(--mm-card);
border: 2rpx solid var(--mm-border);
border-radius: 20rpx;
padding: 24rpx;
box-shadow: var(--mm-shadow-sm);
}
/* xuchao: 名称下方的两栏区(左规则/预订须知,右价格/购买) */
.sku__body {
margin-top: 20rpx;
display: flex;
}
.sku__left {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.sku__name {
font-size: 28rpx;
font-weight: 600;
line-height: 1.4;
color: var(--mm-fg);
}
.sku__rule {
font-size: 22rpx;
color: var(--mm-muted-fg);
}
.sku__know {
margin-top: 14rpx;
display: flex;
align-items: center;
gap: 4rpx;
font-size: 24rpx;
color: var(--mm-accent);
}
.sku__right {
flex: none;
width: 268rpx;
margin-left: 20rpx;
display: flex;
flex-direction: column;
align-items: flex-end;
}
.sku__origin {
font-size: 20rpx;
color: var(--mm-muted-fg);
}
.sku__origin-num {
text-decoration: line-through;
}
.sku__price {
display: flex;
align-items: baseline;
margin-top: 10rpx;
}
.sku__price-label {
font-size: 20rpx;
color: var(--mm-muted-fg);
}
.sku__price-num {
font-size: 44rpx;
line-height: 1;
}
.sku__buy {
margin-top: 16rpx;
width: 160rpx;
height: 64rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 26rpx;
font-weight: 500;
box-shadow: 0 6rpx 16rpx rgba(0, 82, 255, 0.25);
transition: transform 0.15s ease;
}
.sku__buy:active {
transform: scale(0.94);
}
/* ---------- 产品介绍富文本 ---------- */
.intro {
margin-top: 16rpx;
font-size: 26rpx;
line-height: 1.7;
color: var(--mm-fg);
}
.intro /deep/ img {
max-width: 100% !important;
display: block;
border-radius: 12rpx;
}
/* ---------- 预订须知弹层 ---------- */
.pp {
padding: 0 48rpx 80rpx;
}
.pp__head {
height: 120rpx;
line-height: 120rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: var(--mm-fg);
border-bottom: 2rpx solid var(--mm-border);
}
.pp__sec {
padding-top: 40rpx;
}
.pp__row {
display: flex;
margin-top: 24rpx;
}
.pp__label {
flex: none;
width: 120rpx;
font-size: 26rpx;
color: var(--mm-muted-fg);
}
.pp__val {
flex: 1;
margin-left: 48rpx;
font-size: 26rpx;
line-height: 1.6;
color: var(--mm-fg);
}
.pp__parse {
margin-top: 24rpx;
font-size: 26rpx;
line-height: 1.7;
color: var(--mm-fg);
}
</style>