xiangyixiangqin/miniapp/pages/member/index.vue
2026-01-07 17:52:35 +08:00

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>