Browse Source

fix(frontend): 修复 FamilyEmptyState 误显示 - 28处 hasFamily 守卫改为数据优先判断

- 页面级 hasFamily 守卫改为优先判断接口数据/页面参数/token/loading,
  store getter 仅兜底(store.familyId 恒空导致空态吞掉正常内容)
- App.vue 冷启动从接口恢复 familyId,store setFamilyId 持久化供恢复
- login.vue 登录/自动登录同步 familyId 到 store
- 修复 9 处批量修改导致的 computed 对象未闭合语法错误
asus 2 months ago
parent
commit
068f12f805
34 changed files with 136 additions and 4 deletions
  1. 24 0
      cfc-frontend/App.vue
  2. 10 0
      cfc-frontend/components/DimensionProducts.vue
  3. 4 0
      cfc-frontend/components/family-guard.vue
  4. 6 0
      cfc-frontend/components/member-detail-sections/mind-section.vue
  5. 2 0
      cfc-frontend/pages/action-detail/interaction-log.vue
  6. 1 0
      cfc-frontend/pages/action-detail/member-action-detail.vue
  7. 2 0
      cfc-frontend/pages/assessment/purchase.vue
  8. 1 0
      cfc-frontend/pages/body-detail/member-body-detail.vue
  9. 19 1
      cfc-frontend/pages/body/index.vue
  10. 1 0
      cfc-frontend/pages/body/member-body-detail.vue
  11. 1 0
      cfc-frontend/pages/energy/detail.vue
  12. 1 0
      cfc-frontend/pages/growth/create.vue
  13. 1 0
      cfc-frontend/pages/growth/index.vue
  14. 2 0
      cfc-frontend/pages/health/challenge-manage.vue
  15. 2 0
      cfc-frontend/pages/health/gut-index.vue
  16. 4 0
      cfc-frontend/pages/home-pages/child-index.vue
  17. 12 1
      cfc-frontend/pages/home-pages/parent-index.vue
  18. 4 0
      cfc-frontend/pages/index-home/index.vue
  19. 8 0
      cfc-frontend/pages/invite/join.vue
  20. 2 0
      cfc-frontend/pages/login/login.vue
  21. 1 0
      cfc-frontend/pages/mind-detail/member-mind-detail.vue
  22. 5 1
      cfc-frontend/pages/mind/index.vue
  23. 1 0
      cfc-frontend/pages/mind/member-mind-detail.vue
  24. 1 0
      cfc-frontend/pages/parent/child-detail.vue
  25. 2 0
      cfc-frontend/pages/parent/plans/index.vue
  26. 1 0
      cfc-frontend/pages/parent/wishes/index.vue
  27. 1 0
      cfc-frontend/pages/profile-extra/family-members.vue
  28. 2 0
      cfc-frontend/pages/rewards/badge.vue
  29. 1 0
      cfc-frontend/pages/share/index.vue
  30. 2 0
      cfc-frontend/pages/teacher/teacher-profile.vue
  31. 6 1
      cfc-frontend/pages/user-edit/user-edit.vue
  32. 1 0
      cfc-frontend/pages/wisdom-detail/member-wisdom-detail.vue
  33. 1 0
      cfc-frontend/pages/wisdom/member-wisdom-detail.vue
  34. 4 0
      cfc-frontend/store/index.js

+ 24 - 0
cfc-frontend/App.vue

@@ -1,6 +1,7 @@
 <script>
 import config from '@/config.js'
 import Vue from 'vue'
+import { checkUserFamily } from './utils/api.js'
 Vue.prototype.$config = config
 
 export default {
@@ -15,6 +16,14 @@ export default {
     const token = uni.getStorageSync('token')
     if (token) {
       this.$store.commit('setToken', token)
+      // 冷启动恢复 familyId(登录时已持久化,hasFamily getter 依赖)
+      var storedFamilyId = uni.getStorageSync('familyId')
+      if (storedFamilyId) {
+        this.$store.commit('setFamilyId', storedFamilyId)
+      } else {
+        // 历史用户(8ddc5b55 后未重新登录):本地无 familyId,从接口恢复
+        this.restoreFamilyIdFromApi()
+      }
     } else {
       // 无token时尝试自动登录
       this.tryAutoLogin()
@@ -29,12 +38,25 @@ export default {
     var token = uni.getStorageSync('token')
     if (token && token !== this.$store.state.token) {
       this.$store.commit('setToken', token)
+      this.$store.commit('setFamilyId', uni.getStorageSync('familyId'))
     }
   },
   onHide: function() {
     console.log('App Hide')
   },
   methods: {
+    // 从接口恢复 familyId(历史用户冷启动,本地无 familyId 缓存)
+    restoreFamilyIdFromApi() {
+      var self = this
+      checkUserFamily().then(function(res) {
+        if (res && res.data && res.data.inFamily && res.data.familyId) {
+          self.$store.commit('setFamilyId', res.data.familyId)
+          console.log('[App] familyId 已从接口恢复:', res.data.familyId)
+        }
+      }).catch(function(e) {
+        console.log('[App] familyId 接口恢复失败(页面按无家庭态渲染)', e)
+      })
+    },
     async tryAutoLogin() {
       // 开发环境下跳过自动登录,避免无后端时 10s 超时阻塞
       try {
@@ -79,6 +101,8 @@ export default {
         userId: data.userId
       })
       this.$store.commit('setToken', data.token)
+      // 同步 familyId 到 store(hasFamily getter 依赖;auto-login 路径)
+      this.$store.commit('setFamilyId', data.familyId)
     },
     request(url, method, data) {
       return new Promise((resolve, reject) => {

+ 10 - 0
cfc-frontend/components/DimensionProducts.vue

@@ -56,6 +56,16 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      // 应急修复(2026-08-07,对齐 body/index.vue):store.familyId 在 familyId 后端派生改造后
+      // 不再被写入,hasFamily 恒为 false 导致商品区被 v-else 空态吞掉。
+      // 改为基于组件自身数据判断:有 familyId(自加载模式)或父组件传入商品(兼容模式)
+      // 即认为有家庭,store getter 仅作兜底。
+      if (this.familyId) {
+        return true
+      }
+      if (this.products && this.products.length > 0) {
+        return true
+      }
       return this.$store.getters.hasFamily
     },
     displayProducts: function() {

+ 4 - 0
cfc-frontend/components/family-guard.vue

@@ -45,6 +45,10 @@ export default {
 					var userInfo = uni.getStorageSync('userInfo') || {}
 					userInfo.familyId = res.data
 					uni.setStorageSync('userInfo', userInfo)
+					// 同步 familyId 到 store(hasFamily getter 依赖,8ddc5b55 移除存储后此数据流断裂)
+					if (self.$store) {
+						self.$store.commit('setFamilyId', res.data)
+					}
 					self.visible = false
 					uni.showToast({ title: '家庭创建成功', icon: 'success' })
 					if (self.$listeners && self.$listeners.created) {

+ 6 - 0
cfc-frontend/components/member-detail-sections/mind-section.vue

@@ -83,6 +83,12 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      // 应急修复(2026-08-07,对齐 body/index.vue):store.familyId 在 familyId 后端派生改造后
+      // 不再被写入,hasFamily 恒为 false 导致内容区被 v-else 空态吞掉。
+      // 成员详情页必然处于家庭上下文中,基于 props 判断即可,store getter 仅兜底。
+      if (this.memberId || (this.memberInfo && this.memberInfo.familyId)) {
+        return true
+      }
       return this.$store.getters.hasFamily
     }
   },

+ 2 - 0
cfc-frontend/pages/action-detail/interaction-log.vue

@@ -98,6 +98,8 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      var token = uni.getStorageSync('token')
+      if (token) return true
       return this.$store.getters.hasFamily
     },
     canSubmit() {

+ 1 - 0
cfc-frontend/pages/action-detail/member-action-detail.vue

@@ -93,6 +93,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     graphMembers: function() {

+ 2 - 0
cfc-frontend/pages/assessment/purchase.vue

@@ -113,6 +113,8 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      var token = uni.getStorageSync('token')
+      if (token) return true
       return this.$store.getters.hasFamily
     }
   },

+ 1 - 0
cfc-frontend/pages/body-detail/member-body-detail.vue

@@ -93,6 +93,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     graphMembers: function() {

+ 19 - 1
cfc-frontend/pages/body/index.vue

@@ -161,6 +161,9 @@ export default {
       userName: '',
       children: [],
       familyMembersVisible: [],
+      // 接口数据是否已检查(用于 hasFamily 判断,替代已失效的 store.familyId)
+      childrenChecked: false,
+      membersChecked: false,
       sandboxData: null,
       dualDimension: null,
       visibleSections: [],
@@ -185,7 +188,18 @@ export default {
   },
   computed: {
     hasFamily: function() {
-      return this.$store.getters.hasFamily
+      // 应急修复(2026-08-07):store.familyId 在 familyId 后端派生改造后不再被写入,
+      // hasFamily 恒为 false 导致内容区被 v-else 空态吞掉。改为基于接口数据判断:
+      // 有孩子成员或可见家庭成员即认为有家庭。
+      if (!this.isLoggedIn) {
+        return false
+      }
+      // 接口未返回前乐观显示内容区,避免"暂无数据"闪烁
+      if (!this.childrenChecked || !this.membersChecked) {
+        return true
+      }
+      return (this.children && this.children.length > 0) ||
+             (this.familyMembersVisible && this.familyMembersVisible.length > 0)
     },
     /** 七维雷达图:维度标签(API无数据时使用默认标签) */
     radarDimensions: function() {
@@ -290,8 +304,10 @@ export default {
             self.loadDashboardData()
           }
         }
+        self.childrenChecked = true
       }).catch(function(e) {
         console.log('获取孩子列表失败', e)
+        self.childrenChecked = true
       })
     },
     loadDashboardData: function() {
@@ -424,9 +440,11 @@ export default {
       var self = this
       getVisibleFamilyMembers().then(function(res) {
         self.familyMembersVisible = res.data || []
+        self.membersChecked = true
       }).catch(function(e) {
         console.log('获取可见家庭成员失败', e)
         self.familyMembersVisible = []
+        self.membersChecked = true
       })
     },
     scrollToSection: function(section) {

+ 1 - 0
cfc-frontend/pages/body/member-body-detail.vue

@@ -93,6 +93,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     graphMembers: function() {

+ 1 - 0
cfc-frontend/pages/energy/detail.vue

@@ -76,6 +76,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     }
   },

+ 1 - 0
cfc-frontend/pages/growth/create.vue

@@ -150,6 +150,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.targetChildId || (this.children && this.children.length > 0)) return true
       return this.$store.getters.hasFamily
     }
   },

+ 1 - 0
cfc-frontend/pages/growth/index.vue

@@ -102,6 +102,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     filteredRecords: function() {

+ 2 - 0
cfc-frontend/pages/health/challenge-manage.vue

@@ -164,6 +164,8 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.loading) return true
+      if (this.familyId) return true
       return this.$store.getters.hasFamily
     }
   },

+ 2 - 0
cfc-frontend/pages/health/gut-index.vue

@@ -131,6 +131,8 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      var token = uni.getStorageSync('token')
+      if (token) return true
       return this.$store.getters.hasFamily
     }
   },

+ 4 - 0
cfc-frontend/pages/home-pages/child-index.vue

@@ -398,6 +398,10 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.loading) return true
+      if (this.hasChildren) return true
+      if (this.sandboxData) return true
+      if (this.familyMembersVisible && this.familyMembersVisible.length > 0) return true
       return this.$store.getters.hasFamily
     },
     // ---------- 能量沙盘 ----------

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

@@ -580,7 +580,18 @@ export default {
   },
   computed: {
     hasFamily: function() {
-      return this.$store.getters.hasFamily
+      // 应急修复(2026-08-07,对齐 body/index.vue):store.familyId 在 familyId 后端派生改造后
+      // 不再被写入,hasFamily 恒为 false 导致内容区被 v-else 空态吞掉。
+      // 改为基于接口数据判断:能量沙盘有成员或家庭成员可见列表非空即认为有家庭。
+      if (!this.isLoggedIn) {
+        return false
+      }
+      // 接口未返回前乐观显示内容区,避免"暂无数据"闪烁
+      if (this.loading) {
+        return true
+      }
+      return this.hasFamilyData ||
+             (this.familyMembersVisible && this.familyMembersVisible.length > 0)
     },
     isFamilyAdmin() {
       if (!this.familyMembersVisible || this.familyMembersVisible.length === 0) return false

+ 4 - 0
cfc-frontend/pages/index-home/index.vue

@@ -479,6 +479,10 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (!this.isLoggedIn()) return false
+      if (this.loading) return true
+      if (this.sandboxData) return true
+      if (this.familyMembersVisible && this.familyMembersVisible.length > 0) return true
       return this.$store.getters.hasFamily
     },
     isFamilyAdmin: function() {

+ 8 - 0
cfc-frontend/pages/invite/join.vue

@@ -249,6 +249,11 @@ export default {
         var res = await checkUserFamily()
         if (res && res.data) {
           this.familyStatus = res.data
+          // 已在家庭内:同步 familyId 到 store(hasFamily getter 依赖)
+          if (res.data.inFamily && res.data.familyId) {
+            this.$store && this.$store.commit('setFamilyId', res.data.familyId)
+            uni.setStorageSync('familyId', res.data.familyId)
+          }
           // 如果已经在目标家庭,直接显示成功
           if (this.inviteInfo && this.familyStatus.inFamily && this.familyStatus.familyId === this.inviteInfo.familyId) {
             this.accepted = true
@@ -298,6 +303,7 @@ export default {
             uni.setStorageSync('openid', data.openid)
           }
           that.$store && that.$store.commit('setToken', data.token)
+          that.$store && that.$store.commit('setFamilyId', data.familyId)
 
           this.isLoggedIn = true
           uni.showToast({ title: '登录成功', icon: 'success' })
@@ -367,6 +373,8 @@ export default {
             this.accepted = true
             uni.setStorageSync('familyId', this.inviteInfo.familyId)
             uni.setStorageSync('currentFamilyId', this.inviteInfo.familyId)
+            // 审批通过后同步 familyId 到 store(hasFamily getter 依赖)
+            this.$store && this.$store.commit('setFamilyId', this.inviteInfo.familyId)
             uni.showToast({ title: '加入家庭成功', icon: 'success' })
           } else if (this.requestStatus === 'rejected') {
             clearInterval(this.requestTimer)

+ 2 - 0
cfc-frontend/pages/login/login.vue

@@ -150,6 +150,8 @@ export default {
           if (res.data.teacherRejectReason) {
             uni.setStorageSync('teacherRejectReason', res.data.teacherRejectReason)
           }
+          // 同步 familyId 到 store(hasFamily getter 依赖,8ddc5b55 移除存储后此数据流断裂)
+          self.$store.commit('setFamilyId', res.data.familyId)
 
           uni.showToast({ title: '登录成功', icon: 'success' })
 

+ 1 - 0
cfc-frontend/pages/mind-detail/member-mind-detail.vue

@@ -93,6 +93,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     graphMembers: function() {

+ 5 - 1
cfc-frontend/pages/mind/index.vue

@@ -283,7 +283,11 @@ components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnerg
   },
   computed: {
     hasFamily: function() {
-      return this.$store.getters.hasFamily
+      // 应急修复(2026-08-07,对齐 body/index.vue):store.familyId 在 familyId 后端派生改造后
+      // 不再被写入,hasFamily 恒为 false 导致内容区被 v-else 空态吞掉。
+      // 后端所有登录路径均兜底自动创建家庭(UserService.ensureUserHasFamily),
+      // 故登录用户视为有家庭,内容区由内部组件基于接口数据自行渲染。
+      return this.isLoggedIn
     },
     personalityScores: function() {
       if (!this.emiData) return []

+ 1 - 0
cfc-frontend/pages/mind/member-mind-detail.vue

@@ -93,6 +93,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     graphMembers: function() {

+ 1 - 0
cfc-frontend/pages/parent/child-detail.vue

@@ -129,6 +129,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     completedCount() {

+ 2 - 0
cfc-frontend/pages/parent/plans/index.vue

@@ -54,6 +54,8 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.loading) return true
+      if (this.plans && this.plans.length > 0) return true
       return this.$store.getters.hasFamily
     }
   },

+ 1 - 0
cfc-frontend/pages/parent/wishes/index.vue

@@ -171,6 +171,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     },
     pendingCount() {

+ 1 - 0
cfc-frontend/pages/profile-extra/family-members.vue

@@ -151,6 +151,7 @@ export default {
       return this.memberScores[this.detailMember.id] || {}
     },
     hasFamily: function() {
+      if (this.members && this.members.length > 0) return true
       return this.$store.getters.hasFamily
     }
   },

+ 2 - 0
cfc-frontend/pages/rewards/badge.vue

@@ -139,6 +139,8 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      var token = uni.getStorageSync('token')
+      if (token) return true
       return this.$store.getters.hasFamily
     },
     detailIcon() {

+ 1 - 0
cfc-frontend/pages/share/index.vue

@@ -43,6 +43,7 @@ import { getFamilyFootprint } from '../../utils/api.js'
 export default {
   computed: {
     hasFamily: function() {
+      if (this.familyName || (this.members && this.members.length > 0)) return true
       return this.$store.getters.hasFamily
     }
   },

+ 2 - 0
cfc-frontend/pages/teacher/teacher-profile.vue

@@ -94,6 +94,8 @@ export default {
       return map[this.teacherStatus] || this.teacherStatus
     },
     hasFamily: function() {
+      var token = uni.getStorageSync('token')
+      if (token) return true
       return this.$store.getters.hasFamily
     }
   },

+ 6 - 1
cfc-frontend/pages/user-edit/user-edit.vue

@@ -315,6 +315,7 @@ export default {
       return this.maritalOptions.indexOf(this.form.maritalStatus)
     },
     hasFamily: function() {
+      if (this.token) return true
       return this.$store.getters.hasFamily
     },
   },
@@ -629,7 +630,11 @@ export default {
 					// 注册不写入 familyId(后端根据 userId 获取)
 				} else {
 					await updateUserInfo(this.form)
-					await createFamily(this.form.nickname + '的家庭')
+					const famRes = await createFamily(this.form.nickname + '的家庭')
+					// 同步 familyId 到 store(hasFamily getter 依赖)
+					if (famRes && famRes.data) {
+						this.$store.commit('setFamilyId', famRes.data)
+					}
 				}
 				
 				uni.showToast({ title: '创建成功' })

+ 1 - 0
cfc-frontend/pages/wisdom-detail/member-wisdom-detail.vue

@@ -97,6 +97,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     }
   },

+ 1 - 0
cfc-frontend/pages/wisdom/member-wisdom-detail.vue

@@ -97,6 +97,7 @@ export default {
   },
   computed: {
     hasFamily: function() {
+      if (this.memberId) return true
       return this.$store.getters.hasFamily
     }
   },

+ 4 - 0
cfc-frontend/store/index.js

@@ -158,15 +158,19 @@ const store = new Vuex.Store({
       uni.removeStorageSync('currentMemberRole')
       uni.removeStorageSync('isSwitchedMember')
       uni.removeStorageSync('currentView')
+      uni.removeStorageSync('familyId')
     },
     markNoFamily: function(state) {
       if (state.familyId !== '' && state.familyId !== null && state.familyId !== 0) {
         console.warn('[STORE] 后端返回 5001 但本地 familyId 非空,已同步本地为无家庭态', state.familyId)
       }
       state.familyId = ''
+      uni.removeStorageSync('familyId')
     },
     setFamilyId: function(state, familyId) {
       state.familyId = familyId
+      // 持久化供冷启动恢复(hasFamily getter 依赖 state.familyId)
+      uni.setStorageSync('familyId', familyId || '')
     }
   },
   actions: {