| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- <template>
- <view class="member-page">
- <view v-if="loading" class="empty-tip">加载中…</view>
- <!-- 无会员:两档购买卡 -->
- <view v-else-if="member.status === 'none'">
- <view class="head-banner">
- <text class="head-title">年度会员</text>
- <text class="head-sub">一年免费沙龙 + 课程折扣,推荐立减 ¥100</text>
- </view>
- <view class="mcard experience-card">
- <view class="mcard-head">
- <text class="mcard-name">体验卡</text>
- <text class="mcard-price">¥<text class="price-num">365</text><text class="price-year">/年</text></text>
- </view>
- <view class="mcard-benefit" v-for="(b, idx) in experienceBenefits" :key="idx">
- <text class="benefit-dot">·</text>
- <text class="benefit-text">{{ b }}</text>
- </view>
- <text class="mcard-limit">限量 30 张 · 仅 L0 存量学员</text>
- <button class="buy-btn" :loading="buying" :disabled="buying" @click="buy('experience')">立即开通</button>
- </view>
- <view class="mcard companion-card">
- <view class="mcard-head">
- <text class="mcard-name">爱伴星伙</text>
- <text class="mcard-price">¥<text class="price-num">1280</text><text class="price-year">/年</text></text>
- </view>
- <view class="mcard-benefit" v-for="(b, idx) in companionBenefits" :key="idx">
- <text class="benefit-dot">·</text>
- <text class="benefit-text">{{ b }}</text>
- </view>
- <text class="mcard-limit">全年畅享 · 优先报名可携亲友</text>
- <button class="buy-btn" :loading="buying" :disabled="buying" @click="buy('companion')">立即开通</button>
- </view>
- </view>
- <!-- 有会员记录:状态卡 -->
- <view v-else class="has-member">
- <view class="status-card" :class="statusClass">
- <view class="status-head">
- <text class="status-name">{{ member.cardTypeName || '会员卡' }}</text>
- <text class="status-tag">{{ statusText }}</text>
- </view>
- <view class="status-row">
- <text class="status-label">生效时间</text>
- <text class="status-value">{{ formatDateTime(member.startDate) }}</text>
- </view>
- <view class="status-row">
- <text class="status-label">有效期至</text>
- <text class="status-value">{{ formatDateTime(member.validUntil) }}</text>
- </view>
- <view v-if="member.status === 'active'" class="status-row">
- <text class="status-label">免费沙龙</text>
- <text class="status-value">已用 {{ member.salonUsed || 0 }} / {{ member.salonQuota || 0 }} 次</text>
- </view>
- <view v-if="member.status === 'active' && member.noShowCount > 0" class="status-row">
- <text class="status-label">爽约记录</text>
- <text class="status-value warn-text">{{ member.noShowCount }} 次</text>
- </view>
- </view>
- <!-- 权益说明 -->
- <view class="benefit-card">
- <text class="benefit-title">会员权益</text>
- <view class="benefit-item" v-for="(b, idx) in member.benefits || []" :key="idx">
- <text class="benefit-dot">✓</text>
- <text class="benefit-text">{{ b }}</text>
- </view>
- </view>
- <!-- 免费沙龙入口 -->
- <button v-if="member.status === 'active' && member.salonRemain > 0" class="salon-btn" @click="goSalonList">
- 免费沙龙报名(剩余 {{ member.salonRemain }} 次)
- </button>
- <button v-else-if="member.status === 'active'" class="salon-btn disabled-btn" disabled>
- 免费沙龙次数已用完
- </button>
- <!-- 续费 / 退卡 -->
- <view class="action-row">
- <button v-if="member.status !== 'pending'" class="renew-btn" :loading="buying" :disabled="buying" @click="buy(member.cardType)">
- {{ member.status === 'active' ? '续费' : '重新购买' }}
- </button>
- <view v-if="member.refundable" class="refund-link" @click="doRefund">7 日无理由退卡</view>
- <text v-if="!member.refundable && member.status === 'active'" class="refund-tip">已过 7 日退卡期</text>
- </view>
- </view>
- </view>
- </template>
- <script>
- import { getMyMember, createMemberOrder, refundMember } from '@/utils/api.js'
- import { formatDateTime } from '@/utils/format.js'
- import { guardPage } from '@/utils/guard.js'
- export default {
- data() {
- return {
- loading: false,
- buying: false,
- member: { status: 'none' },
- experienceBenefits: ['年度免费沙龙 4 次', 'L1 课程 95 折', '会员社群 / 资料库'],
- companionBenefits: ['年度免费沙龙 12 次', 'L1 课程 9 折', 'L2 课程 95 折', '专家诊室会员价(即将开放)', '会员社群、优先报名']
- }
- },
- computed: {
- statusText() {
- var map = {
- active: this.member.effective ? '生效中' : '已过期',
- pending: '待支付',
- expired: '已过期',
- refunded: '已退卡'
- }
- return map[this.member.status] || this.member.status || ''
- },
- statusClass() {
- if (this.member.status === 'active') {
- return this.member.effective ? 'status-active' : 'status-expired'
- }
- var cls = { pending: 'status-pending', expired: 'status-expired', refunded: 'status-refunded' }
- return cls[this.member.status] || ''
- }
- },
- onLoad() {
- if (!guardPage()) return
- this.loadMember()
- },
- methods: {
- formatDateTime: formatDateTime,
- loadMember() {
- var self = this
- self.loading = true
- getMyMember().then(function(resp) {
- self.member = resp.data || { status: 'none' }
- }).catch(function(err) {
- // 未登录等情况由 guard/全局处理;静默兜底为空态
- self.member = { status: 'none' }
- console.error('[member] loadMember fail', err)
- }).finally(function() {
- self.loading = false
- })
- },
- buy(cardType) {
- var self = this
- if (self.buying) return
- var name = cardType === 'experience' ? '体验卡 ¥365/年' : '爱伴星伙卡 ¥1280/年'
- var tip = self.member.status === 'active' ? '续费将衔接延长有效期,确认开通 ' + name + ' 吗?' : '确认开通 ' + name + ' 吗?'
- uni.showModal({
- title: '开通年度会员',
- content: tip,
- success: function(res) {
- if (!res.confirm) return
- self.buying = true
- uni.showLoading({ title: '下单中…' })
- createMemberOrder(cardType).then(function(resp) {
- var data = resp.data || {}
- uni.hideLoading()
- if (data.status === 'paid') {
- // test-mode 直付成功即已激活
- uni.showToast({ title: '开通成功', icon: 'success' })
- self.loadMember()
- } else {
- uni.showToast({ title: '订单创建成功,等待支付', icon: 'none' })
- }
- }).catch(function(err) {
- uni.hideLoading()
- uni.showToast({ title: (err && err.message) || '开通失败', icon: 'none' })
- }).finally(function() {
- self.buying = false
- })
- }
- })
- },
- doRefund() {
- var self = this
- uni.showModal({
- title: '退卡确认',
- content: '7 日内退卡:退款金额 = 实付 − 已用沙龙次数 × ¥68,确认退卡吗?',
- confirmColor: '#EF5350',
- success: function(res) {
- if (!res.confirm) return
- refundMember().then(function(resp) {
- uni.showModal({
- title: '退卡成功',
- content: '退款金额:¥' + (resp.data && resp.data.refundAmount !== undefined ? ((resp.data.refundAmount / 100).toFixed(2)) : '') + ',原路退回',
- showCancel: false,
- success: function() {
- self.loadMember()
- }
- })
- }).catch(function(err) {
- uni.showToast({ title: (err && err.message) || '退卡失败', icon: 'none' })
- })
- }
- })
- },
- goSalonList() {
- uni.navigateTo({ url: '/pages/salon/list' })
- }
- }
- }
- </script>
- <style scoped>
- .member-page { min-height: 100vh; background: #F5F5F5; padding: 32rpx; }
- .empty-tip { font-size: 26rpx; color: #94877F; padding: 64rpx 0; text-align: center; }
- .head-banner { background: linear-gradient(135deg, #FC7A57, #F9494F); border-radius: 16rpx; padding: 40rpx 32rpx; margin-bottom: 24rpx; }
- .head-title { display: block; font-size: 40rpx; font-weight: 700; color: #FFF; margin-bottom: 8rpx; }
- .head-sub { display: block; font-size: 26rpx; color: rgba(255,255,255,0.9); }
- .mcard { background: #FFF; border-radius: 16rpx; padding: 32rpx; margin-bottom: 24rpx; border: 2rpx solid #E2E8F0; }
- .companion-card { border: 2rpx solid #FED7AA; }
- .mcard-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20rpx; }
- .mcard-name { font-size: 34rpx; font-weight: 700; color: #2A2326; }
- .mcard-price { font-size: 26rpx; color: #FC7A57; }
- .price-num { font-size: 56rpx; font-weight: 700; }
- .price-year { font-size: 24rpx; color: #94877F; }
- .mcard-benefit { display: flex; align-items: center; margin-bottom: 10rpx; }
- .benefit-dot { font-size: 26rpx; color: #FC7A57; margin-right: 10rpx; }
- .benefit-text { font-size: 26rpx; color: #475569; }
- .mcard-limit { display: block; font-size: 22rpx; color: #94877F; margin-top: 8rpx; }
- .buy-btn { width: 100%; height: 84rpx; line-height: 84rpx; background: #FC7A57; color: #FFF; font-size: 30rpx; font-weight: 600; border-radius: 42rpx; border: none; margin-top: 24rpx; }
- .buy-btn:active { opacity: 0.85; }
- .status-card { border-radius: 16rpx; padding: 32rpx; margin-bottom: 24rpx; }
- .status-active { background: linear-gradient(135deg, #22C55E, #16A34A); }
- .status-pending { background: linear-gradient(135deg, #F59E0B, #D97706); }
- .status-expired { background: linear-gradient(135deg, #94A3B8, #64748B); }
- .status-refunded { background: linear-gradient(135deg, #94A3B8, #64748B); }
- .status-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24rpx; }
- .status-name { font-size: 36rpx; font-weight: 700; color: #FFF; }
- .status-tag { font-size: 24rpx; color: #FFF; background: rgba(255,255,255,0.25); padding: 6rpx 20rpx; border-radius: 24rpx; }
- .status-row { display: flex; align-items: center; margin-bottom: 12rpx; }
- .status-label { font-size: 25rpx; color: rgba(255,255,255,0.85); width: 140rpx; flex-shrink: 0; }
- .status-value { font-size: 26rpx; color: #FFF; font-weight: 600; }
- .warn-text { color: #FFF0E8; }
- .benefit-card { background: #FFF; border-radius: 16rpx; padding: 28rpx 32rpx; margin-bottom: 24rpx; }
- .benefit-title { display: block; font-size: 30rpx; font-weight: 700; color: #2A2326; margin-bottom: 16rpx; }
- .benefit-item { display: flex; align-items: center; margin-bottom: 10rpx; }
- .benefit-dot { font-size: 26rpx; color: #22C55E; margin-right: 10rpx; }
- .salon-btn { width: 100%; height: 88rpx; line-height: 88rpx; background: #FC7A57; color: #FFF; font-size: 30rpx; font-weight: 600; border-radius: 44rpx; border: none; margin-bottom: 24rpx; }
- .salon-btn:active { opacity: 0.85; }
- .disabled-btn { background: #CBD5E1; }
- .action-row { display: flex; flex-direction: column; align-items: center; }
- .renew-btn { width: 100%; height: 84rpx; line-height: 84rpx; background: #FFF; color: #FC7A57; font-size: 30rpx; font-weight: 600; border-radius: 42rpx; border: 2rpx solid #FED7AA; }
- .renew-btn:active { background: #FFF0E8; }
- .refund-link { font-size: 26rpx; color: #EF5350; text-decoration: underline; margin-top: 24rpx; padding: 8rpx 16rpx; }
- .refund-tip { font-size: 24rpx; color: #94877F; margin-top: 24rpx; }
- </style>
|