feat: 旅游卡页面重设计 + uni-app升级 + Sass迁移@use
- 旅游卡页面(qrcode/index): 卡面背景图、持卡人信息直接显示、 头像点击查看大图、绑定入口、入园码二维码、卡号复制按钮 - App.vue: getSystemInfoSync → getDeviceInfo, try/catch兜底 - uni-app 升级到 3.0.0-alpha-5010320260611001 (5.13) - Sass: @import → @use 迁移(main.scss, _mixins.scss, _utilities.scss) - pnpm-workspace.yaml 配置更新 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+20
-20
@@ -37,22 +37,22 @@
|
|||||||
"type-check": "vue-tsc --noEmit"
|
"type-check": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dcloudio/uni-app": "3.0.0-4080420251103001",
|
"@dcloudio/uni-app": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-app-harmony": "3.0.0-4080420251103001",
|
"@dcloudio/uni-app-harmony": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-app-plus": "3.0.0-4080420251103001",
|
"@dcloudio/uni-app-plus": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-components": "3.0.0-4080420251103001",
|
"@dcloudio/uni-components": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-h5": "3.0.0-4080420251103001",
|
"@dcloudio/uni-h5": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-alipay": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-baidu": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-harmony": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-harmony": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-jd": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-jd": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-lark": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-lark": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-qq": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-qq": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-toutiao": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-weixin": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-mp-xhs": "3.0.0-4080420251103001",
|
"@dcloudio/uni-mp-xhs": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4080420251103001",
|
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-ui": "^1.5.12",
|
"@dcloudio/uni-ui": "^1.5.12",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.38",
|
"vue": "^3.5.38",
|
||||||
@@ -60,10 +60,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dcloudio/types": "^3.4.8",
|
"@dcloudio/types": "^3.4.8",
|
||||||
"@dcloudio/uni-automator": "3.0.0-4080420251103001",
|
"@dcloudio/uni-automator": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
|
"@dcloudio/uni-cli-shared": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/uni-stacktracey": "3.0.0-4080420251103001",
|
"@dcloudio/uni-stacktracey": "3.0.0-alpha-5010320260611001",
|
||||||
"@dcloudio/vite-plugin-uni": "3.0.0-4080420251103001",
|
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-5010320260611001",
|
||||||
"@vue/runtime-core": "^3.5.38",
|
"@vue/runtime-core": "^3.5.38",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
"sass": "^1.101.0",
|
"sass": "^1.101.0",
|
||||||
|
|||||||
Generated
+315
-277
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
allowBuilds:
|
allowBuilds:
|
||||||
'@parcel/watcher': true
|
'@parcel/watcher': true
|
||||||
core-js: true
|
core-js: true
|
||||||
|
core-js-pure: set this to true or false
|
||||||
esbuild: true
|
esbuild: true
|
||||||
|
|||||||
+17
-13
@@ -5,18 +5,22 @@ import { useUserStore } from './store/user'
|
|||||||
onLaunch(() => {
|
onLaunch(() => {
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
// 仅真机检查隐私协议,模拟器中跳过(getPrivacySetting 在模拟器会触发框架级 timeout)
|
// 仅真机检查隐私协议,模拟器中跳过(getPrivacySetting 在模拟器会触发框架级 timeout)
|
||||||
const sysInfo = uni.getSystemInfoSync()
|
try {
|
||||||
if (sysInfo.platform !== 'devtools' && wx.getPrivacySetting) {
|
const deviceInfo = wx.getDeviceInfo()
|
||||||
wx.getPrivacySetting({
|
if (deviceInfo.platform !== 'devtools' && typeof wx.getPrivacySetting === 'function') {
|
||||||
success: (res: any) => {
|
wx.getPrivacySetting({
|
||||||
if (res.needAuthorization) {
|
success: (res: any) => {
|
||||||
uni.setStorageSync('needPrivacy', true)
|
if (res.needAuthorization) {
|
||||||
} else {
|
uni.setStorageSync('needPrivacy', true)
|
||||||
uni.removeStorageSync('needPrivacy')
|
} else {
|
||||||
}
|
uni.removeStorageSync('needPrivacy')
|
||||||
},
|
}
|
||||||
fail: () => {},
|
},
|
||||||
})
|
fail: () => {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (_e) {
|
||||||
|
// wx.getDeviceInfo 或 wx.getPrivacySetting 不支持时静默跳过
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
@@ -31,7 +35,7 @@ onLaunch(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import './styles/main.scss';
|
@use './styles/main.scss' as *;
|
||||||
|
|
||||||
page {
|
page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|||||||
+252
-221
@@ -7,116 +7,143 @@
|
|||||||
<view class="empty-icon-stripe short" />
|
<view class="empty-icon-stripe short" />
|
||||||
</view>
|
</view>
|
||||||
<text class="empty-text">您还没有旅游卡</text>
|
<text class="empty-text">您还没有旅游卡</text>
|
||||||
<text class="empty-desc">购买旅游卡后即可使用入园码</text>
|
<text class="empty-desc">购买或绑定旅游卡后即可使用入园码</text>
|
||||||
<view class="empty-btn" @click="goBuyCard">
|
<view class="empty-btn" @click="goBuyCard">
|
||||||
<text class="empty-btn-text">去购买</text>
|
<text class="empty-btn-text">去购买</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="empty-btn-secondary" @click="goBindCard">
|
||||||
|
<text class="empty-btn-secondary-text">绑定已有旅游卡</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 持卡状态 -->
|
<!-- 持卡状态 -->
|
||||||
<scroll-view scroll-y class="scroll-content" v-else>
|
<scroll-view scroll-y class="scroll-content" v-else>
|
||||||
<!-- 卡面展示区域(左右滑动切换) -->
|
<!-- 卡面展示区域 -->
|
||||||
<view class="card-swiper-wrap">
|
<view class="card-swiper-wrap">
|
||||||
<swiper
|
<swiper
|
||||||
class="card-swiper"
|
class="card-swiper"
|
||||||
:current="currentCardIndex"
|
:current="currentCardIndex"
|
||||||
@change="onCardChange"
|
@change="onCardChange"
|
||||||
previous-margin="20rpx"
|
previous-margin="24rpx"
|
||||||
next-margin="20rpx"
|
next-margin="24rpx"
|
||||||
>
|
>
|
||||||
<swiper-item v-for="(card, index) in userCards" :key="card.id">
|
<swiper-item v-for="(card, index) in userCards" :key="card.id">
|
||||||
<view class="card-face" :style="{ background: card.gradient }">
|
<view class="card-face">
|
||||||
<view class="card-face-top">
|
<image
|
||||||
<text class="card-face-brand">爱上萍乡</text>
|
class="card-bg"
|
||||||
<image class="card-face-logo" src="/static/logo.png" mode="aspectFit" />
|
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"
|
||||||
</view>
|
mode="widthFix"
|
||||||
<text class="card-face-name">{{ card.cardName }}</text>
|
/>
|
||||||
<view class="card-face-user">
|
<!-- 多卡指示点(卡内右下角) -->
|
||||||
<image
|
<view class="card-dots" v-if="userCards.length > 1">
|
||||||
class="card-face-avatar"
|
<view
|
||||||
:src="userStore.userInfo?.avatarUrl || ''"
|
v-for="(_, idx) in userCards"
|
||||||
mode="aspectFill"
|
:key="idx"
|
||||||
|
class="card-dot"
|
||||||
|
:class="{ active: idx === currentCardIndex }"
|
||||||
/>
|
/>
|
||||||
<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>
|
||||||
</view>
|
</view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</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">
|
|
||||||
<view class="info-panel-icon-wrap">
|
|
||||||
<view class="info-panel-icon-bar" />
|
|
||||||
<view class="info-panel-icon-bar short" />
|
|
||||||
</view>
|
|
||||||
<text class="info-panel-title">持卡信息</text>
|
|
||||||
<view class="info-panel-arrow" :class="{ expanded: infoExpanded }" />
|
|
||||||
</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>
|
|
||||||
<view class="qrcode-countdown">
|
|
||||||
<view class="countdown-icon" />
|
|
||||||
<text>{{ countdown }}秒后自动刷新</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 功能入口 -->
|
<!-- 功能入口 -->
|
||||||
<view class="feature-grid">
|
<view class="feature-panel">
|
||||||
<view class="feature-item" @click="onFeature('reserve')">
|
<view class="feature-grid">
|
||||||
<view class="feature-icon-wrap feature-icon-reserve">
|
<view class="feature-item" @click="goBindCard">
|
||||||
<view class="fi-calendar" />
|
<view class="feature-icon-wrap">
|
||||||
|
<view class="fi-link" />
|
||||||
|
</view>
|
||||||
|
<text class="feature-label">绑定旅游卡</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="feature-label">景区预约</text>
|
<view class="feature-item" @click="onFeature('reserve')">
|
||||||
</view>
|
<view class="feature-icon-wrap">
|
||||||
<view class="feature-item" @click="onFeature('history')">
|
<view class="fi-calendar" />
|
||||||
<view class="feature-icon-wrap feature-icon-history">
|
</view>
|
||||||
<view class="fi-list" />
|
<text class="feature-label">景区预约</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="feature-label">使用记录</text>
|
<view class="feature-item" @click="onFeature('history')">
|
||||||
</view>
|
<view class="feature-icon-wrap">
|
||||||
<view class="feature-item" @click="onFeature('spots')">
|
<view class="fi-list" />
|
||||||
<view class="feature-icon-wrap feature-icon-spots">
|
</view>
|
||||||
<view class="fi-mountain" />
|
<text class="feature-label">使用记录</text>
|
||||||
|
</view>
|
||||||
|
<view class="feature-item" @click="onFeature('spots')">
|
||||||
|
<view class="feature-icon-wrap">
|
||||||
|
<view class="fi-mountain" />
|
||||||
|
</view>
|
||||||
|
<text class="feature-label">权益景区</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="feature-label">权益景区</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 入园码二维码 -->
|
||||||
|
<view class="qrcode-section">
|
||||||
|
<text class="qrcode-title">入园码</text>
|
||||||
|
<view class="qrcode-box">
|
||||||
|
<image
|
||||||
|
class="qrcode-img"
|
||||||
|
src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/QR_code_of_Chinese_Wikipedia_main_page_20131019.svg/250px-QR_code_of_Chinese_Wikipedia_main_page_20131019.svg.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">
|
||||||
|
<text class="zoom-icon">🔍</text>
|
||||||
|
</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>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -127,13 +154,12 @@ import { useUserStore } from '../../store/user'
|
|||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
/** 用户卡片 mock 数据(后续接 API) */
|
|
||||||
interface UserCardData {
|
interface UserCardData {
|
||||||
id: string
|
id: string
|
||||||
cardName: string
|
cardName: string
|
||||||
|
cardNo: string
|
||||||
userName: string
|
userName: string
|
||||||
expireDate: string
|
expireDate: string
|
||||||
gradient: string
|
|
||||||
idType: string
|
idType: string
|
||||||
idNumber: string
|
idNumber: string
|
||||||
phone: string
|
phone: string
|
||||||
@@ -143,49 +169,41 @@ const userCards = ref<UserCardData[]>([
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
cardName: '赣湘全域旅游年卡',
|
cardName: '赣湘全域旅游年卡',
|
||||||
|
cardNo: 'HN2026-0001-8888',
|
||||||
userName: '张三',
|
userName: '张三',
|
||||||
expireDate: '2027.06.24',
|
expireDate: '2027.06.24',
|
||||||
gradient: 'linear-gradient(135deg, #1a237e 0%, #283593 40%, #ffd54f 100%)',
|
|
||||||
idType: '身份证',
|
idType: '身份证',
|
||||||
idNumber: '360***1234',
|
idNumber: '360102199001011234',
|
||||||
phone: '138****5678',
|
phone: '13812345678',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
cardName: '超级玩家卡',
|
cardName: '超级玩家卡',
|
||||||
|
cardNo: 'HN2026-0002-6666',
|
||||||
userName: '张三',
|
userName: '张三',
|
||||||
expireDate: '2027.06.24',
|
expireDate: '2027.06.24',
|
||||||
gradient: 'linear-gradient(135deg, #e65100 0%, #bf360c 40%, #7b1fa2 100%)',
|
|
||||||
idType: '身份证',
|
idType: '身份证',
|
||||||
idNumber: '360***1234',
|
idNumber: '360102199001011234',
|
||||||
phone: '138****5678',
|
phone: '13812345678',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
/** 是否有卡(mock:默认有卡) */
|
|
||||||
const hasCard = computed(() => userCards.value.length > 0)
|
const hasCard = computed(() => userCards.value.length > 0)
|
||||||
|
|
||||||
/** 当前选中的卡片索引 */
|
|
||||||
const currentCardIndex = ref(0)
|
const currentCardIndex = ref(0)
|
||||||
|
|
||||||
/** 当前卡片数据 */
|
|
||||||
const currentCard = computed(() => userCards.value[currentCardIndex.value])
|
const currentCard = computed(() => userCards.value[currentCardIndex.value])
|
||||||
|
|
||||||
/** 持卡信息展开状态 */
|
|
||||||
const infoExpanded = ref(false)
|
|
||||||
|
|
||||||
/** 倒计时(60秒) */
|
|
||||||
const countdown = ref(60)
|
const countdown = ref(60)
|
||||||
let timer: ReturnType<typeof setInterval> | null = null
|
let timer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
function onCardChange(e: any) {
|
function onCardChange(e: any) {
|
||||||
currentCardIndex.value = e.detail.current
|
currentCardIndex.value = e.detail.current
|
||||||
infoExpanded.value = false
|
|
||||||
resetCountdown()
|
resetCountdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleInfo() {
|
function previewAvatar() {
|
||||||
infoExpanded.value = !infoExpanded.value
|
uni.previewImage({
|
||||||
|
urls: ['https://hn12301.oss-cn-shenzhen.aliyuncs.com/hn12301/1782285689132_1782285690033.png'],
|
||||||
|
current: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetCountdown() {
|
function resetCountdown() {
|
||||||
@@ -197,7 +215,6 @@ function startTimer() {
|
|||||||
countdown.value--
|
countdown.value--
|
||||||
if (countdown.value <= 0) {
|
if (countdown.value <= 0) {
|
||||||
resetCountdown()
|
resetCountdown()
|
||||||
// TODO: 刷新二维码(Phase 2 接入 API)
|
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
@@ -206,21 +223,27 @@ function goBuyCard() {
|
|||||||
uni.navigateTo({ url: '/packages/user/pages/card/list' })
|
uni.navigateTo({ url: '/packages/user/pages/card/list' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFeature(type: 'reserve' | 'history' | 'spots') {
|
function copyCardNo() {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: 'PX00002489',
|
||||||
|
success: () => { uni.showToast({ title: '已复制', icon: 'success' }) },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBindCard() {
|
||||||
|
uni.navigateTo({ url: '/packages/user/pages/card/bind' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFeature(type: string) {
|
||||||
uni.showToast({ title: '功能开发中', icon: 'none' })
|
uni.showToast({ title: '功能开发中', icon: 'none' })
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => { startTimer() })
|
||||||
startTimer()
|
onUnmounted(() => { if (timer) clearInterval(timer) })
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (timer) clearInterval(timer)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.page { min-height: 100vh; background: #f5f5f5; }
|
.page { min-height: 100vh; background: #F1F5F9; }
|
||||||
.scroll-content { height: 100vh; }
|
.scroll-content { height: 100vh; }
|
||||||
|
|
||||||
/* === 空状态 === */
|
/* === 空状态 === */
|
||||||
@@ -230,184 +253,192 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
.empty-icon-card {
|
.empty-icon-card {
|
||||||
width: 120rpx; height: 80rpx; border-radius: 12rpx;
|
width: 120rpx; height: 80rpx; border-radius: 12rpx;
|
||||||
background: linear-gradient(135deg, #4A90D9, #357ABD);
|
background: linear-gradient(135deg, #0052FF, #4D7CFF);
|
||||||
display: flex; flex-direction: column; justify-content: center;
|
display: flex; flex-direction: column; justify-content: center;
|
||||||
padding: 16rpx; gap: 8rpx; margin-bottom: 32rpx;
|
padding: 16rpx; gap: 8rpx; margin-bottom: 32rpx;
|
||||||
}
|
}
|
||||||
.empty-icon-stripe {
|
.empty-icon-stripe { height: 8rpx; background: rgba(255,255,255,0.6); border-radius: 4rpx; width: 100%; }
|
||||||
height: 8rpx; background: rgba(255,255,255,0.6); border-radius: 4rpx;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.empty-icon-stripe.short { width: 60%; }
|
.empty-icon-stripe.short { width: 60%; }
|
||||||
.empty-text { font-size: 32rpx; font-weight: bold; color: #333; margin-bottom: 12rpx; }
|
.empty-text { font-size: 32rpx; font-weight: 700; color: #0F172A; margin-bottom: 12rpx; }
|
||||||
.empty-desc { font-size: 26rpx; color: #999; margin-bottom: 40rpx; }
|
.empty-desc { font-size: 26rpx; color: #94A3B8; margin-bottom: 40rpx; }
|
||||||
.empty-btn {
|
.empty-btn {
|
||||||
background: linear-gradient(135deg, #4A90D9, #357ABD);
|
background: #0052FF; border-radius: 44rpx; padding: 20rpx 80rpx;
|
||||||
border-radius: 44rpx; padding: 20rpx 80rpx;
|
|
||||||
min-height: 88rpx; display: flex; align-items: center; justify-content: center;
|
min-height: 88rpx; display: flex; align-items: center; justify-content: center;
|
||||||
transition: opacity 0.2s;
|
margin-bottom: 20rpx; transition: opacity 150ms;
|
||||||
}
|
}
|
||||||
.empty-btn:active { opacity: 0.7; }
|
.empty-btn:active { opacity: 0.7; }
|
||||||
.empty-btn-text { font-size: 30rpx; color: #fff; font-weight: bold; }
|
.empty-btn-text { font-size: 30rpx; color: #fff; font-weight: 600; }
|
||||||
|
.empty-btn-secondary {
|
||||||
|
padding: 20rpx 60rpx; border-radius: 44rpx;
|
||||||
|
border: 2rpx solid #E2E8F0; background: #fff;
|
||||||
|
transition: opacity 150ms;
|
||||||
|
}
|
||||||
|
.empty-btn-secondary:active { opacity: 0.7; }
|
||||||
|
.empty-btn-secondary-text { font-size: 28rpx; color: #475569; }
|
||||||
|
|
||||||
/* === 卡面展示区域 === */
|
/* === 卡面展示区域 === */
|
||||||
.card-swiper-wrap { padding: 30rpx 0 16rpx; }
|
.card-swiper-wrap { margin: 20rpx 20rpx 20rpx; overflow: visible; }
|
||||||
.card-swiper { height: 420rpx; }
|
.card-swiper { height: 440rpx; }
|
||||||
|
|
||||||
.card-face {
|
.card-face {
|
||||||
margin: 0 20rpx; height: 380rpx; border-radius: 24rpx;
|
margin: 0 12rpx;
|
||||||
padding: 32rpx; position: relative; overflow: hidden;
|
border-radius: 20rpx;
|
||||||
display: flex; flex-direction: column; justify-content: space-between;
|
position: relative; overflow: hidden;
|
||||||
|
background: #1E293B;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
}
|
}
|
||||||
.card-face-top {
|
.card-bg {
|
||||||
display: flex; justify-content: space-between; align-items: center;
|
width: 100%; display: block;
|
||||||
}
|
}
|
||||||
.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-dots {
|
||||||
|
position: absolute; bottom: 48rpx; right: 48rpx; z-index: 2;
|
||||||
|
display: flex; gap: 10rpx;
|
||||||
|
}
|
||||||
.card-dot {
|
.card-dot {
|
||||||
width: 12rpx; height: 12rpx; border-radius: 50%;
|
width: 10rpx; height: 10rpx; border-radius: 50%;
|
||||||
background: #ddd; transition: all 0.3s;
|
background: rgba(255,255,255,0.4); transition: all 300ms;
|
||||||
}
|
}
|
||||||
.card-dot.active { background: #4A90D9; width: 24rpx; border-radius: 6rpx; }
|
.card-dot.active { background: #fff; width: 22rpx; border-radius: 5rpx; }
|
||||||
|
|
||||||
/* === 持卡信息 === */
|
/* === 持卡人信息 === */
|
||||||
.info-panel {
|
.holder-panel {
|
||||||
margin: 0 24rpx 20rpx; background: #fff;
|
margin: 0 20rpx; background: #fff;
|
||||||
border-radius: 16rpx; overflow: hidden;
|
border-radius: 20rpx; padding: 28rpx;
|
||||||
transition: opacity 0.2s;
|
box-shadow: 0 4rpx 20rpx rgba(15, 23, 42, 0.06);
|
||||||
}
|
}
|
||||||
.info-panel:active { opacity: 0.7; }
|
.holder-header {
|
||||||
.info-panel-header {
|
display: flex; align-items: center; gap: 20rpx;
|
||||||
display: flex; align-items: center; padding: 24rpx;
|
padding-bottom: 24rpx; margin-bottom: 20rpx;
|
||||||
|
border-bottom: 1rpx solid #F1F5F9;
|
||||||
}
|
}
|
||||||
.info-panel-icon-wrap {
|
.avatar-wrap { position: relative; width: 96rpx; height: 96rpx; flex-shrink: 0; }
|
||||||
width: 32rpx; height: 28rpx; margin-right: 12rpx;
|
.holder-avatar {
|
||||||
display: flex; flex-direction: column; gap: 6rpx;
|
width: 96rpx; height: 96rpx; border-radius: 50%;
|
||||||
|
border: 3rpx solid #E2E8F0;
|
||||||
}
|
}
|
||||||
.info-panel-icon-bar {
|
.avatar-zoom {
|
||||||
height: 4rpx; background: #666; border-radius: 2rpx; width: 100%;
|
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);
|
||||||
}
|
}
|
||||||
.info-panel-icon-bar.short { width: 60%; }
|
.zoom-icon { font-size: 18rpx; }
|
||||||
.info-panel-title { flex: 1; font-size: 28rpx; color: #333; font-weight: 500; }
|
.holder-main { flex: 1; }
|
||||||
.info-panel-arrow {
|
.holder-name { font-size: 32rpx; font-weight: 600; color: #0F172A; display: block; }
|
||||||
width: 14rpx; height: 14rpx;
|
.holder-tag {
|
||||||
border-right: 3rpx solid #999;
|
display: inline-block; font-size: 22rpx; color: #10B981;
|
||||||
border-bottom: 3rpx solid #999;
|
background: #F0FDF4; padding: 2rpx 12rpx; border-radius: 6rpx; margin-top: 6rpx;
|
||||||
transform: rotate(45deg);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
}
|
||||||
.info-panel-arrow.expanded {
|
.holder-details { display: flex; flex-direction: column; gap: 14rpx; }
|
||||||
transform: rotate(-135deg);
|
.detail-row { display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.detail-label { font-size: 26rpx; color: #94A3B8; }
|
||||||
|
.detail-value-wrap { display: flex; align-items: center; gap: 12rpx; }
|
||||||
|
.detail-value { font-size: 26rpx; color: #334155; }
|
||||||
|
.copy-btn {
|
||||||
|
font-size: 22rpx; color: #0052FF;
|
||||||
|
padding: 4rpx 16rpx; border: 1rpx solid #0052FF;
|
||||||
|
border-radius: 8rpx; transition: opacity 150ms;
|
||||||
}
|
}
|
||||||
.info-panel-body { padding: 0 24rpx 24rpx; }
|
.copy-btn:active { opacity: 0.6; }
|
||||||
.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 {
|
.qrcode-section {
|
||||||
display: flex; flex-direction: column; align-items: center;
|
display: flex; flex-direction: column; align-items: center;
|
||||||
padding: 20rpx 24rpx 40rpx;
|
margin: 0 20rpx 20rpx; background: #fff;
|
||||||
|
border-radius: 20rpx; padding: 28rpx;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
.qrcode-title {
|
||||||
|
font-size: 30rpx; font-weight: 600; color: #0F172A;
|
||||||
|
display: block; margin-bottom: 20rpx; align-self: flex-start;
|
||||||
}
|
}
|
||||||
.qrcode-box {
|
.qrcode-box {
|
||||||
width: 400rpx; height: 400rpx; background: #fff;
|
width: 360rpx; height: 360rpx; background: #F8FAFC;
|
||||||
border-radius: 20rpx; display: flex; align-items: center;
|
border-radius: 16rpx; display: flex; align-items: center;
|
||||||
justify-content: center; margin-bottom: 16rpx;
|
justify-content: center; border: 1rpx solid #E2E8F0;
|
||||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
|
margin-bottom: 16rpx;
|
||||||
}
|
|
||||||
.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 {
|
|
||||||
display: flex; align-items: center; gap: 8rpx;
|
|
||||||
font-size: 24rpx; color: #999;
|
|
||||||
}
|
}
|
||||||
|
.qrcode-img { width: 300rpx; height: 300rpx; }
|
||||||
|
.qrcode-countdown { display: flex; align-items: center; gap: 8rpx; }
|
||||||
.countdown-icon {
|
.countdown-icon {
|
||||||
width: 20rpx; height: 20rpx;
|
width: 20rpx; height: 20rpx;
|
||||||
border: 3rpx solid #999; border-radius: 50%;
|
border: 3rpx solid #94A3B8; border-radius: 50%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.countdown-icon::before {
|
.countdown-icon::before {
|
||||||
content: ''; position: absolute;
|
content: ''; position: absolute;
|
||||||
width: 3rpx; height: 8rpx; background: #999;
|
width: 3rpx; height: 8rpx; background: #94A3B8;
|
||||||
top: 3rpx; left: 50%; transform: translateX(-50%);
|
top: 2rpx; left: 50%; transform: translateX(-50%);
|
||||||
border-radius: 1rpx;
|
border-radius: 1rpx;
|
||||||
}
|
}
|
||||||
|
.countdown-text { font-size: 24rpx; color: #94A3B8; }
|
||||||
|
|
||||||
/* === 功能入口 === */
|
/* === 功能入口 === */
|
||||||
|
.feature-panel {
|
||||||
|
margin: 0 20rpx 20rpx; background: #fff;
|
||||||
|
border-radius: 20rpx; padding: 28rpx;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
.feature-grid {
|
.feature-grid {
|
||||||
display: flex; justify-content: space-around;
|
display: flex; justify-content: space-around;
|
||||||
padding: 20rpx 40rpx 40rpx;
|
|
||||||
}
|
}
|
||||||
.feature-item {
|
.feature-item {
|
||||||
display: flex; flex-direction: column; align-items: center;
|
display: flex; flex-direction: column; align-items: center;
|
||||||
min-width: 120rpx;
|
min-width: 110rpx; transition: opacity 150ms;
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
}
|
||||||
.feature-item:active { opacity: 0.6; }
|
.feature-item:active { opacity: 0.6; }
|
||||||
.feature-icon-wrap {
|
.feature-icon-wrap {
|
||||||
width: 100rpx; height: 100rpx; border-radius: 50%;
|
width: 88rpx; height: 88rpx; border-radius: 20rpx;
|
||||||
|
background: #F1F5F9;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
margin-bottom: 12rpx;
|
margin-bottom: 10rpx;
|
||||||
|
transition: background 150ms;
|
||||||
}
|
}
|
||||||
.feature-icon-reserve { background: #E3F2FD; }
|
.feature-item:active .feature-icon-wrap { background: #E2E8F0; }
|
||||||
.feature-icon-history { background: #FFF3E0; }
|
.feature-label { font-size: 24rpx; color: #475569; }
|
||||||
.feature-icon-spots { background: #E8F5E9; }
|
|
||||||
.feature-label { font-size: 24rpx; color: #666; }
|
|
||||||
|
|
||||||
/* CSS 图标:日历 */
|
/* CSS 图标 — 统一使用页面主色 #0052FF */
|
||||||
|
.fi-link {
|
||||||
|
width: 24rpx; height: 24rpx;
|
||||||
|
border: 3rpx solid #0052FF; border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.fi-link::before {
|
||||||
|
content: ''; position: absolute;
|
||||||
|
top: 50%; left: 50%;
|
||||||
|
width: 10rpx; height: 10rpx;
|
||||||
|
background: #0052FF; border-radius: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
.fi-calendar {
|
.fi-calendar {
|
||||||
width: 28rpx; height: 28rpx;
|
width: 28rpx; height: 28rpx;
|
||||||
border: 3rpx solid #1976D2; border-radius: 4rpx;
|
border: 3rpx solid #0052FF; border-radius: 4rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.fi-calendar::before {
|
.fi-calendar::before {
|
||||||
content: ''; position: absolute;
|
content: ''; position: absolute;
|
||||||
top: 10rpx; left: 4rpx; right: 4rpx;
|
top: 10rpx; left: 4rpx; right: 4rpx;
|
||||||
height: 3rpx; background: #1976D2;
|
height: 3rpx; background: #0052FF;
|
||||||
}
|
}
|
||||||
.fi-calendar::after {
|
.fi-calendar::after {
|
||||||
content: ''; position: absolute;
|
content: ''; position: absolute;
|
||||||
top: -6rpx; left: 6rpx;
|
top: -6rpx; left: 6rpx;
|
||||||
width: 3rpx; height: 10rpx; background: #1976D2;
|
width: 3rpx; height: 10rpx; background: #0052FF;
|
||||||
border-radius: 1rpx;
|
border-radius: 1rpx;
|
||||||
box-shadow: 10rpx 0 0 #1976D2;
|
box-shadow: 10rpx 0 0 #0052FF;
|
||||||
}
|
|
||||||
|
|
||||||
/* CSS 图标:列表 */
|
|
||||||
.fi-list {
|
|
||||||
width: 28rpx; display: flex; flex-direction: column; gap: 6rpx;
|
|
||||||
}
|
}
|
||||||
|
.fi-list { width: 28rpx; display: flex; flex-direction: column; gap: 6rpx; }
|
||||||
.fi-list::before, .fi-list::after {
|
.fi-list::before, .fi-list::after {
|
||||||
content: ''; height: 3rpx; background: #E65100; border-radius: 1rpx;
|
content: ''; height: 3rpx; background: #0052FF; border-radius: 1rpx;
|
||||||
}
|
}
|
||||||
.fi-list::before { width: 100%; }
|
.fi-list::before { width: 100%; }
|
||||||
.fi-list::after { width: 70%; }
|
.fi-list::after { width: 70%; }
|
||||||
|
|
||||||
/* CSS 图标:山峰 */
|
|
||||||
.fi-mountain {
|
.fi-mountain {
|
||||||
width: 0; height: 0;
|
width: 0; height: 0;
|
||||||
border-left: 16rpx solid transparent;
|
border-left: 16rpx solid transparent;
|
||||||
border-right: 16rpx solid transparent;
|
border-right: 16rpx solid transparent;
|
||||||
border-bottom: 24rpx solid #2E7D32;
|
border-bottom: 24rpx solid #0052FF;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.fi-mountain::after {
|
.fi-mountain::after {
|
||||||
@@ -416,6 +447,6 @@ onUnmounted(() => {
|
|||||||
width: 0; height: 0;
|
width: 0; height: 0;
|
||||||
border-left: 10rpx solid transparent;
|
border-left: 10rpx solid transparent;
|
||||||
border-right: 10rpx solid transparent;
|
border-right: 10rpx solid transparent;
|
||||||
border-bottom: 16rpx solid #43A047;
|
border-bottom: 16rpx solid #4D7CFF;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// ============================================
|
// ============================================
|
||||||
// SCSS Mixins
|
// SCSS Mixins
|
||||||
// ============================================
|
// ============================================
|
||||||
|
@use 'tokens' as *;
|
||||||
|
|
||||||
// === FLEX ===
|
// === FLEX ===
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// ============================================
|
// ============================================
|
||||||
// Utility Classes
|
// Utility Classes
|
||||||
// ============================================
|
// ============================================
|
||||||
|
@use 'tokens' as *;
|
||||||
|
|
||||||
// === TEXT SIZE ===
|
// === TEXT SIZE ===
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
// Imported globally via App.vue <style lang="scss">
|
// Imported globally via App.vue <style lang="scss">
|
||||||
// Token variables are separately injected via uni.scss
|
// Token variables are separately injected via uni.scss
|
||||||
|
|
||||||
@import 'tokens';
|
@use 'tokens' as *;
|
||||||
@import 'mixins';
|
@use 'mixins' as *;
|
||||||
@import 'animations';
|
@use 'animations' as *;
|
||||||
@import 'utilities';
|
@use 'utilities' as *;
|
||||||
|
|
||||||
// === GLOBAL BASE ===
|
// === GLOBAL BASE ===
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user