瀏覽代碼

feat(frontend): 会员中心页与会员价引导交互

- 新增 pages/membership/index 会员中心页(状态卡/权益亮点/权益对比表)
- 首页能量沙盘下方新增会员入口卡(非会员开通 / 会员≤30天续费)
- 活动详情页非会员显示会员价引流链接
- 活动列表会员价点击跳转开通

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Xiaogang Liao 1 月之前
父節點
當前提交
c31438dd3f

+ 20 - 2
cfc-frontend/components/DimensionActivities.vue

@@ -64,7 +64,7 @@
                   {{ act._statusText }}
                 </text>
                 <text class="card-price" v-if="act.priceLabel">{{ act.priceLabel }}</text>
-                <text class="card-price member-teaser" v-else-if="act.memberPrice && act.memberPrice > 0 && act.memberPrice < (act.price || 0)">会员价 {{ act._memberPriceDisplay }}</text>
+                <text class="card-price member-teaser" v-else-if="act.memberPrice && act.memberPrice > 0 && act.memberPrice < (act.price || 0)" @click.stop="onMemberPriceTap">会员价 {{ act._memberPriceDisplay }}</text>
                 <text class="card-price" v-else-if="act.price && act.price > 0">{{ act._priceDisplay }}</text>
                 <text class="card-price-free" v-else>免费</text>
               </view>
@@ -89,6 +89,7 @@
 <script>
 import config from '@/config.js'
 import { parseDate } from '@/utils/format.js'
+import { getMyMembership } from '@/utils/api.js'
 
 var DIMENSION_MAP = {
   body: { name: '身', color: '#FF8C42', icon: '🌏' },
@@ -106,9 +107,18 @@ export default {
   },
   data: function() {
     return {
-      swiperIndex: 0
+      swiperIndex: 0,
+      isMember: false
     }
   },
+  created: function() {
+    var that = this
+    getMyMembership().then(function(res) {
+      if (res.data && res.data.memberLevel) {
+        that.isMember = res.data.memberLevel !== 'FREE'
+      }
+    }).catch(function() {})
+  },
   computed: {
     displayActivities: function() {
       var self = this
@@ -140,6 +150,14 @@ export default {
     onSwiperChange: function(e) {
       this.swiperIndex = e.detail.current
     },
+    onMemberPriceTap: function() {
+      if (this.isMember) {
+        uni.showToast({ title: '会员价将在报名时自动生效', icon: 'none' })
+        return
+      }
+      uni.showToast({ title: '开通会员享专属会员价', icon: 'none' })
+      uni.navigateTo({ url: '/pages/membership/upgrade' })
+    },
     parseWeights: function(str) {
       if (!str) return []
       try {

+ 6 - 0
cfc-frontend/pages.json

@@ -662,6 +662,12 @@
     {
       "root": "pages/membership",
       "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "会员中心"
+          }
+        },
         {
           "path": "benefits",
           "style": {

+ 30 - 1
cfc-frontend/pages/activity/activity-detail/activity-detail.vue

@@ -55,6 +55,7 @@
             <view class="meta-text">
               <text class="fee-line">孩子:¥{{ feeText(activity.childFee, activity.childMemberFee) }}</text>
               <text class="fee-line">家长:¥{{ feeText(activity.parentFee, activity.parentMemberFee) }}</text>
+              <text class="fee-line member-fee-link" v-if="!isMember && hasMemberPrice" @click.stop="goUpgrade">开通会员享会员价 ›</text>
             </view>
           </view>
           <view class="meta-item" v-else>
@@ -266,7 +267,7 @@
 </template>
 
 <script>
-import { getActivityDetail, activityCheckin, cancelActivityRegistration, createActivityOrder, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback, getFamilyMembers, getShareQrCode } from '@/utils/api.js'
+import { getActivityDetail, activityCheckin, cancelActivityRegistration, createActivityOrder, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback, getFamilyMembers, getShareQrCode, getMyMembership } from '@/utils/api.js'
 import shareMixin from '../../../components/share-mixin.js'
 import MpHtml from '@/components/mp-html/mp-html.vue'
 import ContentSharePoster from '@/components/ContentSharePoster.vue'
@@ -286,6 +287,7 @@ export default {
       checkinPoints: 0,
       registrationStatus: null,
       registrationLoading: false,
+      isMember: false,
       showPenaltyModal: false,
       penaltyAmount: 0,
       showFeedbackModal: false,
@@ -321,9 +323,22 @@ export default {
     if (options && options.checkinDone === '1') {
       this.checkinDone = true
     }
+
+    this.checkMemberStatus()
   },
   methods: {
     // ===== 工具方法 =====
+    checkMemberStatus: function() {
+      var that = this
+      getMyMembership().then(function(res) {
+        if (res.data && res.data.memberLevel) {
+          that.isMember = res.data.memberLevel !== 'FREE'
+        }
+      }).catch(function() {})
+    },
+    goUpgrade: function() {
+      uni.navigateTo({ url: '/pages/membership/upgrade' })
+    },
     formatDate: function(dateStr) {
       if (!dateStr) return ''
       var d = new Date(dateStr.replace(/-/g, '/'))
@@ -740,6 +755,11 @@ export default {
       var a = this.activity || {}
       return (a.childFee > 0) || (a.childMemberFee > 0) || (a.parentFee > 0) || (a.parentMemberFee > 0)
     },
+    // 是否存在会员专享价(用于非会员引导)
+    hasMemberPrice: function() {
+      var a = this.activity || {}
+      return (a.childMemberFee > 0) || (a.parentMemberFee > 0)
+    },
     // 报名按钮可用:需报名 + 未报名 + 展示状态为报名中
     canRegister: function() {
       var a = this.activity || {}
@@ -890,6 +910,15 @@ export default {
   flex: 1;
   line-height: 1.4;
 }
+.fee-line {
+  display: block;
+}
+.member-fee-link {
+  color: #F97316;
+  font-weight: 500;
+  text-decoration: underline;
+  margin-top: 4rpx;
+}
 
 /* 活动介绍 */
 .desc-section {

+ 108 - 2
cfc-frontend/pages/index-home/index.vue

@@ -64,6 +64,20 @@
       showDimensionBar
       starHeight="480rpx" />
 
+    <!-- 会员入口:非会员显示开通 / 快到期显示续费 -->
+    <view v-if="showMemberEntry" class="member-entry" @click="goMembership">
+      <view class="member-entry-icon-wrap">
+        <text class="member-entry-icon">👑</text>
+      </view>
+      <view class="member-entry-texts">
+        <text class="member-entry-title">{{ memberEntryTitle }}</text>
+        <text class="member-entry-desc">{{ memberEntryDesc }}</text>
+      </view>
+      <view class="member-entry-btn">
+        <text class="member-entry-btn-text">{{ memberEntryBtn }}</text>
+      </view>
+    </view>
+
     <!-- 家庭成员选择条 -->
     <FamilyMemberStrip
       :members="familyMembersVisible"
@@ -354,7 +368,7 @@ import CircleCard from '../../components/CircleCard.vue'
 import CircleDetail from '../../components/CircleDetail.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import FamilyChallengeCard from '../../components/FamilyChallengeCard.vue'
-import { acceptParentInvite, productList, getActivityList, getFeaturedArticles, getFamilyEnergySandbox, getVisibleFamilyMembers, getTodayTasksByCategory, getChildren, getEnergyOverview, getProductsByDomain, getChallengeList, getMyCircles, discoverCircles, joinGeneralCircle, leaveGeneralCircle, switchToFamilyMember } from '../../utils/api.js'
+import { acceptParentInvite, productList, getActivityList, getFeaturedArticles, getFamilyEnergySandbox, getVisibleFamilyMembers, getTodayTasksByCategory, getChildren, getEnergyOverview, getProductsByDomain, getChallengeList, getMyCircles, discoverCircles, joinGeneralCircle, leaveGeneralCircle, switchToFamilyMember, getMyMembership } from '../../utils/api.js'
 import store from '../../store/index.js'
 import config from '@/config.js'
 
@@ -446,7 +460,11 @@ export default {
         { icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/shop/index/index' },
         { icon: '\u{1F3EB}', label: '课程', needLogin: false, page: '/pages/discover/index?type=course' }
       ],
-      loggedInInitDone: false
+      loggedInInitDone: false,
+
+      // 会员状态(沙盘下方会员入口使用)
+      isMember: false,
+      membershipDays: 0
     }
   },
   computed: {
@@ -481,6 +499,23 @@ export default {
     },
     energyDotColors: function() {
       return ['#8D6E63', '#FF6B35', '#FFD700', '#4CAF50', '#42A5F5']
+    },
+    // 会员入口显示:非会员显示开通;会员剩余≤30天显示续费
+    showMemberEntry: function() {
+      if (!this.isLoggedIn()) return false
+      if (!this.isMember) return true
+      return this.membershipDays > 0 && this.membershipDays <= 30
+    },
+    memberEntryTitle: function() {
+      if (!this.isMember) return '开通会员,解锁全部特权'
+      return '会员即将到期,续费恢复权益'
+    },
+    memberEntryDesc: function() {
+      if (!this.isMember) return '享专属折扣 · 成长服务 · 规划指导'
+      return '剩余 ' + this.membershipDays + ' 天,续费保持权益不中断'
+    },
+    memberEntryBtn: function() {
+      return this.isMember ? '立即续费' : '立即开通'
     }
   },
   onLoad(options) {
@@ -841,11 +876,27 @@ export default {
         this.loggedInInitDone = true
         this.loadLoggedInData()
       }
+      this.checkMemberStatus()
       this._watchPageReady()
     },
     isLoggedIn() {
       return !!uni.getStorageSync('token')
     },
+    // ===== 会员状态(沙盘下方会员入口) =====
+    checkMemberStatus: function() {
+      var that = this
+      getMyMembership().then(function(res) {
+        var m = res.data || {}
+        that.isMember = !!(m.memberLevel && m.memberLevel !== 'FREE') || !!(m.levelCode && m.levelCode !== 'FREE')
+        var mem = m.membership || m
+        if (mem.daysRemaining != null) {
+          that.membershipDays = mem.daysRemaining
+        }
+      }).catch(function() {})
+    },
+    goMembership: function() {
+      uni.navigateTo({ url: '/pages/membership/upgrade' })
+    },
     normalizeCurrentRole() {
       const role = uni.getStorageSync('role') || 'parent'
       const currentRole = uni.getStorageSync('currentRole')
@@ -1020,6 +1071,61 @@ export default {
   padding-bottom: 100rpx;
 }
 
+/* ===== 会员入口卡(能量沙盘下方) ===== */
+.member-entry {
+  display: flex;
+  align-items: center;
+  margin: 20rpx 30rpx 0;
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  border-radius: 20rpx;
+  padding: 24rpx 30rpx;
+}
+.member-entry-icon-wrap {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 50%;
+  background: rgba(255,255,255,0.2);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+.member-entry-icon {
+  font-size: 36rpx;
+}
+.member-entry-texts {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-width: 0;
+}
+.member-entry-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #fff;
+}
+.member-entry-desc {
+  font-size: 22rpx;
+  color: rgba(255,255,255,0.85);
+  margin-top: 4rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.member-entry-btn {
+  margin-left: 16rpx;
+  padding: 10rpx 24rpx;
+  background: #fff;
+  border-radius: 30rpx;
+  flex-shrink: 0;
+}
+.member-entry-btn-text {
+  font-size: 24rpx;
+  color: #F97316;
+  font-weight: bold;
+}
+
 /* ===== 推荐 ===== */
 .recommend-section {
   margin: 20rpx 30rpx;

+ 343 - 0
cfc-frontend/pages/membership/index.vue

@@ -0,0 +1,343 @@
+<template>
+  <view class="member-center">
+    <scroll-view class="page" scroll-y>
+      <!-- 会员状态卡 -->
+      <view class="status-card">
+        <view class="status-badge" :class="isFree ? 'badge-free' : 'badge-family'">
+          <text class="badge-icon">{{ isFree ? '○' : '●' }}</text>
+          <text class="badge-text">{{ currentLevelName }}</text>
+        </view>
+        <view class="status-body" v-if="membership && membership.levelCode !== 'FREE'">
+          <view class="expiry-row">
+            <text class="expiry-label">到期时间</text>
+            <text class="expiry-value">{{ formatDate(membership.endDate) }}</text>
+          </view>
+          <view class="days-remaining" v-if="membership.isActive">
+            <text class="days-num">{{ membership.daysRemaining || 0 }}</text>
+            <text class="days-unit">天剩余</text>
+          </view>
+          <view class="expired-msg" v-else>
+            <text class="expired-text">会员已过期,续费恢复权益</text>
+          </view>
+        </view>
+        <view class="status-body" v-else>
+          <text class="free-hint">当前为免费版,开通会员解锁全部特权</text>
+        </view>
+      </view>
+
+      <!-- 会员权益亮点 -->
+      <view class="benefit-card">
+        <text class="benefit-card-title">{{ isFree ? '开通会员,即刻享有' : '您的会员权益' }}</text>
+        <view class="feature-list">
+          <view class="feature-item" v-for="(feature, idx) in features" :key="idx">
+            <text class="feature-check">✓</text>
+            <text class="feature-text">{{ feature }}</text>
+          </view>
+        </view>
+        <!-- 主按钮:非会员开通 / 会员续费 -->
+        <button class="btn-main" :class="isFree ? 'btn-upgrade' : 'btn-renew'" @click="goUpgrade">
+          {{ isFree ? '立即开通会员' : '立即续费' }}
+        </button>
+        <view class="benefits-entry" @click="goBenefits">
+          <text class="benefits-entry-icon">👑</text>
+          <text class="benefits-entry-text">查看我的权益</text>
+          <text class="benefits-entry-arrow">›</text>
+        </view>
+      </view>
+
+      <!-- 权益对比表 -->
+      <view class="compare-section">
+        <text class="section-title">权益对比</text>
+        <view class="compare-table">
+          <view class="compare-row header-row">
+            <text class="compare-cell cell-feature">权益项</text>
+            <text class="compare-cell cell-free">免费</text>
+            <text class="compare-cell cell-family">家庭会员</text>
+            <text class="compare-cell cell-premium">高级会员</text>
+          </view>
+          <view class="compare-row" v-for="(item, idx) in compareData" :key="idx">
+            <text class="compare-cell cell-feature">{{ item.name }}</text>
+            <text class="compare-cell cell-free">{{ item.free }}</text>
+            <text class="compare-cell cell-family">{{ item.family }}</text>
+            <text class="compare-cell cell-premium">{{ item.premium }}</text>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+import { getMyMembership } from '../../utils/api.js'
+import { parseDate } from '../../utils/format.js'
+
+export default {
+  data() {
+    return {
+      membership: null,
+      isFree: true,
+      currentLevelName: '免费用户',
+      features: ['折扣购物', '家庭管理', '推荐佣金', '专属活动', '任务特权', '成长报告'],
+      compareData: [
+        { name: '商品折扣', free: '普通价格', family: '9折', premium: '8折' },
+        { name: '家庭管理', free: '基础功能', family: '全部功能', premium: '全部功能+优先响应' },
+        { name: '管家服务', free: '—', family: '—', premium: '每周一次' },
+        { name: '推荐佣金', free: '不可参与', family: '最高15%', premium: '最高20%' },
+        { name: '专属活动', free: '部分参与', family: '全部参与', premium: '优先参与' },
+        { name: '成长报告', free: '月度', family: '每周+深度', premium: '每日+深度+专家解读' },
+        { name: '任务配额', free: '每日5个', family: '不限量', premium: '不限量+优先审核' }
+      ]
+    }
+  },
+  onShow() {
+    this.loadData()
+  },
+  methods: {
+    loadData() {
+      var that = this
+      getMyMembership().then(function(res) {
+        var m = res.data || {}
+        that.membership = m
+        if (m.levelCode && m.levelCode !== 'FREE') {
+          that.isFree = false
+          that.currentLevelName = m.levelName || '家庭会员'
+        } else {
+          that.isFree = true
+          that.currentLevelName = '免费用户'
+        }
+      }).catch(function() {})
+    },
+    goUpgrade() {
+      uni.navigateTo({ url: '/pages/membership/upgrade' })
+    },
+    goBenefits() {
+      uni.navigateTo({ url: '/pages/membership/benefits' })
+    },
+    formatDate(date) {
+      if (!date) return ''
+      var d = parseDate(date)
+      if (!d) return ''
+      var month = d.getMonth() + 1
+      var day = d.getDate()
+      return d.getFullYear() + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page {
+  min-height: 100vh;
+  background: #FFF7ED;
+  padding: 30rpx;
+  box-sizing: border-box;
+}
+
+/* ===== 状态卡片 ===== */
+.status-card {
+  background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 30rpx;
+}
+.status-badge {
+  display: inline-flex;
+  align-items: center;
+  padding: 10rpx 24rpx;
+  border-radius: 9999rpx;
+  font-size: 24rpx;
+}
+.badge-free {
+  background: rgba(255,255,255,0.2);
+  color: #fff;
+}
+.badge-family {
+  background: rgba(255,255,255,0.3);
+  color: #fff;
+}
+.badge-icon {
+  font-size: 20rpx;
+  margin-right: 8rpx;
+}
+.badge-text {
+  font-weight: 600;
+}
+.status-body {
+  margin-top: 24rpx;
+}
+.expiry-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.expiry-label {
+  font-size: 26rpx;
+  color: rgba(255,255,255,0.8);
+}
+.expiry-value {
+  font-size: 28rpx;
+  color: #fff;
+  font-weight: 500;
+}
+.days-remaining {
+  display: flex;
+  align-items: baseline;
+  justify-content: center;
+  margin-top: 20rpx;
+}
+.days-num {
+  font-size: 56rpx;
+  font-weight: bold;
+  color: #FEF3C7;
+}
+.days-unit {
+  font-size: 26rpx;
+  color: rgba(255,255,255,0.7);
+  margin-left: 8rpx;
+}
+.expired-msg {
+  text-align: center;
+  margin-top: 16rpx;
+}
+.expired-text {
+  font-size: 26rpx;
+  color: #FEF3C7;
+}
+.free-hint {
+  font-size: 26rpx;
+  color: rgba(255,255,255,0.85);
+  text-align: center;
+  display: block;
+}
+
+/* ===== 权益卡片 ===== */
+.benefit-card {
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 24rpx rgba(249, 115, 22, 0.08);
+}
+.benefit-card-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #1E293B;
+  display: block;
+  text-align: center;
+  margin-bottom: 16rpx;
+}
+.feature-list {
+  margin: 16rpx 0 24rpx;
+}
+.feature-item {
+  display: flex;
+  align-items: center;
+  padding: 12rpx 0;
+}
+.feature-check {
+  width: 36rpx;
+  height: 36rpx;
+  border-radius: 50%;
+  background: #F97316;
+  color: #fff;
+  font-size: 22rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+.feature-text {
+  font-size: 28rpx;
+  color: #334155;
+}
+.btn-main {
+  width: 100%;
+  border-radius: 44rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  padding: 24rpx 0;
+  border: none;
+  line-height: 1.4;
+}
+.btn-upgrade {
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  color: #fff;
+}
+.btn-renew {
+  background: linear-gradient(135deg, #6366F1, #818CF8);
+  color: #fff;
+}
+.btn-main::after {
+  border: none;
+}
+.benefits-entry {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-top: 24rpx;
+  padding: 20rpx;
+  background: #FFF7ED;
+  border-radius: 16rpx;
+}
+.benefits-entry-icon {
+  font-size: 32rpx;
+  margin-right: 8rpx;
+}
+.benefits-entry-text {
+  font-size: 28rpx;
+  color: #F97316;
+  font-weight: 500;
+}
+.benefits-entry-arrow {
+  font-size: 28rpx;
+  color: #F97316;
+  margin-left: 8rpx;
+}
+
+/* ===== 权益对比表 ===== */
+.compare-section {
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  box-shadow: 0 4rpx 24rpx rgba(249, 115, 22, 0.08);
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #1E293B;
+  display: block;
+  margin-bottom: 20rpx;
+}
+.compare-table {
+  width: 100%;
+}
+.compare-row {
+  display: flex;
+  padding: 14rpx 0;
+  border-bottom: 1rpx solid #F1F5F9;
+}
+.compare-row:last-child {
+  border-bottom: none;
+}
+.header-row {
+  border-bottom: 2rpx solid #FED7AA;
+}
+.compare-cell {
+  flex: 1;
+  font-size: 22rpx;
+  color: #475569;
+  text-align: center;
+  word-break: break-all;
+}
+.cell-feature {
+  flex: 1.2;
+  text-align: left;
+  color: #334155;
+  font-weight: 500;
+}
+.header-row .compare-cell {
+  color: #F97316;
+  font-weight: bold;
+}
+</style>

+ 1 - 1
cfc-frontend/pages/membership/upgrade.vue

@@ -21,7 +21,7 @@
           </view>
         </view>
         <view class="status-body" v-else>
-          <text class="free-hint">当前为免费版,开通家庭会员解锁全部特权</text>
+          <text class="free-hint">当前为免费版,开通会员解锁全部特权</text>
         </view>
       </view>