feat: unified gravity system - VIP members consume gravity instead of unlimited; add monthly gravity top-up cron
This commit is contained in:
@@ -39,6 +39,20 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 配额与会员信息 -->
|
||||
<view class="quota-card" v-if="isLoggedIn">
|
||||
<view class="quota-row">
|
||||
<view class="quota-info">
|
||||
<text class="quota-plan">{{ memberInfo.planName || '免费版' }}</text>
|
||||
<text class="quota-count">引力值 {{ memberInfo.gravity ?? 0 }}</text>
|
||||
</view>
|
||||
<view class="quota-actions">
|
||||
<text class="quota-btn primary" @click="goBuyCredits">补充引力值</text>
|
||||
<text class="quota-btn" :class="memberInfo.plan !== 'free' ? 'owned' : ''" @click="goUpgrade">{{ memberInfo.plan !== 'free' ? '已开通' : '升级会员' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 菜单列表 -->
|
||||
<view class="menu-area">
|
||||
<view class="menu-group">
|
||||
@@ -102,6 +116,7 @@ const userInfo = ref({})
|
||||
const isAdmin = ref(false)
|
||||
const stats = ref({ interviewCount: 0, avgScore: '--', completedCount: 0 })
|
||||
const token = ref('')
|
||||
const memberInfo = ref({ plan: 'free', planName: '免费版', remaining: 0, gravity: 0 })
|
||||
|
||||
const isLoggedIn = computed(() => !!token.value)
|
||||
const maskedId = computed(() => {
|
||||
@@ -114,6 +129,7 @@ const refreshState = () => {
|
||||
if (!token.value) return
|
||||
try { const s = uni.getStorageSync('userInfo'); if (s) userInfo.value = JSON.parse(s) } catch(e) {}
|
||||
loadStats()
|
||||
loadMemberStatus()
|
||||
checkAdmin()
|
||||
fetchUserInfo()
|
||||
}
|
||||
@@ -131,6 +147,16 @@ const fetchUserInfo = async () => {
|
||||
onMounted(refreshState)
|
||||
onShow(refreshState)
|
||||
|
||||
const loadMemberStatus = async () => {
|
||||
if (!token.value) return
|
||||
try {
|
||||
const res = await uni.request({ url: api('/member/status'), method: 'GET', header: { 'Authorization': `Bearer ${token.value}` } })
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && res.data) {
|
||||
memberInfo.value = { plan: res.data.plan || 'free', planName: res.data.planName || '免费版', remaining: res.data.remaining ?? 0, gravity: res.data.gravity ?? 0 }
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
}
|
||||
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
const res = await uni.request({ url: api('/interview/stats/mine'), method: 'GET', header: { 'Authorization': `Bearer ${token.value}` } })
|
||||
@@ -153,6 +179,11 @@ const checkAdmin = () => {
|
||||
}
|
||||
|
||||
const goLogin = () => uni.navigateTo({ url: '/pages/login/login' })
|
||||
const goBuyCredits = () => uni.navigateTo({ url: '/pages/member/member?buy=interview' })
|
||||
const goUpgrade = () => {
|
||||
if (memberInfo.value.plan !== 'free') return // already on a paid plan
|
||||
uni.navigateTo({ url: '/pages/member/member' })
|
||||
}
|
||||
const goCareer = () => uni.navigateTo({ url: '/pages/career/career' })
|
||||
const goHistory = () => uni.switchTab({ url: '/pages/history/history' })
|
||||
const goReviewReview = () => uni.navigateTo({ url: '/pages/review/review' })
|
||||
@@ -197,7 +228,18 @@ const doLogout = () => {
|
||||
.guest-info { flex: 1; }
|
||||
.guest-name { font-size: 30rpx; font-weight: 600; color: #FFFFFF; }
|
||||
|
||||
.menu-area { padding: 0 32rpx 32rpx; margin-top: -40rpx; }
|
||||
.quota-card { margin: -48rpx 32rpx 16rpx; background: #FFFFFF; border-radius: var(--radius-lg); padding: 28rpx 24rpx; box-shadow: var(--shadow-sm); position: relative; z-index: 1; }
|
||||
.quota-row { display: flex; align-items: center; justify-content: space-between; }
|
||||
.quota-info { display: flex; flex-direction: column; gap: 4rpx; }
|
||||
.quota-plan { font-size: 28rpx; font-weight: 700; color: var(--color-text); }
|
||||
.quota-count { font-size: 22rpx; color: #6B7280; }
|
||||
.quota-actions { display: flex; gap: 12rpx; }
|
||||
.quota-btn { font-size: 22rpx; padding: 8rpx 20rpx; border-radius: var(--radius-sm); font-weight: 500; white-space: nowrap; }
|
||||
.quota-btn.primary { background: linear-gradient(135deg, var(--color-gradient-start), var(--color-gradient-mid)); color: #FFF; }
|
||||
.quota-btn.owned { background: #F3F4F6; color: #9CA3AF; }
|
||||
.quota-btn:not(.primary):not(.owned) { background: #FEF3C7; color: #92400E; border: 2rpx solid #F59E0B; }
|
||||
|
||||
.menu-area { padding: 0 32rpx 32rpx; margin-top: 8rpx; }
|
||||
.menu-group { background: #FFFFFF; border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 24rpx; box-shadow: var(--shadow-sm); }
|
||||
.menu-item { display: flex; align-items: center; padding: 28rpx 32rpx; border-bottom: 1rpx solid var(--color-border); }
|
||||
.menu-item:last-child { border-bottom: none; }
|
||||
|
||||
Reference in New Issue
Block a user