2026-07-28-membership-price-tier-update.md 22 KB

会员等级价格体系更新实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement plan. Steps use checkbox (- [ ]) syntax.

Goal: 将会员等级调整为:FREE(¥0)、FAMILY(¥1,314/年)、PREMIUM(高级家庭会员 ¥13,168/年 或 ¥1,314/月),所有价格可通过 sys_config 配置,在有价格/功能差异的页面添加缴费升级提醒。

架构: 后端更新 membership_levels 种子数据 + MembershipService 处理 PREMIUM 等级逻辑 → 前端 plans/upgrade/index 页面展示三个等级 → 商城等页面添加升级提示

Tech Stack: Spring Boot 2.7 + MyBatis-Plus / uni-app Vue 2 小程序 / MySQL 8.0

参考: docs/superpowers/plans/2026-07-27-membership-fix-plan.md(之前审计的修复计划)


文件结构

后端

文件 操作 职责
cfc-backend/.../config/DatabaseInitializer.java 修改 更新 seed sys_config 价格 + membership_levels 种子数据
cfc-backend/.../service/MembershipService.java 修改 新增 PREMIUM 等级处理
cfc-backend/.../controller/MembershipController.java 修改 /api/membership/plans 返回三个等级
cfc-backend/src/main/resources/schema.sql 修改 同步 membership_levels 表注释

前端

文件 操作 职责
cfc-frontend/pages/membership/plans.vue 修改 展示 FREE/FAMILY/PREMIUM 三个方案,从后端加载价格
cfc-frontend/pages/membership/upgrade.vue 修改 展示 FAMILY + PREMIUM 两个付费选项
cfc-frontend/pages/membership/index.vue 修改 兼容 PREMIUM 等级显示
cfc-frontend/utils/api.js 修改 确保所有 API 封装存在
cfc-frontend/pages/shop/index/index.vue 修改 升级文案从"家庭会员"改为通用"付费会员"
cfc-frontend/pages/shop/checkout/checkout.vue 修改 同上
cfc-frontend/pages/shop/cart/cart.vue 修改 同上
cfc-frontend/pages/shop/payment/payment.vue 修改 升级提示引用后端价格

Task 1: 更新数据库种子数据 + SysConfig

现状

DatabaseInitializer 中现有 sys_config 种子数据:

insertSysConfigSeed("member_fee_family", "36500", "家庭会员年费(分)");        // ← ¥365
insertSysConfigSeed("member_fee_premium", "3650", "高级会员年费(元)");       // ← 单价混乱
insertSysConfigSeed("member_discount_family", "9000", "家庭会员折扣(bps)");    // ← 9折
insertSysConfigSeed("member_discount_premium", "0.8", "高级会员折扣率");       // ← 格式不统一
insertSysConfigSeed("member_upgrade_threshold", "131400", "累计消费门槛(分)"); // ← ¥1,314

需要修改

  1. 更新 sys_config 种子值以分(cents)为单位统一:

    • member_fee_family = 131400(¥1,314 → 按分)
    • member_fee_premium = 1316800(¥13,168 → 按分)
    • member_discount_family = 9000(90% = 9折,bps)
    • member_discount_premium = 8000(80% = 8折,bps)
    • member_upgrade_threshold = 131400(¥1,314 消费门槛,不动)
  2. 添加新 sys_config 键:

    • member_fee_premium_monthly = 131400(高级会员月费 ¥1,314)
    • member_fee_family_desc = "家庭会员"(展示名称)
    • member_fee_premium_desc = "高级家庭会员"(展示名称)
  3. MembershipService.java 中的 createOrder() 里 PREMIUM 读取 member_fee_premium / member_fee_premium_monthly

  • Step 1: 修改 DatabaseInitializer sys_config 种子数据

找到 insertSysConfigSeed("member_fee_family", ...) 相关代码,更新为:

insertSysConfigSeed("member_fee_family", "131400", "家庭会员年费(分)");
insertSysConfigSeed("member_fee_premium", "1316800", "高级家庭会员年费(分)");
insertSysConfigSeed("member_fee_premium_monthly", "131400", "高级家庭会员月费(分)");
insertSysConfigSeed("member_discount_family", "9000", "家庭会员商品折扣(bps)");
insertSysConfigSeed("member_discount_premium", "8000", "高级家庭会员商品折扣(bps)");
insertSysConfigSeed("member_upgrade_threshold", "131400", "自动升级家庭会员累计消费门槛(分)");
// 保留现有其他 seed 不变
  • [ ] Step 2: 编译验证

    cd cfc-backend && mvn clean compile -q
    
  • [ ] Step 3: 提交

    git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
    git commit -m "chore: 更新会员价格 sys_config 种子数据
    FAMILY ¥1,314/年, PREMIUM ¥13,168/年或¥1,314/月, 折扣统一bps格式"
    

Task 2: 扩展 MembershipService 支持 PREMIUM 等级

  • Step 1: 修改 canUseFeature() 添加 PREMIUM 权限

在 MembershipService.java 的 canUseFeature() 方法中,在 FAMILY 判断之后添加 PREMIUM:

// PREMIUM 用户全部权益同 FAMILY
if ("PREMIUM".equals(level)) {
    return "free_activities".equals(feature)
            || "free_courses".equals(feature)
            || "full_price_purchase".equals(feature)
            || "referral_commission".equals(feature)
            || "discount_purchase".equals(feature)
            || "member_activities".equals(feature)
            || "create_family".equals(feature)
            || "invite_family".equals(feature)
            || "purchase_commission".equals(feature);
}
  • [ ] Step 2: 修改 applyMemberDiscount() 支持 PREMIUM 更高折扣

    public Integer applyMemberDiscount(Long userId, Integer originalPrice) {
    if (originalPrice == null || originalPrice <= 0) return 0;
    String level = getMemberLevel(userId);
    String discountStr = null;
    if ("PREMIUM".equals(level)) {
        discountStr = sysConfigService.getValue("member_discount_premium");
    } else if ("FAMILY".equals(level)) {
        discountStr = sysConfigService.getValue("member_discount_family");
    }
    if (discountStr != null) {
        int discountBps = Integer.parseInt(discountStr);
        return originalPrice * discountBps / 10000;
    }
    return originalPrice;
    }
    
  • [ ] Step 3: 修改 createOrder() 支持 PREMIUM 定价

在 createOrder() 的 FAMILY/PROVIDER 判断后添加:

} else if ("PREMIUM".equals(levelCode)) {
    if ("monthly".equals(effectivePeriod)) {
        String feeStr = sysConfigService.getValue("member_fee_premium_monthly");
        amount = feeStr != null ? Integer.parseInt(feeStr) : 131400;
    } else {
        String feeStr = sysConfigService.getValue("member_fee_premium");
        amount = feeStr != null ? Integer.parseInt(feeStr) : 1316800;
    }
}
  • [ ] Step 4: 编译验证

    cd cfc-backend && mvn clean compile -q
    
  • [ ] Step 5: 提交

    git add cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java
    git commit -m "feat: MembershipService 支持 PREMIUM 等级
    canUseFeature/applyMemberDiscount/createOrder 增加 PREMIUM 逻辑"
    

Task 3: 更新 MembershipController 计划列表端点

  • Step 1: 确认 /api/membership/plans 已返回所有等级

当前代码(MembershipController.java 行 217-231)已调用 getAllLevels(),返回所有等级。membership_levels 表中有 FREE/FAMILY/PREMIUM(需确认后台种子数据已插入)。无需修改代码,但需确认 DB 有数据。

也可以通过 /api/membership/levels 端点验证,同样返回所有等级。

如果需要额外返回价格说明文本(如突出"推荐"标记),可扩展 MembershipController.getPricingPlans():

plan.put("recommended", "FAMILY".equals(level.getLevelCode()));  // 推荐标记
plan.put("levelDesc", level.getLevelDesc());
  • [ ] Step 2: 提交(如需改动)

    git add cfc-backend/.../controller/MembershipController.java
    git commit -m "feat: /api/membership/plans 增加推荐标记和等级描述"
    

Task 4: 更新小程序 plans.vue 展示三个等级

现状: plans.vue 硬编码 L0(注册会员¥0)、L1(❤️ 一生一世¥1,314/年)、L2(💎 久久一生¥1,314/月),价格和功能均为静态数据。

改为: 从后端 /api/membership/plans 加载三个等级,动态渲染。

  • Step 1: 修改 <template> 使用 v-for 渲染等级卡片

移除硬编码的三张卡片,改为:

<view class="plan-card" v-for="level in levels" :key="level.levelCode"
    :class="{'active': level.levelCode === 'FAMILY'}"
    v-if="level.levelCode !== 'FREE'">
    <!-- 付费等级卡片 -->
    <view class="plan-badge" v-if="level.levelCode === 'FAMILY'">推荐</view>
    <view class="plan-badge premium" v-else-if="level.levelCode === 'PREMIUM'">至尊</view>
    
    <view class="plan-header">
        <text class="plan-name">{{ level.levelName }}</text>
        <text class="plan-tag">{{ level.levelCode === 'FAMILY' ? '年付会员' : '年付/月付' }}</text>
    </view>
    
    <view class="plan-price">
        <text class="price-amount">{{ formatPrice(level.priceYearly) }}</text>
        <text class="price-unit">/年</text>
    </view>
    
    <view class="plan-features">
        <view class="feature-row" v-for="(feat, idx) in parseFeatures(level.features)" :key="idx">
            <text class="feature-icon">✓</text>
            <text class="feature-text">{{ feat }}</text>
        </view>
    </view>
    
    <button class="btn-action" :class="level.levelCode === 'FAMILY' ? 'btn-primary' : 'btn-premium'"
        @click="purchasePlan(level.levelCode)">
        {{ level.levelCode === 'FAMILY' ? '立即开通' : '立即订阅' }}
    </button>
</view>
  • [ ] Step 2: 修改 <script> data 和方法

    import { getMembershipPlans } from '../../utils/api'
    
    export default {
    data() {
    return {
      levels: [],
      l0Features: ['每日 3 次 AI 问答', '基础健康百科', '社区内容浏览'],
      benefits: [
        { icon: '🤖', name: 'AI 健康顾问', desc: '7×24 小时智能问答', bg: '#DBEAFE' },
        { icon: '👨‍👩‍👧‍👦', name: '家庭覆盖', desc: '一次付费全家守护', bg: '#D1FAE5' },
        { icon: '👨‍🏫', name: '专属管家', desc: '认证专业人员服务', bg: '#FEF3C7' },
        { icon: '🛒', name: '商品折扣', desc: '会员专享优惠价', bg: '#FCE7F3' },
        { icon: '💰', name: '消费返利', desc: 'P 点制终身返利', bg: '#FFEDD5' },
        { icon: '📈', name: '推广佣金', desc: 'R0-R4 阶梯奖励', bg: '#E0E7FF' }
      ],
      faqs: [
        { q: '家庭会员适合谁?', a: '适合有 1-2 个孩子的家庭,追求性价比' },
        { q: '高级家庭会员与家庭会员的区别?', a: '享更高折扣(8折 vs 9折)、更多高级服务' },
        { q: '可以取消吗?', a: '可以,到期后降级为普通会员,数据保留 30 天' },
        { q: '消费返利如何计算?', a: '被推荐人消费 P 点 × 返利率' }
      ]
    }
    },
    onLoad() {
    this.loadPlans()
    },
    methods: {
    loadPlans() {
      var self = this
      getMembershipPlans().then(function(res) {
        if (res.code === 200 && Array.isArray(res.data)) {
          self.levels = res.data
        }
      }).catch(function(e) {
        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 })
    }
    }
    }
    

保留原有免费卡片(L0/注册会员),只替换 L1/L2 为从后端动态加载的 FAMILY/PREMIUM。

  • [ ] Step 3: 提交

    git add cfc-frontend/pages/membership/plans.vue
    git commit -m "feat: plans.vue 动态加载三个会员等级
    移除硬编码价格,从后端 /api/membership/plans 获取 FREE/FAMILY/PREMIUM 数据"
    

Task 5: 更新 upgrade.vue 展示 FAMILY + PREMIUM 选项

现状: upgrade.vue 只有家庭会员(FAMILY)一个付费选项,显示档位选择(月/季/年)。

改为: 添加高级家庭会员(PREMIUM)选项卡,用户可在两个付费方案间切换。

  • Step 1: 添加等级切换 tabs

在 template 的 upgrade-card 区域添加等级选择 tabs:

<!-- 等级选择 -->
<view class="level-tabs">
    <view class="level-tab"
        :class="selectedLevel === 'FAMILY' ? 'level-tab-active' : ''"
        @click="selectLevel('FAMILY')">
        <text class="level-tab-name">家庭会员</text>
        <text class="level-tab-price">¥{{ getLevelYearlyPrice('FAMILY') }}/年</text>
    </view>
    <view class="level-tab"
        :class="selectedLevel === 'PREMIUM' ? 'level-tab-active' : ''"
        @click="selectLevel('PREMIUM')">
        <text class="level-tab-name">高级家庭会员</text>
        <text class="level-tab-price">¥{{ getLevelYearlyPrice('PREMIUM') }}/年</text>
    </view>
</view>
  • [ ] Step 2: 修改 script data

    data() {
    return {
        selectedLevel: 'FAMILY',  // 新增:选择的等级
        // ... 保留现有字段
    }
    },
    methods: {
    selectLevel(code) {
        this.selectedLevel = code
    },
    getLevelYearlyPrice(code) {
        for (var i = 0; i < this.levels.length; i++) {
            if (this.levels[i].levelCode === code) {
                return this.levels[i].priceYearly ? (this.levels[i].priceYearly / 100).toFixed(0) : '0'
            }
        }
        return '0'
    },
    // 修改 getPriceForPeriod 根据 selectedLevel 查询
    getPriceForPeriod(periodKey) {
        var plan = null
        for (var i = 0; i < this.levels.length; i++) {
            if (this.levels[i].levelCode === this.selectedLevel) {
                plan = this.levels[i]
                break
            }
        }
        if (!plan) return '0'
        if (periodKey === 'monthly') return plan.priceMonthly ? (plan.priceMonthly / 100).toFixed(0) : '0'
        if (periodKey === 'quarterly') return plan.priceQuarterly ? (plan.priceQuarterly / 100).toFixed(0) : '0'
        if (periodKey === 'yearly') return plan.priceYearly ? (plan.priceYearly / 100).toFixed(0) : '0'
        return '0'
    },
    // 修改 doUpgrade/doRenew 使用 selectedLevel
    doUpgrade() {
        var self = this
        createOrder(self.selectedLevel, 'pay', self.selectedPeriod).then(function(res) {
            uni.showToast({ title: '开通成功', icon: 'success' })
            self.loadData()
        }).catch(function(e) {
            uni.showToast({ title: e.message || '开通失败', icon: 'none' })
        })
    },
    // features 改为根据 selectedLevel 动态
    // compareData 增加 PREMIUM 列
    }
    
  • [ ] Step 3: 权益对比表增加第三列(PREMIUM)

    <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 active">高级会员</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 active">{{ item.premium }}</text>
    </view>
    

data.compareData 更新:

compareData: [
    { name: '商品折扣', free: '普通价格', family: '9折', premium: '8折' },
    { name: '家庭管理', free: '基础功能', family: '全部功能', premium: '全部功能' },
    { name: '推荐佣金', free: '不可参与', family: '最高15%', premium: '最高20%' },
    { name: '专属活动', free: '部分参与', family: '全部参与', premium: '优先参与' },
    { name: '成长报告', free: '月度', family: '每周+深度', premium: '每日+深度+专家解读' },
    { name: '任务配额', free: '每日5个', family: '不限量', premium: '不限量+优先审核' }
]
  • [ ] Step 4: 提交

    git add cfc-frontend/pages/membership/upgrade.vue
    git commit -m "feat: upgrade.vue 展示 FAMILY + PREMIUM 两个付费选项
    等级切换 tabs、动态价格、权益对比三列"
    

Task 6: 更新 membership/index.vue 兼容 PREMIUM

现状: index.vue 已从后端动态加载等级列表,但对比表只有两列(免费 vs 家庭会员),isFamilyMember 判断当前等级。

  • [ ] Step 1: 修改 isFamilyMember 兼容 PREMIUM

    computed: {
    isMember() {
        return this.membership && this.membership.levelCode 
            && this.membership.levelCode !== 'FREE' && this.membership.isActive
    },
    familyPriceText() {
        var familyLevel = this.levels.find(function(l) { return l.levelCode === 'FAMILY' })
        if (familyLevel && familyLevel.priceYearly > 0) {
            return this.formatPrice(familyLevel.priceYearly)
        }
        return '¥1,314.00'
    }
    }
    
  • [ ] Step 2: 等级对比表增加第三列

参照 upgrade.vue 的修改,在 compareData 中增加 premium 字段。

  • Step 3: 等级卡片列表兼容 PREMIUM

当前 v-for="level in levels" 已动态渲染所有等级,无需改动模板。但按钮文字需要适配:

  • level.levelCode === 'FAMILY' ? '开通' : level.levelCode === 'PREMIUM' ? '升级' : ''

    <button class="btn-level-action" 
    v-if="level.levelCode !== currentLevelCode && level.levelCode !== 'FREE'" 
    @click="handlePurchase(level)">
    {{ level.levelCode === 'FAMILY' ? '开通' : '升级' }}
    </button>
    
  • [ ] Step 4: 提交

    git add cfc-frontend/pages/membership/index.vue
    git commit -m "feat: membership/index.vue 兼容 PREMIUM 等级显示
    对比表三列, isMember 判断, 等级卡片列表"
    

Task 7: 商城页面升级提示通用化

现状: 商城页面有"开通家庭会员"的升级提示(shop/index、checkout、cart、payment)。文案写死了"家庭会员"。

改为: 文案改为"开通付费会员"或从配置读取最低付费等级名称和价格。

  • [ ] Step 1: shop/index/index.vue

    <!-- 修改前 -->
    <text class="member-banner-title">开通家庭会员</text>
    <text class="member-banner-desc">享专属折扣与成长服务</text>
    
    <!-- 修改后 -->
    <text class="member-banner-title">开通付费会员</text>
    <text class="member-banner-desc">享专属折扣与成长服务,最低仅 ¥{{ minMemberPrice }}/年</text>
    

添加 minMemberPrice:

data: { minMemberPrice: '1,314' }, // 从 levels 中找最便宜的付费等级

// loadData 中添加:
try {
    var levelsRes = await getMembershipLevels()
    var minPrice = null
    for (var i = 0; i < (levelsRes.data || []).length; i++) {
        var l = levelsRes.data[i]
        if (l.levelCode !== 'FREE' && l.priceYearly && (!minPrice || l.priceYearly < minPrice)) {
            minPrice = l.priceYearly
        }
    }
    if (minPrice) that.minMemberPrice = (minPrice / 100).toLocaleString()
} catch(e) {}
  • [ ] Step 2: shop/checkout/checkout.vue (同样修改)

    <!-- 修改前 -->
    <text class="member-upgrade-text">开通家庭会员享全场商品会员专享价</text>
    <!-- 修改后 -->
    <text class="member-upgrade-text">开通付费会员享全场商品会员专享价</text>
    
  • [ ] Step 3: shop/cart/cart.vue (同样修改)

    <!-- 修改前 -->
    <text class="member-hint-text">开通家庭会员享全场商品折扣</text>
    <!-- 修改后 -->
    <text class="member-hint-text">开通付费会员享全场商品折扣</text>
    
  • [ ] Step 4: shop/payment/payment.vue

    <!-- 修改前 -->
    <text class="upgrade-card-title">支付成功!开通家庭会员更划算</text>
    <!-- 修改后 -->
    <text class="upgrade-card-title">支付成功!开通付费会员更划算</text>
    

同时修改 familyYearlyPrice 从后端获取:

// 现有 this.familyYearlyPrice 从 /api/membership/levels 获取,确认已实现
  • [ ] Step 5: 提交

    git add cfc-frontend/pages/shop/index/index.vue cfc-frontend/pages/shop/checkout/checkout.vue cfc-frontend/pages/shop/cart/cart.vue cfc-frontend/pages/shop/payment/payment.vue
    git commit -m "fix: 商城升级提示文案通用化
    '开通家庭会员'改为'开通付费会员',价格从后端动态获取"
    

Task 8: 管理端会员价格配置页面(可选)

如果现有 cfc-web/src/views/admin/SysConfig.vue 可以编辑 sys_config,则不需要新建页面。确认后补充。

  • [ ] Step 1: 检查 SysConfig.vue 是否支持编辑 sys_config

    ls cfc-web/src/views/admin/SysConfig*.vue 2>/dev/null || echo "需要新建"
    
  • [ ] Step 2: 如需新建,参考已有配置页面

(留出,视检查结果决定)


依赖与执行顺序

Task 1 (sys_config seed) ──→ Task 2 (MembershipService) ──→ 后端完成
                                                                    │
Task 4 (plans.vue) ───────────────────────────────────────────────┘
Task 5 (upgrade.vue) ─────────────────────────────────────────────┘
Task 6 (index.vue) ───────────────────────────────────────────────┘
Task 7 (shop prompts) ── 独立,可并行 ──→ 前端完成

可并行:Task 1+2(后端连续),Task 4+5+6+7(前端皆依赖后端出数,但可先改代码)


自检清单

  • membership_levels 表种子数据包含 FREE/FAMILY/PREMIUM 正确价格
  • sys_config 中所有价格键值以分(cents)为单位统一
  • MembershipService.canUseFeature() 涵盖 PREMIUM
  • MembershipService.applyMemberDiscount() 区分 FAMILY(9折)/PREMIUM(8折)
  • MembershipService.createOrder() 处理 PREMIUM 月付/年付
  • plans.vue 无硬编码价格残留
  • upgrade.vue 可在 FAMILY/PREMIUM 间切换
  • index.vue 对比表三列
  • 商城页面文案通用化
  • mvn clean compile 通过