2026-06-04-pre-login-discovery.md 25 KB

登录前浏览体验 Implementation Plan

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

Goal: 未登录用户打开小程序即可浏览发现页(五行沙盘+活动+商品)和商城,无需看到登录页

Architecture: 未登录时隐藏原生 TabBar,使用自定义底部导航(发现/商城);发现页为静态 HTML 内容(零后端依赖)。登录后恢复原生 4Tab。

Tech Stack: uni-app Vue 2 (微信小程序)


Task 1: 品牌更名 溪艾福→浠艾福

Files:

  • Modify: cfc-frontend/pages/index/parent-index.vue (4处)
  • Modify: cfc-frontend/pages/index/child-index.vue (2处)
  • Modify: cfc-frontend/pages.json (1处)
  • Modify: cfc-frontend/pages/profile/profile.vue (1处)
  • Modify: cfc-frontend/utils/api.js (1处)
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java (4处)

  • [ ] Step 1: 替换所有前端「溪艾福」→「浠艾福」

Replace in parent-index.vue:

  • Line 68: 五行能量沙盘(溪艾福)→ 五行能量沙盘(浠艾福)
  • Line 125: 会员Banner(溪福俱乐部)→ 会员Banner(浠艾福俱乐部)
  • Line 1213: 溪福俱乐部 → 浠艾福俱乐部
  • Line 1249: 五行能量沙盘(溪艾福)→ 五行能量沙盘(浠艾福)

Replace in child-index.vue:

  • Line 54: 五行能量沙盘(溪艾福)→ 五行能量沙盘(浠艾福)
  • Line 881: 五行能量沙盘(溪艾福)→ 五行能量沙盘(浠艾福)

Replace in pages.json:

  • Line 329: 溪福俱乐部 → 浠艾福俱乐部

Replace in profile.vue:

  • Line 52: 溪福俱乐部 → 浠艾福俱乐部

Replace in api.js:

  • Line 722: 溪福俱乐部 → 浠艾福俱乐部

Replace in DatabaseInitializer.java (comments only):

  • "溪福俱乐部" → "浠艾福俱乐部" (4 occurrences)

  • [ ] Step 2: Commit

    git add $(rtk list -u --modified -- cfc-frontend/ cfc-backend/)
    git commit -m "chore: 品牌名溪艾福/溪福俱乐部统一改为浠艾福/浠艾福俱乐部"
    

Task 2: 提取五行沙盘为独立组件

Files:

  • Create: cfc-frontend/components/wuxing-sandbox.vue
  • Modify: cfc-frontend/pages/index/parent-index.vue
  • Modify: cfc-frontend/pages/index/child-index.vue

  • [ ] Step 1: 创建 wuxing-sandbox.vue 组件

    <template>
    <view class="wuxing-sandbox">
    <view class="sandbox-header">
      <text class="sandbox-title">五行能量沙盘</text>
      <text class="sandbox-badge">{{ badgeText }}</text>
    </view>
    <view class="sandbox-star">
      <view class="star-point star-point-top" @click="onPointClick('mind')">
        <text class="point-icon">🔥</text>
        <text class="point-label">心·火</text>
        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
      </view>
      <view class="star-point star-point-left" @click="onPointClick('action')">
        <text class="point-icon">🌿</text>
        <text class="point-label">行·木</text>
        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
      </view>
      <view class="star-point star-point-right" @click="onPointClick('wealth')">
        <text class="point-icon">💧</text>
        <text class="point-label">富·水</text>
        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
      </view>
      <view class="star-point star-point-bl" @click="onPointClick('wisdom')">
        <text class="point-icon">⚔️</text>
        <text class="point-label">智·金</text>
        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
      </view>
      <view class="star-point star-point-br" @click="onPointClick('body')">
        <text class="point-icon">🌏</text>
        <text class="point-label">身·土</text>
        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
      </view>
      <view class="star-center">
        <text class="center-text">五行平衡</text>
        <text class="center-sub">共同成长</text>
      </view>
    </view>
    <view class="shenke-hint" v-if="mode === 'preview'">
      <text class="hint-text">🌱 完成测评,点亮「身·土」</text>
    </view>
    <view class="dimension-bar" v-if="mode !== 'preview'">
      <view class="dim-item inactive">🌿行</view>
      <text class="dim-arrow">→</text>
      <view class="dim-item inactive">🔥心</view>
      <text class="dim-arrow">→</text>
      <view class="dim-item inactive">🌏身</view>
      <text class="dim-arrow">→</text>
      <view class="dim-item inactive">⚔️智</view>
      <text class="dim-arrow">→</text>
      <view class="dim-item inactive">💧富</view>
    </view>
    </view>
    </template>
    
    <script>
    export default {
    props: {
    mode: { type: String, default: 'preview' }, // preview | parent | child
    badgeText: { type: String, default: '综合成长力 0%' }
    },
    methods: {
    onPointClick(domain) {
      this.$emit('point-click', domain)
    }
    }
    }
    </script>
    
    <style scoped>
    .wuxing-sandbox {
    margin: 20rpx 30rpx;
    padding: 30rpx;
    background: linear-gradient(135deg, #1a1a2e, #16213e);
    border-radius: 24rpx;
    position: relative;
    overflow: hidden;
    }
    .sandbox-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 30rpx;
    }
    .sandbox-title {
    font-size: 28rpx;
    font-weight: bold;
    color: #FFD700;
    }
    .sandbox-badge {
    font-size: 22rpx;
    color: #aaa;
    background: rgba(255,255,255,0.1);
    padding: 6rpx 16rpx;
    border-radius: 20rpx;
    }
    .sandbox-star {
    position: relative;
    height: 400rpx;
    display: flex;
    justify-content: center;
    align-items: center;
    }
    .star-point {
    position: absolute;
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 120rpx;
    }
    .star-point-top { top: 0; left: 50%; transform: translateX(-50%); }
    .star-point-left { top: 120rpx; left: 20rpx; }
    .star-point-right { top: 120rpx; right: 20rpx; }
    .star-point-bl { bottom: 40rpx; left: 60rpx; }
    .star-point-br { bottom: 40rpx; right: 60rpx; }
    .point-icon { font-size: 50rpx; margin-bottom: 6rpx; }
    .point-label { font-size: 22rpx; color: #fff; font-weight: bold; }
    .point-status { font-size: 18rpx; color: #FF6B6B; margin-top: 4rpx; }
    .star-center {
    width: 120rpx; height: 120rpx;
    background: radial-gradient(circle, rgba(255,215,0,0.2), rgba(255,215,0,0.05));
    border: 2rpx solid rgba(255,215,0,0.3);
    border-radius: 50%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    }
    .center-text { font-size: 22rpx; color: #FFD700; font-weight: bold; }
    .center-sub { font-size: 18rpx; color: #aaa; }
    .shenke-hint { text-align: center; margin-top: 10rpx; }
    .hint-text { font-size: 22rpx; color: rgba(255,215,0,0.7); }
    .dimension-bar {
    display: flex; align-items: center; justify-content: center;
    margin-top: 20rpx; gap: 8rpx;
    }
    .dim-item {
    font-size: 22rpx; color: #FFD700;
    background: rgba(255,215,0,0.1);
    padding: 6rpx 14rpx; border-radius: 12rpx;
    }
    .dim-arrow { font-size: 20rpx; color: rgba(255,215,0,0.3); }
    </style>
    
  • [ ] Step 2: 替换 parent-index.vue 内联沙盘为组件

Remove the inline wuxing-sandbox HTML+CSS block from parent-index.vue (the entire block from <!-- 五行能量沙盘(浠艾福) --> to its closing </view>), replace with:

      <wuxing-sandbox mode="parent" badgeText="综合成长力 0%" @point-click="goToDomain" />

Add import:

import WuxingSandbox from '../../components/wuxing-sandbox.vue'

Add to components:

components: { WuxingSandbox }
  • Step 3: 替换 child-index.vue 内联沙盘为组件

Same pattern as Step 2 — replace inline sandbox with <wuxing-sandbox mode="child" badgeText="综合 0%" @point-click="goToDomain" />

Add import + components registration.

Remove the entire inline <style> block for .wuxing-sandbox CSS from both parent-index.vue and child-index.vue (since it now lives in the component).

  • [ ] Step 4: Commit

    git add cfc-frontend/components/wuxing-sandbox.vue cfc-frontend/pages/index/parent-index.vue cfc-frontend/pages/index/child-index.vue
    git commit -m "refactor: 提取五行沙盘为独立组件(wuxing-sandbox.vue)支持三种模式"
    

Task 3: 创建自定义底部导航组件

Files:

  • Create: cfc-frontend/components/bottom-nav.vue

  • [ ] Step 1: 创建 bottom-nav.vue 组件

    <template>
    <view class="bottom-nav">
    <view
      class="nav-item"
      :class="{ active: current === 'discover' }"
      @click="switchTab('discover')"
    >
      <text class="nav-icon">🔍</text>
      <text class="nav-text">发现</text>
    </view>
    <view
      class="nav-item"
      :class="{ active: current === 'shop' }"
      @click="switchTab('shop')"
    >
      <text class="nav-icon">🛒</text>
      <text class="nav-text">商城</text>
    </view>
    </view>
    </template>
    
    <script>
    export default {
    props: {
    current: { type: String, default: 'discover' }
    },
    methods: {
    switchTab(tab) {
      if (tab === this.current) return
      if (tab === 'discover') {
        uni.redirectTo({ url: '/pages/discover/index' })
      } else if (tab === 'shop') {
        uni.redirectTo({ url: '/pages/shop/index' })
      }
    }
    }
    }
    </script>
    
    <style scoped>
    .bottom-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 100rpx;
    background: #fff;
    border-top: 1rpx solid #eee;
    display: flex;
    align-items: center;
    justify-content: space-around;
    padding-bottom: env(safe-area-inset-bottom);
    z-index: 100;
    }
    .nav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex: 1;
    height: 100%;
    }
    .nav-item.active .nav-text { color: #FF6B6B; }
    .nav-icon { font-size: 40rpx; }
    .nav-text { font-size: 20rpx; color: #999; margin-top: 4rpx; }
    .nav-item.active .nav-text { font-weight: bold; }
    </style>
    
  • [ ] Step 2: Commit

    git add cfc-frontend/components/bottom-nav.vue
    git commit -m "feat: 创建自定义底部导航组件(bottom-nav)用于未登录态2Tab展示"
    

Task 4: 重构 App.vue 登录态 + TabBar 切换逻辑

Files:

  • Modify: cfc-frontend/App.vue

  • [ ] Step 1: 修改 App.vue onLaunch

Add login state check: if no token → hide native TabBar, redirect to discover. If has token → show native TabBar, proceed normally.

<script>
export default {
  onLaunch: function() {
    console.log('App Launch')
    const token = uni.getStorageSync('token')
    if (token) {
      this.$store.commit('setToken', token)
      // 已登录:确保原生 TabBar 显示
      uni.showTabBar()
    } else {
      // 未登录:隐藏原生 TabBar,跳转到发现页
      uni.hideTabBar()
      // 如果当前不在发现页或商城页,则跳转
      const pages = getCurrentPages()
      const currentRoute = pages.length > 0 ? pages[pages.length - 1].route : ''
      if (currentRoute !== 'pages/discover/index' && currentRoute !== 'pages/shop/index') {
        uni.reLaunch({ url: '/pages/discover/index' })
      }
    }
  },
  onShow: function() {
    // 每次显示时检查登录状态,同步 TabBar
    const token = uni.getStorageSync('token')
    if (token) {
      uni.showTabBar()
    } else {
      uni.hideTabBar()
    }
  },
  // ... rest of existing methods (tryAutoLogin, saveLoginInfo, request)
}
</script>
  • Step 2: 确保 login.vue 登录成功后隐藏原生 TabBar + 跳转发现页

The existing login success handler does uni.reLaunch({ url: '/pages/index/index' }). This should be changed to NOT re-show the native TabBar for unauthenticated users — but since login succeeded, they ARE authenticated, so showing the TabBar is correct.

Actually, the flow should be:

  1. User logs in successfully
  2. uni.showTabBar() → restore native TabBar
  3. uni.reLaunch({ url: '/pages/index/index' }) → go to home page (which now has TabBar)

This should already work with the App.vue changes. No need to modify login.vue TabBar logic — just ensure the navigateToHome function works.

  • [ ] Step 3: Commit

    git add cfc-frontend/App.vue
    git commit -m "feat: App.vue 添加登录态TabBar切换逻辑(未登录隐藏原生TabBar)"
    

Task 5: 重构发现页(核心页面)

Files:

  • Modify: cfc-frontend/pages/discover/index.vue

  • [ ] Step 1: 完整重写 discover/index.vue

    <template>
    <view class="discover-page">
    <!-- Banner -->
    <view class="banner">
      <view class="banner-content">
        <text class="banner-brand">🌟 浠艾福</text>
        <text class="banner-title">Care Family Club</text>
        <text class="banner-subtitle">以五行五维为内核的家庭主动健康管理平台</text>
        <view class="banner-tags">
          <text class="tag">🏆 任务积分</text>
          <text class="tag">⭐ 五行沙盘</text>
          <text class="tag">👨‍👩‍👧 家庭共育</text>
        </view>
      </view>
    </view>
    
    <!-- 五行能量沙盘 -->
    <wuxing-sandbox mode="preview" @point-click="requireLogin" />
    
    <!-- 精选活动 -->
    <view class="section">
      <view class="section-header">
        <text class="section-title">🎯 精选活动</text>
      </view>
      <scroll-view class="activity-scroll" scroll-x>
        <view
          class="activity-card"
          v-for="(item, idx) in activities"
          :key="idx"
          @click="requireLogin('activity')"
        >
          <view class="activity-icon">{{ item.icon }}</view>
          <text class="activity-name">{{ item.name }}</text>
          <text class="activity-desc">{{ item.desc }}</text>
          <text class="activity-price">{{ item.price }}</text>
          <view class="activity-btn">查看详情 →</view>
        </view>
      </scroll-view>
    </view>
    
    <!-- 推荐商品 -->
    <view class="section">
      <view class="section-header">
        <text class="section-title">🛍️ 推荐商品</text>
      </view>
      <view
        class="product-card"
        v-for="(item, idx) in products"
        :key="idx"
        @click="requireLogin('product')"
      >
        <view class="product-icon">{{ item.icon }}</view>
        <view class="product-info">
          <text class="product-name">{{ item.name }}</text>
          <view class="product-meta">
            <text class="product-rating">{{ item.rating }}</text>
            <text class="product-sold">{{ item.sold }}</text>
          </view>
          <text class="product-desc">{{ item.desc }}</text>
          <text class="product-price">{{ item.price }}</text>
        </view>
      </view>
    </view>
    
    <!-- 登录引导 -->
    <view class="login-cta" @click="requireLogin('cta')">
      <text class="cta-title">🔒 登录解锁完整功能</text>
      <text class="cta-sub">创建家庭 · 管理任务 · 查看五行能量沙盘</text>
      <view class="cta-btn">立即登录 →</view>
    </view>
    
    <!-- 自定义底部导航 -->
    <bottom-nav current="discover" />
    
    <!-- 底部占位 -->
    <view style="height: 120rpx;"></view>
    </view>
    </template>
    
    <script>
    import WuxingSandbox from '../../components/wuxing-sandbox.vue'
    import BottomNav from '../../components/bottom-nav.vue'
    
    export default {
    components: { WuxingSandbox, BottomNav },
    data() {
    return {
      activities: [
        { icon: '🧘', name: '亲子瑜伽体验课', desc: '适合5-12岁儿童与家长', price: '¥199', tag: '🔥 心·火' },
        { icon: '💬', name: '情绪管理工作坊', desc: '帮助孩子识别和管理情绪', price: '免费', tag: '🔥 心·火' },
        { icon: '🦠', name: '肠道菌群调养讲座', desc: '从肠道健康到大脑发育', price: '¥299', tag: '🌏 身·土' }
      ],
      products: [
        { icon: '🧘', name: '亲子瑜伽体验课', desc: '适合5-12岁儿童与家长共同参与的瑜伽课程', rating: '★★★★☆', sold: '已售230份', price: '¥199' },
        { icon: '🎮', name: '舒尔特方格专注力训练', desc: '经典注意力训练游戏,每日5分钟', rating: '★★★★★', sold: '已售568份', price: '¥49' }
      ]
    }
    },
    methods: {
    requireLogin(action) {
      const token = uni.getStorageSync('token')
      if (token) return
      uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/discover/index') })
    }
    }
    }
    </script>
    
    <style scoped>
    .discover-page {
    min-height: 100vh;
    background: #f8f8f8;
    padding-bottom: 20rpx;
    }
    
    /* Banner */
    .banner {
    margin: 20rpx 30rpx;
    padding: 40rpx 30rpx;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border-radius: 24rpx;
    }
    .banner-content { display: flex; flex-direction: column; }
    .banner-brand { font-size: 36rpx; font-weight: bold; color: #fff; }
    .banner-title { font-size: 26rpx; color: rgba(255,255,255,0.8); margin-top: 4rpx; }
    .banner-subtitle { font-size: 24rpx; color: rgba(255,255,255,0.7); margin-top: 12rpx; line-height: 1.5; }
    .banner-tags { display: flex; gap: 12rpx; margin-top: 20rpx; flex-wrap: wrap; }
    .tag { background: rgba(255,255,255,0.2); padding: 6rpx 16rpx; border-radius: 20rpx; font-size: 22rpx; color: #fff; }
    
    /* Sections */
    .section { margin: 20rpx 30rpx; }
    .section-header { margin-bottom: 16rpx; }
    .section-title { font-size: 28rpx; font-weight: bold; color: #333; }
    
    /* Activity scroll */
    .activity-scroll { display: flex; flex-direction: row; white-space: nowrap; overflow-x: auto; }
    .activity-card {
    display: inline-flex;
    flex-direction: column;
    width: 240rpx;
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx 20rpx;
    margin-right: 20rpx;
    box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
    }
    .activity-icon { font-size: 60rpx; margin-bottom: 12rpx; }
    .activity-name { font-size: 26rpx; font-weight: bold; color: #333; white-space: normal; }
    .activity-desc { font-size: 22rpx; color: #999; margin-top: 8rpx; white-space: normal; }
    .activity-price { font-size: 28rpx; color: #FF6B6B; font-weight: bold; margin-top: 12rpx; }
    .activity-btn {
    margin-top: 12rpx;
    padding: 8rpx 0;
    background: #667eea;
    color: #fff;
    text-align: center;
    border-radius: 16rpx;
    font-size: 22rpx;
    }
    
    /* Product cards */
    .product-card {
    display: flex;
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 16rpx;
    box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
    }
    .product-icon { font-size: 72rpx; margin-right: 20rpx; }
    .product-info { flex: 1; }
    .product-name { font-size: 28rpx; font-weight: bold; color: #333; display: block; }
    .product-meta { display: flex; gap: 16rpx; margin-top: 6rpx; }
    .product-rating { font-size: 22rpx; color: #FFA500; }
    .product-sold { font-size: 22rpx; color: #999; }
    .product-desc { font-size: 22rpx; color: #888; margin-top: 8rpx; display: block; }
    .product-price { font-size: 32rpx; color: #FF6B6B; font-weight: bold; margin-top: 8rpx; display: block; }
    
    /* Login CTA */
    .login-cta {
    margin: 20rpx 30rpx;
    padding: 32rpx;
    background: linear-gradient(135deg, #667eea, #764ba2);
    border-radius: 20rpx;
    display: flex;
    flex-direction: column;
    align-items: center;
    }
    .cta-title { font-size: 28rpx; font-weight: bold; color: #fff; }
    .cta-sub { font-size: 22rpx; color: rgba(255,255,255,0.8); margin-top: 8rpx; }
    .cta-btn {
    margin-top: 16rpx;
    padding: 12rpx 48rpx;
    background: #fff;
    color: #667eea;
    border-radius: 30rpx;
    font-size: 26rpx;
    font-weight: bold;
    }
    </style>
    
  • [ ] Step 2: Commit

    git add cfc-frontend/pages/discover/index.vue
    git commit -m "feat: 重构发现页为预登录首页(沙盘+活动+商品+登录引导)"
    

Task 6: 商城页添加未登录态预览

Files:

  • Modify: cfc-frontend/pages/shop/index.vue

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

Replace placeholder with a preview layout showing product categories + product cards, plus bottom nav and login gate.

<template>
  <view class="shop-page">
    <!-- 分类标签 -->
    <view class="category-bar">
      <view class="category-item active">全部</view>
      <view class="category-item">课程</view>
      <view class="category-item">活动</view>
      <view class="category-item">商品</view>
    </view>

    <!-- 商品列表(静态预览) -->
    <view class="product-grid">
      <view class="product-card" v-for="(item, idx) in products" :key="idx" @click="requireLogin">
        <view class="product-img">{{ item.icon }}</view>
        <view class="product-body">
          <text class="product-name">{{ item.name }}</text>
          <text class="product-price">{{ item.price }}</text>
        </view>
      </view>
    </view>

    <!-- 登录引导 -->
    <view class="login-hint">
      <text class="hint-text">🔒 登录查看全部商品和服务</text>
    </view>

    <!-- 自定义底部导航 -->
    <bottom-nav current="shop" />

    <!-- 底部占位 -->
    <view style="height: 120rpx;"></view>
  </view>
</template>

<script>
import BottomNav from '../../components/bottom-nav.vue'

export default {
  components: { BottomNav },
  data() {
    return {
      products: [
        { icon: '🧘', name: '亲子瑜伽体验课', price: '¥199' },
        { icon: '🎮', name: '舒尔特方格专注力训练', price: '¥49' },
        { icon: '📖', name: '儿童情绪管理绘本套装', price: '¥89' },
        { icon: '🧩', name: '家庭专注力训练礼盒', price: '¥159' }
      ]
    }
  },
  methods: {
    requireLogin() {
      const token = uni.getStorageSync('token')
      if (token) return
      uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/shop/index') })
    }
  }
}
</script>

<style scoped>
.shop-page {
  min-height: 100vh;
  background: #f8f8f8;
  padding-bottom: 20rpx;
}
.category-bar {
  display: flex;
  gap: 16rpx;
  padding: 20rpx 30rpx;
  background: #fff;
  border-bottom: 1rpx solid #f0f0f0;
}
.category-item {
  padding: 8rpx 24rpx;
  border-radius: 20rpx;
  font-size: 24rpx;
  color: #666;
  background: #f5f5f5;
}
.category-item.active {
  background: #667eea;
  color: #fff;
}
.product-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16rpx;
  padding: 20rpx 30rpx;
}
.product-card {
  background: #fff;
  border-radius: 16rpx;
  overflow: hidden;
  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
}
.product-img {
  height: 180rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 80rpx;
  background: #f9f9f9;
}
.product-body { padding: 16rpx; }
.product-name { font-size: 24rpx; font-weight: bold; color: #333; display: block; }
.product-price { font-size: 28rpx; color: #FF6B6B; font-weight: bold; margin-top: 8rpx; display: block; }
.login-hint {
  text-align: center;
  padding: 24rpx;
  font-size: 24rpx;
  color: #999;
}
</style>
  • [ ] Step 2: Commit

    git add cfc-frontend/pages/shop/index.vue
    git commit -m "feat: 商城页添加未登录预览态(静态商品展示+登录引导)"
    

Task 7: Login 页添加 redirect 参数支持

Files:

  • Modify: cfc-frontend/pages/login/login.vue

  • [ ] Step 1: 修改 login.vue onLoad 接受 redirect 参数

    onLoad(options) {
    // 处理邀请码
    if (options.inviteCode) {
      uni.setStorageSync('inviteCode', options.inviteCode)
      uni.setStorageSync('inviteType', options.inviteType || 'family')
    }
    // 存储登录后跳转地址
    if (options.redirect) {
      this.redirectUrl = decodeURIComponent(options.redirect)
    }
    },
    

Add to data:

  data() {
    return {
      // ... existing data
      redirectUrl: ''
    }
  },
  • Step 2: 修改 navigateToHome 使用 redirect 参数

Replace the existing navigateToHome method:

    navigateToHome(role) {
      if (this.redirectUrl) {
        // 有 redirect 则跳回来源页
        const redirect = this.redirectUrl
        this.redirectUrl = ''
        uni.reLaunch({ url: redirect })
      } else {
        // 默认通过 TabBar 首页跳转
        uni.reLaunch({ url: '/pages/index/index' })
      }
    }
  • [ ] Step 3: Commit

    git add cfc-frontend/pages/login/login.vue
    git commit -m "feat: 登录页支持redirect参数(登录后返回来源页)"
    

Self-Review

1. Spec coverage:

  • 品牌更名: ✅ Task 1 覆盖所有 9 处前端 + 4 处后端注释
  • 发现页重构 (沙盘+活动+商品): ✅ Task 5
  • 五行沙盘交互点(五角→登录): ✅ Task 2 (emits point-click), Task 5 (requireLogin handler)
  • 活动区: ✅ Task 5 (3张活动卡片,点击→requireLogin)
  • 商品区: ✅ Task 5 (2张商品卡片,点击→requireLogin)
  • 商城页未登录预览: ✅ Task 6
  • 预登录 TabBar 仅显示发现+商城: ✅ Task 3 (bottom-nav) + Task 4 (App.vue hideTabBar)
  • 登录页不改造: ✅ Task 7 只加 redirect 参数,不改造 UI
  • 登录后恢复原生 TabBar: ✅ Task 4
  • 退出登录回到未登录态: ✅ implicit via App.vue onShow check
  • 沙盘组件复用: ✅ Task 2 (preview/parent/child 三模式)

2. Placeholder scan: No TBD, TODO, or empty sections.

3. Type consistency:

  • @point-click used in component emit and parent handler ✅
  • requireLogin() used consistently across discover and shop pages ✅
  • redirect parameter format consistent ✅