1232
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
<!-- 预约提醒横幅 -->
|
||||
<view v-if="scenic.needReservation" class="notice-banner">
|
||||
<view class="icon-wrap icon-alert" />
|
||||
<text class="notice-text">需预约后出行,未预约无法入园</text>
|
||||
<rich-text class="notice-text" :nodes="scenic.needReservation" />
|
||||
</view>
|
||||
|
||||
<!-- 特色标签行 -->
|
||||
@@ -132,7 +132,26 @@
|
||||
<rich-text class="notice-html" :nodes="scenic.bookingNoticeHtml" />
|
||||
</view>
|
||||
<view v-if="activeTab === 'intro'" class="content-section">
|
||||
<text class="intro-text">{{ scenic.introduction }}</text>
|
||||
<rich-text class="notice-html" :nodes="scenic.introduction" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 店铺公告弹窗 -->
|
||||
<view v-if="noticeVisible" class="notice-overlay" @click="noticeVisible = false">
|
||||
<view class="notice-popup" @click.stop>
|
||||
<view class="notice-popup-accent" />
|
||||
<view class="notice-popup-header">
|
||||
<view class="notice-popup-icon" />
|
||||
<text class="notice-popup-title">{{ storeNotice.title }}</text>
|
||||
</view>
|
||||
<scroll-view class="notice-popup-body" scroll-y>
|
||||
<rich-text :nodes="storeNotice.content" />
|
||||
</scroll-view>
|
||||
<view class="notice-popup-footer">
|
||||
<view class="notice-popup-btn" @click="noticeVisible = false">
|
||||
<text>我知道了</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -143,14 +162,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onLoad, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { post, getToken } from '../../../api/request'
|
||||
|
||||
interface NoticeItem { title: string; content: string }
|
||||
interface CardBenefit { type: string; cardName: string; skuName: string; remainingCount: number; freeCount: number; bookingDeadline: string; cardModelId: string; resourceId: string; goodsId: string }
|
||||
interface ScenicDetail {
|
||||
id: number; name: string; price: number; freeCount: number; level: string
|
||||
hours: string; needReservation: boolean; bookingDeadline: string
|
||||
hours: string; needReservation: string; bookingDeadline: string
|
||||
phone: string; address: string; tags: string[]; images: string[]
|
||||
introduction: string; bookingNotice: NoticeItem[]; bookingNoticeHtml: string; cards: CardBenefit[]
|
||||
scenicId: number; shopId: number; mainPic: string; status: number
|
||||
@@ -160,6 +179,9 @@ const scenic = ref<ScenicDetail>({ tags: [], images: [], cards: [], bookingNotic
|
||||
const activeTab = ref<'notice' | 'intro'>('notice')
|
||||
const currentSlide = ref(0)
|
||||
const loading = ref(true)
|
||||
const currentShopId = ref('')
|
||||
const storeNotice = ref<{ title: string; content: string }>({ title: '', content: '' })
|
||||
const noticeVisible = ref(false)
|
||||
|
||||
function onSwiperChange(e: any) {
|
||||
currentSlide.value = e.detail.current
|
||||
@@ -171,6 +193,7 @@ const statusLabel = computed(() => ['', '正常运营', '审核中', '暂停运
|
||||
|
||||
onLoad((options) => {
|
||||
const shopId = options?.shopId || options?.id || ''
|
||||
currentShopId.value = shopId
|
||||
if (!shopId) {
|
||||
uni.showToast({ title: '缺少景区参数', icon: 'none' })
|
||||
uni.navigateBack()
|
||||
@@ -178,8 +201,26 @@ onLoad((options) => {
|
||||
}
|
||||
uni.setNavigationBarTitle({ title: '景区详情' })
|
||||
fetchDetail(String(shopId))
|
||||
fetchShopNotice(String(shopId))
|
||||
})
|
||||
|
||||
async function fetchShopNotice(shopId: string) {
|
||||
try {
|
||||
const res = await post<any>('/mp-api/mpShopNotice/queryShopNoticePage', {
|
||||
shopId,
|
||||
pageNo: 1,
|
||||
pageSize: 5,
|
||||
})
|
||||
const list = res?.list || res?.data?.list || []
|
||||
if (list.length > 0) {
|
||||
storeNotice.value = { title: list[0].title || '', content: list[0].content || '' }
|
||||
noticeVisible.value = true
|
||||
}
|
||||
} catch {
|
||||
// silently fail
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDetail(shopId: string) {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -220,6 +261,18 @@ async function fetchDetail(shopId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
onShareAppMessage(() => ({
|
||||
title: scenic.value.name || '景区详情',
|
||||
path: `/packages/content/pages/scenic-detail?shopId=${currentShopId.value}`,
|
||||
imageUrl: scenic.value.images?.[0] || '',
|
||||
}))
|
||||
|
||||
onShareTimeline(() => ({
|
||||
title: scenic.value.name || '景区详情',
|
||||
query: `shopId=${currentShopId.value}`,
|
||||
imageUrl: scenic.value.images?.[0] || '',
|
||||
}))
|
||||
|
||||
function onHoursClick() {
|
||||
uni.showToast({ title: '营业时间仅供参考', icon: 'none' })
|
||||
}
|
||||
@@ -575,7 +628,7 @@ $icon-crown: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' w
|
||||
|
||||
.deadline-text {
|
||||
margin-left: auto;
|
||||
font-size: $font-size-xs;
|
||||
font-size: $font-size-sm;
|
||||
color: $uv-tips-color;
|
||||
}
|
||||
|
||||
@@ -705,6 +758,113 @@ $icon-crown: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' w
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ====== 店铺公告弹窗 ====== */
|
||||
.notice-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
backdrop-filter: blur(4rpx);
|
||||
z-index: $z-modal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: overlayIn 250ms ease;
|
||||
}
|
||||
|
||||
@keyframes overlayIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.notice-popup {
|
||||
width: 620rpx;
|
||||
max-height: 70vh;
|
||||
background: $color-card;
|
||||
border-radius: $radius-xl;
|
||||
box-shadow: $shadow-xl;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: popupIn 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
@keyframes popupIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.notice-popup-accent {
|
||||
height: 6rpx;
|
||||
background: $gradient-accent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notice-popup-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: $spacing-6 $spacing-5 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notice-popup-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
flex-shrink: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 24 24' fill='none' stroke='%233c9cff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9'/%3E%3Cpath d='M13.73 21a2 2 0 0 1-3.46 0'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin-bottom: $spacing-2;
|
||||
}
|
||||
|
||||
.notice-popup-title {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $uv-main-color;
|
||||
font-family: $font-family-display;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.notice-popup-body {
|
||||
padding: $spacing-3 $spacing-5 $spacing-3;
|
||||
font-size: $font-size-sm;
|
||||
color: $uv-tips-color;
|
||||
line-height: 1.9;
|
||||
max-height: 45vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.notice-popup-footer {
|
||||
padding: 0 $spacing-5 $spacing-5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notice-popup-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
background: $gradient-accent;
|
||||
border-radius: $radius-full;
|
||||
transition: opacity $transition-fast;
|
||||
|
||||
text {
|
||||
font-size: $font-size-base;
|
||||
color: #fff;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
&:active { opacity: 0.85; }
|
||||
}
|
||||
|
||||
/* ====== 底部安全区 ====== */
|
||||
.safe-bottom {
|
||||
height: calc(40rpx + env(safe-area-inset-bottom));
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<view class="name-price-row">
|
||||
<text class="scenic-name">{{ scenic.name }}</text>
|
||||
<view class="price-group">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">{{ scenic.price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -122,7 +122,7 @@
|
||||
<text>全年还剩{{ scenic.remainingCount }}次</text>
|
||||
</view>
|
||||
<view v-else-if="scenic.freeCount > 0" class="remaining-tag">
|
||||
<text>全年可用{{ scenic.freeCount }}次</text>
|
||||
<text>全年最多可用{{ scenic.freeCount }}次</text>
|
||||
</view>
|
||||
<view class="benefit-tag" :class="scenic.needReservation ? 'warning' : 'success'">
|
||||
<text>{{ scenic.needReservation ? '需预约' : '免预约' }}</text>
|
||||
@@ -132,7 +132,7 @@
|
||||
<!-- 温馨提示 -->
|
||||
<view v-if="scenic.tips" class="tips-bar">
|
||||
<view class="tips-icon">!</view>
|
||||
<text class="tips-text">{{ scenic.tips }}</text>
|
||||
<rich-text class="tips-text" :nodes="scenic.tips" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -195,7 +195,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onLoad, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { post } from '../../../api/request'
|
||||
|
||||
interface ScenicItem {
|
||||
@@ -592,7 +592,12 @@ function clearSearch() {
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length <= 1) {
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
} else {
|
||||
uni.navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
function onCardClick(scenic: ScenicItem) {
|
||||
@@ -600,6 +605,18 @@ function onCardClick(scenic: ScenicItem) {
|
||||
url: `/packages/content/pages/scenic-detail?shopId=${scenic.shopId}`,
|
||||
})
|
||||
}
|
||||
|
||||
onShareAppMessage(() => ({
|
||||
title: navTitle.value,
|
||||
path: '/packages/content/pages/scenic-list',
|
||||
imageUrl: 'https://aliyunoss.hn12301.net/sd/aizaipingxiang.png',
|
||||
}))
|
||||
|
||||
onShareTimeline(() => ({
|
||||
title: navTitle.value,
|
||||
query: '',
|
||||
imageUrl: 'https://aliyunoss.hn12301.net/sd/aizaipingxiang.png',
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -997,6 +1014,7 @@ function onCardClick(scenic: ScenicItem) {
|
||||
font-size: $font-size-xl;
|
||||
color: $uv-error;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-left: 5rpx;
|
||||
}
|
||||
|
||||
/* 营业时间 + 营业状态 同行 */
|
||||
@@ -1173,7 +1191,8 @@ function onCardClick(scenic: ScenicItem) {
|
||||
.tips-text {
|
||||
font-size: $font-size-sm;
|
||||
color: $uv-content-color;
|
||||
line-height: 1.5;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ====== uni-popup 弹窗内容 ====== */
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onLoad, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { post, getToken } from '../../../api/request'
|
||||
import { BASE_URL } from '../../../config'
|
||||
import { useUserStore } from '../../../store/user'
|
||||
@@ -42,6 +42,7 @@ const buyPrice = ref('0')
|
||||
const skuId = ref('')
|
||||
const loading = ref(true)
|
||||
const paying = ref(false)
|
||||
const shareTitle = ref('爱上萍乡旅游年卡')
|
||||
|
||||
onLoad((query) => {
|
||||
goodsId.value = (query?.goodsId as string) || '821926819630853'
|
||||
@@ -55,6 +56,7 @@ function sanitizeHtml(html: string): string {
|
||||
|
||||
interface CardModelDetail {
|
||||
cardModel: any
|
||||
name: string
|
||||
goods: { id: string; mainPic: string }
|
||||
goodsInfo: { introduction: string }
|
||||
sku: Array<{ id: string; buyPrice: number }>
|
||||
@@ -65,6 +67,7 @@ onMounted(async () => {
|
||||
const data = await post<CardModelDetail>(`${BASE_URL}/open-api/xgh/cardModelDetail?goodsId=${goodsId.value}`, {})
|
||||
cardModleId.value = data.cardModel?.id || ''
|
||||
mainPic.value = data.goods?.mainPic || ''
|
||||
shareTitle.value = data.name || data.cardModel?.name || '爱上萍乡旅游年卡'
|
||||
introduction.value = sanitizeHtml(data.goodsInfo?.introduction || '')
|
||||
const sku = data.sku?.[0]
|
||||
skuId.value = sku?.id || ''
|
||||
@@ -77,6 +80,18 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
onShareAppMessage(() => ({
|
||||
title: shareTitle.value,
|
||||
path: `/packages/user/pages/buy?goodsId=${goodsId.value}`,
|
||||
imageUrl: mainPic.value,
|
||||
}))
|
||||
|
||||
onShareTimeline(() => ({
|
||||
title: shareTitle.value,
|
||||
query: `goodsId=${goodsId.value}`,
|
||||
imageUrl: mainPic.value,
|
||||
}))
|
||||
|
||||
async function handleBuy() {
|
||||
if (!userStore.isLogin) {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<view class="promo" @click="goCardDetail()">
|
||||
<image
|
||||
class="promo-bg"
|
||||
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?fm=jpg&q=80&w=750&h=400&fit=crop"
|
||||
src="https://aliyunoss.hn12301.net/sd/aishang_goods.jpg"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="promo-overlay" />
|
||||
@@ -136,6 +136,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import AnnouncementModal from '../../components/announcement-modal.vue'
|
||||
import { post } from '../../api/request'
|
||||
import type { BannerItem } from '../../api/types'
|
||||
@@ -187,6 +188,18 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
onShareAppMessage(() => ({
|
||||
title: '爱上萍乡旅游年卡',
|
||||
path: '/pages/index/index',
|
||||
imageUrl: 'https://aliyunoss.hn12301.net/sd/aizaipingxiang.png',
|
||||
}))
|
||||
|
||||
onShareTimeline(() => ({
|
||||
title: '爱上萍乡旅游年卡',
|
||||
query: '',
|
||||
imageUrl: 'https://aliyunoss.hn12301.net/sd/aizaipingxiang.png',
|
||||
}))
|
||||
|
||||
function onBannerClick(banner: BannerItem) {
|
||||
if (!banner.linkUrl) return
|
||||
if (banner.linkUrl.startsWith('http')) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
>
|
||||
<view class="btn-primary__inner">
|
||||
<view class="wx-icon" />
|
||||
<text class="btn-primary__text">微信一键登录</text>
|
||||
<text class="btn-primary__text">一键登录</text>
|
||||
</view>
|
||||
</button>
|
||||
<!-- 已同意条款:微信授权按钮 -->
|
||||
@@ -33,7 +33,7 @@
|
||||
>
|
||||
<view v-if="!loading" class="btn-primary__inner">
|
||||
<view class="wx-icon" />
|
||||
<text class="btn-primary__text">微信一键登录</text>
|
||||
<text class="btn-primary__text">一键登录</text>
|
||||
</view>
|
||||
<view v-else class="btn-primary__inner">
|
||||
<view class="loading-spinner" />
|
||||
@@ -102,7 +102,7 @@ function openPrivacy() {
|
||||
uni.navigateTo({ url: '/pages/login/privacy' })
|
||||
}
|
||||
|
||||
/** 微信一键登录(获取手机号后调后端 OAuth2 登录接口) */
|
||||
/** 一键登录(获取手机号后调后端 OAuth2 登录接口) */
|
||||
async function handleLogin(e: AnyObject) {
|
||||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
return
|
||||
|
||||
@@ -334,7 +334,7 @@ function onFaceModalConfirm() {
|
||||
const featureActions = [
|
||||
{ label: '我要绑定', icon: 'https://aliyunoss.hntgov.net/pxmall/card.png' },
|
||||
{ label: '我要预约', icon: 'https://aliyunoss.hntgov.net/pxmall/reserve.png' },
|
||||
{ label: '使用记录', icon: 'https://aliyunoss.hntgov.net/pxmall/record.png' },
|
||||
{ label: '预约/使用记录', icon: 'https://aliyunoss.hntgov.net/pxmall/record.png' },
|
||||
{ label: '使用说明', icon: 'https://aliyunoss.hntgov.net/pxmall/scenic.png' },
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user