1
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<view class="me">
|
||||
<view v-if="!isLoggedIn" class="card login">
|
||||
<view class="login-title">登录/注册以同步数据</view>
|
||||
<button class="login-btn" type="primary" @click="goLogin">登录</button>
|
||||
<button class="login-btn minor" @click="goRegister">注册</button>
|
||||
</view>
|
||||
<view class="card user">
|
||||
<image class="avatar" :src="avatarUrl" mode="aspectFill" @error="onAvatarError" />
|
||||
<view class="meta">
|
||||
@@ -9,6 +14,24 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- VIP 卡片(置于“会员与订单”分组上方) -->
|
||||
<view class="card vip" :class="{ active: vipIsVip }">
|
||||
<view class="vip-row">
|
||||
<text class="vip-badge">{{ vipIsVip ? 'VIP' : '非VIP' }}</text>
|
||||
<text class="vip-title">会员状态</text>
|
||||
</view>
|
||||
<view class="vip-meta">
|
||||
<view class="item">
|
||||
<text class="label">开始</text>
|
||||
<text class="value">{{ vipStartDisplay }}</text>
|
||||
</view>
|
||||
<view class="item">
|
||||
<text class="label">结束</text>
|
||||
<text class="value">{{ vipEndDisplay }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="group">
|
||||
<view class="group-title">会员与订单</view>
|
||||
<view class="cell" @click="goVip">
|
||||
@@ -61,9 +84,9 @@
|
||||
<text>关于与协议</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="cell danger" @click="logout">
|
||||
<text>退出登录</text>
|
||||
</view>
|
||||
<view v-if="isLoggedIn" class="cell danger" @click="logout">
|
||||
<text>退出登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -75,24 +98,47 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
avatarUrl: '/static/logo.png',
|
||||
shopName: '我的店铺',
|
||||
mobile: ''
|
||||
shopName: '未登录',
|
||||
mobile: '',
|
||||
pendingJsCode: '',
|
||||
logging: false,
|
||||
vipIsVip: false,
|
||||
vipStart: '',
|
||||
vipEnd: ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.fetchProfile()
|
||||
onShow() {
|
||||
this.fetchProfile()
|
||||
this.loadVipFromStorage()
|
||||
try {
|
||||
if (uni.getStorageSync('TOKEN')) {
|
||||
// 已登录时刷新资料并隐藏登录卡片
|
||||
this.$forceUpdate && this.$forceUpdate()
|
||||
}
|
||||
} catch(e) {}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() { try { return !!uni.getStorageSync('TOKEN') } catch(e){ return false } },
|
||||
mobileDisplay() {
|
||||
const m = String(this.mobile || '')
|
||||
return m.length === 11 ? m.slice(0,3) + '****' + m.slice(7) : (m || '未绑定手机号')
|
||||
}
|
||||
},
|
||||
vipStartDisplay() { return this.formatDisplay(this.vipStart) },
|
||||
vipEndDisplay() { return this.formatDisplay(this.vipEnd) }
|
||||
},
|
||||
methods: {
|
||||
// 登录相关方法已移除
|
||||
async fetchProfile() {
|
||||
// 后端暂无专门店铺/用户信息接口,先使用概览接口作为在线性检测与占位数据来源
|
||||
// 未登录则不触发任何用户/店铺接口,也不加载本地用户字段
|
||||
const hasToken = (() => { try { return !!uni.getStorageSync('TOKEN') } catch(e){ return false } })()
|
||||
if (!hasToken) {
|
||||
this.shopName = '未登录'
|
||||
this.avatarUrl = '/static/logo.png'
|
||||
this.mobile = ''
|
||||
return
|
||||
}
|
||||
// 已登录:拉取概览以确认在线状态,并回填本地用户信息
|
||||
try { await get('/api/dashboard/overview') } catch(e) {}
|
||||
// 读取本地可能保存的店铺名与头像
|
||||
try {
|
||||
const storeName = uni.getStorageSync('SHOP_NAME') || ''
|
||||
const avatar = uni.getStorageSync('USER_AVATAR') || ''
|
||||
@@ -102,10 +148,69 @@ export default {
|
||||
this.mobile = phone
|
||||
} catch(e) {}
|
||||
},
|
||||
loadVipFromStorage() {
|
||||
try {
|
||||
const isVip = String(uni.getStorageSync('USER_VIP_IS_VIP') || 'false').toLowerCase() === 'true'
|
||||
const start = uni.getStorageSync('USER_VIP_START') || ''
|
||||
const end = uni.getStorageSync('USER_VIP_END') || ''
|
||||
this.vipIsVip = isVip
|
||||
this.vipStart = start
|
||||
this.vipEnd = end
|
||||
} catch(e) {}
|
||||
},
|
||||
formatDisplay(value) {
|
||||
if (!value) return '-'
|
||||
const s = String(value)
|
||||
// 简单规范化:只保留到分钟
|
||||
const m = s.match(/^(\d{4}-\d{2}-\d{2})([ T](\d{2}:\d{2}))/)
|
||||
if (m) return `${m[1]} ${m[3]}`
|
||||
return s
|
||||
},
|
||||
startLogin() {
|
||||
if (this.logging) return
|
||||
this.logging = true
|
||||
const tryOnce = async () => ({})
|
||||
uni.login({ provider: 'weixin', success: async (res) => {
|
||||
this.pendingJsCode = res.code || ''
|
||||
if (!this.pendingJsCode) { this.logging = false; return uni.showToast({ title: '获取登录code失败', icon: 'none' }) }
|
||||
try {
|
||||
await tryOnce()
|
||||
} catch(e) {
|
||||
const msg = (e && e.message) || ''
|
||||
if (msg.includes('40163') || msg.toLowerCase().includes('been used')) {
|
||||
// 40163:换新 code 再试一次
|
||||
uni.login({ provider: 'weixin', success: async (r2) => {
|
||||
const fresh = r2.code || ''
|
||||
if (!fresh) { this.logging = false; return }
|
||||
try {
|
||||
await tryOnce()
|
||||
} finally { this.logging = false }
|
||||
} })
|
||||
return
|
||||
}
|
||||
} finally {
|
||||
this.logging = false
|
||||
}
|
||||
}, fail: () => { this.logging = false; uni.showToast({ title: '微信登录失败', icon: 'none' }) } })
|
||||
},
|
||||
goLogin(){ uni.navigateTo({ url: '/pages/auth/login' }) },
|
||||
goRegister(){ uni.navigateTo({ url: '/pages/auth/register' }) },
|
||||
onGetPhoneNumber(e) {
|
||||
if (this.logging) return
|
||||
this.logging = true
|
||||
const phoneCode = ''
|
||||
// 为避免 40163(code been used),此处重新获取一次 jsCode
|
||||
uni.login({ provider: 'weixin', success: (res) => {
|
||||
const jsCode = res.code || ''
|
||||
if (!jsCode) { this.logging = false; return uni.showToast({ title: '获取登录code失败', icon: 'none' }) }
|
||||
Promise.resolve().finally(() => { this.logging = false })
|
||||
}, fail: () => { this.logging = false; uni.showToast({ title: '微信登录失败', icon: 'none' }) } })
|
||||
},
|
||||
goSmsLogin(){ uni.navigateTo({ url: '/pages/my/sms-login' }) },
|
||||
onAvatarError() {
|
||||
this.avatarUrl = '/static/logo.png'
|
||||
},
|
||||
goVip() { uni.showToast({ title: 'VIP会员(开发中)', icon: 'none' }) },
|
||||
goVip() { uni.navigateTo({ url: '/pages/my/vip' }) },
|
||||
goMyOrders() { uni.switchTab({ url: '/pages/detail/index' }) },
|
||||
goSupplier() { uni.navigateTo({ url: '/pages/supplier/select' }) },
|
||||
goCustomer() { uni.navigateTo({ url: '/pages/customer/select' }) },
|
||||
@@ -116,17 +221,19 @@ export default {
|
||||
goSystemParams() { uni.showToast({ title: '系统参数(开发中)', icon: 'none' }) },
|
||||
goAbout() { uni.navigateTo({ url: '/pages/my/about' }) },
|
||||
logout() {
|
||||
try {
|
||||
try {
|
||||
uni.removeStorageSync('TOKEN')
|
||||
uni.removeStorageSync('USER_AVATAR')
|
||||
uni.removeStorageSync('USER_NAME')
|
||||
uni.removeStorageSync('USER_MOBILE')
|
||||
uni.removeStorageSync('SHOP_NAME')
|
||||
uni.showToast({ title: '已退出', icon: 'none' })
|
||||
setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }) }, 300)
|
||||
} catch(e) {
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
}
|
||||
uni.removeStorageSync('LOGINED')
|
||||
uni.removeStorageSync('LOGIN_PHONE')
|
||||
uni.removeStorageSync('DEFAULT_USER_ID')
|
||||
uni.setStorageSync('ENABLE_DEFAULT_USER', 'false')
|
||||
uni.removeStorageSync('USER_AVATAR')
|
||||
uni.removeStorageSync('USER_NAME')
|
||||
uni.removeStorageSync('USER_MOBILE')
|
||||
uni.removeStorageSync('SHOP_NAME')
|
||||
uni.showToast({ title: '已清理本地信息', icon: 'none' })
|
||||
setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }) }, 300)
|
||||
} catch(e) { uni.reLaunch({ url: '/pages/index/index' }) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +241,11 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
.me { padding: 24rpx; }
|
||||
.card.login { display: flex; flex-direction: column; gap: 16rpx; padding: 22rpx; background: $uni-bg-color-grey; border-radius: 16rpx; margin-bottom: 24rpx; }
|
||||
.login-title { font-size: 28rpx; font-weight: 700; }
|
||||
.login-btn { }
|
||||
.login-btn.minor { background: $uni-bg-color-hover; color: $uni-text-color; }
|
||||
.hint { font-size: 22rpx; color: $uni-text-color-grey; }
|
||||
.card.user { display: flex; gap: 18rpx; padding: 22rpx; background: $uni-bg-color-grey; border-radius: 16rpx; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.16); align-items: center; }
|
||||
.avatar { width: 120rpx; height: 120rpx; border-radius: 60rpx; background: $uni-bg-color-hover; }
|
||||
.meta { display: flex; flex-direction: column; gap: 6rpx; }
|
||||
@@ -141,12 +253,33 @@ export default {
|
||||
.phone { font-size: 26rpx; color: $uni-text-color-grey; }
|
||||
.role { font-size: 22rpx; color: $uni-text-color-grey; }
|
||||
|
||||
/* VIP 卡片样式 */
|
||||
.card.vip { margin-top: 24rpx; padding: 22rpx; background: $uni-bg-color-grey; border-radius: 16rpx; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.12); }
|
||||
.card.vip.active { border: 1rpx solid rgba(255, 208, 0, 0.6); background-image: radial-gradient(60% 60% at 80% 0%, rgba(255, 214, 0, 0.08), transparent 60%); }
|
||||
.vip-row { display: flex; align-items: center; gap: 12rpx; margin-bottom: 10rpx; }
|
||||
.vip-badge { background: #f1c40f; color: #111; font-weight: 800; padding: 2rpx 10rpx; border-radius: 8rpx; font-size: 22rpx; }
|
||||
.vip-title { font-size: 28rpx; font-weight: 700; color: $uni-text-color; }
|
||||
.vip-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 8rpx 16rpx; }
|
||||
.vip-meta .item { display: flex; align-items: center; gap: 10rpx; }
|
||||
.vip-meta .label { width: 80rpx; color: $uni-text-color-grey; font-size: 24rpx; }
|
||||
.vip-meta .value { color: $uni-text-color; font-size: 26rpx; word-break: break-all; }
|
||||
|
||||
.group { margin-top: 24rpx; background: $uni-bg-color-grey; border-radius: 16rpx; overflow: hidden; }
|
||||
.group-title { padding: 18rpx 22rpx; font-size: 26rpx; color: $uni-text-color-grey; background: $uni-bg-color-hover; }
|
||||
.cell { display: flex; align-items: center; padding: 26rpx 22rpx; border-top: 1rpx solid $uni-border-color; color: $uni-text-color; }
|
||||
.cell .desc { margin-left: auto; margin-right: 8rpx; font-size: 22rpx; color: $uni-text-color-grey; }
|
||||
.cell .arrow { margin-left: auto; color: #99a2b3; }
|
||||
.cell.danger { color: #dd524d; justify-content: center; font-weight: 700; }
|
||||
|
||||
/* 简易对话框样式 */
|
||||
.dialog-mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; z-index: 999; }
|
||||
.dialog { width: 600rpx; background: #fff; border-radius: 16rpx; padding: 24rpx; box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.2); }
|
||||
.dialog-title { font-size: 30rpx; font-weight: 700; margin-bottom: 16rpx; }
|
||||
.dialog-input { width: 100%; height: 72rpx; padding: 0 16rpx; border: 1rpx solid $uni-border-color; border-radius: 10rpx; background: #fff; color: $uni-text-color; }
|
||||
.dialog-actions { display: flex; gap: 16rpx; margin-top: 18rpx; justify-content: flex-end; }
|
||||
.dialog-btn { padding: 16rpx 22rpx; border-radius: 10rpx; }
|
||||
.dialog-btn.cancel { background: $uni-bg-color-hover; color: $uni-text-color; }
|
||||
.dialog-btn.confirm { background: #2979ff; color: #fff; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
144
frontend/pages/my/sms-login.vue
Normal file
144
frontend/pages/my/sms-login.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<view class="page sms-login">
|
||||
<view class="card">
|
||||
<view class="title">短信验证码登录</view>
|
||||
<view class="form">
|
||||
<input class="input" type="number" maxlength="11" placeholder="请输入手机号" v-model="phone"/>
|
||||
<view class="row">
|
||||
<input class="input code" type="number" maxlength="6" placeholder="请输入验证码" v-model="code"/>
|
||||
<button class="send" :disabled="countdown>0 || sending" @click="sendCode">{{ btnText }}</button>
|
||||
</view>
|
||||
<button class="login" type="primary" :disabled="logging" @click="doLogin">登录/注册</button>
|
||||
<button class="login" :disabled="logging" @click="quickRegister">注册为店主</button>
|
||||
</view>
|
||||
<view class="hint">首次登录将自动创建店铺与店主用户。</view>
|
||||
|
||||
<view class="debug">
|
||||
<view class="debug-title" @click="toggleDebug">请求体示例(点击{{ showDebug? '收起':'展开' }})</view>
|
||||
<view v-if="showDebug" class="debug-body">
|
||||
<view class="code-title">POST /api/auth/sms/send</view>
|
||||
<view class="code-wrap">
|
||||
<text class="code">{{ sendBodyJson }}</text>
|
||||
<button size="mini" class="copy" @click="copy(sendBodyJson)">复制</button>
|
||||
</view>
|
||||
<view class="code-title">POST /api/auth/sms/login</view>
|
||||
<view class="code-wrap">
|
||||
<text class="code">{{ loginBodyJson }}</text>
|
||||
<button size="mini" class="copy" @click="copy(loginBodyJson)">复制</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { post } from '../../common/http.js'
|
||||
|
||||
export default {
|
||||
data(){
|
||||
return { phone: '', code: '', countdown: 0, timer: null, sending: false, logging: false, showDebug: true }
|
||||
},
|
||||
computed:{
|
||||
btnText(){ return this.countdown>0 ? `${this.countdown}s` : (this.sending ? '发送中...' : '获取验证码') }
|
||||
,
|
||||
trimmedPhone(){ return String(this.phone||'').trim() },
|
||||
sendBodyJson(){ return JSON.stringify({ phone: this.trimmedPhone, scene: 'login' }, null, 2) },
|
||||
loginBodyJson(){ return JSON.stringify({ phone: this.trimmedPhone, code: String(this.code||'').trim() }, null, 2) }
|
||||
},
|
||||
onUnload(){ if (this.timer) clearInterval(this.timer) },
|
||||
methods:{
|
||||
validatePhone(p){ return /^1\d{10}$/.test(String(p||'').trim()) },
|
||||
startCountdown(sec){
|
||||
this.countdown = sec
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
this.timer = setInterval(() => {
|
||||
if (this.countdown<=1) { clearInterval(this.timer); this.timer=null; this.countdown=0; return }
|
||||
this.countdown--
|
||||
}, 1000)
|
||||
},
|
||||
async sendCode(){
|
||||
if (this.sending || this.countdown>0) return
|
||||
const p = String(this.phone||'').trim()
|
||||
if (!this.validatePhone(p)) return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||
this.sending = true
|
||||
try {
|
||||
const res = await post('/api/auth/sms/send', { phone: p, scene: 'login' })
|
||||
const cd = Number(res && res.cooldownSec || 60)
|
||||
this.startCountdown(cd)
|
||||
uni.showToast({ title: '验证码已发送', icon: 'none' })
|
||||
} catch(e) {
|
||||
const msg = (e && e.message) || '发送失败'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
} finally { this.sending=false }
|
||||
},
|
||||
async doLogin(){
|
||||
if (this.logging) return
|
||||
const p = String(this.phone||'').trim()
|
||||
const c = String(this.code||'').trim()
|
||||
if (!this.validatePhone(p)) return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||
if (!/^\d{6}$/.test(c)) return uni.showToast({ title: '验证码格式不正确', icon: 'none' })
|
||||
this.logging = true
|
||||
try {
|
||||
const data = await post('/api/auth/sms/login', { phone: p, code: c })
|
||||
if (data && data.token) {
|
||||
uni.setStorageSync('TOKEN', data.token)
|
||||
if (data.user && data.user.phone) uni.setStorageSync('USER_MOBILE', data.user.phone)
|
||||
uni.showToast({ title: '登录成功', icon: 'none' })
|
||||
setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }) }, 300)
|
||||
}
|
||||
} catch(e) {
|
||||
const msg = (e && e.message) || '登录失败'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
} finally { this.logging=false }
|
||||
}
|
||||
,
|
||||
async quickRegister(){
|
||||
if (this.logging) return
|
||||
const p = String(this.phone||'').trim()
|
||||
if (!this.validatePhone(p)) return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||
this.logging = true
|
||||
try {
|
||||
const data = await post('/api/auth/register', { phone: p })
|
||||
if (data && data.token) {
|
||||
uni.setStorageSync('TOKEN', data.token)
|
||||
if (data.user && data.user.phone) uni.setStorageSync('USER_MOBILE', data.user.phone)
|
||||
uni.showToast({ title: '注册成功', icon: 'none' })
|
||||
setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }) }, 300)
|
||||
}
|
||||
} catch(e) {
|
||||
const msg = (e && e.message) || '注册失败'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
} finally { this.logging=false }
|
||||
}
|
||||
,
|
||||
copy(text){
|
||||
try { uni.setClipboardData({ data: String(text||'') }); uni.showToast({ title: '已复制', icon: 'none' }) } catch(e) {}
|
||||
}
|
||||
,
|
||||
toggleDebug(){ this.showDebug = !this.showDebug }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sms-login { padding: 24rpx; }
|
||||
.card { background: $uni-bg-color-grey; border-radius: 16rpx; padding: 28rpx; }
|
||||
.title { font-size: 32rpx; font-weight: 700; margin-bottom: 20rpx; }
|
||||
.form { display: flex; flex-direction: column; gap: 16rpx; }
|
||||
.row { display: flex; gap: 12rpx; }
|
||||
.input { background: #fff; border: 1rpx solid $uni-border-color; border-radius: 12rpx; padding: 20rpx; font-size: 28rpx; flex: 1; }
|
||||
.input.code { flex: 1; }
|
||||
.send { min-width: 220rpx; }
|
||||
.login { margin-top: 8rpx; }
|
||||
.hint { margin-top: 12rpx; font-size: 22rpx; color: $uni-text-color-grey; }
|
||||
.debug { margin-top: 20rpx; }
|
||||
.debug-title { font-size: 26rpx; color: $uni-text-color-grey; }
|
||||
.debug-body { margin-top: 12rpx; display: flex; flex-direction: column; gap: 12rpx; }
|
||||
.code-title { font-size: 24rpx; color: $uni-text-color-grey; }
|
||||
.code-wrap { position: relative; background: #fff; border: 1rpx solid $uni-border-color; border-radius: 12rpx; padding: 16rpx; }
|
||||
.code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 24rpx; white-space: pre-wrap; }
|
||||
.copy { position: absolute; top: 8rpx; right: 8rpx; }
|
||||
</style>
|
||||
|
||||
|
||||
369
frontend/pages/my/vip.vue
Normal file
369
frontend/pages/my/vip.vue
Normal file
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<view class="vip-page" style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); min-height: 100vh;">
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="main-content">
|
||||
<!-- VIP状态头部 -->
|
||||
<view class="vip-header">
|
||||
<view class="vip-crown">
|
||||
<text class="crown-icon">👑</text>
|
||||
</view>
|
||||
<text class="vip-title">{{ isVip ? 'VIP会员' : '成为VIP会员' }}</text>
|
||||
<text class="vip-subtitle">{{ isVip ? '尊享专属特权' : '解锁更多权益' }}</text>
|
||||
<view class="vip-status" :class="{ active: isVip }">
|
||||
<text class="status-text">{{ isVip ? 'VIP会员' : '普通用户' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 会员功能 -->
|
||||
<view class="features-section">
|
||||
<text class="section-title">会员功能</text>
|
||||
<view class="feature-card">
|
||||
<view class="feature-icon">💾</view>
|
||||
<text class="feature-text">永久存储数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- VIP状态信息 -->
|
||||
<view v-if="isVip" class="vip-info">
|
||||
<view class="info-card">
|
||||
<text class="info-label">会员状态</text>
|
||||
<text class="info-value active">已激活</text>
|
||||
</view>
|
||||
<view class="info-card">
|
||||
<text class="info-label">有效期至</text>
|
||||
<text class="info-value">{{ expireDisplay }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 价格和购买 -->
|
||||
<view v-if="!isVip" class="purchase-section">
|
||||
<view class="price-card">
|
||||
<text class="price-label">会员价格</text>
|
||||
<view class="price-display">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-amount">{{ price }}</text>
|
||||
<text class="price-period">/月</text>
|
||||
</view>
|
||||
</view>
|
||||
<button class="purchase-btn" @click="onPay">
|
||||
<text class="btn-text">立即开通VIP</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 背景装饰 -->
|
||||
<view class="bg-decoration">
|
||||
<view class="decoration-circle circle-1"></view>
|
||||
<view class="decoration-circle circle-2"></view>
|
||||
<view class="decoration-circle circle-3"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VIP_PRICE_PER_MONTH } from '../../common/config.js'
|
||||
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
isVip: false,
|
||||
expire: '',
|
||||
price: VIP_PRICE_PER_MONTH
|
||||
}
|
||||
},
|
||||
onShow(){
|
||||
this.loadVip()
|
||||
},
|
||||
computed: {
|
||||
expireDisplay(){
|
||||
const s = String(this.expire || '')
|
||||
return s || '11年11月11日'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadVip(){
|
||||
try {
|
||||
this.isVip = String(uni.getStorageSync('USER_VIP_IS_VIP') || 'false').toLowerCase() === 'true'
|
||||
this.expire = uni.getStorageSync('USER_VIP_END') || ''
|
||||
} catch(e) {}
|
||||
},
|
||||
onPay(){
|
||||
uni.showToast({ title: '静态页面演示:支付功能未接入', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background: #1a1a2e !important;
|
||||
}
|
||||
|
||||
.vip-page {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%) !important;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 60rpx 40rpx 40rpx;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* VIP头部区域 */
|
||||
.vip-header {
|
||||
text-align: center;
|
||||
margin-bottom: 80rpx;
|
||||
|
||||
.vip-crown {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.crown-icon {
|
||||
font-size: 80rpx;
|
||||
filter: drop-shadow(0 4rpx 12rpx rgba(255, 215, 0, 0.3));
|
||||
}
|
||||
}
|
||||
|
||||
.vip-title {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin-bottom: 16rpx;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.vip-subtitle {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.vip-status {
|
||||
display: inline-block;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 50rpx;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(10rpx);
|
||||
border: 1rpx solid rgba(255, 215, 0, 0.4);
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(45deg, #ffd700, #ffed4e);
|
||||
border: 1rpx solid rgba(255, 215, 0, 0.3);
|
||||
|
||||
.status-text {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 会员功能区域 */
|
||||
.features-section {
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(15rpx);
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid rgba(255, 215, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4rpx);
|
||||
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 60rpx;
|
||||
margin-bottom: 24rpx;
|
||||
filter: drop-shadow(0 4rpx 12rpx rgba(255, 215, 0, 0.3));
|
||||
}
|
||||
|
||||
.feature-text {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* VIP信息卡片 */
|
||||
.vip-info {
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
.info-card {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(15rpx);
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border: 1rpx solid rgba(255, 215, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
|
||||
&.active {
|
||||
color: #ffd700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 购买区域 */
|
||||
.purchase-section {
|
||||
.price-card {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(15rpx);
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
border: 1rpx solid rgba(255, 215, 0, 0.3);
|
||||
|
||||
.price-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.price-display {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.price-symbol {
|
||||
font-size: 32rpx;
|
||||
color: #ffd700;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.price-amount {
|
||||
font-size: 60rpx;
|
||||
font-weight: 700;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.price-period {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.purchase-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(45deg, #ffd700, #ffed4e);
|
||||
border-radius: 50rpx;
|
||||
border: none;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 215, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 4rpx 16rpx rgba(255, 215, 0, 0.4);
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 背景装饰 */
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
|
||||
.decoration-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
|
||||
&.circle-1 {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
top: -150rpx;
|
||||
right: -100rpx;
|
||||
}
|
||||
|
||||
&.circle-2 {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
bottom: 200rpx;
|
||||
left: -100rpx;
|
||||
}
|
||||
|
||||
&.circle-3 {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
top: 50%;
|
||||
right: 50rpx;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 375px) {
|
||||
.benefits-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.vip-header .vip-title {
|
||||
font-size: 42rpx;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user