- P0: uni.scss 全局 Token 修正(字体SourceHanSerifCN、色彩#111/#333/#666/#999/#eeeeee、删除600字重) - P0: 分包6页面色彩迁移(#4A90D9→#3c9cff、#E74C3C→#e53935、圆角统一24rpx) - P1: qrcode页面全量SCSS迁移(12个硬编码色值替换、emoji图标替换、圆角统一) - P1: index页面价格色修正(#f9ae3d→#e53935、按钮主色统一、硬编码字号对齐) - P2: 10个页面字号/圆角/字体统一(notice-list/scenic/login/profile/faq/card-bind等) - P3: 全局 font-weight:600 替换为500/700(5处),删除semibold变量和工具类 - 新增 -size-title: 38rpx Token - 构建验证通过 Co-Authored-By: Claude <noreply@anthropic.com>
413 lines
13 KiB
Vue
413 lines
13 KiB
Vue
<template>
|
||
<view class="page">
|
||
<!-- 未持卡状态 -->
|
||
<view class="empty-state" v-if="!hasCard">
|
||
<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="goBuyCard">
|
||
<text class="empty-btn-text">去购买</text>
|
||
</view>
|
||
<view class="empty-btn-secondary" @click="goBindCard">
|
||
<text class="empty-btn-secondary-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="24rpx"
|
||
next-margin="24rpx"
|
||
>
|
||
<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"
|
||
/>
|
||
<!-- 多卡指示点(卡内右下角) -->
|
||
<view class="card-dots" v-if="userCards.length > 1">
|
||
<view
|
||
v-for="(_, idx) in userCards"
|
||
:key="idx"
|
||
class="card-dot"
|
||
:class="{ active: idx === currentCardIndex }"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
</view>
|
||
|
||
<!-- 功能入口 -->
|
||
<view class="feature-panel">
|
||
<view class="feature-grid">
|
||
<view class="feature-item" v-for="item in featureActions" :key="item.label" @click="onFeatureClick(item)">
|
||
<image class="feature-icon-img" :src="item.icon" mode="aspectFit" />
|
||
<text class="feature-label">{{ item.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 入园码二维码 -->
|
||
<view class="qrcode-section">
|
||
<text class="qrcode-title">入园码</text>
|
||
<view class="qrcode-box">
|
||
<image
|
||
class="qrcode-img"
|
||
src="/static/qr-demo.png"
|
||
mode="aspectFit"
|
||
/>
|
||
</view>
|
||
<view class="qrcode-countdown">
|
||
<view class="countdown-icon" />
|
||
<text class="countdown-text">{{ countdown }}秒后自动刷新</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 持卡人信息(页面底部) -->
|
||
<view class="holder-panel">
|
||
<view class="holder-header">
|
||
<view class="avatar-wrap" @click="previewAvatar">
|
||
<image
|
||
class="holder-avatar"
|
||
src="https://hn12301.oss-cn-shenzhen.aliyuncs.com/hn12301/1782285689132_1782285690033.png"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="avatar-zoom">
|
||
<view class="zoom-icon" />
|
||
</view>
|
||
</view>
|
||
<view class="holder-main">
|
||
<text class="holder-name">{{ currentCard.userName }}</text>
|
||
<text class="holder-tag">已激活</text>
|
||
</view>
|
||
</view>
|
||
<view class="holder-details">
|
||
<view class="detail-row">
|
||
<text class="detail-label">卡种名称</text>
|
||
<text class="detail-value">{{ currentCard.cardName }}</text>
|
||
</view>
|
||
<view class="detail-row">
|
||
<text class="detail-label">旅游卡卡号</text>
|
||
<view class="detail-value-wrap">
|
||
<text class="detail-value">PX00002489</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>
|
||
</view>
|
||
<view class="detail-row">
|
||
<text class="detail-label">证件类型</text>
|
||
<text class="detail-value">{{ currentCard.idType }}</text>
|
||
</view>
|
||
<view class="detail-row">
|
||
<text class="detail-label">证件号码</text>
|
||
<text class="detail-value">{{ currentCard.idNumber }}</text>
|
||
</view>
|
||
<view class="detail-row">
|
||
<text class="detail-label">手机号码</text>
|
||
<text class="detail-value">{{ currentCard.phone }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 底部占位(滑到底时与tabBar的间隔) -->
|
||
<view style="height: 48rpx;" />
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||
import { useUserStore } from '../../store/user'
|
||
|
||
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 hasCard = computed(() => userCards.value.length > 0)
|
||
const currentCardIndex = ref(0)
|
||
const currentCard = computed(() => userCards.value[currentCardIndex.value])
|
||
const countdown = ref(60)
|
||
let timer: ReturnType<typeof setInterval> | null = null
|
||
|
||
function onCardChange(e: any) {
|
||
currentCardIndex.value = e.detail.current
|
||
resetCountdown()
|
||
}
|
||
|
||
function previewAvatar() {
|
||
uni.previewImage({
|
||
urls: ['https://hn12301.oss-cn-shenzhen.aliyuncs.com/hn12301/1782285689132_1782285690033.png'],
|
||
current: 0,
|
||
})
|
||
}
|
||
|
||
function resetCountdown() {
|
||
countdown.value = 60
|
||
}
|
||
|
||
function startTimer() {
|
||
timer = setInterval(() => {
|
||
countdown.value--
|
||
if (countdown.value <= 0) {
|
||
resetCountdown()
|
||
}
|
||
}, 1000)
|
||
}
|
||
|
||
function goBuyCard() {
|
||
uni.navigateTo({ url: '/packages/user/pages/card/list' })
|
||
}
|
||
|
||
function copyCardNo() {
|
||
uni.setClipboardData({
|
||
data: 'PX00002489',
|
||
success: () => { uni.showToast({ title: '已复制', icon: 'success' }) },
|
||
})
|
||
}
|
||
|
||
function goBindCard() {
|
||
uni.navigateTo({ url: '/pages/card-bind/card-bind' })
|
||
}
|
||
|
||
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' },
|
||
]
|
||
|
||
function onFeatureClick(item: { label: string }) {
|
||
if (item.label === '我要绑定') {
|
||
uni.navigateTo({ url: '/pages/card-bind/card-bind' })
|
||
return
|
||
}
|
||
if (item.label === '我要预约') {
|
||
uni.navigateTo({ url: '/pages/scenic-list/scenic-list' })
|
||
return
|
||
}
|
||
if (item.label === '使用说明') {
|
||
uni.navigateTo({ url: '/pages/usage-guide/usage-guide' })
|
||
return
|
||
}
|
||
if (item.label === '使用记录') {
|
||
uni.navigateTo({ url: '/pages/reserve/reserve-list' })
|
||
return
|
||
}
|
||
uni.showToast({ title: `${item.label}功能开发中`, icon: 'none' })
|
||
}
|
||
|
||
onMounted(() => { startTimer() })
|
||
onUnmounted(() => { if (timer) clearInterval(timer) })
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
$font: 'SourceHanSerifCN', '思源宋体', -apple-system, 'PingFang SC', sans-serif;
|
||
$primary: #3c9cff;
|
||
|
||
.page { min-height: 100vh; background: #f7f8fa; font-family: $font; }
|
||
.scroll-content { height: 100vh; }
|
||
|
||
/* === 空状态 === */
|
||
.empty-state {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
justify-content: center; min-height: 100vh; padding: 40rpx;
|
||
}
|
||
.empty-icon-card {
|
||
width: 120rpx; height: 80rpx; border-radius: 24rpx;
|
||
background: linear-gradient(135deg, $primary, #398ade);
|
||
display: flex; flex-direction: column; justify-content: center;
|
||
padding: 16rpx; gap: 8rpx; margin-bottom: 32rpx;
|
||
}
|
||
.empty-icon-stripe { height: 8rpx; background: rgba(255,255,255,0.6); border-radius: 4rpx; width: 100%; }
|
||
.empty-icon-stripe.short { width: 60%; }
|
||
.empty-text { font-size: 32rpx; font-weight: 700; color: #111111; margin-bottom: 12rpx; }
|
||
.empty-desc { font-size: 26rpx; color: #666666; margin-bottom: 40rpx; }
|
||
.empty-btn {
|
||
background: $primary; border-radius: 44rpx; padding: 20rpx 80rpx;
|
||
min-height: 88rpx; display: flex; align-items: center; justify-content: center;
|
||
margin-bottom: 20rpx; transition: opacity 150ms;
|
||
}
|
||
.empty-btn:active { opacity: 0.7; }
|
||
.empty-btn-text { font-size: 30rpx; color: #fff; font-weight: 500; }
|
||
.empty-btn-secondary {
|
||
padding: 20rpx 60rpx; border-radius: 44rpx;
|
||
border: 2rpx solid #eeeeee; background: #fff;
|
||
transition: opacity 150ms;
|
||
}
|
||
.empty-btn-secondary:active { opacity: 0.7; }
|
||
.empty-btn-secondary-text { font-size: 28rpx; color: #333333; }
|
||
|
||
/* === 卡面展示区域 === */
|
||
.card-swiper-wrap { margin: 20rpx 20rpx 20rpx; overflow: visible; }
|
||
.card-swiper { height: 440rpx; }
|
||
.card-face {
|
||
margin: 0 12rpx;
|
||
border-radius: 24rpx;
|
||
position: relative; overflow: hidden;
|
||
background: #222222;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.card-bg {
|
||
width: 100%; display: block;
|
||
}
|
||
|
||
/* 指示点(卡内右下角) */
|
||
.card-dots {
|
||
position: absolute; bottom: 48rpx; right: 48rpx; z-index: 2;
|
||
display: flex; gap: 10rpx;
|
||
}
|
||
.card-dot {
|
||
width: 10rpx; height: 10rpx; border-radius: 50%;
|
||
background: rgba(255,255,255,0.4); transition: all 300ms;
|
||
}
|
||
.card-dot.active { background: #fff; width: 22rpx; border-radius: 5rpx; }
|
||
|
||
/* === 持卡人信息 === */
|
||
.holder-panel {
|
||
margin: 0 20rpx; background: #fff;
|
||
border-radius: 24rpx; padding: 28rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(15, 23, 42, 0.06);
|
||
}
|
||
.holder-header {
|
||
display: flex; align-items: center; gap: 20rpx;
|
||
padding-bottom: 24rpx; margin-bottom: 20rpx;
|
||
border-bottom: 1rpx solid #f7f8fa;
|
||
}
|
||
.avatar-wrap { position: relative; width: 96rpx; height: 96rpx; flex-shrink: 0; }
|
||
.holder-avatar {
|
||
width: 96rpx; height: 96rpx; border-radius: 50%;
|
||
border: 3rpx solid #eeeeee;
|
||
}
|
||
.avatar-zoom {
|
||
position: absolute; bottom: -2rpx; right: -2rpx;
|
||
width: 34rpx; height: 34rpx; background: #fff;
|
||
border-radius: 50%; display: flex; align-items: center;
|
||
justify-content: center; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
||
}
|
||
.zoom-icon {
|
||
width: 24rpx; height: 24rpx;
|
||
border: 3rpx solid $primary; border-radius: 50%;
|
||
position: relative;
|
||
}
|
||
.zoom-icon::after {
|
||
content: ''; width: 8rpx; height: 3rpx;
|
||
background: $primary; position: absolute;
|
||
bottom: -4rpx; right: -4rpx;
|
||
transform: rotate(45deg); border-radius: 2rpx;
|
||
}
|
||
.holder-main { flex: 1; }
|
||
.holder-name { font-size: 32rpx; font-weight: 500; color: #111111; display: block; }
|
||
.holder-tag {
|
||
display: inline-block; font-size: 22rpx; color: #00b42a;
|
||
background: #f0fff4; padding: 2rpx 12rpx; border-radius: 6rpx; margin-top: 6rpx;
|
||
}
|
||
.holder-details { display: flex; flex-direction: column; gap: 14rpx; }
|
||
.detail-row { display: flex; justify-content: space-between; align-items: center; }
|
||
.detail-label { font-size: 26rpx; color: #666666; }
|
||
.detail-value-wrap { display: flex; align-items: center; gap: 12rpx; }
|
||
.detail-value { font-size: 26rpx; color: #333333; }
|
||
.copy-btn {
|
||
font-size: 22rpx; color: $primary;
|
||
padding: 4rpx 16rpx; border: 1rpx solid $primary;
|
||
border-radius: 8rpx; transition: opacity 150ms;
|
||
}
|
||
.copy-btn:active { opacity: 0.6; }
|
||
|
||
/* === 二维码区域 === */
|
||
.qrcode-section {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
margin: 0 20rpx 20rpx; background: #fff;
|
||
border-radius: 24rpx; padding: 28rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(15, 23, 42, 0.06);
|
||
}
|
||
.qrcode-title {
|
||
font-size: 30rpx; font-weight: 500; color: #111111;
|
||
display: block; margin-bottom: 20rpx; align-self: flex-start;
|
||
}
|
||
.qrcode-box {
|
||
width: 360rpx; height: 360rpx; background: #f8fafc;
|
||
border-radius: 16rpx; display: flex; align-items: center;
|
||
justify-content: center; border: 1rpx solid #eeeeee;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
.qrcode-img { width: 300rpx; height: 300rpx; }
|
||
.qrcode-countdown { display: flex; align-items: center; gap: 8rpx; }
|
||
.countdown-icon {
|
||
width: 20rpx; height: 20rpx;
|
||
border: 3rpx solid #666666; border-radius: 50%;
|
||
position: relative;
|
||
}
|
||
.countdown-icon::before {
|
||
content: ''; position: absolute;
|
||
width: 3rpx; height: 8rpx; background: #666666;
|
||
top: 2rpx; left: 50%; transform: translateX(-50%);
|
||
border-radius: 1rpx;
|
||
}
|
||
.countdown-text { font-size: 24rpx; color: #666666; }
|
||
|
||
/* === 功能入口 === */
|
||
.feature-panel {
|
||
margin: 0 20rpx 20rpx; background: #fff;
|
||
border-radius: 24rpx; padding: 28rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(15, 23, 42, 0.06);
|
||
}
|
||
.feature-grid {
|
||
display: flex; justify-content: space-around;
|
||
}
|
||
.feature-item {
|
||
display: flex; flex-direction: column; align-items: center;
|
||
min-width: 110rpx; transition: opacity 150ms;
|
||
}
|
||
.feature-item:active { opacity: 0.6; }
|
||
.feature-icon-img {
|
||
width: 88rpx; height: 88rpx;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
.feature-label { font-size: 24rpx; color: #333333; }
|
||
</style>
|