第一个版本
This commit is contained in:
+241
-77
@@ -1,7 +1,26 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 未登录状态 -->
|
||||
<view class="empty-state" v-if="notLoggedIn">
|
||||
<view class="empty-icon-card">
|
||||
<view class="empty-icon-stripe" />
|
||||
<view class="empty-icon-stripe short" />
|
||||
</view>
|
||||
<text class="empty-text">请先登录</text>
|
||||
<text class="empty-desc">登录后可查看您的旅游卡</text>
|
||||
<view class="empty-btn" @click="goLogin">
|
||||
<text class="empty-btn-text">立即登录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<view class="loading-state" v-else-if="loading">
|
||||
<view class="loading-spinner" />
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 未持卡状态 -->
|
||||
<view class="empty-state" v-if="!hasCard">
|
||||
<view class="empty-state" v-else-if="!hasCard && loaded">
|
||||
<view class="empty-icon-card">
|
||||
<view class="empty-icon-stripe" />
|
||||
<view class="empty-icon-stripe short" />
|
||||
@@ -24,23 +43,28 @@
|
||||
class="card-swiper"
|
||||
:current="currentCardIndex"
|
||||
@change="onCardChange"
|
||||
previous-margin="24rpx"
|
||||
next-margin="24rpx"
|
||||
circular
|
||||
previous-margin="60rpx"
|
||||
next-margin="60rpx"
|
||||
>
|
||||
<swiper-item v-for="(card, index) in userCards" :key="card.id">
|
||||
<view class="card-face">
|
||||
<image
|
||||
class="card-bg"
|
||||
src="https://hn12301.oss-cn-shenzhen.aliyuncs.com/hn12301/%E5%88%9D%E5%BF%83%E6%BA%90%20%E6%B9%98%E9%84%82%E8%B5%A3%E6%96%87%E6%97%85%E4%B8%80%E5%8D%A1%E9%80%9A_1777447994254.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<!-- 多卡指示点(卡内右下角) -->
|
||||
<swiper-item v-for="(card, index) in userCards" :key="card.cardNo">
|
||||
<view
|
||||
class="card-face"
|
||||
:class="{ 'card-face--active': index === currentCardIndex }"
|
||||
>
|
||||
<view class="card-face-inner">
|
||||
<image
|
||||
class="card-bg"
|
||||
:src="card.cardModelPic || 'https://hn12301.oss-cn-shenzhen.aliyuncs.com/hn12301/%E5%88%9D%E5%BF%83%E6%BA%90%20%E6%B9%98%E9%84%82%E8%B5%A3%E6%96%87%E6%97%85%E4%B8%80%E5%8D%A1%E9%80%9A_1777447994254.png'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="card-dots" v-if="userCards.length > 1">
|
||||
<view
|
||||
v-for="(_, idx) in userCards"
|
||||
:key="idx"
|
||||
class="card-dot"
|
||||
:class="{ active: idx === currentCardIndex }"
|
||||
:class="{ active: idx === index }"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@@ -59,12 +83,12 @@
|
||||
</view>
|
||||
|
||||
<!-- 入园码二维码 -->
|
||||
<view class="qrcode-section">
|
||||
<view class="qrcode-section" v-if="!showFaceModal && !isLeaving">
|
||||
<text class="qrcode-title">入园码</text>
|
||||
<view class="qrcode-box">
|
||||
<image
|
||||
class="qrcode-img"
|
||||
src="/static/qr-demo.png"
|
||||
:src="userInfoData?.qrCode || '/static/qr-demo.png'"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
@@ -80,7 +104,7 @@
|
||||
<view class="avatar-wrap" @click="previewAvatar">
|
||||
<image
|
||||
class="holder-avatar"
|
||||
src="https://hn12301.oss-cn-shenzhen.aliyuncs.com/hn12301/1782285689132_1782285690033.png"
|
||||
:src="userInfoData?.img || '/static/logo.png'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="avatar-zoom">
|
||||
@@ -88,124 +112,190 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="holder-main">
|
||||
<text class="holder-name">{{ currentCard.userName }}</text>
|
||||
<text class="holder-tag">已激活</text>
|
||||
<text class="holder-name">{{ userInfoData?.userName || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="holder-details">
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">卡种名称</text>
|
||||
<text class="detail-value">{{ currentCard.cardName }}</text>
|
||||
<text class="detail-value">{{ currentCard.cardModelName }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">旅游卡卡号</text>
|
||||
<view class="detail-value-wrap">
|
||||
<text class="detail-value">PX00002489</text>
|
||||
<text class="detail-value">{{ currentCard.cardNo }}</text>
|
||||
<text class="copy-btn" @click="copyCardNo">复制</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">有效期至</text>
|
||||
<text class="detail-value">{{ currentCard.expireDate }}</text>
|
||||
<text class="detail-value">{{ currentCard.validityTime }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">证件类型</text>
|
||||
<text class="detail-value">{{ currentCard.idType }}</text>
|
||||
<text class="detail-value">{{ userInfoData?.idType || '-' }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">证件号码</text>
|
||||
<text class="detail-value">{{ currentCard.idNumber }}</text>
|
||||
<text class="detail-value">{{ userInfoData?.idNo || '-' }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">手机号码</text>
|
||||
<text class="detail-value">{{ currentCard.phone }}</text>
|
||||
<text class="detail-value">{{ userInfoData?.mobile || userStore.userInfo?.phone || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部占位(滑到底时与tabBar的间隔) -->
|
||||
<view style="height: 48rpx;" />
|
||||
<view style="height: 48rpx;"/>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 人脸绑定提醒弹窗 -->
|
||||
<ConfirmModal
|
||||
v-model="showFaceModal"
|
||||
title="系统安全升级提醒"
|
||||
:content="'为保障您的旅游卡信息安全、规范入园使用,平台已启动人脸核验升级。\n根据规则要求,使用旅游卡前需完成人脸绑定,核验仅用于身份核实,人脸信息严格保密,不会挪作他用。\n请立即完成人脸绑定,方可继续使用全部功能。'"
|
||||
confirmText="前往人脸绑定"
|
||||
@confirm="onFaceModalConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { onShow, onHide } from '@dcloudio/uni-app'
|
||||
import { useUserStore } from '../../store/user'
|
||||
import { getUserCardList, getQrAndUserInfo } from '../../api/card'
|
||||
import type { UserCardListItem, QrAndUserInfoResult } from '../../api/types'
|
||||
import ConfirmModal from '../../components/confirm-modal.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
interface UserCardData {
|
||||
id: string
|
||||
cardName: string
|
||||
cardNo: string
|
||||
userName: string
|
||||
expireDate: string
|
||||
idType: string
|
||||
idNumber: string
|
||||
phone: string
|
||||
}
|
||||
|
||||
const userCards = ref<UserCardData[]>([
|
||||
{
|
||||
id: '1',
|
||||
cardName: '赣湘全域旅游年卡',
|
||||
cardNo: 'HN2026-0001-8888',
|
||||
userName: '张三',
|
||||
expireDate: '2027.06.24',
|
||||
idType: '身份证',
|
||||
idNumber: '360102199001011234',
|
||||
phone: '13812345678',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
cardName: '超级玩家卡',
|
||||
cardNo: 'HN2026-0002-6666',
|
||||
userName: '张三',
|
||||
expireDate: '2027.06.24',
|
||||
idType: '身份证',
|
||||
idNumber: '360102199001011234',
|
||||
phone: '13812345678',
|
||||
},
|
||||
])
|
||||
const notLoggedIn = ref(false)
|
||||
const loading = ref(false)
|
||||
const loaded = ref(false)
|
||||
const userCards = ref<UserCardListItem[]>([])
|
||||
const userInfoData = ref<QrAndUserInfoResult | null>(null)
|
||||
const showFaceModal = ref(false)
|
||||
const isLeaving = ref(false)
|
||||
|
||||
const hasCard = computed(() => userCards.value.length > 0)
|
||||
const currentCardIndex = ref(0)
|
||||
const currentCard = computed(() => userCards.value[currentCardIndex.value])
|
||||
const countdown = ref(60)
|
||||
const countdown = ref(20)
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
/** 获取用户年卡列表 */
|
||||
async function fetchCards() {
|
||||
const mobile = uni.getStorageSync('phone') as string
|
||||
if (!mobile) {
|
||||
loaded.value = true
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const list = (await getUserCardList({ mobile, saleChannel: 'xgh' })) || []
|
||||
userCards.value = list
|
||||
|
||||
const firstCard = list[0]
|
||||
if (firstCard?.needFaceImgFlag) {
|
||||
showFaceModal.value = true
|
||||
}
|
||||
|
||||
if (list.length > 0) {
|
||||
fetchUserInfo(firstCard)
|
||||
}
|
||||
} catch {
|
||||
userCards.value = []
|
||||
} finally {
|
||||
loaded.value = true
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 刷新当前卡的人脸绑定状态(从接口获取最新数据) */
|
||||
async function refreshNeedFace() {
|
||||
const mobile = uni.getStorageSync('phone') as string
|
||||
if (!mobile) return
|
||||
try {
|
||||
const list = (await getUserCardList({ mobile, saleChannel: 'xgh' })) || []
|
||||
userCards.value = list
|
||||
|
||||
const current = list[currentCardIndex.value]
|
||||
if (current) {
|
||||
fetchUserInfo(current)
|
||||
if (current.needFaceImgFlag) {
|
||||
showFaceModal.value = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 静默失败,沿用旧数据
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取持卡人信息和入园码 */
|
||||
async function fetchUserInfo(card: UserCardListItem) {
|
||||
const mobile = uni.getStorageSync('phone') as string
|
||||
if (!mobile || !card.cardNo || !card.thirdId) return
|
||||
try {
|
||||
const res = await getQrAndUserInfo({
|
||||
cardNum: card.cardNo,
|
||||
thirdId: card.thirdId,
|
||||
mobile,
|
||||
channel: 'Channel_SZH',
|
||||
scenicId: '',
|
||||
})
|
||||
userInfoData.value = res
|
||||
} catch {
|
||||
userInfoData.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function onCardChange(e: any) {
|
||||
currentCardIndex.value = e.detail.current
|
||||
resetCountdown()
|
||||
const card = userCards.value[currentCardIndex.value]
|
||||
if (card) {
|
||||
fetchUserInfo(card)
|
||||
if (card.needFaceImgFlag) {
|
||||
showFaceModal.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function previewAvatar() {
|
||||
if (!userInfoData.value?.img) return
|
||||
uni.previewImage({
|
||||
urls: ['https://hn12301.oss-cn-shenzhen.aliyuncs.com/hn12301/1782285689132_1782285690033.png'],
|
||||
urls: [userInfoData.value.img],
|
||||
current: 0,
|
||||
})
|
||||
}
|
||||
|
||||
function resetCountdown() {
|
||||
countdown.value = 60
|
||||
countdown.value = 20
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
timer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) {
|
||||
const card = currentCard.value
|
||||
if (card) fetchUserInfo(card)
|
||||
resetCountdown()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function goLogin() {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
|
||||
function goBuyCard() {
|
||||
uni.navigateTo({ url: '/packages/user/pages/card/list' })
|
||||
}
|
||||
|
||||
function copyCardNo() {
|
||||
uni.setClipboardData({
|
||||
data: 'PX00002489',
|
||||
data: currentCard.value.cardNo,
|
||||
success: () => { uni.showToast({ title: '已复制', icon: 'success' }) },
|
||||
})
|
||||
}
|
||||
@@ -214,11 +304,36 @@ function goBindCard() {
|
||||
uni.navigateTo({ url: '/packages/user/pages/bind' })
|
||||
}
|
||||
|
||||
const idTypeLabelMap: Record<string, string> = {
|
||||
'身份证': '1',
|
||||
'护照': '2',
|
||||
'港澳居民来往内地通行证': '3',
|
||||
'台湾居民来往大陆通行证': '4',
|
||||
}
|
||||
|
||||
function onFaceModalConfirm() {
|
||||
isLeaving.value = true
|
||||
showFaceModal.value = false
|
||||
|
||||
const card = currentCard.value
|
||||
const info = userInfoData.value
|
||||
if (!card || !info) {
|
||||
goBindCard()
|
||||
return
|
||||
}
|
||||
|
||||
const idTypeValue = idTypeLabelMap[info.idType || ''] || '1'
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/packages/user/pages/bind?faceBind=1&cardNo=${card.cardNo}&userName=${info.userName || ''}&mobile=${info.mobile || ''}&idType=${idTypeValue}&idNo=${info.idNo || ''}`
|
||||
})
|
||||
}
|
||||
|
||||
const featureActions = [
|
||||
{ label: '我要绑定', icon: '/static/icon/card.png' },
|
||||
{ label: '我要预约', icon: '/static/icon/reserve.png' },
|
||||
{ label: '使用记录', icon: '/static/icon/record.png' },
|
||||
{ label: '使用说明', icon: '/static/icon/scenic.png' },
|
||||
{ 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/scenic.png' },
|
||||
]
|
||||
|
||||
function onFeatureClick(item: { label: string }) {
|
||||
@@ -227,7 +342,10 @@ function onFeatureClick(item: { label: string }) {
|
||||
return
|
||||
}
|
||||
if (item.label === '我要预约') {
|
||||
uni.navigateTo({ url: '/packages/content/pages/scenic-list' })
|
||||
const url = currentCard.value?.thirdId
|
||||
? `/packages/content/pages/scenic-list?cardModelId=${currentCard.value.thirdId}`
|
||||
: '/packages/content/pages/scenic-list'
|
||||
uni.navigateTo({ url })
|
||||
return
|
||||
}
|
||||
if (item.label === '使用说明') {
|
||||
@@ -241,7 +359,20 @@ function onFeatureClick(item: { label: string }) {
|
||||
uni.showToast({ title: `${item.label}功能开发中`, icon: 'none' })
|
||||
}
|
||||
|
||||
onMounted(() => { startTimer() })
|
||||
onShow(() => {
|
||||
notLoggedIn.value = !userStore.isLogin
|
||||
if (!userStore.isLogin) return
|
||||
isLeaving.value = false
|
||||
if (loaded.value) {
|
||||
refreshNeedFace()
|
||||
} else {
|
||||
fetchCards()
|
||||
}
|
||||
if (!timer) startTimer()
|
||||
})
|
||||
onHide(() => {
|
||||
if (timer) { clearInterval(timer); timer = null }
|
||||
})
|
||||
onUnmounted(() => { if (timer) clearInterval(timer) })
|
||||
</script>
|
||||
|
||||
@@ -250,6 +381,22 @@ onUnmounted(() => { if (timer) clearInterval(timer) })
|
||||
.page { min-height: 100vh; background: $color-background; font-family: $font-family-body; }
|
||||
.scroll-content { height: 100vh; }
|
||||
|
||||
/* === 加载中 === */
|
||||
.loading-state {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; min-height: 100vh; gap: 24rpx;
|
||||
}
|
||||
.loading-spinner {
|
||||
width: 48rpx; height: 48rpx;
|
||||
border: 4rpx solid #e0e0e0;
|
||||
border-top-color: $uv-primary;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
.loading-text { font-size: $font-size-base; color: $uv-tips-color; }
|
||||
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* === 空状态 === */
|
||||
.empty-state {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
@@ -280,18 +427,35 @@ onUnmounted(() => { if (timer) clearInterval(timer) })
|
||||
.empty-btn-secondary:active { opacity: 0.7; }
|
||||
.empty-btn-secondary-text { font-size: $font-size-base; color: $uv-content-color; }
|
||||
|
||||
/* === 卡面展示区域 === */
|
||||
.card-swiper-wrap { margin: 20rpx 20rpx 20rpx; overflow: visible; }
|
||||
.card-swiper { height: 440rpx; }
|
||||
/* === 卡面展示区域(3D Coverflow) === */
|
||||
.card-swiper-wrap { margin: 14rpx 14rpx 14rpx; overflow: visible; perspective: 1200rpx; }
|
||||
.card-swiper { height: 360rpx; }
|
||||
.card-face {
|
||||
margin: 0 12rpx;
|
||||
border-radius: 24rpx;
|
||||
position: relative; overflow: hidden;
|
||||
background: #222222;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: 340rpx;
|
||||
margin: 0 8rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
transform: scale(0.88);
|
||||
opacity: 0.6;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.card-face--active {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
box-shadow: 0 8rpx 32rpx rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
.card-face-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-bg {
|
||||
width: 100%; display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 指示点(卡内右下角) */
|
||||
|
||||
Reference in New Issue
Block a user