320 lines
10 KiB
Vue
320 lines
10 KiB
Vue
<template>
|
||
<view class="page">
|
||
<!-- 未持卡状态 -->
|
||
<view class="empty-state" v-if="!hasCard">
|
||
<text class="empty-icon">🎫</text>
|
||
<text class="empty-text">您还没有旅游卡</text>
|
||
<text class="empty-desc">购买旅游卡后即可使用入园码</text>
|
||
<view class="empty-btn" @click="goBuyCard">
|
||
<text class="empty-btn-text">去购买</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 持卡状态 -->
|
||
<scroll-view scroll-y class="scroll-content" v-else>
|
||
<!-- 卡面展示区域(左右滑动切换) -->
|
||
<view class="card-swiper-wrap">
|
||
<swiper
|
||
class="card-swiper"
|
||
:current="currentCardIndex"
|
||
@change="onCardChange"
|
||
previous-margin="20rpx"
|
||
next-margin="20rpx"
|
||
>
|
||
<swiper-item v-for="(card, index) in userCards" :key="card.id">
|
||
<view class="card-face" :style="{ background: card.gradient }">
|
||
<view class="card-face-top">
|
||
<text class="card-face-brand">爱上萍乡</text>
|
||
<image class="card-face-logo" src="/static/logo.png" mode="aspectFit" />
|
||
</view>
|
||
<text class="card-face-name">{{ card.cardName }}</text>
|
||
<view class="card-face-user">
|
||
<image
|
||
class="card-face-avatar"
|
||
:src="userStore.userInfo?.avatarUrl || ''"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="card-face-user-info">
|
||
<text class="card-face-username">{{ card.userName }}</text>
|
||
<text class="card-face-expiry">有效期至 {{ card.expireDate }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
<!-- 指示点 -->
|
||
<view class="card-dots" v-if="userCards.length > 1">
|
||
<view
|
||
v-for="(card, index) in userCards"
|
||
:key="index"
|
||
class="card-dot"
|
||
:class="{ active: index === currentCardIndex }"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 持卡信息(可展开/收起) -->
|
||
<view class="info-panel" @click="toggleInfo">
|
||
<view class="info-panel-header">
|
||
<text class="info-panel-icon">📋</text>
|
||
<text class="info-panel-title">持卡信息</text>
|
||
<text class="info-panel-arrow">{{ infoExpanded ? '▲' : '▼' }}</text>
|
||
</view>
|
||
<view class="info-panel-body" v-if="infoExpanded">
|
||
<view class="info-row">
|
||
<text class="info-label">证件类型</text>
|
||
<text class="info-value">{{ currentCard.idType }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">证件号码</text>
|
||
<text class="info-value">{{ currentCard.idNumber }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">手机号码</text>
|
||
<text class="info-value">{{ currentCard.phone }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 二维码区域 -->
|
||
<view class="qrcode-section">
|
||
<view class="qrcode-box">
|
||
<view class="qrcode-placeholder">
|
||
<text class="qrcode-placeholder-text">入园码</text>
|
||
<text class="qrcode-placeholder-sub">(二维码占位)</text>
|
||
</view>
|
||
</view>
|
||
<text class="qrcode-countdown">⏱ {{ countdown }}秒后自动刷新</text>
|
||
</view>
|
||
|
||
<!-- 功能入口 -->
|
||
<view class="feature-grid">
|
||
<view class="feature-item" @click="onFeature('reserve')">
|
||
<view class="feature-icon-wrap feature-icon-reserve">
|
||
<text class="feature-icon">📅</text>
|
||
</view>
|
||
<text class="feature-label">景区预约</text>
|
||
</view>
|
||
<view class="feature-item" @click="onFeature('history')">
|
||
<view class="feature-icon-wrap feature-icon-history">
|
||
<text class="feature-icon">📋</text>
|
||
</view>
|
||
<text class="feature-label">使用记录</text>
|
||
</view>
|
||
<view class="feature-item" @click="onFeature('spots')">
|
||
<view class="feature-icon-wrap feature-icon-spots">
|
||
<text class="feature-icon">🏔️</text>
|
||
</view>
|
||
<text class="feature-label">权益景区</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||
import { useUserStore } from '../../store/user'
|
||
|
||
const userStore = useUserStore()
|
||
|
||
/** 用户卡片 mock 数据(后续接 API) */
|
||
interface UserCardData {
|
||
id: string
|
||
cardName: string
|
||
userName: string
|
||
expireDate: string
|
||
gradient: string
|
||
idType: string
|
||
idNumber: string
|
||
phone: string
|
||
}
|
||
|
||
const userCards = ref<UserCardData[]>([
|
||
{
|
||
id: '1',
|
||
cardName: '赣湘全域旅游年卡',
|
||
userName: '张三',
|
||
expireDate: '2027.06.24',
|
||
gradient: 'linear-gradient(135deg, #1a237e 0%, #283593 40%, #ffd54f 100%)',
|
||
idType: '身份证',
|
||
idNumber: '360***1234',
|
||
phone: '138****5678',
|
||
},
|
||
{
|
||
id: '2',
|
||
cardName: '超级玩家卡',
|
||
userName: '张三',
|
||
expireDate: '2027.06.24',
|
||
gradient: 'linear-gradient(135deg, #e65100 0%, #bf360c 40%, #7b1fa2 100%)',
|
||
idType: '身份证',
|
||
idNumber: '360***1234',
|
||
phone: '138****5678',
|
||
},
|
||
])
|
||
|
||
/** 是否有卡(mock:默认有卡) */
|
||
const hasCard = computed(() => userCards.value.length > 0)
|
||
|
||
/** 当前选中的卡片索引 */
|
||
const currentCardIndex = ref(0)
|
||
|
||
/** 当前卡片数据 */
|
||
const currentCard = computed(() => userCards.value[currentCardIndex.value])
|
||
|
||
/** 持卡信息展开状态 */
|
||
const infoExpanded = ref(false)
|
||
|
||
/** 倒计时(60秒) */
|
||
const countdown = ref(60)
|
||
let timer: ReturnType<typeof setInterval> | null = null
|
||
|
||
function onCardChange(e: any) {
|
||
currentCardIndex.value = e.detail.current
|
||
infoExpanded.value = false
|
||
resetCountdown()
|
||
}
|
||
|
||
function toggleInfo() {
|
||
infoExpanded.value = !infoExpanded.value
|
||
}
|
||
|
||
function resetCountdown() {
|
||
countdown.value = 60
|
||
}
|
||
|
||
function startTimer() {
|
||
timer = setInterval(() => {
|
||
countdown.value--
|
||
if (countdown.value <= 0) {
|
||
resetCountdown()
|
||
// TODO: 刷新二维码(Phase 2 接入 API)
|
||
}
|
||
}, 1000)
|
||
}
|
||
|
||
function goBuyCard() {
|
||
uni.navigateTo({ url: '/packages/user/pages/card/list' })
|
||
}
|
||
|
||
function onFeature(type: 'reserve' | 'history' | 'spots') {
|
||
uni.showToast({ title: '功能开发中', icon: 'none' })
|
||
}
|
||
|
||
onMounted(() => {
|
||
startTimer()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
if (timer) clearInterval(timer)
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page { min-height: 100vh; background: #f5f5f5; }
|
||
.scroll-content { height: 100vh; }
|
||
|
||
/* === 空状态 === */
|
||
.empty-state {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
justify-content: center; min-height: 100vh; padding: 40rpx;
|
||
}
|
||
.empty-icon { font-size: 120rpx; margin-bottom: 24rpx; }
|
||
.empty-text { font-size: 32rpx; font-weight: bold; color: #333; margin-bottom: 12rpx; }
|
||
.empty-desc { font-size: 26rpx; color: #999; margin-bottom: 40rpx; }
|
||
.empty-btn {
|
||
background: linear-gradient(135deg, #4A90D9, #357ABD);
|
||
border-radius: 44rpx; padding: 20rpx 80rpx;
|
||
}
|
||
.empty-btn-text { font-size: 30rpx; color: #fff; font-weight: bold; }
|
||
|
||
/* === 卡面展示区域 === */
|
||
.card-swiper-wrap { padding: 30rpx 0 16rpx; }
|
||
.card-swiper { height: 420rpx; }
|
||
|
||
.card-face {
|
||
margin: 0 20rpx; height: 380rpx; border-radius: 24rpx;
|
||
padding: 32rpx; position: relative; overflow: hidden;
|
||
display: flex; flex-direction: column; justify-content: space-between;
|
||
}
|
||
.card-face-top {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
}
|
||
.card-face-brand { font-size: 24rpx; color: rgba(255,255,255,0.8); }
|
||
.card-face-logo { width: 48rpx; height: 48rpx; }
|
||
.card-face-name { font-size: 32rpx; font-weight: bold; color: #fff; }
|
||
.card-face-user { display: flex; align-items: center; }
|
||
.card-face-avatar {
|
||
width: 80rpx; height: 80rpx; border-radius: 50%;
|
||
border: 4rpx solid rgba(255,255,255,0.5);
|
||
background: rgba(255,255,255,0.2); margin-right: 16rpx;
|
||
}
|
||
.card-face-user-info { display: flex; flex-direction: column; }
|
||
.card-face-username { font-size: 28rpx; font-weight: bold; color: #fff; }
|
||
.card-face-expiry { font-size: 22rpx; color: rgba(255,255,255,0.7); margin-top: 4rpx; }
|
||
|
||
/* 指示点 */
|
||
.card-dots { display: flex; justify-content: center; gap: 12rpx; padding: 12rpx 0; }
|
||
.card-dot {
|
||
width: 12rpx; height: 12rpx; border-radius: 50%;
|
||
background: #ddd; transition: all 0.3s;
|
||
}
|
||
.card-dot.active { background: #4A90D9; width: 24rpx; border-radius: 6rpx; }
|
||
|
||
/* === 持卡信息 === */
|
||
.info-panel {
|
||
margin: 0 24rpx 20rpx; background: #fff;
|
||
border-radius: 16rpx; overflow: hidden;
|
||
}
|
||
.info-panel-header {
|
||
display: flex; align-items: center; padding: 24rpx;
|
||
}
|
||
.info-panel-icon { font-size: 28rpx; margin-right: 8rpx; }
|
||
.info-panel-title { flex: 1; font-size: 28rpx; color: #333; font-weight: 500; }
|
||
.info-panel-arrow { font-size: 24rpx; color: #999; }
|
||
.info-panel-body { padding: 0 24rpx 24rpx; }
|
||
.info-row {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
padding: 16rpx 0; border-top: 1rpx solid #f5f5f5;
|
||
}
|
||
.info-label { font-size: 26rpx; color: #999; }
|
||
.info-value { font-size: 26rpx; color: #333; }
|
||
|
||
/* === 二维码区域 === */
|
||
.qrcode-section {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
padding: 20rpx 24rpx 40rpx;
|
||
}
|
||
.qrcode-box {
|
||
width: 400rpx; height: 400rpx; background: #fff;
|
||
border-radius: 20rpx; display: flex; align-items: center;
|
||
justify-content: center; margin-bottom: 16rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
|
||
}
|
||
.qrcode-placeholder {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
}
|
||
.qrcode-placeholder-text { font-size: 32rpx; color: #333; font-weight: bold; }
|
||
.qrcode-placeholder-sub { font-size: 24rpx; color: #999; margin-top: 8rpx; }
|
||
.qrcode-countdown { font-size: 24rpx; color: #999; }
|
||
|
||
/* === 功能入口 === */
|
||
.feature-grid {
|
||
display: flex; justify-content: space-around;
|
||
padding: 20rpx 40rpx 40rpx;
|
||
}
|
||
.feature-item {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
}
|
||
.feature-icon-wrap {
|
||
width: 100rpx; height: 100rpx; border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
.feature-icon-reserve { background: #E3F2FD; }
|
||
.feature-icon-history { background: #FFF3E0; }
|
||
.feature-icon-spots { background: #E8F5E9; }
|
||
.feature-icon { font-size: 44rpx; }
|
||
.feature-label { font-size: 24rpx; color: #666; }
|
||
</style>
|