123
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
VITE_API_BASE_URL=https://dscp-dev.hn12301.net
|
VITE_API_BASE_URL=https://dscp-uat.hn12301.net
|
||||||
VITE_APP_TITLE=湖南12301文旅专区
|
VITE_APP_TITLE=湖南12301文旅专区
|
||||||
VITE_USE_MOCK=true
|
VITE_USE_MOCK=true
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const loading = ref(true)
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'aspx', type: 'FAQ' })
|
const data = await post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'FAQ' })
|
||||||
faqList.value = (data || []).map(n => ({
|
faqList.value = (data || []).map(n => ({
|
||||||
id: n.id,
|
id: n.id,
|
||||||
title: n.title,
|
title: n.title,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const notices = ref<NoticeItem[]>([])
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'aspx', type: 'NOTICE' })
|
const data = await post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'NOTICE' })
|
||||||
notices.value = (data || []).map(n => ({
|
notices.value = (data || []).map(n => ({
|
||||||
id: n.id,
|
id: n.id,
|
||||||
title: n.title,
|
title: n.title,
|
||||||
|
|||||||
@@ -129,11 +129,7 @@
|
|||||||
<!-- 模块5:Tab内容区域 -->
|
<!-- 模块5:Tab内容区域 -->
|
||||||
<view class="tab-content">
|
<view class="tab-content">
|
||||||
<view v-if="activeTab === 'notice'" class="content-section">
|
<view v-if="activeTab === 'notice'" class="content-section">
|
||||||
<view class="content-block" v-for="(item, idx) in scenic.bookingNotice" :key="idx">
|
<rich-text class="notice-html" :nodes="scenic.bookingNoticeHtml" />
|
||||||
<text class="block-title">{{ item.title }}</text>
|
|
||||||
<view class="divider" />
|
|
||||||
<text class="block-text">{{ item.content }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<view v-if="activeTab === 'intro'" class="content-section">
|
<view v-if="activeTab === 'intro'" class="content-section">
|
||||||
<text class="intro-text">{{ scenic.introduction }}</text>
|
<text class="intro-text">{{ scenic.introduction }}</text>
|
||||||
@@ -156,7 +152,7 @@ interface ScenicDetail {
|
|||||||
id: number; name: string; price: number; freeCount: number; level: string
|
id: number; name: string; price: number; freeCount: number; level: string
|
||||||
hours: string; needReservation: boolean; bookingDeadline: string
|
hours: string; needReservation: boolean; bookingDeadline: string
|
||||||
phone: string; address: string; tags: string[]; images: string[]
|
phone: string; address: string; tags: string[]; images: string[]
|
||||||
introduction: string; bookingNotice: NoticeItem[]; cards: CardBenefit[]
|
introduction: string; bookingNotice: NoticeItem[]; bookingNoticeHtml: string; cards: CardBenefit[]
|
||||||
scenicId: number; shopId: number; mainPic: string; status: number
|
scenicId: number; shopId: number; mainPic: string; status: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +204,7 @@ async function fetchDetail(shopId: string) {
|
|||||||
images: res.images || (res.mainPic ? [res.mainPic] : []),
|
images: res.images || (res.mainPic ? [res.mainPic] : []),
|
||||||
introduction: res.introduction || '',
|
introduction: res.introduction || '',
|
||||||
bookingNotice: res.bookingNotice || [],
|
bookingNotice: res.bookingNotice || [],
|
||||||
|
bookingNoticeHtml: res.bookingNoticeHtml || '',
|
||||||
cards: res.cards || [],
|
cards: res.cards || [],
|
||||||
scenicId: res.scenicId || res.id || shopId,
|
scenicId: res.scenicId || res.id || shopId,
|
||||||
shopId: res.shopId || shopId,
|
shopId: res.shopId || shopId,
|
||||||
@@ -593,8 +590,7 @@ $icon-crown: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' w
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: $spacing-3;
|
padding: $spacing-3;
|
||||||
margin: 0 $spacing-3;
|
margin: $spacing-3 $spacing-3;
|
||||||
margin-bottom: $spacing-3;
|
|
||||||
background: $uv-primary-light;
|
background: $uv-primary-light;
|
||||||
border-radius: $radius-full;
|
border-radius: $radius-full;
|
||||||
|
|
||||||
@@ -702,6 +698,13 @@ $icon-crown: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' w
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notice-html {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
color: $uv-tips-color;
|
||||||
|
line-height: 1.8;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
/* ====== 底部安全区 ====== */
|
/* ====== 底部安全区 ====== */
|
||||||
.safe-bottom {
|
.safe-bottom {
|
||||||
height: calc(40rpx + env(safe-area-inset-bottom));
|
height: calc(40rpx + env(safe-area-inset-bottom));
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<view class="back-arrow" />
|
<view class="back-arrow" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="nav-title">全部合作景区</text>
|
<text class="nav-title">{{ navTitle }}</text>
|
||||||
<view class="nav-side" />
|
<view class="nav-side" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<text class="dropdown-label" :class="{ active: selectedLevel }">{{ selectedLevel || '景区等级' }}</text>
|
<text class="dropdown-label" :class="{ active: selectedLevel }">{{ selectedLevel || '景区等级' }}</text>
|
||||||
<view class="dropdown-arrow" />
|
<view class="dropdown-arrow" />
|
||||||
</view>
|
</view>
|
||||||
<view class="dropdown-trigger" @click="openPicker('cardType')">
|
<view class="dropdown-trigger" :class="{ 'dropdown-trigger--disabled': fromQrcode }" @click="!fromQrcode && openPicker('cardType')">
|
||||||
<text class="dropdown-label" :class="{ active: selectedCardType }">{{ selectedCardTypeLabel }}</text>
|
<text class="dropdown-label" :class="{ active: selectedCardType }">{{ selectedCardTypeLabel }}</text>
|
||||||
<view class="dropdown-arrow" />
|
<view class="dropdown-arrow" />
|
||||||
</view>
|
</view>
|
||||||
@@ -255,6 +255,8 @@ stickyHeaderHeight.value = 100
|
|||||||
// ========== URL 参数 ==========
|
// ========== URL 参数 ==========
|
||||||
const cardNumber = ref('')
|
const cardNumber = ref('')
|
||||||
const cardModelId = ref('')
|
const cardModelId = ref('')
|
||||||
|
const fromQrcode = computed(() => !!cardModelId.value)
|
||||||
|
const navTitle = computed(() => fromQrcode.value ? '旅游卡景区预约' : '全部合作景区')
|
||||||
|
|
||||||
// ========== 分页 ==========
|
// ========== 分页 ==========
|
||||||
const pageNo = ref(1)
|
const pageNo = ref(1)
|
||||||
@@ -761,6 +763,11 @@ function onCardClick(scenic: ScenicItem) {
|
|||||||
&:active {
|
&:active {
|
||||||
background: $color-muted;
|
background: $color-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-label {
|
.dropdown-label {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const loading = ref(true)
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'aspx', type: 'GUIDE' })
|
const data = await post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'GUIDE' })
|
||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
imageUrl.value = data[0].imageUrl || ''
|
imageUrl.value = data[0].imageUrl || ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="agreement__text">
|
<view class="agreement__text">
|
||||||
<text class="agreement__label">我已阅读并知晓</text>
|
<text class="agreement__label">我已阅读并知晓</text>
|
||||||
<text class="agreement__link" @tap.stop="showNotice = true">《用卡须知》</text>
|
<text class="agreement__link" @tap.stop="openNotice">《用卡须知》</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 弹窗:证件类型选择 -->
|
<!-- 弹窗:证件类型选择 -->
|
||||||
<uni-popup ref="idTypePopup" type="bottom" :mask-click="true" @change="(e: any) => showIdTypePicker = e.show">
|
<uni-popup ref="idTypePopup" type="bottom" :mask-click="true" mask-background="transparent" @change="(e: any) => showIdTypePicker = e.show">
|
||||||
<view class="popup-sheet">
|
<view class="popup-sheet">
|
||||||
<view class="popup-header">
|
<view class="popup-header">
|
||||||
<text class="popup-title">选择证件类型</text>
|
<text class="popup-title">选择证件类型</text>
|
||||||
@@ -140,21 +140,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</uni-popup>
|
</uni-popup>
|
||||||
|
|
||||||
<!-- 弹窗:用卡须知 -->
|
|
||||||
<uni-popup ref="noticePopup" type="bottom" :mask-click="true" @change="(e: any) => showNotice = e.show">
|
|
||||||
<view class="popup-sheet">
|
|
||||||
<view class="popup-header">
|
|
||||||
<text class="popup-title">用卡须知</text>
|
|
||||||
<view class="popup-close" @tap="showNotice = false">
|
|
||||||
<view class="popup-close__icon" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<scroll-view scroll-y class="popup-body">
|
|
||||||
<text class="popup-content">1. 年卡仅限本人使用,不得转借他人。\n2. 入园时需出示有效身份证件及年卡二维码。\n3. 年卡有效期内可不限次数游览合作景区。\n4. 部分景区节假日需提前预约,请以景区公告为准。\n5. 年卡一经激活,不予退换。</text>
|
|
||||||
</scroll-view>
|
|
||||||
</view>
|
|
||||||
</uni-popup>
|
|
||||||
|
|
||||||
<!-- 隐藏 canvas,用于图片压缩 -->
|
<!-- 隐藏 canvas,用于图片压缩 -->
|
||||||
<canvas
|
<canvas
|
||||||
canvas-id="myCanvas"
|
canvas-id="myCanvas"
|
||||||
@@ -165,7 +150,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, watch, computed } from 'vue'
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { checkFaceFlag, uploadImage, bindCard as apiBindCard, bindFaceImg } from '../api/card'
|
import { checkFaceFlag, uploadImage, bindCard as apiBindCard, bindFaceImg } from '../api/card'
|
||||||
|
|
||||||
@@ -203,9 +188,7 @@ const agreed = ref(false)
|
|||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const uploading = ref(false)
|
const uploading = ref(false)
|
||||||
const needFaceBind = ref(false)
|
const needFaceBind = ref(false)
|
||||||
const showNotice = ref(false)
|
|
||||||
const showIdTypePicker = ref(false)
|
const showIdTypePicker = ref(false)
|
||||||
const noticePopup = ref<any>(null)
|
|
||||||
const idTypePopup = ref<any>(null)
|
const idTypePopup = ref<any>(null)
|
||||||
|
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
@@ -229,10 +212,10 @@ onLoad(async (options) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(showNotice, (v) => {
|
/** 打开用卡须知(跳转新页面) */
|
||||||
if (v) noticePopup.value?.open()
|
function openNotice() {
|
||||||
else noticePopup.value?.close()
|
uni.navigateTo({ url: '/packages/user/pages/card-notice' })
|
||||||
})
|
}
|
||||||
|
|
||||||
watch(showIdTypePicker, (v) => {
|
watch(showIdTypePicker, (v) => {
|
||||||
if (v) idTypePopup.value?.open()
|
if (v) idTypePopup.value?.open()
|
||||||
@@ -756,6 +739,8 @@ function handleCancel() {
|
|||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
box-shadow: none;
|
||||||
|
padding-bottom: 0;
|
||||||
font-family: $font-family-body;
|
font-family: $font-family-body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,19 +798,6 @@ function handleCancel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-body {
|
|
||||||
padding: 32rpx;
|
|
||||||
max-height: 55vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-content {
|
|
||||||
font-family: $font-family-body;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
font-weight: 400;
|
|
||||||
color: $uv-content-color;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== ID Type Picker List ===== */
|
/* ===== ID Type Picker List ===== */
|
||||||
.id-type-list {
|
.id-type-list {
|
||||||
padding: 16rpx 0;
|
padding: 16rpx 0;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<text class="loading-text">加载中...</text>
|
<text class="loading-text">加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<image class="card-image" :src="mainPic" mode="widthFix" />
|
|
||||||
<view class="intro-section">
|
<view class="intro-section">
|
||||||
<rich-text :nodes="introduction" />
|
<rich-text :nodes="introduction" />
|
||||||
</view>
|
</view>
|
||||||
@@ -18,9 +17,9 @@
|
|||||||
<text class="price-value">{{ buyPrice }}</text>
|
<text class="price-value">{{ buyPrice }}</text>
|
||||||
<text class="price-original">¥3800+</text>
|
<text class="price-original">¥3800+</text>
|
||||||
</view>
|
</view>
|
||||||
<button class="btn-buy" :disabled="paying" @tap="handleBuy">
|
<view class="btn-buy" :class="{ 'btn-buy--disabled': paying }" @tap="handleBuy">
|
||||||
<text class="btn-buy__text">{{ paying ? '支付中...' : '立即购买' }}</text>
|
<text class="btn-buy__text">{{ paying ? '支付中...' : '立即购买' }}</text>
|
||||||
</button>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -95,6 +94,8 @@ async function handleBuy() {
|
|||||||
mobile,
|
mobile,
|
||||||
couponId: '',
|
couponId: '',
|
||||||
})
|
})
|
||||||
|
const orderId = orderRes.orderId
|
||||||
|
const outTradeNo = orderRes.outTradeNo
|
||||||
|
|
||||||
const openId = getToken()
|
const openId = getToken()
|
||||||
if (!openId) {
|
if (!openId) {
|
||||||
@@ -130,12 +131,12 @@ async function handleBuy() {
|
|||||||
|
|
||||||
uni.showToast({ title: '支付成功', icon: 'success' })
|
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.switchTab({ url: '/pages/index/index' })
|
uni.redirectTo({ url: `/packages/user/pages/order/detail?orderId=${outTradeNo}` })
|
||||||
}, 1500)
|
}, 1500)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
uni.showToast({ title: err?.errMsg?.includes('cancel') ? '已取消支付' : '支付失败,请重试', icon: 'none' })
|
uni.showToast({ title: err?.errMsg?.includes('cancel') ? '已取消支付' : '支付失败,请重试', icon: 'none' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.redirectTo({ url: '/packages/user/pages/order/list' })
|
uni.redirectTo({ url: `/packages/user/pages/order/list?outTradeNo=${outTradeNo}` })
|
||||||
}, 1500)
|
}, 1500)
|
||||||
} finally {
|
} finally {
|
||||||
paying.value = false
|
paying.value = false
|
||||||
@@ -149,6 +150,7 @@ $bar-height: 120rpx;
|
|||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: $color-card;
|
background: $color-card;
|
||||||
|
padding-top: 20rpx;
|
||||||
padding-bottom: calc(#{$bar-height} + env(safe-area-inset-bottom));
|
padding-bottom: calc(#{$bar-height} + env(safe-area-inset-bottom));
|
||||||
font-family: $font-family-body;
|
font-family: $font-family-body;
|
||||||
}
|
}
|
||||||
@@ -165,14 +167,6 @@ $bar-height: 120rpx;
|
|||||||
color: $uv-tips-color;
|
color: $uv-tips-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 图片 ===== */
|
|
||||||
.card-image {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 富文本介绍 ===== */
|
/* ===== 富文本介绍 ===== */
|
||||||
.intro-section {
|
.intro-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -185,15 +179,13 @@ $bar-height: 120rpx;
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-height: $bar-height;
|
min-height: $bar-height;
|
||||||
background: $color-card;
|
background: $color-card;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
padding: 20rpx 32rpx;
|
||||||
padding: 0 0rpx 0 178rpx;
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
@@ -207,6 +199,8 @@ $bar-height: 120rpx;
|
|||||||
.price-row {
|
.price-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.price-symbol {
|
.price-symbol {
|
||||||
@@ -246,15 +240,12 @@ $bar-height: 120rpx;
|
|||||||
box-shadow: 0 4rpx 16rpx rgba(60, 156, 255, 0.3);
|
box-shadow: 0 4rpx 16rpx rgba(60, 156, 255, 0.3);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&::after {
|
&:active {
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active:not([disabled]) {
|
|
||||||
transform: scale(0.97);
|
transform: scale(0.97);
|
||||||
|
background: $uv-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled] {
|
&--disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,5 +256,9 @@ $bar-height: 120rpx;
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 2rpx;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<view class="bg-ornament" />
|
||||||
|
<view class="scroll-wrap">
|
||||||
|
<view class="content">
|
||||||
|
<view v-if="loading" class="loading">
|
||||||
|
<view class="loading-spinner" />
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
<rich-text v-else class="rich-body" :nodes="content" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { post } from '../../../api/request'
|
||||||
|
import { showToast } from '../../../utils'
|
||||||
|
|
||||||
|
const content = ref('')
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const list = await post<any[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'CARD_NOTICE' })
|
||||||
|
content.value = list?.[0]?.content || ''
|
||||||
|
} catch {
|
||||||
|
showToast('加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(180deg, #f5f7fa 0%, #ffffff 140rpx);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-ornament {
|
||||||
|
position: fixed;
|
||||||
|
top: -120rpx;
|
||||||
|
right: -80rpx;
|
||||||
|
width: 360rpx;
|
||||||
|
height: 360rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, rgba(60, 156, 255, 0.08), rgba(60, 156, 255, 0.02));
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrap {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-body {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $uv-content-color;
|
||||||
|
line-height: 1.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 120rpx 0;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border: 4rpx solid #e8ecf1;
|
||||||
|
border-top-color: $uv-primary;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: $uv-tips-color;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -718,7 +718,7 @@ $bar-height: 120rpx;
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0 48rpx;
|
padding: 20rpx 48rpx;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|||||||
@@ -94,13 +94,14 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 入园码弹窗 -->
|
<!-- 入园码弹窗 -->
|
||||||
<uni-popup ref="codePopup" type="center" :mask-click="true">
|
<uni-popup ref="codePopup" type="center" :mask-click="true" @close="closeCode">
|
||||||
<view class="code-modal">
|
<view class="code-modal">
|
||||||
<text class="code-modal__title">入园码</text>
|
<text class="code-modal__title">入园码</text>
|
||||||
<view class="qr-box">
|
<view class="qr-box">
|
||||||
<image v-if="codeSrc" class="qr-img" :src="codeSrc" mode="aspectFit" />
|
<image v-if="codeSrc" class="qr-img" :src="codeSrc" mode="aspectFit" />
|
||||||
</view>
|
</view>
|
||||||
<text class="code-modal__desc">入园时请出示此二维码+本人身份证</text>
|
<text class="code-modal__desc">入园时请出示此二维码+本人身份证</text>
|
||||||
|
<text class="code-modal__countdown">{{ countdown }}秒后自动刷新</text>
|
||||||
</view>
|
</view>
|
||||||
</uni-popup>
|
</uni-popup>
|
||||||
|
|
||||||
@@ -191,7 +192,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch, onUnmounted } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { post } from '../../../../api/request'
|
import { post } from '../../../../api/request'
|
||||||
import { BASE_URL } from '../../../../config'
|
import { BASE_URL } from '../../../../config'
|
||||||
@@ -235,6 +236,16 @@ const detailPopup = ref<any>(null)
|
|||||||
const codeSrc = ref('')
|
const codeSrc = ref('')
|
||||||
const detailRecord = ref<ReserveRecord | null>(null)
|
const detailRecord = ref<ReserveRecord | null>(null)
|
||||||
const cancelRecord = ref<ReserveRecord | null>(null)
|
const cancelRecord = ref<ReserveRecord | null>(null)
|
||||||
|
const countdown = ref(20)
|
||||||
|
let refreshTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimer()
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.offUserCaptureScreen()
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
|
||||||
const records = ref<ReserveRecord[]>([])
|
const records = ref<ReserveRecord[]>([])
|
||||||
|
|
||||||
@@ -293,6 +304,15 @@ async function fetchRecords(append = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.setVisualEffectOnCapture({ visualEffect: 'hidden' })
|
||||||
|
wx.onUserCaptureScreen(() => {
|
||||||
|
uni.showToast({ title: '该页面截图无效,请不要截图', icon: 'none' })
|
||||||
|
if (currentRecord.value) {
|
||||||
|
fetchCode(currentRecord.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
fetchRecords()
|
fetchRecords()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -335,14 +355,49 @@ async function confirmCancel() {
|
|||||||
|
|
||||||
async function showCode(r: ReserveRecord) {
|
async function showCode(r: ReserveRecord) {
|
||||||
codeSrc.value = ''
|
codeSrc.value = ''
|
||||||
|
currentRecord.value = r
|
||||||
|
await fetchCode(r)
|
||||||
|
codePopup.value?.open()
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCode() {
|
||||||
|
clearTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTimer() {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearInterval(refreshTimer)
|
||||||
|
refreshTimer = null
|
||||||
|
}
|
||||||
|
if (countdownTimer) {
|
||||||
|
clearInterval(countdownTimer)
|
||||||
|
countdownTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
clearTimer()
|
||||||
|
countdown.value = 20
|
||||||
|
const id = currentRecord.value?.id
|
||||||
|
if (!id) return
|
||||||
|
refreshTimer = setInterval(() => fetchCode(currentRecord.value!), 20000)
|
||||||
|
countdownTimer = setInterval(() => {
|
||||||
|
countdown.value--
|
||||||
|
if (countdown.value <= 0) countdown.value = 20
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentRecord = ref<ReserveRecord | null>(null)
|
||||||
|
|
||||||
|
async function fetchCode(r: ReserveRecord) {
|
||||||
|
countdown.value = 20
|
||||||
try {
|
try {
|
||||||
const res = await post<any>(`${BASE_URL}/zhy-api/cardBag/getQrByAppointmentId?appointmentId=${r.id}`)
|
const res = await post<any>(`${BASE_URL}/zhy-api/cardBag/getQrByAppointmentId?appointmentId=${r.id}`)
|
||||||
if (res.qrType == 1) {
|
if (res.qrType == 1) {
|
||||||
codeSrc.value = 'data:image/png;base64,' + res.qrCode
|
codeSrc.value = 'data:image/png;base64,' + res.qrCode
|
||||||
codePopup.value?.open()
|
|
||||||
} else if (res.qrType == 2) {
|
} else if (res.qrType == 2) {
|
||||||
codeSrc.value = res.qrUrl
|
codeSrc.value = res.qrUrl
|
||||||
codePopup.value?.open()
|
|
||||||
} else {
|
} else {
|
||||||
await fetchQrByCard(r)
|
await fetchQrByCard(r)
|
||||||
}
|
}
|
||||||
@@ -363,7 +418,6 @@ async function fetchQrByCard(r: ReserveRecord) {
|
|||||||
})
|
})
|
||||||
if (res.qrCode) {
|
if (res.qrCode) {
|
||||||
codeSrc.value = res.qrCode.startsWith('data:') ? res.qrCode : 'data:image/png;base64,' + res.qrCode
|
codeSrc.value = res.qrCode.startsWith('data:') ? res.qrCode : 'data:image/png;base64,' + res.qrCode
|
||||||
codePopup.value?.open()
|
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ icon: 'none', title: '暂无核销码' })
|
uni.showToast({ icon: 'none', title: '暂无核销码' })
|
||||||
}
|
}
|
||||||
@@ -628,6 +682,13 @@ function previewPhoto(url: string) {
|
|||||||
|
|
||||||
.code-modal__desc { font-family: $font-family-body; font-size: $font-size-sm; color: $uv-tips-color; }
|
.code-modal__desc { font-family: $font-family-body; font-size: $font-size-sm; color: $uv-tips-color; }
|
||||||
|
|
||||||
|
.code-modal__countdown {
|
||||||
|
font-family: $font-family-body;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $uv-placeholder-color;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Cancel Modal ===== */
|
/* ===== Cancel Modal ===== */
|
||||||
.cancel-modal {
|
.cancel-modal {
|
||||||
width: 600rpx; background: $color-card; border-radius: $radius-lg; padding: 40rpx;
|
width: 600rpx; background: $color-card; border-radius: $radius-lg; padding: 40rpx;
|
||||||
|
|||||||
@@ -72,20 +72,21 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 入园码弹窗 -->
|
<!-- 入园码弹窗 -->
|
||||||
<uni-popup ref="codePopup" type="center" :mask-click="true">
|
<uni-popup ref="codePopup" type="center" :mask-click="true" @close="closeCode">
|
||||||
<view class="code-modal">
|
<view class="code-modal">
|
||||||
<text class="code-modal__title">入园码</text>
|
<text class="code-modal__title">入园码</text>
|
||||||
<view class="qr-box">
|
<view class="qr-box">
|
||||||
<image v-if="codeSrc" class="qr-img" :src="codeSrc" mode="aspectFit" />
|
<image v-if="codeSrc" class="qr-img" :src="codeSrc" mode="aspectFit" />
|
||||||
</view>
|
</view>
|
||||||
<text class="code-modal__desc">入园时请出示此二维码+本人身份证</text>
|
<text class="code-modal__desc">入园时请出示此二维码+本人身份证</text>
|
||||||
|
<text class="code-modal__countdown">{{ countdown }}秒后自动刷新</text>
|
||||||
</view>
|
</view>
|
||||||
</uni-popup>
|
</uni-popup>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onUnmounted } from 'vue'
|
||||||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
||||||
import { post } from '../../../../api/request'
|
import { post } from '../../../../api/request'
|
||||||
import { BASE_URL } from '../../../../config'
|
import { BASE_URL } from '../../../../config'
|
||||||
@@ -105,8 +106,27 @@ const codeSrc = ref('')
|
|||||||
const codePopup = ref<any>(null)
|
const codePopup = ref<any>(null)
|
||||||
const cardNumber = ref('')
|
const cardNumber = ref('')
|
||||||
const cardModelId = ref('')
|
const cardModelId = ref('')
|
||||||
|
const countdown = ref(20)
|
||||||
|
let refreshTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimer()
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.offUserCaptureScreen()
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
|
||||||
onLoad(async (query: any) => {
|
onLoad(async (query: any) => {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.setVisualEffectOnCapture({ visualEffect: 'hidden' })
|
||||||
|
wx.onUserCaptureScreen(() => {
|
||||||
|
uni.showToast({ title: '该页面截图无效,请不要截图', icon: 'none' })
|
||||||
|
if (codePopup.value) {
|
||||||
|
fetchCode()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
const appointId = query?.appointId
|
const appointId = query?.appointId
|
||||||
if (!appointId) return
|
if (!appointId) return
|
||||||
const phone = (uni.getStorageSync('phone') as string) || ''
|
const phone = (uni.getStorageSync('phone') as string) || ''
|
||||||
@@ -146,14 +166,44 @@ onBackPress(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function showCode() {
|
async function showCode() {
|
||||||
|
await fetchCode()
|
||||||
|
codePopup.value?.open()
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCode() {
|
||||||
|
clearTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTimer() {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearInterval(refreshTimer)
|
||||||
|
refreshTimer = null
|
||||||
|
}
|
||||||
|
if (countdownTimer) {
|
||||||
|
clearInterval(countdownTimer)
|
||||||
|
countdownTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
clearTimer()
|
||||||
|
countdown.value = 20
|
||||||
|
refreshTimer = setInterval(fetchCode, 20000)
|
||||||
|
countdownTimer = setInterval(() => {
|
||||||
|
countdown.value--
|
||||||
|
if (countdown.value <= 0) countdown.value = 20
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCode() {
|
||||||
|
countdown.value = 20
|
||||||
try {
|
try {
|
||||||
const res = await post<any>(`${BASE_URL}/zhy-api/cardBag/getQrByAppointmentId?appointmentId=${reserveId.value}`)
|
const res = await post<any>(`${BASE_URL}/zhy-api/cardBag/getQrByAppointmentId?appointmentId=${reserveId.value}`)
|
||||||
if (res.qrType == 1) {
|
if (res.qrType == 1) {
|
||||||
codeSrc.value = 'data:image/png;base64,' + res.qrCode
|
codeSrc.value = 'data:image/png;base64,' + res.qrCode
|
||||||
codePopup.value?.open()
|
|
||||||
} else if (res.qrType == 2) {
|
} else if (res.qrType == 2) {
|
||||||
codeSrc.value = res.qrUrl
|
codeSrc.value = res.qrUrl
|
||||||
codePopup.value?.open()
|
|
||||||
} else {
|
} else {
|
||||||
await fetchQrByCard()
|
await fetchQrByCard()
|
||||||
}
|
}
|
||||||
@@ -471,6 +521,13 @@ function goHome() {
|
|||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-modal__countdown {
|
||||||
|
font-family: $font-family-body;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $uv-placeholder-color;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.qr-box {
|
.qr-box {
|
||||||
width: 360rpx;
|
width: 360rpx;
|
||||||
height: 360rpx;
|
height: 360rpx;
|
||||||
|
|||||||
@@ -89,6 +89,12 @@
|
|||||||
"navigationBarTitleText": "绑卡激活"
|
"navigationBarTitleText": "绑卡激活"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/card-notice",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用卡须知"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/buy",
|
"path": "pages/buy",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import AnnouncementModal from '../../components/announcement-modal.vue'
|
import AnnouncementModal from '../../components/announcement-modal.vue'
|
||||||
import { post } from '../../api/request'
|
import { post } from '../../api/request'
|
||||||
import type { BannerItem } from '../../api/types'
|
import type { BannerItem } from '../../api/types'
|
||||||
|
import { useUserStore } from '../../store/user'
|
||||||
|
|
||||||
interface ScenicItem {
|
interface ScenicItem {
|
||||||
id: number
|
id: number
|
||||||
@@ -157,12 +158,13 @@ const showAllNotices = ref(false)
|
|||||||
const displayNotices = computed(() =>
|
const displayNotices = computed(() =>
|
||||||
showAllNotices.value ? announcements.value : announcements.value.slice(0, 2)
|
showAllNotices.value ? announcements.value : announcements.value.slice(0, 2)
|
||||||
)
|
)
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const [bannerData, noticeData, spotsData] = await Promise.all([
|
const [bannerData, noticeData, spotsData] = await Promise.all([
|
||||||
post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'aspx', type: 'BANNER' }),
|
post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'BANNER' }),
|
||||||
post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'aspx', type: 'NOTICE' }),
|
post<BannerItem[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'NOTICE' }),
|
||||||
post<any>('/mp-api/mpScenic/queryScenicList', { channel: 'pingxiang', pageNo: 1, pageSize: 6, phone: uni.getStorageSync('phone') || '' }),
|
post<any>('/mp-api/mpScenic/queryScenicList', { channel: 'pingxiang', pageNo: 1, pageSize: 6, phone: uni.getStorageSync('phone') || '' }),
|
||||||
])
|
])
|
||||||
banners.value = bannerData || []
|
banners.value = bannerData || []
|
||||||
@@ -207,6 +209,10 @@ const quickActions = [
|
|||||||
/** 处理金刚区点击 */
|
/** 处理金刚区点击 */
|
||||||
function onQuickAction(item: { label: string }) {
|
function onQuickAction(item: { label: string }) {
|
||||||
if (item.label === '我要绑卡') {
|
if (item.label === '我要绑卡') {
|
||||||
|
if (!userStore.isLogin) {
|
||||||
|
uni.navigateTo({ url: '/pages/login/login' })
|
||||||
|
return
|
||||||
|
}
|
||||||
uni.navigateTo({ url: '/packages/user/pages/bind' })
|
uni.navigateTo({ url: '/packages/user/pages/bind' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,94 +1,96 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="content">
|
<view class="bg-ornament" />
|
||||||
<text class="title">服务条款</text>
|
<view class="scroll-wrap">
|
||||||
<text class="update-time">更新日期:2025年6月1日</text>
|
<view class="content">
|
||||||
|
<view v-if="loading" class="loading">
|
||||||
<view class="section">
|
<view class="loading-spinner" />
|
||||||
<text class="section-title">一、总则</text>
|
<text class="loading-text">加载中...</text>
|
||||||
<text class="section-body">欢迎您使用爱上萍乡小程序(以下简称"本平台")。本服务条款是您与本平台之间关于使用本平台服务所订立的协议。请您在使用本平台服务前,仔细阅读并充分理解本条款内容。</text>
|
</view>
|
||||||
<text class="section-body">如您不同意本条款的任何内容,请勿使用本平台服务。您一旦使用本平台服务,即视为您已充分理解并同意本条款全部内容。</text>
|
<rich-text v-else class="rich-body" :nodes="content" />
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">二、服务内容</text>
|
|
||||||
<text class="section-body">本平台为用户提供文旅年卡浏览、购买、激活、绑定及入园码展示等相关服务。具体服务内容以平台实际提供为准。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">三、用户注册与账户安全</text>
|
|
||||||
<text class="section-body">您通过微信授权登录即完成注册。您应妥善保管账户信息及登录凭证,因您个人原因导致的信息泄露或损失,由您自行承担。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">四、用户行为规范</text>
|
|
||||||
<text class="section-body">您在使用本平台服务时,应遵守中华人民共和国相关法律法规,不得利用本平台从事任何违法违规活动,不得干扰本平台的正常运行。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">五、知识产权</text>
|
|
||||||
<text class="section-body">本平台的所有内容,包括但不限于文字、图片、界面设计、商标等,均受知识产权法律保护,未经许可不得擅自使用。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">六、免责声明</text>
|
|
||||||
<text class="section-body">因不可抗力或非本平台原因导致的服务中断或数据丢失,本平台不承担责任。本平台有权根据需要修改服务条款,修改后的条款将在平台公布后生效。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">七、条款修改</text>
|
|
||||||
<text class="section-body">本平台有权根据需要不时地制定、修改本服务条款,修改后的条款将被公布在本平台上。如您继续使用本平台服务,即视为您已接受经修订的条款。</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { post } from '../../api/request'
|
||||||
|
import { showToast } from '../../utils'
|
||||||
|
|
||||||
|
const content = ref('')
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const list = await post<any[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'TERMS' })
|
||||||
|
content.value = list?.[0]?.content || ''
|
||||||
|
} catch {
|
||||||
|
showToast('加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #FFFFFF;
|
background: linear-gradient(180deg, #f5f7fa 0%, #ffffff 140rpx);
|
||||||
padding: $spacing-6 $spacing-6 $spacing-12;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-ornament {
|
||||||
|
position: fixed;
|
||||||
|
top: -120rpx;
|
||||||
|
right: -80rpx;
|
||||||
|
width: 360rpx;
|
||||||
|
height: 360rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, rgba(60, 156, 255, 0.08), rgba(60, 156, 255, 0.02));
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrap {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
max-width: 100%;
|
padding: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.rich-body {
|
||||||
display: block;
|
font-size: 28rpx;
|
||||||
font-family: $font-family-display;
|
|
||||||
font-size: $font-size-2xl;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
color: $uv-main-color;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: $spacing-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-time {
|
|
||||||
display: block;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
color: $uv-tips-color;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: $spacing-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
margin-bottom: $spacing-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
display: block;
|
|
||||||
font-size: $font-size-lg;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
color: $uv-main-color;
|
|
||||||
margin-bottom: $spacing-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-body {
|
|
||||||
display: block;
|
|
||||||
font-size: $font-size-base;
|
|
||||||
color: $uv-content-color;
|
color: $uv-content-color;
|
||||||
line-height: $line-height-relaxed;
|
line-height: 1.9;
|
||||||
margin-bottom: $spacing-2;
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 120rpx 0;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border: 4rpx solid #e8ecf1;
|
||||||
|
border-top-color: $uv-primary;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: $uv-tips-color;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -73,8 +73,15 @@ const userStore = useUserStore()
|
|||||||
const agreed = ref(false)
|
const agreed = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const showContent = ref(false)
|
const showContent = ref(false)
|
||||||
|
const preLoginCode = ref('')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const res = await uni.login({ provider: 'weixin' })
|
||||||
|
preLoginCode.value = (res as any)?.code || ''
|
||||||
|
} catch {
|
||||||
|
// silent — fallback in handleLogin
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showContent.value = true
|
showContent.value = true
|
||||||
}, 80)
|
}, 80)
|
||||||
@@ -109,17 +116,21 @@ async function handleLogin(e: AnyObject) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginRes = await new Promise<UniNamespace.LoginRes>((resolve, reject) => {
|
let code = preLoginCode.value
|
||||||
uni.login({ provider: 'weixin', success: resolve, fail: reject })
|
if (!code) {
|
||||||
})
|
const loginRes = await new Promise<UniNamespace.LoginRes>((resolve, reject) => {
|
||||||
|
uni.login({ provider: 'weixin', success: resolve, fail: reject })
|
||||||
|
})
|
||||||
|
code = loginRes.code || ''
|
||||||
|
}
|
||||||
|
|
||||||
if (!loginRes?.code) {
|
if (!code) {
|
||||||
showToast('微信登录失败')
|
showToast('微信登录失败')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await userStore.wxLogin({
|
await userStore.wxLogin({
|
||||||
code: loginRes.code,
|
code,
|
||||||
encryptedData,
|
encryptedData,
|
||||||
iv
|
iv
|
||||||
})
|
})
|
||||||
@@ -225,6 +236,11 @@ $icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' w
|
|||||||
|
|
||||||
.btn-primary--disabled {
|
.btn-primary--disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
background: $uv-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary[disabled] {
|
||||||
|
background: $uv-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:active:not(.btn-primary--disabled) {
|
.btn-primary:active:not(.btn-primary--disabled) {
|
||||||
|
|||||||
+78
-80
@@ -1,98 +1,96 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="content">
|
<view class="bg-ornament" />
|
||||||
<text class="title">隐私条款</text>
|
<view class="scroll-wrap">
|
||||||
<text class="update-time">更新日期:2025年6月1日</text>
|
<view class="content">
|
||||||
|
<view v-if="loading" class="loading">
|
||||||
<view class="section">
|
<view class="loading-spinner" />
|
||||||
<text class="section-title">一、信息收集</text>
|
<text class="loading-text">加载中...</text>
|
||||||
<text class="section-body">为向您提供服务,我们可能会收集以下信息:微信授权登录信息(OpenID)、手机号码、年卡购买及激活信息。我们仅收集为提供服务所必需的最少信息。</text>
|
</view>
|
||||||
</view>
|
<rich-text v-else class="rich-body" :nodes="content" />
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">二、信息使用</text>
|
|
||||||
<text class="section-body">我们收集的信息将用于:验证您的身份、处理年卡的购买与激活、提供入园码服务、发送服务通知。我们不会将您的个人信息用于与上述目的无关的其他用途。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">三、信息存储</text>
|
|
||||||
<text class="section-body">您的个人信息将存储在中华人民共和国境内的安全服务器上。我们采用业界标准的加密技术保护数据传输过程,并采取严格的数据访问控制措施。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">四、信息共享</text>
|
|
||||||
<text class="section-body">未经您的同意,我们不会向第三方共享、转让或公开披露您的个人信息,法律法规另有规定的除外。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">五、信息安全</text>
|
|
||||||
<text class="section-body">我们采取符合业界标准的安全防护措施来保护您的个人信息,防止数据未经授权的访问、公开披露、使用、修改、损坏或丢失。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">六、您的权利</text>
|
|
||||||
<text class="section-body">您有权访问、更正、删除您的个人信息。如需行使上述权利,请通过平台客服渠道联系我们,我们将在合理期限内予以处理。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">七、未成年人保护</text>
|
|
||||||
<text class="section-body">如您是未满18周岁的未成年人,请在您的监护人陪同下阅读本隐私条款,并在取得监护人对您使用本平台服务的同意后,方可使用本平台服务。</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="section">
|
|
||||||
<text class="section-title">八、条款更新</text>
|
|
||||||
<text class="section-body">我们可能不时更新本隐私条款。更新后的条款将在平台上公布,建议您定期查阅。如您在隐私条款更新后继续使用本平台服务,即视为您同意更新后的隐私条款。</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { post } from '../../api/request'
|
||||||
|
import { showToast } from '../../utils'
|
||||||
|
|
||||||
|
const content = ref('')
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const list = await post<any[]>('/mp-api/mpContent/queryList', { channel: 'pingxiang', type: 'PRIVACY' })
|
||||||
|
content.value = list?.[0]?.content || ''
|
||||||
|
} catch {
|
||||||
|
showToast('加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #FFFFFF;
|
background: linear-gradient(180deg, #f5f7fa 0%, #ffffff 140rpx);
|
||||||
padding: $spacing-6 $spacing-6 $spacing-12;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-ornament {
|
||||||
|
position: fixed;
|
||||||
|
top: -120rpx;
|
||||||
|
right: -80rpx;
|
||||||
|
width: 360rpx;
|
||||||
|
height: 360rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, rgba(60, 156, 255, 0.08), rgba(60, 156, 255, 0.02));
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrap {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
max-width: 100%;
|
padding: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.rich-body {
|
||||||
display: block;
|
font-size: 28rpx;
|
||||||
font-family: $font-family-display;
|
|
||||||
font-size: $font-size-2xl;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
color: $uv-main-color;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: $spacing-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-time {
|
|
||||||
display: block;
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
color: $uv-tips-color;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: $spacing-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
margin-bottom: $spacing-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
display: block;
|
|
||||||
font-size: $font-size-lg;
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
color: $uv-main-color;
|
|
||||||
margin-bottom: $spacing-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-body {
|
|
||||||
display: block;
|
|
||||||
font-size: $font-size-base;
|
|
||||||
color: $uv-content-color;
|
color: $uv-content-color;
|
||||||
line-height: $line-height-relaxed;
|
line-height: 1.9;
|
||||||
margin-bottom: $spacing-2;
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 120rpx 0;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border: 4rpx solid #e8ecf1;
|
||||||
|
border-top-color: $uv-primary;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
color: $uv-tips-color;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -44,8 +44,8 @@
|
|||||||
:current="currentCardIndex"
|
:current="currentCardIndex"
|
||||||
@change="onCardChange"
|
@change="onCardChange"
|
||||||
circular
|
circular
|
||||||
previous-margin="60rpx"
|
previous-margin="120rpx"
|
||||||
next-margin="60rpx"
|
next-margin="120rpx"
|
||||||
>
|
>
|
||||||
<swiper-item v-for="(card, index) in userCards" :key="card.cardNo">
|
<swiper-item v-for="(card, index) in userCards" :key="card.cardNo">
|
||||||
<view
|
<view
|
||||||
@@ -194,9 +194,10 @@ async function fetchCards() {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const list = (await getUserCardList({ mobile, saleChannel: 'xgh' })) || []
|
const list = (await getUserCardList({ mobile, saleChannel: 'xgh' })) || []
|
||||||
userCards.value = list
|
const allowedIds = ['820093340659013', '820093438328133']
|
||||||
|
userCards.value = list.filter((c: any) => allowedIds.includes(String(c.cardModelId)))
|
||||||
|
|
||||||
const firstCard = list[0]
|
const firstCard = userCards.value[0]
|
||||||
if (firstCard?.needFaceImgFlag) {
|
if (firstCard?.needFaceImgFlag) {
|
||||||
showFaceModal.value = true
|
showFaceModal.value = true
|
||||||
}
|
}
|
||||||
@@ -218,9 +219,10 @@ async function refreshNeedFace() {
|
|||||||
if (!mobile) return
|
if (!mobile) return
|
||||||
try {
|
try {
|
||||||
const list = (await getUserCardList({ mobile, saleChannel: 'xgh' })) || []
|
const list = (await getUserCardList({ mobile, saleChannel: 'xgh' })) || []
|
||||||
userCards.value = list
|
const allowedIds = ['820093340659013', '820093438328133']
|
||||||
|
userCards.value = list.filter((c: any) => allowedIds.includes(String(c.cardModelId)))
|
||||||
|
|
||||||
const current = list[currentCardIndex.value]
|
const current = userCards.value[currentCardIndex.value]
|
||||||
if (current) {
|
if (current) {
|
||||||
fetchUserInfo(current)
|
fetchUserInfo(current)
|
||||||
if (current.needFaceImgFlag) {
|
if (current.needFaceImgFlag) {
|
||||||
@@ -290,7 +292,7 @@ function goLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function goBuyCard() {
|
function goBuyCard() {
|
||||||
uni.navigateTo({ url: '/packages/user/pages/card/list' })
|
uni.navigateTo({ url: '/packages/user/pages/buy' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyCardNo() {
|
function copyCardNo() {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
Reference in New Issue
Block a user