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 (微信小程序)
Files:
cfc-frontend/pages/index/parent-index.vue (4处)cfc-frontend/pages/index/child-index.vue (2处)cfc-frontend/pages.json (1处)cfc-frontend/pages/profile/profile.vue (1处)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:
Replace in child-index.vue:
Replace in pages.json:
Replace in profile.vue:
Replace in api.js:
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: 品牌名溪艾福/溪福俱乐部统一改为浠艾福/浠艾福俱乐部"
Files:
cfc-frontend/components/wuxing-sandbox.vuecfc-frontend/pages/index/parent-index.vueModify: 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 }
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)支持三种模式"
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展示"
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>
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:
uni.showTabBar() → restore native TabBaruni.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)"
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: 重构发现页为预登录首页(沙盘+活动+商品+登录引导)"
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: 商城页添加未登录预览态(静态商品展示+登录引导)"
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: ''
}
},
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参数(登录后返回来源页)"
1. Spec coverage:
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 ✅