Procházet zdrojové kódy

feat(frontend): 首页新手任务进度条置顶 + 无家庭成员空状态引导

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot před 1 měsícem
rodič
revize
8f25120b62

+ 113 - 0
cfc-frontend/components/OnboardingProgressBar.vue

@@ -0,0 +1,113 @@
+<template>
+  <view class="opb-card" v-if="visible" @click="goOnboarding">
+    <view class="opb-head">
+      <text class="opb-title">🎁 新手成长礼包</text>
+      <text class="opb-count">{{ completed }}/{{ total }} 已完成</text>
+    </view>
+    <view class="opb-bar">
+      <view class="opb-bar-fill" :style="{ width: percent + '%' }"></view>
+    </view>
+    <view class="opb-tip">完成新手任务,赢取积分与五维能量 →</view>
+  </view>
+</template>
+
+<script>
+import { getOnboardingProgress } from '../utils/api.js'
+
+export default {
+  name: 'OnboardingProgressBar',
+  data() {
+    return {
+      taskList: []
+    }
+  },
+  computed: {
+    total() {
+      return this.taskList.length
+    },
+    completed() {
+      var count = 0
+      for (var i = 0; i < this.taskList.length; i++) {
+        var st = this.taskList[i].status
+        if (st === 'COMPLETED' || st === 'CLAIMED') {
+          count++
+        }
+      }
+      return count
+    },
+    percent() {
+      if (!this.total) return 0
+      return Math.round((this.completed / this.total) * 100)
+    },
+    visible() {
+      if (!this.taskList || this.taskList.length === 0) return false
+      // 全部 CLAIMED 后不再展示
+      for (var i = 0; i < this.taskList.length; i++) {
+        if (this.taskList[i].status !== 'CLAIMED') return true
+      }
+      return false
+    }
+  },
+  created() {
+    this.reload()
+  },
+  methods: {
+    reload: function() {
+      var self = this
+      getOnboardingProgress().then(function(res) {
+        var records = (res.data && res.data.records) || res.data || []
+        if (!Array.isArray(records)) {
+          records = []
+        }
+        self.taskList = records
+      }).catch(function(e) {
+        console.error('获取新手任务进度失败', e)
+      })
+    },
+    goOnboarding: function() {
+      uni.navigateTo({ url: '/pages/profile-extra/onboarding' })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.opb-card {
+  margin: 20rpx 24rpx;
+  padding: 24rpx 28rpx;
+  background: linear-gradient(135deg, #FFF7ED, #FFEDD5);
+  border: 2rpx solid #FED7AA;
+  border-radius: 20rpx;
+}
+.opb-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 14rpx;
+}
+.opb-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #9A3412;
+}
+.opb-count {
+  font-size: 24rpx;
+  color: #C2410C;
+}
+.opb-bar {
+  height: 16rpx;
+  background: #FED7AA;
+  border-radius: 8rpx;
+  overflow: hidden;
+}
+.opb-bar-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #F97316, #FF8C42);
+  border-radius: 8rpx;
+}
+.opb-tip {
+  margin-top: 12rpx;
+  font-size: 24rpx;
+  color: #C2410C;
+}
+</style>

+ 60 - 1
cfc-frontend/pages/home-pages/parent-index.vue

@@ -9,6 +9,9 @@
   tagline="家庭健康成长俱乐部"
   quote="给全家的一站式幸福提案" />
 
+<!-- ===== 新手任务进度条(onboarding 置顶引导) ===== -->
+<OnboardingProgressBar ref="onboardingProgress" />
+
 <!-- ===== 家庭成员选择条 ===== -->
 <FamilyMemberStrip
   v-if="isLoggedIn"
@@ -19,6 +22,14 @@
   @switch="onFamilyMemberSwitch"
   @removed="loadFamilyMembersVisible" />
 
+<!-- ===== 无家庭成员空状态引导卡 ===== -->
+<view class="empty-family-card" v-if="isLoggedIn && familyMembersVisible.length === 0">
+  <view class="empty-family-icon">🏠</view>
+  <text class="empty-family-title">创建我的家庭</text>
+  <text class="empty-family-desc">添加家庭成员,开启五维能量之旅</text>
+  <button class="empty-family-btn" @click="goAddMember">立即添加</button>
+</view>
+
 <!-- ===== 微行动卡片(P0:有效启动) ===== -->
 <MicroActionCard
   v-if="selectedMemberId && selectedChild && selectedChild.id"
@@ -482,10 +493,11 @@ import DailySummary from '../../components/DailySummary.vue'
 import HealthAlertBanner from '../../components/HealthAlertBanner.vue'
 import CircleRanking from '../../components/CircleRanking.vue'
 import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
+import OnboardingProgressBar from '../../components/OnboardingProgressBar.vue'
 import feedbackEngine from '../../utils/feedback-engine.js'
 
 export default {
-  components: { PageBanner, RecommendedFeed, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading, FamilyRelationGraph, WuxingSandbox, ContactCard, ContactImport, FamilyMemberStrip, MicroActionCard, FeedbackToast, MilestoneCelebration, DailySummary, HealthAlertBanner, FamilyHealthRanking, FamilyChallengeCard, CircleRanking, ArticleBookshelf },
+  components: { PageBanner, RecommendedFeed, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading, FamilyRelationGraph, WuxingSandbox, ContactCard, ContactImport, FamilyMemberStrip, MicroActionCard, FeedbackToast, MilestoneCelebration, DailySummary, HealthAlertBanner, FamilyHealthRanking, FamilyChallengeCard, CircleRanking, ArticleBookshelf, OnboardingProgressBar },
   data() {
     return {
       nickname: '',
@@ -645,6 +657,9 @@ export default {
       this.isLoggedIn = true
       this.loadData()
     }
+    if (this.$refs.onboardingProgress) {
+      this.$refs.onboardingProgress.reload()
+    }
   },
   onPullDownRefresh() {
     this.loadData().finally(() => {
@@ -933,6 +948,9 @@ export default {
       this.loadData()
       feedbackEngine.trigger('micro_action')
     },
+    goAddMember() {
+      uni.navigateTo({ url: '/pages/family/add-member' })
+    },
     onDailySummaryDismiss() {
       // DailySummary 已关闭,无额外操作
     },
@@ -2040,4 +2058,45 @@ export default {
   opacity: 0.8;
 }
 
+/* 无家庭成员空状态引导卡 */
+.empty-family-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 20rpx 24rpx;
+  padding: 48rpx 30rpx;
+  background: #fff;
+  border-radius: 24rpx;
+  border: 2rpx dashed #FED7AA;
+}
+.empty-family-icon {
+  font-size: 88rpx;
+  margin-bottom: 16rpx;
+}
+.empty-family-title {
+  font-size: 34rpx;
+  font-weight: 600;
+  color: #9A3412;
+  margin-bottom: 10rpx;
+}
+.empty-family-desc {
+  font-size: 26rpx;
+  color: #999;
+  margin-bottom: 28rpx;
+}
+.empty-family-btn {
+  width: 320rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #fff;
+  background: linear-gradient(135deg, #F97316, #FF8C42);
+  border-radius: 40rpx;
+  border: none;
+}
+.empty-family-btn::after {
+  border: none;
+}
+
 </style>

+ 5 - 3
cfc-frontend/pages/profile-extra/onboarding.vue

@@ -7,7 +7,7 @@
         <view class="progress-bar">
           <view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
         </view>
-        <text class="progress-text">{{ completedCount }}/6 已完成</text>
+        <text class="progress-text">{{ completedCount }}/{{ taskList.length }} 已完成</text>
       </view>
       <text class="progress-hint">完成所有任务,领取丰厚奖励!</text>
     </view>
@@ -52,7 +52,8 @@ var TASK_META = {
   ADD_CHILD: { icon: '👨\u200D👩\u200D👧\u200D👦', name: '添加家庭成员' },
   FIRST_CHECKIN: { icon: '✅', name: '完成首次健康打卡' },
   FIRST_TASK: { icon: '🎯', name: '完成首次任务' },
-  BIND_REFERRAL: { icon: '🤝', name: '绑定推荐人' }
+  BIND_REFERRAL: { icon: '🤝', name: '绑定推荐人' },
+  COMPLETE_SELF_CHECK: { icon: '🔍', name: '完成一次五维自检' }
 }
 
 export default {
@@ -73,7 +74,8 @@ export default {
       return count
     },
     progressPercent() {
-      return Math.round((this.completedCount / 6) * 100)
+      var total = this.taskList.length || 6
+      return Math.round((this.completedCount / total) * 100)
     }
   },
   onLoad() {