v1.19 全面改版:VIP会员系统、我的收益、创业老板排行、阅读量排序

- 后端: users表新增VIP字段, 4个VIP API (purchase/status/profile/members)
- 后端: hot接口改按user_tracks阅读量排序
- 后端: orders表支持vip产品类型, migrate新增vip_fields迁移
- 小程序「我的」: 推广中心改为我的收益, 头像VIP标识, VIP入口卡片
- 小程序「我的」: 最近阅读显示真实章节名称
- 小程序首页: 去掉内容概览, 新增创业老板排行(4列网格)
- 小程序首页: 精选推荐从hot接口获取, goToRead增加track记录
- 新增页面: VIP详情页, 会员详情页
- 开发文档精简为10个标准目录, 创建SKILL.md, 需求日志规范化

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
卡若
2026-02-23 14:07:41 +08:00
parent e91a5d9f7a
commit afc2376e96
49 changed files with 1898 additions and 561 deletions

View File

@@ -1,45 +1,44 @@
/**
* Soul创业派对 - 我的页面
* 开发: 卡若
* 技术支持: 存客宝
*/
const app = getApp()
Page({
data: {
// 系统信息
statusBarHeight: 44,
navBarHeight: 88,
// 用户状态
isLoggedIn: false,
userInfo: null,
// 统计数据
totalSections: 62,
purchasedCount: 0,
referralCount: 0,
earnings: 0,
pendingEarnings: 0,
// VIP状态
isVip: false,
vipExpireDate: '',
vipDaysRemaining: 0,
vipPrice: 1980,
// 阅读统计
totalReadTime: 0,
matchHistory: 0,
// Tab切换
activeTab: 'overview', // overview | footprint
// 最近阅读
activeTab: 'overview',
recentChapters: [],
// 章节映射表id->title
chapterMap: {},
menuList: [
{ id: 'orders', title: '我的订单', icon: '📦', count: 0 },
{ id: 'referral', title: '推广中心', icon: '🎁', badge: '' },
{ id: 'about', title: '关于作者', icon: '👤', iconBg: 'brand' }
],
// 登录弹窗
showLoginModal: false,
isLoggingIn: false
},
@@ -49,35 +48,62 @@ Page({
statusBarHeight: app.globalData.statusBarHeight,
navBarHeight: app.globalData.navBarHeight
})
this.loadChapterMap()
this.initUserStatus()
},
onShow() {
// 设置TabBar选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 3 })
}
this.initUserStatus()
},
// 加载章节名称映射
async loadChapterMap() {
try {
const res = await app.request('/api/book/all-chapters')
if (res && res.data) {
const map = {}
const sections = Array.isArray(res.data) ? res.data : []
sections.forEach(s => {
if (s.id) map[s.id] = s.title || s.sectionTitle || `章节 ${s.id}`
})
this.setData({ chapterMap: map })
// 有了映射后刷新最近阅读
this.refreshRecentChapters()
}
} catch (e) {
console.log('[My] 加载章节数据失败', e)
}
},
// 刷新最近阅读列表(用真实标题)
refreshRecentChapters() {
const { purchasedSections } = app.globalData
const map = this.data.chapterMap
const recentList = (purchasedSections || []).slice(-5).reverse().map(id => ({
id,
title: map[id] || `章节 ${id}`
}))
this.setData({ recentChapters: recentList })
},
// 初始化用户状态
initUserStatus() {
async initUserStatus() {
const { isLoggedIn, userInfo, hasFullBook, purchasedSections } = app.globalData
if (isLoggedIn && userInfo) {
// 转换为对象数组
const recentList = (purchasedSections || []).slice(-5).map(id => ({
id: id,
title: `章节 ${id}`
const map = this.data.chapterMap
const recentList = (purchasedSections || []).slice(-5).reverse().map(id => ({
id,
title: map[id] || `章节 ${id}`
}))
// 截短用户ID显示
const userId = userInfo.id || ''
const userIdShort = userId.length > 20 ? userId.slice(0, 10) + '...' + userId.slice(-6) : userId
// 获取微信号(优先显示)
const userWechat = wx.getStorageSync('user_wechat') || userInfo.wechat || ''
this.setData({
isLoggedIn: true,
userInfo,
@@ -90,74 +116,58 @@ Page({
recentChapters: recentList,
totalReadTime: Math.floor(Math.random() * 200) + 50
})
// 查询VIP状态
this.loadVipStatus(userId)
} else {
this.setData({
isLoggedIn: false,
userInfo: null,
userIdShort: '',
purchasedCount: 0,
referralCount: 0,
earnings: 0,
pendingEarnings: 0,
recentChapters: []
isLoggedIn: false, userInfo: null, userIdShort: '',
purchasedCount: 0, referralCount: 0, earnings: 0, pendingEarnings: 0,
recentChapters: [], isVip: false
})
}
},
// 微信原生获取头像button open-type="chooseAvatar" 回调)
// 查询VIP状态
async loadVipStatus(userId) {
try {
const res = await app.request(`/api/vip/status?userId=${userId}`)
if (res && res.success) {
this.setData({
isVip: res.data.isVip,
vipExpireDate: res.data.expireDate || '',
vipDaysRemaining: res.data.daysRemaining || 0,
vipPrice: res.data.price || 1980
})
}
} catch (e) {
console.log('[My] VIP状态查询失败', e)
}
},
// 微信原生获取头像
async onChooseAvatar(e) {
const avatarUrl = e.detail.avatarUrl
if (!avatarUrl) return
wx.showLoading({ title: '更新中...', mask: true })
try {
const userInfo = this.data.userInfo
userInfo.avatar = avatarUrl
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, avatar: avatarUrl }
method: 'POST', data: { userId: userInfo.id, avatar: avatarUrl }
})
wx.hideLoading()
wx.showToast({ title: '头像已获取', icon: 'success' })
} catch (e) {
wx.hideLoading()
console.log('同步头像失败', e)
wx.showToast({ title: '头像已更新', icon: 'success' })
}
},
// 微信原生获取昵称input type="nickname" 回调)
async onNicknameInput(e) {
const nickname = e.detail.value
if (!nickname || nickname === this.data.userInfo?.nickname) return
try {
const userInfo = this.data.userInfo
userInfo.nickname = nickname
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, nickname }
})
wx.showToast({ title: '昵称已获取', icon: 'success' })
} catch (e) {
console.log('同步昵称失败', e)
}
},
// 点击昵称修改(备用)
// 点击昵称修改
editNickname() {
wx.showModal({
title: '修改昵称',
@@ -167,69 +177,36 @@ Page({
if (res.confirm && res.content) {
const newNickname = res.content.trim()
if (newNickname.length < 1 || newNickname.length > 20) {
wx.showToast({ title: '昵称1-20个字符', icon: 'none' })
return
wx.showToast({ title: '昵称1-20个字符', icon: 'none' }); return
}
// 更新本地
const userInfo = this.data.userInfo
userInfo.nickname = newNickname
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
try {
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, nickname: newNickname }
method: 'POST', data: { userId: userInfo.id, nickname: newNickname }
})
} catch (e) {
console.log('同步昵称到服务器失败', e)
}
} catch (e) { console.log('同步昵称失败', e) }
wx.showToast({ title: '昵称已更新', icon: 'success' })
}
}
})
},
// 复制用户ID
copyUserId() {
const userId = this.data.userInfo?.id || ''
if (!userId) {
wx.showToast({ title: '暂无ID', icon: 'none' })
return
}
wx.setClipboardData({
data: userId,
success: () => {
wx.showToast({ title: 'ID已复制', icon: 'success' })
}
})
if (!userId) { wx.showToast({ title: '暂无ID', icon: 'none' }); return }
wx.setClipboardData({ data: userId, success: () => wx.showToast({ title: 'ID已复制', icon: 'success' }) })
},
// 切换Tab
switchTab(e) {
const tab = e.currentTarget.dataset.tab
this.setData({ activeTab: tab })
},
switchTab(e) { this.setData({ activeTab: e.currentTarget.dataset.tab }) },
showLogin() { this.setData({ showLoginModal: true }) },
closeLoginModal() { if (!this.data.isLoggingIn) this.setData({ showLoginModal: false }) },
// 显示登录弹窗
showLogin() {
this.setData({ showLoginModal: true })
},
// 关闭登录弹窗
closeLoginModal() {
if (this.data.isLoggingIn) return
this.setData({ showLoginModal: false })
},
// 微信登录
async handleWechatLogin() {
this.setData({ isLoggingIn: true })
try {
const result = await app.login()
if (result) {
@@ -240,24 +217,13 @@ Page({
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
}
} catch (e) {
console.error('微信登录错误:', e)
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
} finally {
this.setData({ isLoggingIn: false })
}
} finally { this.setData({ isLoggingIn: false }) }
},
// 手机号登录(需要用户授权)
async handlePhoneLogin(e) {
// 检查是否有授权code
if (!e.detail.code) {
// 用户拒绝授权或获取失败,尝试使用微信登录
console.log('手机号授权失败,尝试微信登录')
return this.handleWechatLogin()
}
if (!e.detail.code) return this.handleWechatLogin()
this.setData({ isLoggingIn: true })
try {
const result = await app.loginWithPhone(e.detail.code)
if (result) {
@@ -268,39 +234,26 @@ Page({
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
}
} catch (e) {
console.error('手机号登录错误:', e)
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
} finally {
this.setData({ isLoggingIn: false })
}
} finally { this.setData({ isLoggingIn: false }) }
},
// 点击菜单
handleMenuTap(e) {
const id = e.currentTarget.dataset.id
if (!this.data.isLoggedIn && id !== 'about') {
this.showLogin()
return
}
const routes = {
orders: '/pages/purchases/purchases',
referral: '/pages/referral/referral',
about: '/pages/about/about'
}
if (routes[id]) {
wx.navigateTo({ url: routes[id] })
}
if (!this.data.isLoggedIn && id !== 'about') { this.showLogin(); return }
const routes = { orders: '/pages/purchases/purchases', about: '/pages/about/about' }
if (routes[id]) wx.navigateTo({ url: routes[id] })
},
// 跳转VIP页面
goToVip() {
if (!this.data.isLoggedIn) { this.showLogin(); return }
wx.navigateTo({ url: '/pages/vip/vip' })
},
// 绑定微信号
bindWechat() {
wx.showModal({
title: '绑定微信号',
editable: true,
placeholderText: '请输入微信号',
title: '绑定微信号', editable: true, placeholderText: '请输入微信号',
success: async (res) => {
if (res.confirm && res.content) {
const wechat = res.content.trim()
@@ -313,24 +266,18 @@ Page({
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, wechat }
method: 'POST', data: { userId: userInfo.id, wechat }
})
wx.showToast({ title: '绑定成功', icon: 'success' })
} catch (e) {
console.log('绑定微信号失败', e)
wx.showToast({ title: '已保存到本地', icon: 'success' })
}
} catch (e) { wx.showToast({ title: '已保存到本地', icon: 'success' }) }
}
}
})
},
// 清除缓存
clearCache() {
wx.showModal({
title: '清除缓存',
content: '确定要清除本地缓存吗?不会影响账号数据',
title: '清除缓存', content: '确定要清除本地缓存吗?不会影响账号数据',
success: (res) => {
if (res.confirm) {
const userInfo = wx.getStorageSync('userInfo')
@@ -344,41 +291,14 @@ Page({
})
},
// 跳转到阅读页
goToRead(e) {
const id = e.currentTarget.dataset.id
wx.navigateTo({ url: `/pages/read/read?id=${id}` })
},
goToRead(e) { wx.navigateTo({ url: `/pages/read/read?id=${e.currentTarget.dataset.id}` }) },
goToChapters() { wx.switchTab({ url: '/pages/chapters/chapters' }) },
goToAbout() { wx.navigateTo({ url: '/pages/about/about' }) },
goToMatch() { wx.switchTab({ url: '/pages/match/match' }) },
// 跳转到目录
goToChapters() {
wx.switchTab({ url: '/pages/chapters/chapters' })
},
// 跳转到关于页
goToAbout() {
wx.navigateTo({ url: '/pages/about/about' })
},
// 跳转到匹配
goToMatch() {
wx.switchTab({ url: '/pages/match/match' })
},
// 跳转到推广中心
goToReferral() {
if (!this.data.isLoggedIn) {
this.showLogin()
return
}
wx.navigateTo({ url: '/pages/referral/referral' })
},
// 退出登录
handleLogout() {
wx.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
title: '退出登录', content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
app.logout()
@@ -389,6 +309,5 @@ Page({
})
},
// 阻止冒泡
stopPropagation() {}
})

View File

@@ -1,17 +1,13 @@
<!--pages/my/my.wxml-->
<!--Soul创业实验 - 我的页面 1:1还原Web版本-->
<!--Soul创业实验 - 我的页面-->
<view class="page page-transition">
<!-- 自定义导航栏 -->
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
<text class="nav-title brand-color">我的</text>
</view>
</view>
<!-- 导航栏占位 -->
<view class="nav-placeholder" style="height: {{statusBarHeight + 44}}px;"></view>
<!-- 用户卡片 - 未登录状态 - 只显示登录提示 -->
<!-- 未登录 -->
<view class="user-card card-gradient login-card" wx:if="{{!isLoggedIn}}">
<view class="login-prompt">
<view class="login-icon-large">🔐</view>
@@ -24,22 +20,21 @@
</view>
</view>
<!-- 用户卡片 - 已登录状态 -->
<!-- 已登录 - 用户卡片 -->
<view class="user-card card-gradient" wx:else>
<view class="user-header-row">
<!-- 头像 - 点击选择头像 -->
<button class="avatar-btn-simple" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
<view class="avatar">
<view class="avatar {{isVip ? 'avatar-vip' : 'avatar-normal'}}">
<image class="avatar-img" wx:if="{{userInfo.avatar}}" src="{{userInfo.avatar}}" mode="aspectFill"/>
<text class="avatar-text" wx:else>{{userInfo.nickname[0] || '微'}}</text>
<view class="vip-badge" wx:if="{{isVip}}">VIP</view>
</view>
</button>
<!-- 用户信息 -->
<view class="user-info-block">
<view class="user-name-row">
<text class="user-name" bindtap="editNickname">{{userInfo.nickname || '点击设置昵称'}}</text>
<text class="edit-icon-small">✎</text>
<view class="vip-tag" wx:if="{{isVip}}">创业伙伴</view>
</view>
<view class="user-id-row" bindtap="copyUserId">
<text class="user-id">{{userWechat ? '微信: ' + userWechat : 'ID: ' + userIdShort}}</text>
@@ -47,7 +42,7 @@
</view>
</view>
</view>
<view class="stats-grid">
<view class="stat-item">
<text class="stat-value brand-color">{{purchasedCount}}</text>
@@ -58,62 +53,64 @@
<text class="stat-label">推荐好友</text>
</view>
<view class="stat-item">
<text class="stat-value gold-color">{{earnings > 0 ? '¥' + earnings : '--'}}</text>
<text class="stat-label">待领收益</text>
<text class="stat-value gold-color">{{pendingEarnings > 0 ? '¥' + pendingEarnings : '--'}}</text>
<text class="stat-label">我的收益</text>
</view>
</view>
</view>
<!-- Tab切换 - 仅登录用户显示 -->
<view class="tab-bar-custom" wx:if="{{isLoggedIn}}">
<view
class="tab-item {{activeTab === 'overview' ? 'tab-active' : ''}}"
bindtap="switchTab"
data-tab="overview"
>概览</view>
<view
class="tab-item {{activeTab === 'footprint' ? 'tab-active' : ''}}"
bindtap="switchTab"
data-tab="footprint"
>
<text class="tab-icon">👣</text>
<text>我的足迹</text>
<!-- VIP入口卡片 -->
<view class="vip-card" wx:if="{{isLoggedIn}}" bindtap="goToVip">
<view class="vip-card-inner" wx:if="{{!isVip}}">
<view class="vip-card-left">
<text class="vip-card-icon">👑</text>
<view class="vip-card-info">
<text class="vip-card-title">开通VIP会员</text>
<text class="vip-card-desc">解锁全部章节 · 匹配创业伙伴</text>
</view>
</view>
<view class="vip-card-price">
<text class="vip-price-num">¥{{vipPrice}}</text>
<text class="vip-price-unit">/年</text>
</view>
</view>
<view class="vip-card-inner vip-active" wx:else>
<view class="vip-card-left">
<text class="vip-card-icon">👑</text>
<view class="vip-card-info">
<text class="vip-card-title gold-color">VIP会员</text>
<text class="vip-card-desc">剩余 {{vipDaysRemaining}} 天</text>
</view>
</view>
<text class="vip-manage-btn">管理 →</text>
</view>
</view>
<!-- Tab切换 -->
<view class="tab-bar-custom" wx:if="{{isLoggedIn}}">
<view class="tab-item {{activeTab === 'overview' ? 'tab-active' : ''}}" bindtap="switchTab" data-tab="overview">概览</view>
<view class="tab-item {{activeTab === 'footprint' ? 'tab-active' : ''}}" bindtap="switchTab" data-tab="footprint">
<text class="tab-icon">👣</text><text>我的足迹</text>
</view>
</view>
<!-- 概览内容 - 仅登录用户显示 -->
<!-- 概览内容 -->
<view class="tab-content" wx:if="{{activeTab === 'overview' && isLoggedIn}}">
<!-- 菜单列表 -->
<view class="menu-card card">
<view
class="menu-item"
wx:for="{{menuList}}"
wx:key="id"
bindtap="handleMenuTap"
data-id="{{item.id}}"
>
<view class="menu-item" wx:for="{{menuList}}" wx:key="id" bindtap="handleMenuTap" data-id="{{item.id}}">
<view class="menu-left">
<view class="menu-icon {{item.iconBg === 'brand' ? 'icon-brand' : item.iconBg === 'gray' ? 'icon-gray' : ''}}">
{{item.icon}}
</view>
<view class="menu-icon {{item.iconBg === 'brand' ? 'icon-brand' : ''}}">{{item.icon}}</view>
<text class="menu-title">{{item.title}}</text>
</view>
<view class="menu-right">
<text class="menu-count" wx:if="{{item.count !== undefined}}">{{item.count}}笔</text>
<text class="menu-badge gold-color" wx:if="{{item.badge}}">{{item.badge}}</text>
<text class="menu-arrow">→</text>
</view>
</view>
</view>
<!-- 账号设置 -->
<view class="settings-card card">
<view class="card-title">
<text class="title-icon">⚙️</text>
<text>账号设置</text>
</view>
<view class="card-title"><text class="title-icon">⚙️</text><text>账号设置</text></view>
<view class="settings-list">
<view class="settings-item" bindtap="bindWechat">
<text class="settings-label">绑定微信号</text>
@@ -135,12 +132,8 @@
<!-- 足迹内容 -->
<view class="tab-content" wx:if="{{activeTab === 'footprint' && isLoggedIn}}">
<!-- 阅读统计 -->
<view class="stats-card card">
<view class="card-title">
<text class="title-icon">👁️</text>
<text>阅读统计</text>
</view>
<view class="card-title"><text class="title-icon">👁️</text><text>阅读统计</text></view>
<view class="stats-row">
<view class="stat-box">
<text class="stat-icon brand-color">📖</text>
@@ -160,20 +153,10 @@
</view>
</view>
<!-- 最近阅读 -->
<view class="recent-card card">
<view class="card-title">
<text class="title-icon">📖</text>
<text>最近阅读</text>
</view>
<view class="card-title"><text class="title-icon">📖</text><text>最近阅读</text></view>
<view class="recent-list" wx:if="{{recentChapters.length > 0}}">
<view
class="recent-item"
wx:for="{{recentChapters}}"
wx:key="id"
bindtap="goToRead"
data-id="{{item.id}}"
>
<view class="recent-item" wx:for="{{recentChapters}}" wx:key="id" bindtap="goToRead" data-id="{{item.id}}">
<view class="recent-left">
<text class="recent-index">{{index + 1}}</text>
<text class="recent-title">{{item.title}}</text>
@@ -188,12 +171,8 @@
</view>
</view>
<!-- 匹配记录 -->
<view class="match-card card">
<view class="card-title">
<text class="title-icon">👥</text>
<text>匹配记录</text>
</view>
<view class="card-title"><text class="title-icon">👥</text><text>匹配记录</text></view>
<view class="empty-state">
<text class="empty-icon">👥</text>
<text class="empty-text">暂无匹配记录</text>
@@ -202,27 +181,20 @@
</view>
</view>
<!-- 登录弹窗 - 只保留微信登录 -->
<!-- 登录弹窗 -->
<view class="modal-overlay" wx:if="{{showLoginModal}}" bindtap="closeLoginModal">
<view class="modal-content" catchtap="stopPropagation">
<view class="modal-close" bindtap="closeLoginModal">✕</view>
<view class="login-icon">🔐</view>
<text class="login-title">登录 Soul创业实验</text>
<text class="login-desc">登录后可购买章节、解锁更多内容</text>
<button
class="btn-wechat"
bindtap="handleWechatLogin"
disabled="{{isLoggingIn}}"
>
<button class="btn-wechat" bindtap="handleWechatLogin" disabled="{{isLoggingIn}}">
<text class="btn-wechat-icon">微</text>
<text>{{isLoggingIn ? '登录中...' : '微信快捷登录'}}</text>
</button>
<text class="login-notice">登录即表示同意《用户协议》和《隐私政策》</text>
</view>
</view>
<!-- 底部留白 -->
<view class="bottom-space"></view>
</view>

View File

@@ -1042,3 +1042,94 @@
color: #ff4d4f;
font-size: 28rpx;
}
/* === VIP 头像标识 === */
.avatar-normal {
border: 4rpx solid rgba(255,255,255,0.2);
}
.avatar-vip {
border: 4rpx solid #FFD700;
box-shadow: 0 0 16rpx rgba(255,215,0,0.5);
position: relative;
}
.vip-badge {
position: absolute;
bottom: -4rpx;
right: -4rpx;
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #000;
font-size: 18rpx;
font-weight: bold;
padding: 2rpx 10rpx;
border-radius: 12rpx;
line-height: 1.4;
}
.vip-tag {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #000;
font-size: 20rpx;
font-weight: 600;
padding: 4rpx 14rpx;
border-radius: 16rpx;
margin-left: 12rpx;
}
/* === VIP入口卡片 === */
.vip-card {
margin: 16rpx 24rpx;
border-radius: 20rpx;
overflow: hidden;
}
.vip-card-inner {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 28rpx;
background: linear-gradient(135deg, rgba(255,215,0,0.12), rgba(255,165,0,0.08));
border: 1rpx solid rgba(255,215,0,0.25);
border-radius: 20rpx;
}
.vip-card-inner.vip-active {
background: linear-gradient(135deg, rgba(255,215,0,0.2), rgba(255,165,0,0.12));
border-color: rgba(255,215,0,0.4);
}
.vip-card-left {
display: flex;
align-items: center;
gap: 16rpx;
}
.vip-card-icon {
font-size: 44rpx;
}
.vip-card-info {
display: flex;
flex-direction: column;
}
.vip-card-title {
font-size: 30rpx;
font-weight: 600;
color: rgba(255,255,255,0.95);
}
.vip-card-desc {
font-size: 22rpx;
color: rgba(255,255,255,0.5);
margin-top: 4rpx;
}
.vip-card-price {
display: flex;
align-items: baseline;
}
.vip-price-num {
font-size: 36rpx;
font-weight: bold;
color: #FFD700;
}
.vip-price-unit {
font-size: 22rpx;
color: rgba(255,215,0,0.7);
margin-left: 4rpx;
}
.vip-manage-btn {
font-size: 26rpx;
color: #FFD700;
}