Przeglądaj źródła

feat: 家庭加入改为申请审批流程 + familyId 后端派生(移除前端 familyId 存储依赖与冗余传参)

asus 1 miesiąc temu
rodzic
commit
8ddc5b550c
32 zmienionych plików z 132 dodań i 121 usunięć
  1. 2 1
      cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java
  2. 5 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/DanAssessmentController.java
  3. 13 7
      cfc-backend/src/main/java/com/etotem/cfc/controller/InviteCardController.java
  4. 22 5
      cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyUserController.java
  5. 6 2
      cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java
  6. 0 1
      cfc-frontend/components/DimensionProducts.vue
  7. 1 3
      cfc-frontend/components/family-guard.vue
  8. 4 4
      cfc-frontend/components/member-detail-sections/mind-section.vue
  9. 0 2
      cfc-frontend/pages/assessment/apply.vue
  10. 0 2
      cfc-frontend/pages/assessment/purchase.vue
  11. 1 3
      cfc-frontend/pages/growth/create.vue
  12. 4 4
      cfc-frontend/pages/growth/index.vue
  13. 2 2
      cfc-frontend/pages/health/challenge-manage.vue
  14. 3 3
      cfc-frontend/pages/home-pages/parent-index.vue
  15. 2 5
      cfc-frontend/pages/index-home/index.vue
  16. 0 3
      cfc-frontend/pages/invite/join.vue
  17. 1 2
      cfc-frontend/pages/login/login.vue
  18. 1 7
      cfc-frontend/pages/mind/index.vue
  19. 2 4
      cfc-frontend/pages/parent/wishes/index.vue
  20. 34 1
      cfc-frontend/pages/profile-extra/family-members.vue
  21. 5 5
      cfc-frontend/pages/profile/components/ProfileMenu.vue
  22. 3 3
      cfc-frontend/pages/rewards/badge.vue
  23. 1 1
      cfc-frontend/pages/share/index.vue
  24. 1 3
      cfc-frontend/pages/tianpan/daily-fortune.vue
  25. 3 15
      cfc-frontend/pages/tianpan/index.vue
  26. 1 3
      cfc-frontend/pages/tianpan/related-items.vue
  27. 1 2
      cfc-frontend/pages/tianpan/relation-detail.vue
  28. 5 5
      cfc-frontend/pages/user-edit/user-edit.vue
  29. 2 2
      cfc-frontend/pages/wealth-sub/insurance-planning.vue
  30. 0 2
      cfc-frontend/store/index.js
  31. 1 6
      cfc-frontend/store/modules/tianpan.js
  32. 6 11
      cfc-frontend/utils/api.js

+ 2 - 1
cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java

@@ -61,7 +61,6 @@ public class JwtInterceptor implements HandlerInterceptor {
         "/api/mind/alert/active",
         "/api/mind/alert/checkin-alerts",
         "/api/mind/traditional/mirror",
-        "/api/mind/traditional/family-dashboard",
         "/api/mind/traditional/compatibility"
     };
 
@@ -185,5 +184,7 @@ public class JwtInterceptor implements HandlerInterceptor {
         request.setAttribute("userId", -1L);
         request.setAttribute("role", "anonymous");
         request.setAttribute("roles", Collections.singletonList("anonymous"));
+        // familyId 属性必须始终存在,避免 @RequestAttribute("familyId") 抛 ServletRequestBindingException
+        request.setAttribute("familyId", null);
     }
 }

+ 5 - 2
cfc-backend/src/main/java/com/etotem/cfc/controller/DanAssessmentController.java

@@ -77,9 +77,12 @@ public class DanAssessmentController {
 
     // ===== 家庭配置 =====
 
-@Operation(summary = "获取家庭测评配置")
+    @Operation(summary = "获取家庭测评配置")
     @PostMapping("/family/config")
-    public Result<FamilyAssessmentConfig> getFamilyConfig(@RequestParam Long familyId) {
+    public Result<FamilyAssessmentConfig> getFamilyConfig(@RequestAttribute("familyId") Long familyId) {
+        if (familyId == null) {
+            return Result.noFamily("请先创建或加入家庭");
+        }
         return Result.success(assessmentService.getFamilyConfig(familyId));
     }
 

+ 13 - 7
cfc-backend/src/main/java/com/etotem/cfc/controller/InviteCardController.java

@@ -42,20 +42,25 @@ public class InviteCardController {
     @PostMapping("/generate")
     @Operation(summary = "生成邀请码")
     public Result<Map<String, Object>> generate(@RequestAttribute("userId") Long userId,
+                                                @RequestAttribute("familyId") Long familyId,
                                                 @RequestBody Map<String, Object> params) {
         String type = params.get("type") != null ? params.get("type").toString() : null;
-        Object refIdObj = params.get("refId");
-        if (type == null || refIdObj == null) {
-            return Result.error("缺少必要参数: type/refId");
+        if (type == null) {
+            return Result.error("缺少必要参数: type");
         }
 
-        Long refId = Long.valueOf(refIdObj.toString());
         User user = userMapper.selectById(userId);
         if (user == null) {
             return Result.error("用户不存在");
         }
 
+        Long refId;
         if ("child".equals(type)) {
+            Object refIdObj = params.get("refId");
+            if (refIdObj == null) {
+                return Result.error("缺少必要参数: refId");
+            }
+            refId = Long.valueOf(refIdObj.toString());
             FamilyMember child = familyMemberMapper.selectById(refId);
             if (child == null) {
                 return Result.error("孩子不存在");
@@ -64,13 +69,14 @@ public class InviteCardController {
                 return Result.error("无权生成该孩子的邀请");
             }
         } else if ("family".equals(type) || "guide".equals(type)) {
+            if (familyId == null) {
+                return Result.error("用户未加入家庭");
+            }
+            refId = familyId;
             Family family = familyMapper.selectById(refId);
             if (family == null) {
                 return Result.error("家庭不存在");
             }
-            if (!Objects.equals(user.getFamilyId(), family.getId())) {
-                return Result.error("无权生成该家庭的邀请");
-            }
         } else {
             return Result.error("不支持的邀请码类型");
         }

+ 22 - 5
cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyUserController.java

@@ -3,12 +3,14 @@ package com.etotem.cfc.controller.family;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.*;
+import com.etotem.cfc.entity.FamilyJoinRequest;
 import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.UserMapper;
+import com.etotem.cfc.service.FamilyJoinRequestService;
 import com.etotem.cfc.service.UserService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -38,6 +40,9 @@ public class FamilyUserController {
     @Resource
     private FamilyMemberMapper familyMemberMapper;
 
+    @Resource
+    private FamilyJoinRequestService familyJoinRequestService;
+
     @Operation(summary = "获取用户信息")
     @PostMapping("/info")
     public Result<User> getUserInfo(@RequestAttribute("userId") Long userId) {
@@ -55,12 +60,24 @@ public class FamilyUserController {
 
     @Operation(summary = "加入家庭(通过邀请码)")
     @PostMapping("/join-family")
-    public Result<Boolean> joinFamily(@RequestAttribute("userId") Long userId,
-        @RequestBody Map<String, String> body) {
+    public Result<Map<String, Object>> joinFamily(@RequestAttribute("userId") Long userId,
+        @RequestBody Map<String, String> body) {
         String inviteCode = body.get("inviteCode");
-        boolean success = userService.joinFamily(userId, inviteCode);
-        return Result.success(success);
-    }
+        if (inviteCode == null || inviteCode.trim().isEmpty()) {
+            return Result.error("邀请码不能为空");
+        }
+
+        try {
+            FamilyJoinRequest request = familyJoinRequestService.createJoinRequest(userId, inviteCode);
+            Family family = familyMapper.selectById(request.getTargetFamilyId());
+            Map<String, Object> result = new HashMap<>();
+            result.put("requestId", request.getId());
+            result.put("familyName", family != null ? family.getName() : "");
+            return Result.success("申请已提交,等待家庭管理员审批", result);
+        } catch (Exception e) {
+            return Result.error(e.getMessage());
+        }
+    }
 
     @Operation(summary = "创建新家庭")
     @PostMapping("/create-family")

+ 6 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java

@@ -26,6 +26,7 @@ import java.util.*;
 
 import com.etotem.cfc.entity.UserProfileHistory;
 import com.etotem.cfc.mapper.UserProfileHistoryMapper;
+import com.etotem.cfc.service.FamilyJoinRequestService;
 
 
 @Slf4j
@@ -68,6 +69,9 @@ private FamilyInvitationService familyInvitationService;
     @Resource
     private FamilyMemberService familyMemberService;
 
+    @Resource
+    private FamilyJoinRequestService familyJoinRequestService;
+
     public LoginResultDTO wechatLogin(WechatLoginDTO dto) {
         // 1. 通过code获取openid
         Map<String, String> sessionData = wechatService.code2Session(dto.getCode());
@@ -496,10 +500,10 @@ private FamilyInvitationService familyInvitationService;
         user.setUpdatedAt(new Date());
         userMapper.insert(user);
 
-        // 如果有邀请码,加入家庭
+        // 如果有邀请码,提交加入家庭请求(需管理员审批)
         if (dto.getInviteCode() != null && !dto.getInviteCode().isEmpty()) {
             try {
-                joinFamily(user.getId(), dto.getInviteCode());
+                familyJoinRequestService.createJoinRequest(user.getId(), dto.getInviteCode());
             } catch (Exception e) {
                 // 忽略邀请码错误
             }

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

@@ -81,7 +81,6 @@ export default {
       self.loading = true
       var params = {
         dimensionCode: self.dimensionCode,
-        familyId: self.familyId,
         limit: 6
       }
       getDimensionProducts(params).then(function(res) {

+ 1 - 3
cfc-frontend/components/family-guard.vue

@@ -41,9 +41,7 @@ export default {
 			createFamily(name).then(function(res) {
 				self.loading = false
 				if (res && res.code === 200 && res.data) {
-					uni.setStorageSync('familyId', res.data)
-					uni.setStorageSync('currentFamilyId', res.data)
-					// 同步 userInfo 缓存
+					// 同步 userInfo 缓存(familyId 由后端 JWT 属性注入,无需单独存储)
 					var userInfo = uni.getStorageSync('userInfo') || {}
 					userInfo.familyId = res.data
 					uni.setStorageSync('userInfo', userInfo)

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

@@ -86,25 +86,25 @@ export default {
       var familyId = (this.memberInfo && this.memberInfo.familyId) || ''
 
       if (familyId) {
-        getRelationshipScores(familyId).then(function(res) {
+        getRelationshipScores().then(function(res) {
           if (res.code === 200 && res.data) {
             self.relationScores = res.data
           }
         }).catch(function(e) { console.log('加载关系评分失败', e) })
 
-        getContactList({ familyId: familyId }).then(function(res) {
+        getContactList({}).then(function(res) {
           if (res.code === 200 && res.data) {
             self.contactList = Array.isArray(res.data) ? res.data : (res.data.records || [])
           }
         }).catch(function(e) { console.log('加载联系人失败', e) })
 
-        getHealthAlerts(familyId).then(function(res) {
+        getHealthAlerts().then(function(res) {
           if (res.code === 200 && res.data) {
             self.healthAlerts = Array.isArray(res.data) ? res.data : []
           }
         }).catch(function(e) { console.log('加载告警失败', e) })
 
-        getMilestones(familyId, 30).then(function(res) {
+        getMilestones(30).then(function(res) {
           if (res.code === 200 && res.data) {
             self.milestones = Array.isArray(res.data) ? res.data : []
           }

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

@@ -316,10 +316,8 @@ export default {
           }
         } else {
           const userId = uni.getStorageSync('userId')
-          const familyId = uni.getStorageSync('familyId')
           const res = await createAssessmentAppointment({
             userId: userId,
-            familyId: familyId,
             memberId: this.selectedChildId,
             teacherId: this.selectedGuideId,
             assessorId: this.selectedGuideId,

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

@@ -262,12 +262,10 @@ export default {
       uni.showLoading({ title: '创建订单中...' })
 
       var userId = uni.getStorageSync('userId')
-      var familyId = uni.getStorageSync('familyId')
       var self = this
 
       try {
         var createRes = await createAssessmentOrder({
-          familyId: familyId,
           userId: userId,
           memberId: memberId,
           guideId: this.selectedGuideId,

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

@@ -193,7 +193,6 @@ export default {
 
       uni.showLoading({ title: '创建中...' })
       try {
-        var familyId = uni.getStorageSync('familyId')
         var submitData = {
           memberId: this.formData.memberId,
           recordTitle: this.formData.recordTitle || '成长档案',
@@ -205,8 +204,7 @@ export default {
           schoolProvince: this.formData.schoolProvince || null,
           schoolCity: this.formData.schoolCity || null,
           schoolDistrict: this.formData.schoolDistrict || null,
-          schoolStreet: this.formData.schoolStreet || null,
-          familyId: familyId
+          schoolStreet: this.formData.schoolStreet || null
         }
 
         var res = await createGrowthRecord(submitData)

+ 4 - 4
cfc-frontend/pages/growth/index.vue

@@ -178,14 +178,14 @@ export default {
     },
     async prepareGuideInvite() {
       try {
-        var familyId = uni.getStorageSync('familyId')
-        if (!familyId) {
+        var ui = uni.getStorageSync('userInfo') || {};
+        if (!ui.familyId) {
           this.$refs.familyGuard.show()
           return
         }
-        var userInfo = uni.getStorageSync('userInfo')
+        var userInfo = ui
         var generateInviteCard = require('../../utils/api.js').generateInviteCard
-        var res = await generateInviteCard('guide', familyId)
+        var res = await generateInviteCard('guide')
         var code = res.data.code
         this.shareData = {
           title: (userInfo && userInfo.nickname ? userInfo.nickname : '家长') + ' 邀请你成为成长规划师',

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

@@ -189,8 +189,8 @@ export default {
       if (!self.familyId) return
       self.loading = true
       Promise.all([
-        getChallengeList({ familyId: self.familyId }),
-        getChallengeHistory({ familyId: self.familyId })
+        getChallengeList({}),
+        getChallengeHistory({})
       ]).then(function(results) {
         self.activeChallenges = Array.isArray(results[0].data) ? results[0].data : []
         self.historyChallenges = Array.isArray(results[1].data) ? results[1].data : []

+ 3 - 3
cfc-frontend/pages/home-pages/parent-index.vue

@@ -960,13 +960,13 @@ export default {
       var familyId = this._familyId
       if (!familyId) return
       var self = this
-      getFamilyRanking({ familyId: familyId }).then(function(res) {
+      getFamilyRanking({}).then(function(res) {
         if (res.code === 200 && res.data) {
           self.rankingMembers = res.data.members || []
           self.rankingTotalScore = res.data.totalScore || 0
         }
       }).catch(function() {})
-      getChallengeList({ familyId: familyId }).then(function(res) {
+      getChallengeList({}).then(function(res) {
         if (res.code === 200 && Array.isArray(res.data)) {
           self.activeChallenges = res.data
         }
@@ -977,7 +977,7 @@ export default {
       if (!familyId) return
       var self = this
       self.circleLoading = true
-      getCircleRanking({ familyId: familyId }).then(function(res) {
+      getCircleRanking({}).then(function(res) {
         if (res.code === 200 && res.data) {
           self.circleName = res.data.circleName || ''
           self.circleRankings = res.data.rankings || []

+ 2 - 5
cfc-frontend/pages/index-home/index.vue

@@ -690,10 +690,8 @@ export default {
       }).catch(function() {})
     },
     loadChallenges: function() {
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) return
       var self = this
-      getChallengeList({ familyId: familyId }).then(function(res) {
+      getChallengeList({}).then(function(res) {
         if (res.code === 200 && Array.isArray(res.data)) {
           self.activeChallenges = res.data
         }
@@ -1039,8 +1037,7 @@ export default {
                 const result = await acceptParentInvite(code)
                 uni.hideLoading()
                 if (result.code === 200) {
-                  const { familyId, role, token } = result.data
-                  uni.setStorageSync('familyId', familyId)
+                  const { role, token } = result.data
                   uni.setStorageSync('role', role)
                   uni.setStorageSync('token', token)
                   uni.showToast({ title: '加入成功', icon: 'success' })

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

@@ -297,9 +297,6 @@ export default {
         var res = await acceptInvitation(this.token, familyRole, this.mergeFamily)
         if (res && res.code === 200) {
           this.accepted = true
-          // 更新缓存的 familyId
-          uni.setStorageSync('familyId', this.inviteInfo.familyId)
-          uni.setStorageSync('currentFamilyId', this.inviteInfo.familyId)
           uni.showToast({ title: '加入家庭成功', icon: 'success' })
         } else {
           uni.showToast({ title: res.message || '加入失败', icon: 'none' })

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

@@ -142,8 +142,7 @@ export default {
           }
           uni.setStorageSync('userInfo', {
             nickname: res.data.nickname,
-            userId: res.data.userId,
-            familyId: res.data.familyId
+            userId: res.data.userId
           })
           if (res.data.teacherStatus) {
             uni.setStorageSync('teacherStatus', res.data.teacherStatus)

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

@@ -866,18 +866,12 @@ components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnerg
     },
     loadCompatibilityData: function() {
       var self = this
-      var familyId = uni.getStorageSync('currentFamilyId')
-      if (!familyId) {
-        var userInfo = uni.getStorageSync('userInfo')
-        if (userInfo && userInfo.familyId) familyId = userInfo.familyId
-      }
-      if (!familyId) return
       var token = uni.getStorageSync('token')
       uni.request({
         url: config.API_BASE_URL + '/api/mind/traditional/family-dashboard',
         method: 'POST',
         header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
-        data: { familyId: familyId },
+        data: {},
         success: function(res) {
           if (res.data && res.data.code === 200 && res.data.data) {
             self.compatibilityData = res.data.data

+ 2 - 4
cfc-frontend/pages/parent/wishes/index.vue

@@ -154,7 +154,6 @@ export default {
   data() {
     return {
       activeTab: 'pending',
-      familyId: '',
       memberId: '',
       childName: '',
       pendingWishes: [],
@@ -179,7 +178,6 @@ export default {
     }
   },
   onShow() {
-    this.familyId = uni.getStorageSync('familyId')
     this.loadData()
   },
   onLoad(options) {
@@ -192,7 +190,7 @@ export default {
     async loadData() {
       try {
         // 获取待处理列表
-        const pendingRes = await getPendingWishes(this.familyId)
+        const pendingRes = await getPendingWishes()
         let wishes = pendingRes.data || []
 
         // 如果指定了 memberId,按孩子过滤
@@ -202,7 +200,7 @@ export default {
         this.pendingWishes = wishes
         
         // 获取全部心愿
-        const allRes = await getWishes({ familyId: this.familyId })
+        const allRes = await getWishes({})
         let allWishes = allRes.data || []
         if (this.memberId) {
           allWishes = allWishes.filter(w => String(w.memberId) === String(this.memberId))

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

@@ -34,6 +34,9 @@
     <!-- ===== 分享邀请卡海报 ===== -->
     <SharePoster :show="showPoster" :referralCode="inviteCode" :qrCodeBase64="inviteQrCode" :nickname="userNickname" :userAvatar="userAvatar" @close="showPoster = false" />
 
+    <!-- ===== 无家庭引导弹窗 ===== -->
+    <family-guard ref="familyGuard" @created="onFamilyCreated" />
+
     <!-- ===== 成员详情弹窗 ===== -->
     <view class="modal-mask" v-if="showMemberDetailModal" @click="closeMemberDetail">
       <view class="member-detail-card" @click.stop>
@@ -114,13 +117,15 @@ import {
 } from '../../utils/api.js'
 import store from '../../store/index.js'
 import SharePoster from '@/components/SharePoster.vue'
+import FamilyGuard from '../../components/family-guard.vue'
 
 export default {
-  components: { SharePoster },
+  components: { SharePoster, FamilyGuard },
   data() {
     return {
       userId: 0,
       familyId: 0,
+      pendingCreate: false,
       familyName: '',
       inviteCode: '',
       inviteQrCode: '',
@@ -145,6 +150,12 @@ export default {
       return this.memberScores[this.detailMember.id] || {}
     }
   },
+  onLoad(options) {
+    // 支持 ?action=create 直达:无家庭用户引导创建
+    if (options && options.action === 'create') {
+      this.pendingCreate = true
+    }
+  },
   onShow() {
     var userInfo = uni.getStorageSync('userInfo')
     this.userId = (userInfo && userInfo.id) || 0
@@ -192,11 +203,33 @@ export default {
           this.inviteCode = data.inviteCode || ''
           this.creatorId = data.creatorId
           this.isAdmin = this.creatorId === this.userId
+          this.checkPendingCreate()
+        } else if (res.code === 5001 || (res.message && res.message.indexOf('未加入家庭') >= 0)) {
+          // 无家庭:弹出创建家庭引导
+          this.showFamilyGuard()
         }
       } catch (e) {
         // 静默处理
       }
     },
+    showFamilyGuard() {
+      var self = this
+      setTimeout(function() {
+        if (self.$refs.familyGuard) {
+          self.$refs.familyGuard.show()
+        }
+      }, 300)
+    },
+    checkPendingCreate() {
+      if (this.pendingCreate) {
+        this.pendingCreate = false
+        this.showFamilyGuard()
+      }
+    },
+    onFamilyCreated() {
+      // 家庭创建成功后刷新成员列表
+      this.loadMembers()
+    },
     showMemberDetail(member) {
       this.detailMember = member
       this.showMemberDetailModal = true

+ 5 - 5
cfc-frontend/pages/profile/components/ProfileMenu.vue

@@ -195,22 +195,22 @@ export default {
     },
     
     showInviteActionSheet() {
-      const familyId = uni.getStorageSync('familyId')
-      const userInfo = uni.getStorageSync('userInfo')
-      if (!familyId) {
+      var ui = uni.getStorageSync('userInfo') || {}
+      if (!ui.familyId) {
         this.$refs.familyGuard.show()
         return
       }
+      const userInfo = ui
       const items = ['👨‍👩‍👧‍👦 邀请家人', '👨‍🏫 邀请成长规划师']
       const types = ['family', 'guide']
       uni.showActionSheet({
         itemList: items,
         success: (res) => {
-          this.inviteGenerate(types[res.tapIndex], familyId, userInfo)
+          this.inviteGenerate(types[res.tapIndex], userInfo)
         }
       })
     },
-    inviteGenerate(type, familyId, userInfo) {
+    inviteGenerate(type, userInfo) {
       const inviteTypes = {
         family: {
           title: (userInfo && userInfo.nickname ? userInfo.nickname : '家人') + ' 邀请你加入家庭',

+ 3 - 3
cfc-frontend/pages/rewards/badge.vue

@@ -229,10 +229,10 @@ export default {
       }
     },
     async loadFamilyRanking() {
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) return
+      var ui = uni.getStorageSync('userInfo') || {}
+      if (!ui.familyId) return
       try {
-        var res = await getFamilyBadgeRanking(familyId)
+        var res = await getFamilyBadgeRanking()
         this.familyRanking = res.data || []
       } catch (e) {
         console.error('加载家庭排名失败', e)

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

@@ -54,7 +54,7 @@ export default {
   methods: {
     loadData: function(familyId) {
       var self = this
-      getFamilyFootprint({ familyId: familyId }).then(function(res) {
+      getFamilyFootprint({}).then(function(res) {
         if (res.code === 200 && res.data) {
           self.members = res.data.members || []
           self.totalScore = res.data.totalScore || 0

+ 1 - 3
cfc-frontend/pages/tianpan/daily-fortune.vue

@@ -64,9 +64,7 @@ export default {
     loadDailyFortune: function() {
       var self = this
       self.loading = true; self.error = null
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) { self.error = '请先选择家庭'; return }
-      self.$store.dispatch('tianpan/loadDailyFortune', { familyId: familyId, date: self.todayStr })
+      self.$store.dispatch('tianpan/loadDailyFortune', { date: self.todayStr })
         .then(function(data) { self.fortune = data })
         .catch(function(e) { self.error = e.message || '加载失败' })
         .finally(function() { self.loading = false })

+ 3 - 15
cfc-frontend/pages/tianpan/index.vue

@@ -200,11 +200,7 @@ export default {
     },
     
     loadDashboardData: function() {
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) {
-        return
-      }
-      this.loadDashboard({ familyId: familyId, year: this.currentYear, daysAhead: 7 })
+      this.loadDashboard({ year: this.currentYear, daysAhead: 7 })
         .then(this.drawCanvas)
     },
     
@@ -594,13 +590,9 @@ export default {
     },
     
     loadDailyFortune: function() {
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) {
-        return
-      }
       var today = new Date()
       var dateStr = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
-      this.loadDailyFortune({ familyId: familyId, date: dateStr })
+      this.loadDailyFortune({ date: dateStr })
         .then(this.setFortuneText)
     },
     
@@ -613,11 +605,7 @@ export default {
     },
     
     loadRelatedItems: function() {
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) {
-        return
-      }
-      this.loadRelatedItems({ familyId: familyId })
+      this.loadRelatedItems({})
     }
   },
   watch: {

+ 1 - 3
cfc-frontend/pages/tianpan/related-items.vue

@@ -90,9 +90,7 @@ export default {
     loadRelatedItems: function() {
       var self = this
       self.loading = true; self.error = null
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) { self.error = '请先选择家庭'; return }
-      self.$store.dispatch('tianpan/loadRelatedItems', { familyId: familyId, daysAhead: 7 })
+      self.$store.dispatch('tianpan/loadRelatedItems', { daysAhead: 7 })
         .then(function(data) { self.items = data || self.items })
         .catch(function(e) { self.error = e.message || '加载失败' })
         .finally(function() { self.loading = false })

+ 1 - 2
cfc-frontend/pages/tianpan/relation-detail.vue

@@ -190,8 +190,7 @@ export default {
     loadData: function() {
       this.loading = true
       this.error = null
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId || !this.memberId) {
+      if (!this.memberId) {
         this.error = '参数错误'
         this.loading = false
         return

+ 5 - 5
cfc-frontend/pages/user-edit/user-edit.vue

@@ -246,7 +246,7 @@
 </template>
 
 <script>
-import { updateUserInfo, joinFamily, createFamily, directRegister, getUserInfo } from '../../utils/api.js'
+import { updateUserInfo, createFamily, directRegister, getUserInfo, requestJoinByCode } from '../../utils/api.js'
 import AddressPicker from '../../components/address-picker.vue'
 
 export default {
@@ -576,13 +576,13 @@ export default {
 					uni.setStorageSync('token', regRes.data.token)
 					uni.setStorageSync('userId', userId)
 					uni.setStorageSync('role', regRes.data.role)
-					uni.setStorageSync('familyId', regRes.data.familyId)
+					// 邀请码注册不写入 familyId(加入需管理员审批)
 				} else {
 					await updateUserInfo(this.form)
-					await joinFamily(this.form.inviteCode)
+					await requestJoinByCode(this.form.inviteCode)
 				}
 				
-				uni.showToast({ title: '加入成功' })
+				uni.showToast({ title: '申请已提交,等待管理员审批' })
 				setTimeout(() => {
 					this.navigateToHome()
 				}, 1500)
@@ -618,7 +618,7 @@ export default {
 					uni.setStorageSync('token', regRes.data.token)
 					uni.setStorageSync('userId', userId)
 					uni.setStorageSync('role', regRes.data.role)
-					uni.setStorageSync('familyId', regRes.data.familyId)
+					// 注册不写入 familyId(后端根据 userId 获取)
 				} else {
 					await updateUserInfo(this.form)
 					await createFamily(this.form.nickname + '的家庭')

+ 2 - 2
cfc-frontend/pages/wealth-sub/insurance-planning.vue

@@ -224,8 +224,8 @@ export default {
   },
   methods: {
     async loadPlanning() {
-      var familyId = uni.getStorageSync('familyId')
-      if (!familyId) {
+      var ui = uni.getStorageSync('userInfo') || {}
+      if (!ui.familyId) {
         this.loading = false
         this.$refs.familyGuard.show()
         return

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

@@ -38,7 +38,6 @@ const store = new Vuex.Store({
       uni.setStorageSync('userId', userInfo.userId)
       uni.setStorageSync('role', userInfo.role)
       uni.setStorageSync('currentRole', userInfo.role)
-      uni.setStorageSync('familyId', userInfo.familyId)
       state.isSwitchedChild = false
       state.currentView = 'self'
       uni.setStorageSync('isSwitchedChild', false)
@@ -146,7 +145,6 @@ const store = new Vuex.Store({
       uni.removeStorageSync('isSwitchedTeacher')
       uni.removeStorageSync('userId')
       uni.removeStorageSync('role')
-      uni.removeStorageSync('familyId')
       uni.removeStorageSync('openid')
       uni.removeStorageSync('userInfo')
       uni.removeStorageSync('nickname')

+ 1 - 6
cfc-frontend/store/modules/tianpan.js

@@ -29,12 +29,7 @@ export default {
     loadDashboard({ commit, state }) {
       commit('SET_LOADING', true)
       commit('SET_ERROR', null)
-      const familyId = uni.getStorageSync('familyId')
-      if (!familyId) {
-        commit('SET_LOADING', false)
-        return Promise.reject(new Error('no familyId'))
-      }
-      return tianpanDashboard({ familyId: familyId, year: state.currentYear, daysAhead: state.daysAhead })
+      return tianpanDashboard({ year: state.currentYear, daysAhead: state.daysAhead })
         .then(function(res) {
           if (res && res.code === 200) {
             commit('SET_DASHBOARD', res.data)

+ 6 - 11
cfc-frontend/utils/api.js

@@ -32,7 +32,7 @@ function _showNoFamilyDialog() {
       if (r.confirm) {
         uni.navigateTo({ url: '/pages/profile-extra/family-members?action=create' })
       } else if (r.cancel) {
-        uni.navigateTo({ url: '/pages/invite/join' })
+        uni.navigateTo({ url: '/pages/profile-extra/family-members' })
       }
     }
   })
@@ -1145,7 +1145,10 @@ export const getFamilyCompletionStats = (params) => {
 }
 
 export const generateInviteCard = (type, refId) => {
-  return request('/api/invite-card/generate', 'POST', { type, refId })
+  if (type === 'child') {
+    return request('/api/invite-card/generate', 'POST', { type, refId })
+  }
+  return request('/api/invite-card/generate', 'POST', { type })
 }
 
 export const verifyInviteCard = (code) => {
@@ -1158,8 +1161,7 @@ export const acceptInviteCard = (code, phone) => {
 
 // 瑙勫垝甯堥個璇峰闀?
 export const generateParentInvite = () => {
-  const familyId = uni.getStorageSync('familyId')
-  return request('/api/guide/parent-invite/generate', 'POST', { familyId })
+  return request('/api/guide/parent-invite/generate', 'POST', {})
 }
 
 export const acceptParentInvite = (code) => {
@@ -1176,11 +1178,6 @@ export const generateFamilyInviteQrCode = () => {
   return request('/api/family/invite/qrcode', 'POST')
 }
 
-// 通过邀请码接受邀请(扫码后调用)
-export const acceptFamilyInviteByCode = (inviteCode) => {
-  return request('/api/family/invite/accept-by-code', 'POST', { inviteCode })
-}
-
 // ===== 瀹跺涵閭€璇凤紙浠ょ墝鐗堬紝鏀寔鎵爜娴侊級 =====
 
 // 鐢熸垚瀹跺涵閭€璇凤紙鍚皬绋嬪簭鐮佹垨浜岀淮鐮侊級
@@ -1372,7 +1369,6 @@ export const aiSendHealthButlerMessage = (data) => {
   var conversationId = data && data.conversationId ? data.conversationId : ''
   var userId = uni.getStorageSync('userId') || ''
   var memberId = data && data.memberId ? data.memberId : null
-  var familyId = ''
   var langgraphBody = {
     query: query,
     user: userId,
@@ -1383,7 +1379,6 @@ export const aiSendHealthButlerMessage = (data) => {
   }
   var inputs = {}
   if (memberId) { inputs.child_id = memberId }
-  if (familyId) { inputs.family_id = familyId }
   if (data && data.reportId) { inputs.report_id = data.reportId }
   if (data && data.focus) { inputs.focus = data.focus }
   if (Object.keys(inputs).length > 0) { langgraphBody.inputs = inputs }