初始提交:一场soul的创业实验-永平 网站与小程序
Made-with: Cursor
This commit is contained in:
143
miniprogram/pages/vip/vip.js
Normal file
143
miniprogram/pages/vip/vip.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import accessManager from '../../utils/chapterAccessManager'
|
||||
const app = getApp()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 44,
|
||||
isVip: false,
|
||||
daysRemaining: 0,
|
||||
expireDateStr: '',
|
||||
price: 1980,
|
||||
originalPrice: 6980,
|
||||
/* 按 premium_membership_landing_v1 设计稿 */
|
||||
contentRights: [
|
||||
{ title: '解锁全部章节', desc: '365天全案精读', icon: '📖' },
|
||||
{ title: '案例库', desc: '100+创业实战案例', icon: '📚' },
|
||||
{ title: '智能纪要', desc: 'AI每日精华推送', icon: '💡' },
|
||||
{ title: '会议纪要库', desc: '往期完整沉淀', icon: '📁' }
|
||||
],
|
||||
socialRights: [
|
||||
{ title: '匹配创业伙伴', desc: '精准人脉匹配', icon: '👥' },
|
||||
{ title: '创业老板排行', desc: '项目曝光展示', icon: '📊' },
|
||||
{ title: '链接资源', desc: '深度私域资源池', icon: '🔗' },
|
||||
{ title: '专属VIP标识', desc: '金色尊享光圈', icon: '✓' }
|
||||
],
|
||||
purchasing: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
wx.showShareMenu({ withShareTimeline: true })
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
this.loadVipInfo()
|
||||
},
|
||||
|
||||
async loadVipInfo() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (!userId) return
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true })
|
||||
if (res?.success) {
|
||||
const d = res.data
|
||||
let expStr = ''
|
||||
if (d.expireDate) {
|
||||
const dt = new Date(d.expireDate)
|
||||
expStr = `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`
|
||||
}
|
||||
this.setData({
|
||||
isVip: d.isVip,
|
||||
daysRemaining: d.daysRemaining,
|
||||
expireDateStr: expStr,
|
||||
price: d.price || 1980
|
||||
})
|
||||
}
|
||||
} catch (e) { console.log('[VIP] 加载失败', e) }
|
||||
},
|
||||
|
||||
async handlePurchase() {
|
||||
let userId = app.globalData.userInfo?.id
|
||||
let openId = app.globalData.openId || app.globalData.userInfo?.open_id
|
||||
if (!userId || !openId) {
|
||||
wx.showLoading({ title: '登录中...', mask: true })
|
||||
try {
|
||||
await app.login()
|
||||
userId = app.globalData.userInfo?.id
|
||||
openId = app.globalData.openId || app.globalData.userInfo?.open_id
|
||||
wx.hideLoading()
|
||||
if (!userId || !openId) {
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '登录失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
this.setData({ purchasing: true })
|
||||
try {
|
||||
const payRes = await app.request('/api/miniprogram/pay', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
openId,
|
||||
userId,
|
||||
productType: 'vip',
|
||||
productId: 'vip_annual',
|
||||
amount: this.data.price,
|
||||
description: '卡若创业派对VIP年度会员(365天)'
|
||||
}
|
||||
})
|
||||
if (payRes?.success && payRes.data?.payParams) {
|
||||
wx.requestPayment({
|
||||
...payRes.data.payParams,
|
||||
success: async () => {
|
||||
wx.showToast({ title: 'VIP开通成功', icon: 'success' })
|
||||
await this._onVipPaymentSuccess()
|
||||
},
|
||||
fail: () => wx.showToast({ title: '支付取消', icon: 'none' })
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: payRes?.error || '支付参数获取失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[VIP] 购买失败', e)
|
||||
wx.showToast({ title: '购买失败,请稍后重试', icon: 'none' })
|
||||
} finally { this.setData({ purchasing: false }) }
|
||||
},
|
||||
|
||||
async _onVipPaymentSuccess() {
|
||||
wx.showLoading({ title: '同步权益中...', mask: true })
|
||||
try {
|
||||
await new Promise(r => setTimeout(r, 1500))
|
||||
await accessManager.refreshUserPurchaseStatus()
|
||||
await this.loadVipInfo()
|
||||
app.globalData.hasFullBook = true
|
||||
const userInfo = app.globalData.userInfo || {}
|
||||
userInfo.hasFullBook = true
|
||||
app.globalData.userInfo = userInfo
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
const pages = getCurrentPages()
|
||||
pages.forEach(p => {
|
||||
if (typeof p.initUserStatus === 'function') p.initUserStatus()
|
||||
else if (typeof p.updateUserStatus === 'function') p.updateUserStatus()
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[VIP] 支付后同步失败:', e)
|
||||
}
|
||||
wx.hideLoading()
|
||||
},
|
||||
|
||||
goBack() { getApp().goBackOrToHome() },
|
||||
|
||||
onShareAppMessage() {
|
||||
const ref = app.getMyReferralCode()
|
||||
return {
|
||||
title: 'Soul创业派对 - VIP会员',
|
||||
path: ref ? `/pages/vip/vip?ref=${ref}` : '/pages/vip/vip'
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const ref = app.getMyReferralCode()
|
||||
return { title: 'Soul创业派对 - VIP会员', query: ref ? `ref=${ref}` : '' }
|
||||
}
|
||||
})
|
||||
1
miniprogram/pages/vip/vip.json
Normal file
1
miniprogram/pages/vip/vip.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "usingComponents": {}, "navigationStyle": "custom" }
|
||||
55
miniprogram/pages/vip/vip.wxml
Normal file
55
miniprogram/pages/vip/vip.wxml
Normal file
@@ -0,0 +1,55 @@
|
||||
<!-- VIP会员页 - 按 premium_membership_landing_v1 设计稿 -->
|
||||
<view class="page">
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-back" bindtap="goBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<text class="nav-title">卡若创业派对</text>
|
||||
<view class="nav-placeholder-r"></view>
|
||||
</view>
|
||||
<view style="height: {{statusBarHeight + 44}}px;"></view>
|
||||
<!-- 会员宣传区(已去掉 VIP PREMIUM 标签) -->
|
||||
<view class="vip-hero {{isVip ? 'vip-hero-active' : ''}}">
|
||||
<view class="vip-hero-title">加入卡若的</view>
|
||||
<view class="vip-hero-title gold">创业派对 会员</view>
|
||||
<text class="vip-hero-sub" wx:if="{{isVip}}">有效期至 {{expireDateStr}}(剩余{{daysRemaining}}天)</text>
|
||||
<text class="vip-hero-sub" wx:else>一次加入 尊享终身陪伴与成长</text>
|
||||
</view>
|
||||
<!-- 双列权益:内容权益 + 社交权益 -->
|
||||
<view class="rights-grid">
|
||||
<view class="rights-col">
|
||||
<view class="rights-col-header">
|
||||
<text class="rights-dot rights-dot-teal"></text>
|
||||
<text class="rights-col-title">内容权益</text>
|
||||
</view>
|
||||
<view class="benefit-card" wx:for="{{contentRights}}" wx:key="title">
|
||||
<text class="benefit-icon">{{item.icon || '✓'}}</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-title">{{item.title}}</text>
|
||||
<text class="benefit-desc">{{item.desc}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rights-col">
|
||||
<view class="rights-col-header">
|
||||
<text class="rights-dot rights-dot-gold"></text>
|
||||
<text class="rights-col-title rights-col-title-gold">社交权益</text>
|
||||
</view>
|
||||
<view class="benefit-card" wx:for="{{socialRights}}" wx:key="title">
|
||||
<text class="benefit-icon benefit-icon-gold">{{item.icon || '✓'}}</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-title">{{item.title}}</text>
|
||||
<text class="benefit-desc">{{item.desc}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部固定购买按钮(非 VIP 时显示,用 view 避让 button 默认 margin) -->
|
||||
<view class="buy-footer" wx:if="{{!isVip}}">
|
||||
<view class="buy-btn-fixed {{purchasing ? 'buy-btn-disabled' : ''}}" bindtap="handlePurchase">
|
||||
{{purchasing ? "处理中..." : "¥" + price + "/年 加入创业派对"}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bottom-space"></view>
|
||||
</view>
|
||||
67
miniprogram/pages/vip/vip.wxss
Normal file
67
miniprogram/pages/vip/vip.wxss
Normal file
@@ -0,0 +1,67 @@
|
||||
.page { background: #000; min-height: 100vh; color: #fff; }
|
||||
.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; height: 44px; padding: 0 24rpx; background: rgba(0,0,0,0.9); }
|
||||
.nav-back { width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: center; }
|
||||
.back-icon { font-size: 44rpx; color: #fff; }
|
||||
.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; }
|
||||
.nav-placeholder-r { width: 60rpx; }
|
||||
|
||||
.vip-hero { margin: 24rpx; padding: 48rpx 32rpx; border-radius: 24rpx; background: linear-gradient(135deg, rgba(0,206,209,0.08), rgba(255,215,0,0.06)); border: 1rpx solid rgba(0,206,209,0.2); }
|
||||
.vip-hero-active { border-color: rgba(255,215,0,0.4); background: linear-gradient(135deg, rgba(255,215,0,0.15), rgba(0,206,209,0.08)); }
|
||||
.vip-hero-tag { display: inline-block; background: rgba(0,206,209,0.15); color: #00CED1; font-size: 22rpx; padding: 6rpx 16rpx; border-radius: 16rpx; margin-bottom: 20rpx; }
|
||||
.vip-hero-title { display: block; font-size: 44rpx; font-weight: bold; color: #fff; }
|
||||
.gold { color: #FFD700; }
|
||||
.vip-hero-sub { display: block; font-size: 24rpx; color: rgba(255,255,255,0.5); margin-top: 12rpx; }
|
||||
|
||||
/* 双列权益 - 按 premium_membership_landing_v1 */
|
||||
.rights-grid { display: flex; gap: 24rpx; margin: 24rpx; }
|
||||
.rights-col { flex: 1; min-width: 0; }
|
||||
.rights-col-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 16rpx; padding-left: 8rpx; }
|
||||
.rights-dot { width: 8rpx; height: 24rpx; border-radius: 4rpx; }
|
||||
.rights-dot-teal { background: #4FD1C5; }
|
||||
.rights-dot-gold { background: #FFBD2E; }
|
||||
.rights-col-title { font-size: 24rpx; font-weight: bold; color: #4FD1C5; letter-spacing: 2rpx; }
|
||||
.rights-col-title-gold { color: #FFBD2E; }
|
||||
.benefit-card { display: flex; flex-direction: column; gap: 16rpx; padding: 24rpx; margin-bottom: 16rpx; background: #141414; border: 1rpx solid rgba(255,255,255,0.05); border-radius: 24rpx; }
|
||||
.benefit-icon { font-size: 36rpx; color: #4FD1C5; }
|
||||
.benefit-icon-gold { color: #FFBD2E; }
|
||||
.benefit-info { display: flex; flex-direction: column; }
|
||||
.benefit-title { font-size: 26rpx; font-weight: bold; color: #fff; }
|
||||
.benefit-desc { font-size: 20rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; line-height: 1.4; }
|
||||
|
||||
/* 底部固定购买按钮 - 宽度拉满屏幕(用 view 替代 button 避让默认 margin) */
|
||||
.buy-footer { position: fixed; bottom: 0; left: 0; right: 0; padding: 24rpx 20rpx; padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); background: rgba(0,0,0,0.95); border-top: 1rpx solid rgba(255,255,255,0.05); z-index: 50; box-sizing: border-box; }
|
||||
.buy-btn-fixed {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: linear-gradient(135deg, #FFD700, #FFB000); color: #000;
|
||||
font-size: 32rpx; font-weight: bold; border-radius: 48rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(255,188,46,0.2);
|
||||
}
|
||||
.buy-btn-disabled { opacity: 0.6; pointer-events: none; }
|
||||
.bottom-spacer { height: 180rpx; }
|
||||
|
||||
.profile-card { margin: 24rpx; padding: 32rpx; background: rgba(255,255,255,0.04); border: 1rpx solid rgba(255,255,255,0.08); border-radius: 20rpx; }
|
||||
.profile-title { font-size: 30rpx; font-weight: 600; color: rgba(255,255,255,0.9); display: block; margin-bottom: 24rpx; }
|
||||
.avatar-row { display: flex; align-items: center; margin-bottom: 24rpx; }
|
||||
.avatar-label { font-size: 26rpx; color: rgba(255,255,255,0.6); width: 140rpx; flex-shrink: 0; }
|
||||
.avatar-slot { width: 128rpx; height: 128rpx; border-radius: 50%; background: rgba(255,255,255,0.06); border: 2rpx dashed rgba(255,255,255,0.2); overflow: hidden; display: flex; align-items: center; justify-content: center; }
|
||||
.avatar-slot .avatar-img { width: 100%; height: 100%; }
|
||||
.avatar-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; color: rgba(255,255,255,0.4); }
|
||||
.avatar-add { font-size: 48rpx; line-height: 1; }
|
||||
.avatar-hint { font-size: 20rpx; margin-top: 8rpx; }
|
||||
.form-group { margin-bottom: 24rpx; }
|
||||
.form-label { font-size: 26rpx; color: rgba(255,255,255,0.6); display: block; margin-bottom: 10rpx; }
|
||||
.form-input { box-sizing: border-box; width: 100%; min-height: 76rpx; background: rgba(255,255,255,0.06); border: 1rpx solid rgba(255,255,255,0.1); border-radius: 12rpx; padding: 16rpx 24rpx; }
|
||||
.form-input input { width: 100%; font-size: 28rpx; color: #fff; line-height: 1.5; background: transparent; }
|
||||
.form-placeholder { color: rgba(255,255,255,0.35); }
|
||||
.save-btn-wrap { margin-top: 32rpx; width: 100%; display: flex; justify-content: center; }
|
||||
.save-btn { display: block; margin: 0; padding: 0; width: 100%; height: 88rpx; line-height: 88rpx; text-align: center; background: linear-gradient(135deg, #00CED1, #20B2AA); color: #000; font-size: 32rpx; font-weight: 600; border-radius: 44rpx; border: none; box-sizing: border-box; }
|
||||
.save-btn::after { border: none; margin: 0; padding: 0; }
|
||||
|
||||
.author-section { margin: 32rpx 24rpx; padding: 24rpx; border-top: 1rpx solid rgba(255,255,255,0.08); }
|
||||
.author-row { display: flex; justify-content: space-between; padding: 8rpx 0; }
|
||||
.author-label { font-size: 24rpx; color: rgba(255,255,255,0.4); }
|
||||
.author-name { font-size: 24rpx; color: rgba(255,255,255,0.7); }
|
||||
|
||||
.bottom-space { height: 120rpx; }
|
||||
Reference in New Issue
Block a user