Browse Source

feat: plans.vue 动态加载会员等级 + FAQ 更新

移除硬编码 L1/L2,从 /api/membership/plans 动态加载 FAMILY/PREMIUM 等级数据
Sisyphus 1 month ago
parent
commit
48fb9c3f05
2 changed files with 43 additions and 100 deletions
  1. 39 100
      cfc-frontend/pages/membership/plans.vue
  2. 4 0
      cfc-frontend/utils/api.js

+ 39 - 100
cfc-frontend/pages/membership/plans.vue

@@ -1,11 +1,12 @@
 <template>
   <scroll-view class="container" scroll-y>
     <view class="plans-header">
-      <text class="header-title">一生一世家庭会员</text>
+      <text class="header-title">会员方案</text>
       <text class="header-subtitle">全家人的健康守护</text>
     </view>
 
     <view class="plans-list">
+      <!-- 免费卡片(静态) -->
       <view class="plan-card plan-l0">
         <view class="plan-header">
           <text class="plan-name">注册会员</text>
@@ -24,42 +25,29 @@
         <button class="btn-action" disabled>当前等级</button>
       </view>
 
-      <view class="plan-card plan-l1 active">
-        <view class="plan-badge">推荐</view>
+      <!-- 付费等级卡片(从后端动态加载) -->
+      <view class="plan-card" v-for="level in levels" :key="level.levelCode"
+        :class="{'active': level.recommended}">
+        <view class="plan-badge" v-if="level.recommended">推荐</view>
+        <view class="plan-badge premium" v-else-if="level.levelCode === 'PREMIUM'">至尊</view>
         <view class="plan-header">
-          <text class="plan-name">❤️ 一生一世</text>
-          <text class="plan-tag">年付会员</text>
+          <text class="plan-name">{{ level.levelName }}</text>
+          <text class="plan-tag">{{ level.levelCode === 'FAMILY' ? '年付会员' : '年付/月付' }}</text>
         </view>
         <view class="plan-price">
-          <text class="price-amount">¥1,314</text>
+          <text class="price-amount">{{ formatPrice(level.yearly) }}</text>
           <text class="price-unit">/年</text>
         </view>
-        <view class="plan-features">
-          <view class="feature-row" v-for="(feat, idx) in l1Features" :key="idx">
-            <text class="feature-icon">✓</text>
-            <text class="feature-text">{{ feat }}</text>
-          </view>
-        </view>
-        <button class="btn-action btn-primary" @click="purchasePlan('L1')">立即开通</button>
-      </view>
-
-      <view class="plan-card plan-l2">
-        <view class="plan-badge premium">至尊</view>
-        <view class="plan-header">
-          <text class="plan-name">💎 久久一生</text>
-          <text class="plan-tag">连续包月</text>
-        </view>
-        <view class="plan-price">
-          <text class="price-amount">¥1,314</text>
-          <text class="price-unit">/月</text>
-        </view>
-        <view class="plan-features">
-          <view class="feature-row" v-for="(feat, idx) in l2Features" :key="idx">
+        <view class="plan-features" v-if="level.levelDesc">
+          <view class="feature-row" v-for="(feat, idx) in parseFeatures(level.levelDesc)" :key="idx">
             <text class="feature-icon">✓</text>
             <text class="feature-text">{{ feat }}</text>
           </view>
         </view>
-        <button class="btn-action btn-premium" @click="purchasePlan('L2')">立即订阅</button>
+        <button class="btn-action" :class="level.recommended ? 'btn-primary' : 'btn-premium'"
+          @click="purchasePlan(level.levelCode)">
+          {{ level.levelCode === 'FAMILY' ? '立即开通' : '立即订阅' }}
+        </button>
       </view>
     </view>
 
@@ -87,38 +75,17 @@
 </template>
 
 <script>
-import { getSubscriptionPlans, createSubscriptionOrder } from '../../utils/api.js'
+import { getMembershipPlans } from '../../utils/api.js'
 
 export default {
   data() {
     return {
-      plans: [],
-      currentPlan: 'L0',
+      levels: [],
       l0Features: [
         '每日 3 次 AI 问答',
         '基础健康百科',
         '社区内容浏览'
       ],
-      l1Features: [
-        '7×24 小时 AI 健康顾问',
-        '报告智能解读',
-        '家庭成员上限 10 人',
-        '商品 8-9 折优惠',
-        '月度健康计划',
-        '认证管家服务',
-        '推荐佣金 R0-R4',
-        '消费返利 P×20%-40%'
-      ],
-      l2Features: [
-        'L1 全部权益',
-        '深度 AI 分析 + 多模态',
-        '家庭成员上限 15 人',
-        '商品 6-8 折优惠',
-        '双周健康计划',
-        '资深管家 (1:30)',
-        '专家团会诊每年 4 次',
-        '消费返利 +5% 加成'
-      ],
       benefits: [
         { icon: '🤖', name: 'AI 健康顾问', desc: '7×24 小时智能问答', bg: '#DBEAFE' },
         { icon: '👨‍👩‍👧‍👦', name: '家庭覆盖', desc: '一次付费全家守护', bg: '#D1FAE5' },
@@ -128,10 +95,10 @@ export default {
         { icon: '📈', name: '推广佣金', desc: 'R0-R4 阶梯奖励', bg: '#E0E7FF' }
       ],
       faqs: [
-        { q: 'L1 一生一世适合谁?', a: '适合有 1-2 个孩子的家庭,追求性价比,希望获得 AI 健康顾问 + 认证管家服务' },
-        { q: 'L2 久久一生与 L1 的区别?', a: 'L2 享资深管家 (1:30 配比)、深度 AI 分析、专家会诊、更高折扣和返利加成' },
-        { q: 'L2 可以取消吗?', a: '可以,取消后当前月结束后降级为 L0,健康档案保留 30 天' },
-        { q: '消费返利如何计算?', a: '被推荐人消费 P 点总和 × 推广人等级返利率,L2 会员额外 +5% 加成' }
+        { q: '家庭会员适合谁?', a: '适合有 1-2 个孩子的家庭,追求性价比,希望获得 AI 健康顾问 + 认证管家服务' },
+        { q: '高级家庭会员与家庭会员的区别?', a: '高级会员享资深管家 (1:30 配比)、深度 AI 分析、专家会诊、更高折扣和返利加成' },
+        { q: '可以取消吗?', a: '可以,取消后当前月结束后降级为免费用户,健康档案保留 30 天' },
+        { q: '消费返利如何计算?', a: '被推荐人消费 P 点总和 × 推广人等级返利率,高级会员额外 +5% 加成' }
       ]
     }
   },
@@ -139,55 +106,27 @@ export default {
     this.loadPlans()
   },
   methods: {
-    async loadPlans() {
-      try {
-        const res = await getSubscriptionPlans()
-        if (res.data) {
-          this.plans = res.data
-        }
-      } catch (e) {
-        console.error('获取订阅方案失败', e)
-      }
-    },
-    purchasePlan(level) {
+    loadPlans() {
       var self = this
-      uni.showModal({
-        title: '确认购买',
-        content: '确认购买' + (level === 'L1' ? '一生一世年付会员' : '久久一生连续包月') + '?',
-        success: function(res) {
-          if (res.confirm) {
-            self.doPurchase(level)
-          }
-        }
-      })
-    },
-    doPurchase(level) {
-      var self = this
-      var data = { level: level }
-      createSubscriptionOrder(data).then(function(res) {
-        if (res.data && res.data.paymentUrl) {
-          uni.requestPayment({
-            provider: 'wxpay',
-            orderInfo: res.data.paymentUrl,
-            success: function(payRes) {
-              uni.showToast({ title: '支付成功', icon: 'success' })
-            },
-            fail: function(payErr) {
-              if (payErr.errMsg && payErr.errMsg.indexOf('cancel') !== -1) {
-                uni.showToast({ title: '已取消支付', icon: 'none' })
-              } else {
-                uni.showToast({ title: '支付失败', icon: 'none' })
-              }
-            }
-          })
-        } else if (res.data && res.data.testMode) {
-          uni.showToast({ title: '购买成功(测试模式)', icon: 'success' })
-        } else {
-          uni.showToast({ title: '购买成功', icon: 'success' })
+      getMembershipPlans().then(function(res) {
+        if (res.code === 200 && Array.isArray(res.data)) {
+          self.levels = res.data
         }
       }).catch(function(e) {
-        uni.showToast({ title: e.message || '购买失败', icon: 'none' })
+        console.error('获取方案失败', e)
       })
+    },
+    formatPrice(cents) {
+      if (!cents && cents !== 0) return '¥0'
+      return '¥' + (cents / 100).toLocaleString()
+    },
+    parseFeatures(features) {
+      if (!features) return []
+      if (Array.isArray(features)) return features
+      try { return JSON.parse(features) } catch (e) { return [] }
+    },
+    purchasePlan(levelCode) {
+      uni.navigateTo({ url: '/pages/membership/upgrade?level=' + levelCode })
     }
   }
 }

+ 4 - 0
cfc-frontend/utils/api.js

@@ -289,6 +289,10 @@ export const getMembershipLevels = () => {
 	return request('/api/membership/levels', 'POST')
 }
 
+export const getMembershipPlans = () => {
+	return request('/api/membership/plans', 'POST')
+}
+
 export const getMyMembership = () => {
 	return request('/api/membership/my', 'POST')
 }