627 lines
16 KiB
Vue
627 lines
16 KiB
Vue
<template>
|
|
<view class="member-page">
|
|
<!-- 页面加载状态 -->
|
|
<Loading type="page" :loading="pageLoading" />
|
|
|
|
<!-- 头部用户信息 -->
|
|
<view class="header-section">
|
|
<view class="user-info">
|
|
<image
|
|
class="user-avatar"
|
|
:src="userInfo.avatar || defaultAvatar"
|
|
mode="aspectFill"
|
|
/>
|
|
<view class="user-detail">
|
|
<text class="user-nickname">{{ userInfo.nickname || '未设置昵称' }}</text>
|
|
<view class="member-status" v-if="userInfo.isMember">
|
|
<text class="status-icon">👑</text>
|
|
<text class="status-text">{{ memberLevelText }}</text>
|
|
</view>
|
|
<view class="member-status not-member" v-else>
|
|
<text class="status-text">暂未开通会员</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 会员等级选择 (Requirements 10.1) -->
|
|
<view class="member-tiers">
|
|
<view class="section-title">选择会员等级</view>
|
|
<view class="tier-list">
|
|
<view
|
|
v-for="tier in memberTiers"
|
|
:key="tier.level"
|
|
class="tier-card"
|
|
:class="{ 'selected': selectedTier === tier.level, [`tier-${tier.level}`]: true }"
|
|
@click="handleSelectTier(tier.level)"
|
|
>
|
|
<view class="tier-header">
|
|
<text class="tier-name">{{ tier.name }}</text>
|
|
<view class="tier-badge" v-if="tier.badge">{{ tier.badge }}</view>
|
|
</view>
|
|
<view class="tier-price">
|
|
<text class="price-symbol">¥</text>
|
|
<text class="price-value">{{ tier.price }}</text>
|
|
</view>
|
|
<view class="tier-desc">{{ tier.desc }}</view>
|
|
<view class="tier-check" v-if="selectedTier === tier.level">
|
|
<text>✓</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 会员权益说明 -->
|
|
<view class="benefits-section">
|
|
<view class="section-title">会员权益</view>
|
|
<view class="benefits-list">
|
|
<view
|
|
v-for="(benefit, index) in currentBenefits"
|
|
:key="index"
|
|
class="benefit-item"
|
|
>
|
|
<view class="benefit-icon">{{ benefit.icon }}</view>
|
|
<view class="benefit-content">
|
|
<text class="benefit-title">{{ benefit.title }}</text>
|
|
<text class="benefit-desc">{{ benefit.desc }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 底部购买按钮 (Requirements 10.2) -->
|
|
<view class="bottom-action">
|
|
<view class="price-info">
|
|
<text class="label">应付金额:</text>
|
|
<text class="price">¥{{ selectedTierInfo?.price || 0 }}</text>
|
|
</view>
|
|
<button
|
|
class="btn-purchase"
|
|
:disabled="!selectedTier || purchasing"
|
|
@click="handlePurchase"
|
|
>
|
|
<text v-if="purchasing">支付中...</text>
|
|
<text v-else>立即开通</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useUserStore } from '@/store/user.js'
|
|
import { useConfigStore } from '@/store/config.js'
|
|
import { getMemberInfo } from '@/api/member.js'
|
|
import { createOrder } from '@/api/order.js'
|
|
import Loading from '@/components/Loading/index.vue'
|
|
|
|
export default {
|
|
name: 'MemberPage',
|
|
components: {
|
|
Loading
|
|
},
|
|
setup() {
|
|
const userStore = useUserStore()
|
|
const configStore = useConfigStore()
|
|
|
|
// 页面状态
|
|
const pageLoading = ref(true)
|
|
const purchasing = ref(false)
|
|
const selectedTier = ref(1) // 默认选中第一个
|
|
|
|
// 从 configStore 获取默认头像
|
|
const defaultAvatar = computed(() => configStore.defaultAvatar || '/static/logo.png')
|
|
|
|
// 会员等级映射
|
|
const memberLevelMap = {
|
|
1: '不限时会员',
|
|
2: '诚意会员',
|
|
3: '家庭版会员'
|
|
}
|
|
|
|
/**
|
|
* 会员等级配置 (Requirements 10.1)
|
|
* 三种会员等级:不限时会员(1299), 诚意会员(1999), 家庭版(2999)
|
|
*/
|
|
const memberTiers = ref([
|
|
{
|
|
level: 1,
|
|
name: '不限时会员',
|
|
price: 1299,
|
|
desc: '基础会员权益,不限时使用',
|
|
badge: ''
|
|
},
|
|
{
|
|
level: 2,
|
|
name: '诚意会员',
|
|
price: 1999,
|
|
desc: '更多解锁次数,优先推荐',
|
|
badge: '推荐'
|
|
},
|
|
{
|
|
level: 3,
|
|
name: '家庭版',
|
|
price: 2999,
|
|
desc: '可绑定家人,共享会员权益',
|
|
badge: '超值'
|
|
}
|
|
])
|
|
|
|
/**
|
|
* 各等级会员权益
|
|
*/
|
|
const benefitsMap = {
|
|
1: [
|
|
{ icon: '🔓', title: '无限解锁', desc: '不限次数解锁联系方式' },
|
|
{ icon: '👀', title: '查看访客', desc: '查看谁看过我的资料' },
|
|
{ icon: '🔍', title: '高级搜索', desc: '使用全部筛选条件' },
|
|
{ icon: '📱', title: '专属客服', desc: '一对一客服支持' }
|
|
],
|
|
2: [
|
|
{ icon: '🔓', title: '无限解锁', desc: '不限次数解锁联系方式' },
|
|
{ icon: '👀', title: '查看访客', desc: '查看谁看过我的资料' },
|
|
{ icon: '🔍', title: '高级搜索', desc: '使用全部筛选条件' },
|
|
{ icon: '📱', title: '专属客服', desc: '一对一客服支持' },
|
|
{ icon: '⭐', title: '优先推荐', desc: '资料优先展示给其他用户' },
|
|
{ icon: '💎', title: '诚意标识', desc: '展示诚意会员专属标识' }
|
|
],
|
|
3: [
|
|
{ icon: '🔓', title: '无限解锁', desc: '不限次数解锁联系方式' },
|
|
{ icon: '👀', title: '查看访客', desc: '查看谁看过我的资料' },
|
|
{ icon: '🔍', title: '高级搜索', desc: '使用全部筛选条件' },
|
|
{ icon: '📱', title: '专属客服', desc: '一对一客服支持' },
|
|
{ icon: '⭐', title: '优先推荐', desc: '资料优先展示给其他用户' },
|
|
{ icon: '💎', title: '诚意标识', desc: '展示诚意会员专属标识' },
|
|
{ icon: '👨👩👧', title: '家庭共享', desc: '可绑定1位家人共享权益' },
|
|
{ icon: '🎁', title: '专属礼包', desc: '家庭版专属福利礼包' }
|
|
]
|
|
}
|
|
|
|
// 计算属性
|
|
const userInfo = computed(() => ({
|
|
userId: userStore.userId,
|
|
nickname: userStore.nickname,
|
|
avatar: userStore.avatar,
|
|
isMember: userStore.isMember,
|
|
memberLevel: userStore.memberLevel
|
|
}))
|
|
|
|
const memberLevelText = computed(() => {
|
|
return memberLevelMap[userStore.memberLevel] || '会员'
|
|
})
|
|
|
|
const selectedTierInfo = computed(() => {
|
|
return memberTiers.value.find(t => t.level === selectedTier.value)
|
|
})
|
|
|
|
const currentBenefits = computed(() => {
|
|
return benefitsMap[selectedTier.value] || benefitsMap[1]
|
|
})
|
|
|
|
// 加载会员信息
|
|
const loadMemberInfo = async () => {
|
|
try {
|
|
const res = await getMemberInfo()
|
|
if (res && res.success && res.data) {
|
|
// 更新用户会员状态
|
|
userStore.setMemberStatus(res.data.isMember, res.data.memberLevel)
|
|
}
|
|
} catch (error) {
|
|
console.error('加载会员信息失败:', error)
|
|
}
|
|
}
|
|
|
|
// 初始化页面
|
|
const initPage = async () => {
|
|
pageLoading.value = true
|
|
try {
|
|
userStore.restoreFromStorage()
|
|
await loadMemberInfo()
|
|
} catch (error) {
|
|
console.error('初始化页面失败:', error)
|
|
} finally {
|
|
pageLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 选择会员等级
|
|
const handleSelectTier = (level) => {
|
|
selectedTier.value = level
|
|
}
|
|
|
|
/**
|
|
* 购买会员 (Requirements 10.2, 10.3, 10.4)
|
|
* 1. 创建订单
|
|
* 2. 调用微信支付
|
|
* 3. 支付成功后更新会员状态
|
|
*/
|
|
const handlePurchase = async () => {
|
|
if (!selectedTier.value || purchasing.value) return
|
|
|
|
purchasing.value = true
|
|
|
|
try {
|
|
// 创建订单 (Requirements 10.2)
|
|
const orderRes = await createOrder({
|
|
orderType: 1, // 会员订单
|
|
memberLevel: selectedTier.value
|
|
})
|
|
|
|
if (!orderRes || !orderRes.success) {
|
|
uni.showToast({
|
|
title: orderRes?.message || '创建订单失败',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
|
|
const paymentParams = orderRes.data
|
|
|
|
// 调用微信支付 (Requirements 10.3)
|
|
await requestPayment(paymentParams)
|
|
|
|
// 支付成功,更新会员状态 (Requirements 10.4)
|
|
userStore.setMemberStatus(true, selectedTier.value)
|
|
|
|
uni.showToast({
|
|
title: '开通成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
// 延迟返回上一页
|
|
setTimeout(() => {
|
|
uni.navigateBack()
|
|
}, 1500)
|
|
|
|
} catch (error) {
|
|
console.error('购买失败:', error)
|
|
|
|
if (error.errMsg && error.errMsg.includes('cancel')) {
|
|
uni.showToast({ title: '支付已取消', icon: 'none' })
|
|
} else {
|
|
uni.showToast({
|
|
title: error.message || '支付失败,请重试',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
} finally {
|
|
purchasing.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 调用微信支付
|
|
* @param {Object} params - 支付参数
|
|
*/
|
|
const requestPayment = (params) => {
|
|
return new Promise((resolve, reject) => {
|
|
uni.requestPayment({
|
|
provider: 'wxpay',
|
|
timeStamp: params.timeStamp,
|
|
nonceStr: params.nonceStr,
|
|
package: params.package,
|
|
signType: params.signType || 'MD5',
|
|
paySign: params.paySign,
|
|
success: resolve,
|
|
fail: reject
|
|
})
|
|
})
|
|
}
|
|
|
|
onMounted(() => {
|
|
initPage()
|
|
})
|
|
|
|
return {
|
|
pageLoading,
|
|
purchasing,
|
|
selectedTier,
|
|
memberTiers,
|
|
userInfo,
|
|
memberLevelText,
|
|
selectedTierInfo,
|
|
currentBenefits,
|
|
defaultAvatar,
|
|
handleSelectTier,
|
|
handlePurchase
|
|
}
|
|
},
|
|
// 页面显示时刷新数据
|
|
onShow() {
|
|
const userStore = useUserStore()
|
|
userStore.restoreFromStorage()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
.member-page {
|
|
min-height: 100vh;
|
|
background-color: #f8f8f8;
|
|
padding-bottom: 180rpx;
|
|
}
|
|
|
|
// 头部用户信息
|
|
.header-section {
|
|
background: linear-gradient(135deg, #ffd700 0%, #ffb800 100%);
|
|
padding: 60rpx 30rpx 80rpx;
|
|
|
|
.user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.user-avatar {
|
|
width: 120rpx;
|
|
height: 120rpx;
|
|
border-radius: 50%;
|
|
border: 4rpx solid rgba(255, 255, 255, 0.5);
|
|
margin-right: 24rpx;
|
|
}
|
|
|
|
.user-detail {
|
|
flex: 1;
|
|
|
|
.user-nickname {
|
|
font-size: 36rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
display: block;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.member-status {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.status-icon {
|
|
font-size: 28rpx;
|
|
margin-right: 8rpx;
|
|
}
|
|
|
|
.status-text {
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
}
|
|
|
|
&.not-member {
|
|
.status-text {
|
|
color: #666;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 会员等级选择
|
|
.member-tiers {
|
|
margin: -40rpx 20rpx 20rpx;
|
|
|
|
.section-title {
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
padding-left: 10rpx;
|
|
}
|
|
|
|
.tier-list {
|
|
display: flex;
|
|
gap: 16rpx;
|
|
|
|
.tier-card {
|
|
flex: 1;
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 24rpx 16rpx;
|
|
position: relative;
|
|
border: 2rpx solid #eee;
|
|
transition: all 0.3s;
|
|
|
|
&.selected {
|
|
border-color: #ffd700;
|
|
box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.3);
|
|
}
|
|
|
|
&.tier-2 {
|
|
&.selected {
|
|
border-color: #ff6b6b;
|
|
box-shadow: 0 4rpx 20rpx rgba(255, 107, 107, 0.3);
|
|
}
|
|
}
|
|
|
|
&.tier-3 {
|
|
&.selected {
|
|
border-color: #9c27b0;
|
|
box-shadow: 0 4rpx 20rpx rgba(156, 39, 176, 0.3);
|
|
}
|
|
}
|
|
|
|
.tier-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 16rpx;
|
|
|
|
.tier-name {
|
|
font-size: 26rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.tier-badge {
|
|
font-size: 20rpx;
|
|
padding: 4rpx 12rpx;
|
|
background: linear-gradient(135deg, #ff6b6b 0%, #ff5252 100%);
|
|
color: #fff;
|
|
border-radius: 20rpx;
|
|
}
|
|
}
|
|
|
|
.tier-price {
|
|
display: flex;
|
|
align-items: baseline;
|
|
margin-bottom: 12rpx;
|
|
|
|
.price-symbol {
|
|
font-size: 24rpx;
|
|
color: #ff6b6b;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.price-value {
|
|
font-size: 44rpx;
|
|
color: #ff6b6b;
|
|
font-weight: 700;
|
|
}
|
|
}
|
|
|
|
.tier-desc {
|
|
font-size: 22rpx;
|
|
color: #999;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.tier-check {
|
|
position: absolute;
|
|
top: 12rpx;
|
|
right: 12rpx;
|
|
width: 36rpx;
|
|
height: 36rpx;
|
|
background: linear-gradient(135deg, #ffd700 0%, #ffb800 100%);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
text {
|
|
font-size: 24rpx;
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
&.tier-2 .tier-check {
|
|
background: linear-gradient(135deg, #ff6b6b 0%, #ff5252 100%);
|
|
}
|
|
|
|
&.tier-3 .tier-check {
|
|
background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 会员权益
|
|
.benefits-section {
|
|
margin: 0 20rpx;
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 24rpx;
|
|
|
|
.section-title {
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.benefits-list {
|
|
.benefit-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
padding: 20rpx 0;
|
|
border-bottom: 1rpx solid #f5f5f5;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.benefit-icon {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
font-size: 36rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 20rpx;
|
|
}
|
|
|
|
.benefit-content {
|
|
flex: 1;
|
|
|
|
.benefit-title {
|
|
font-size: 28rpx;
|
|
font-weight: 500;
|
|
color: #333;
|
|
display: block;
|
|
margin-bottom: 6rpx;
|
|
}
|
|
|
|
.benefit-desc {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 底部购买按钮
|
|
.bottom-action {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: #fff;
|
|
padding: 20rpx 30rpx;
|
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
|
|
|
.price-info {
|
|
display: flex;
|
|
align-items: baseline;
|
|
|
|
.label {
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
}
|
|
|
|
.price {
|
|
font-size: 40rpx;
|
|
font-weight: 700;
|
|
color: #ff6b6b;
|
|
}
|
|
}
|
|
|
|
.btn-purchase {
|
|
width: 280rpx;
|
|
height: 88rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(135deg, #ffd700 0%, #ffb800 100%);
|
|
border-radius: 44rpx;
|
|
border: none;
|
|
|
|
&::after {
|
|
border: none;
|
|
}
|
|
|
|
text {
|
|
font-size: 32rpx;
|
|
color: #333;
|
|
font-weight: 600;
|
|
}
|
|
|
|
&[disabled] {
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
}
|
|
</style>
|