فهرست منبع

refactor(frontend): 统一家庭成员接口迁移到 getFamilyMemberList,修复 quick-survey :key 警告

- FamilyMembersController.listMembers 支持 visibleOnly/includeFamily 参数
- api.js 新增 getFamilyMemberList 唯一入口,移除 getFamilyMembers/getVisibleFamilyMembers/listFamilyMembers 废弃导出
- 迁移 24 处调用点(8 getFamilyMembers + 13 getVisibleFamilyMembers + 3 listFamilyMembers)
- quick-survey.vue 3 处 :key 表达式改为方法调用
- api.test.js 测试同步更新
liaoxg 1 ماه پیش
والد
کامیت
3e0011babc

+ 14 - 6
cfc-frontend/__tests__/api/api.test.js

@@ -244,16 +244,24 @@ describe('user & family API', () => {
     expect(lastRequest().data.name).toBe('幸福之家')
   })
 
-  test('getFamilyMembers sends POST', async () => {
+  test('getFamilyMemberList sends POST to unified endpoint', async () => {
     mockSuccess({ code: 200 })
-    await api.getFamilyMembers()
-    expect(lastRequest().url).toMatch(/\/api\/family\/user\/family-members$/)
+    await api.getFamilyMemberList()
+    expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/)
   })
 
-  test('getVisibleFamilyMembers sends POST', async () => {
+  test('getFamilyMemberList sends visibleOnly param', async () => {
     mockSuccess({ code: 200 })
-    await api.getVisibleFamilyMembers()
-    expect(lastRequest().url).toMatch(/\/api\/family\/user\/members\/visible$/)
+    await api.getFamilyMemberList({ visibleOnly: true })
+    expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/)
+    expect(lastRequest().data.visibleOnly).toBe(true)
+  })
+
+  test('getFamilyMemberList sends includeFamily param', async () => {
+    mockSuccess({ code: 200 })
+    await api.getFamilyMemberList({ includeFamily: true })
+    expect(lastRequest().url).toMatch(/\/api\/family\/member\/list$/)
+    expect(lastRequest().data.includeFamily).toBe(true)
   })
 
   test('updateMemberVisibility sends POST with params', async () => {

+ 2 - 2
cfc-frontend/components/FamilyMemberStrip.vue

@@ -132,7 +132,7 @@
 <script>
 import FamilyMemberCard from '@/components/FamilyMemberCard'
 import SharePoster from '@/components/SharePoster.vue'
-import { kickFamilyMemberById, createFamily, generateFamilyInviteQrCode, getPairwiseRelationships, getVisibleFamilyMembers } from '@/utils/api'
+import { kickFamilyMemberById, createFamily, generateFamilyInviteQrCode, getPairwiseRelationships, getFamilyMemberList } from '@/utils/api'
 import { parseDate } from '@/utils/format.js'
 
 export default {
@@ -310,7 +310,7 @@ export default {
       var self = this
       var token = uni.getStorageSync('token')
       if (!token) return
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         if (res && res.code === 200) {
           var arr = res.data || []
           if (arr instanceof Array && arr.length > 0) {

+ 2 - 2
cfc-frontend/components/ProfileMenu.vue

@@ -121,7 +121,7 @@
 </template>
 
 <script>
-import { setPassword, verifyPassword, updateMemberVisibility, getVisibleFamilyMembers, vendorStatus, ensureFamily, getUserIntent, chooseUserIntent } from '../utils/api.js'
+import { setPassword, verifyPassword, updateMemberVisibility, getFamilyMemberList, vendorStatus, ensureFamily, getUserIntent, chooseUserIntent } from '../utils/api.js'
 import IntentPicker from './intent-picker.vue'
 
 export default {
@@ -164,7 +164,7 @@ export default {
     async loadShowToFamily() {
       try {
         const uid = this.$store.state.userId || uni.getStorageSync('userId')
-        const res = await getVisibleFamilyMembers()
+        const res = await getFamilyMemberList({ visibleOnly: true })
         const members = res.data || []
         const self = members.find(m => m.id == uid)
         if (self) {

+ 2 - 2
cfc-frontend/pages/action-detail/index.vue

@@ -135,7 +135,7 @@ import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import RadarChart from '../../components/RadarChart.vue'
 import DimensionSubDims from '../../components/DimensionSubDims.vue'
-import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getEnergySandbox, getVisibleFamilyMembers } from '../../utils/api.js'
+import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getEnergySandbox, getFamilyMemberList } from '../../utils/api.js'
 
 export default {
   components: { TabTransition, PageBanner, FamilyEnergyBar, FamilyRelationGraph, FamilyMemberStrip, DimensionTasks, DimensionActivities, DimensionProducts, DimensionIntroCard, AIFloatingAvatar, RadarChart, DimensionSubDims },
@@ -384,7 +384,7 @@ export default {
     },
     loadFamilyMembersVisible: function() {
       var self = this
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         self.familyMembersVisible = res.data || []
         self.membersChecked = true
       }).catch(function(e) {

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

@@ -60,7 +60,7 @@
 </template>
 
 <script>
-import { listFamilyMembers, addInteraction, getInteractionTypes } from '../../utils/api.js'
+import { getFamilyMemberList, addInteraction, getInteractionTypes } from '../../utils/api.js'
 
 export default {
   data() {
@@ -105,7 +105,7 @@ export default {
   methods: {
     async loadMembers() {
       try {
-        var res = await listFamilyMembers()
+        var res = await getFamilyMemberList()
         if (res.code === 200 && res.data) {
           this.members = res.data || []
         }

+ 27 - 27
cfc-frontend/pages/activity/activity-detail/activity-detail.vue

@@ -267,7 +267,7 @@
 </template>
 
 <script>
-import { getActivityDetail, activityCheckin, cancelActivityRegistration, createActivityOrder, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback, getFamilyMembers, getShareQrCode, getMyMembership } from '@/utils/api.js'
+import { getActivityDetail, activityCheckin, cancelActivityRegistration, createActivityOrder, submitActivityFeedback, getActivityFeedbackList, getMyActivityFeedback, getFamilyMemberList, getShareQrCode, getMyMembership } from '@/utils/api.js'
 import config from '@/config.js'
 import shareMixin from '../../../components/share-mixin.js'
 import MpHtml from '@/components/mp-html/mp-html.vue'
@@ -571,34 +571,34 @@ export default {
     // ===== 报名表单 =====
     loadFamilyMembers: function() {
       var self = this
-      getFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         if (res && res.code === 200 && res.data) {
           var list = []
-          // parents
-          var parents = res.data.parents || []
-          for (var i = 0; i < parents.length; i++) {
-            list.push({
-              id: 'p_' + parents[i].id,
-              name: parents[i].nickname || parents[i].name || '',
-              phone: parents[i].phone || '',
-              relation: '家长',
-              memberId: parents[i].id,
-              source: 'parent',
-              targetType: 'user'
-            })
-          }
-          // children
-          var children = res.data.children || []
-          for (var j = 0; j < children.length; j++) {
-            list.push({
-              id: 'c_' + children[j].id,
-              name: children[j].nickname || children[j].name || '',
-              phone: children[j].phone || '',
-              relation: '孩子',
-              memberId: children[j].userId || children[j].id,
-              source: 'child',
-              targetType: 'member'
-            })
+          var members = res.data
+          // members: flat List<FamilyMemberVO>,source='parent' 为家长(users表),其余为孩子/成员(family_members表)
+          for (var i = 0; i < members.length; i++) {
+            var m = members[i]
+            if (m.source === 'parent') {
+              list.push({
+                id: 'p_' + m.id,
+                name: m.nickname || m.name || '',
+                phone: m.phone || '',
+                relation: '家长',
+                memberId: m.id,
+                source: 'parent',
+                targetType: 'user'
+              })
+            } else {
+              list.push({
+                id: 'c_' + m.id,
+                name: m.nickname || m.name || '',
+                phone: m.phone || '',
+                relation: '孩子',
+                memberId: m.userId || m.id,
+                source: 'child',
+                targetType: 'member'
+              })
+            }
           }
           self.familyMembers = list
         }

+ 2 - 2
cfc-frontend/pages/body-detail/index.vue

@@ -116,7 +116,7 @@ import DimensionSubDims from '../../components/DimensionSubDims.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
-import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getEnergySandbox, getVisibleFamilyMembers, getDimensionOverview } from '../../utils/api.js'
+import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getEnergySandbox, getFamilyMemberList, getDimensionOverview } from '../../utils/api.js'
 
 export default {
   components: { TabTransition, PageBanner, FamilyEnergyBar, RadarChart, DimensionSubDims, AIFloatingAvatar, FamilyMemberStrip, DimensionIntroCard },
@@ -356,7 +356,7 @@ export default {
     },
     loadFamilyMembersVisible: function() {
       var self = this
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         self.familyMembersVisible = res.data || []
         self.membersChecked = true
       }).catch(function(e) {

+ 2 - 2
cfc-frontend/pages/energy/index.vue

@@ -165,7 +165,7 @@
 
 <script>
 import RadarChart from '../../components/RadarChart.vue'
-import { getFamilyEnergySandbox, getVisibleFamilyMembers } from '../../utils/api.js'
+import { getFamilyEnergySandbox, getFamilyMemberList } from '../../utils/api.js'
 
 var DIM_CONFIG = [
   { code: 'body',    label: '身', icon: '💪', color: '#10B981', gradient: 'linear-gradient(135deg,#10B981,#34D399)' },
@@ -296,7 +296,7 @@ export default {
           }
         }
         // 加载家庭成员
-        var memRes = await getVisibleFamilyMembers()
+        var memRes = await getFamilyMemberList({ visibleOnly: true })
         if (memRes && memRes.code === 200 && memRes.data) {
           this.familyMembers = memRes.data
           // 默认选中第一个非自己(如果有)

+ 2 - 2
cfc-frontend/pages/health/ai-questionnaire.vue

@@ -61,7 +61,7 @@
 </template>
 
 <script>
-import { aiQSceneList, aiQStart, aiQAnswer, aiQFinish, getFamilyMembers } from '../../utils/api.js'
+import { aiQSceneList, aiQStart, aiQAnswer, aiQFinish, getFamilyMemberList } from '../../utils/api.js'
 
 export default {
   data() {
@@ -117,7 +117,7 @@ export default {
     },
     loadMembers: function() {
       var self = this
-      getFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         if (res && res.code === 200 && res.data) {
           var list = res.data instanceof Array ? res.data : []
           self.members = list.filter(function(m) { return m.id })

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

@@ -180,7 +180,7 @@
 </template>
 
 <script>
-import { getChallengeList, getChallengeHistory, createChallenge, updateChallenge, deleteChallenge, getChallengeTemplates, listFamilyMembers } from '../../utils/api.js'
+import { getChallengeList, getChallengeHistory, createChallenge, updateChallenge, deleteChallenge, getChallengeTemplates, getFamilyMemberList } from '../../utils/api.js'
 
 export default {
   data() {
@@ -257,7 +257,7 @@ export default {
     },
     async loadMembers() {
       try {
-        const res = await listFamilyMembers()
+        const res = await getFamilyMemberList()
         this.familyMembers = Array.isArray(res.data) ? res.data : []
         this.selectedMemberIds = (this.familyMembers || []).map(m => m.id)
       } catch (e) {

+ 2 - 2
cfc-frontend/pages/health/health-plan-summary.vue

@@ -144,7 +144,7 @@
 </template>
 
 <script>
-import { getFamilyMembers, getHealthStatus, getDietPreferences, aiSendMessage, createTask } from '../../utils/api'
+import { getFamilyMemberList, getHealthStatus, getDietPreferences, aiSendMessage, createTask } from '../../utils/api'
 import { parseDate } from '../../utils/format'
 
 export default {
@@ -198,7 +198,7 @@ export default {
     getMemberKey: function(m, idx) { return m.memberId || idx },
     loadExecutors: function() {
       var self = this
-      getFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         if (res && res.code === 200 && res.data) {
           var list = res.data instanceof Array ? res.data : []
           self.executors = list

+ 6 - 6
cfc-frontend/pages/health/report-confirm.vue

@@ -353,7 +353,7 @@
 
 <script>
 import { confirmReportPreview, discardReportDraft, confirmTongue, discardTongue, parseReportDraft } from '../../utils/api.js'
-import { addFamilyMember, getFamilyMembers } from '../../utils/api.js'
+import { addFamilyMember, getFamilyMemberList } from '../../utils/api.js'
 
 export default {
   data() {
@@ -631,12 +631,12 @@ export default {
     loadFamilyMembers() {
       var self = this
       // 从后端获取真实家庭成员(家长 + 孩子),storage 中并无该数据
-      getFamilyMembers().then(function(res) {
-        var data = (res && res.data) || {}
-        var parents = (data.parents || []).map(function(p) {
-          return { id: p.id, nickname: p.realName || p.nickname, gender: p.gender, role: 'parent' }
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
+        var list = (res && res.data) || []
+        var parents = list.filter(function(m) { return m.source === 'parent' }).map(function(p) {
+          return { id: p.id, nickname: p.nickname, gender: p.gender, role: 'parent' }
         })
-        var children = (data.children || []).map(function(c) {
+        var children = list.filter(function(m) { return m.source !== 'parent' }).map(function(c) {
           return { id: c.id, nickname: c.nickname, gender: c.gender, age: c.age, role: 'child' }
         })
         self.familyMembers = parents.concat(children)

+ 2 - 2
cfc-frontend/pages/health/report-detail.vue

@@ -372,7 +372,7 @@
 
 <script>
 import ReportBlocksRenderer from '../../components/report-blocks-renderer.vue'
-import { getReportDetail, getDanReportDetail, editHealthReport, parseReportDraft, confirmReportPreview, discardReportDraft, confirmTongue, discardTongue, getFamilyMembers, addFamilyMember } from '../../utils/api.js'
+import { getReportDetail, getDanReportDetail, editHealthReport, parseReportDraft, confirmReportPreview, discardReportDraft, confirmTongue, discardTongue, getFamilyMemberList, addFamilyMember } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 
 export default {
@@ -665,7 +665,7 @@ export default {
     },
     loadFamilyMembers() {
       var self = this
-      getFamilyMembers({ familyId: self.familyId || 0 }).then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         if (res.code === 200) {
           self.familyMembers = res.data || []
         }

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

@@ -311,7 +311,7 @@ B) 积分卡片 — Claymorphism 暖橙黏土风格
 </template>
 
 <script>
-import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getVisibleFamilyMembers } from '../../utils/api.js'
+import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getFamilyMemberList } from '../../utils/api.js'
 import PageBanner from '../../components/PageBanner.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
@@ -511,7 +511,7 @@ export default {
     },
     loadFamilyMembersVisible() {
       var self = this
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         self.familyMembersVisible = res.data || []
       }).catch(function(e) {
         console.log('获取可见家庭成员失败', e)

+ 6 - 5
cfc-frontend/pages/home-pages/parent-index.vue

@@ -477,7 +477,7 @@
 </template>
 
 <script>
-import { getFamilyMembers, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getEnergyOverview, getVisibleFamilyMembers, getActivityList, getProductsByDomain, getContactList, getFeaturedArticles, getActiveAlerts, dismissAlert, getFamilyRanking, getChallengeList, getCircleRanking, getInviteQrCode, switchToFamilyMember } from '../../utils/api.js'
+import { getFamilyMemberList, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getEnergyOverview, getActivityList, getProductsByDomain, getContactList, getFeaturedArticles, getActiveAlerts, dismissAlert, getFamilyRanking, getChallengeList, getCircleRanking, getInviteQrCode, switchToFamilyMember } from '../../utils/api.js'
 import store from '../../store/index.js'
 import PageBanner from '../../components/PageBanner.vue'
 import PlayfulCard from '../../components/PlayfulCard.vue'
@@ -726,10 +726,11 @@ export default {
         if (!dashboardLoaded) {
           try {
             const currentUserId = uni.getStorageSync('userId')
-            const membersRes = await getFamilyMembers()
+            const membersRes = await getFamilyMemberList({ visibleOnly: true, includeFamily: true })
             const data = membersRes.data || {}
-            const parents = (data.parents || []).filter(function(p) { return p && p.id && p.id !== currentUserId }).map(function(p) { return { ...p, role: 'parent' } })
-            const children = (data.children || []).filter(function(c) { return c && c.id }).map(function(c) { return { ...c, role: 'child' } })
+            const members = data.members || []
+            const parents = members.filter(function(p) { return p.source === 'parent' && p.id && p.id !== currentUserId }).map(function(p) { return { ...p, role: 'parent' } })
+            const children = members.filter(function(c) { return c.source !== 'parent' && c.id }).map(function(c) { return { ...c, role: 'child' } })
             this.familyMembers = [].concat(parents, children)
             // 不自动选孩子:默认显示家长自己,等待用户从 FamilyMemberStrip 选择
             // 保存 familyId 用于后续请求
@@ -934,7 +935,7 @@ export default {
     // 加载可见家庭成员
     async loadFamilyMembersVisible() {
       try {
-        var res = await getVisibleFamilyMembers()
+        var res = await getFamilyMemberList({ visibleOnly: true })
         this.familyMembersVisible = res.data || []
       } catch (e) {
         console.log('获取可见家庭成员失败', e)

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

@@ -345,7 +345,7 @@ import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
 import FamilyChallengeCard from '../../components/FamilyChallengeCard.vue'
 import IntentPicker from '../../components/intent-picker.vue'
 import JourneyCard from '../../components/journey-card.vue'
-import { acceptParentInvite, productList, getActivityList, getFeaturedArticles, getFamilyEnergySandbox, getVisibleFamilyMembers, getChildren, getChallengeList, switchToFamilyMember, getMyMembership, updateChallengeProgress, respondChallenge, getTodayTasks, completeTask as completeTaskApi, getTodayParentTasks, completeParentTask as completeParentTaskApi, getTaskHistory, getNotices, getNotificationList, getUserIntent, chooseUserIntent } from '../../utils/api.js'
+import { acceptParentInvite, productList, getActivityList, getFeaturedArticles, getFamilyEnergySandbox, getFamilyMemberList, getChildren, getChallengeList, switchToFamilyMember, getMyMembership, updateChallengeProgress, respondChallenge, getTodayTasks, completeTask as completeTaskApi, getTodayParentTasks, completeParentTask as completeParentTaskApi, getTaskHistory, getNotices, getNotificationList, getUserIntent, chooseUserIntent } from '../../utils/api.js'
 import nav from '../../utils/nav.js'
 import config from '@/config.js'
 import { parseDate } from '../../utils/format.js'
@@ -756,7 +756,7 @@ export default {
     },
     loadFamilyMembers: function() {
       var self = this
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         if (res.code === 200 && res.data) {
           self.familyMembersVisible = Array.isArray(res.data) ? res.data : (res.data.list || [])
         }

+ 2 - 2
cfc-frontend/pages/mind-detail/index.vue

@@ -173,7 +173,7 @@ import PsychCrisisBanner from '../../components/PsychCrisisBanner.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import FamilyTianpanCard from '../../components/FamilyTianpanCard.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
-import { getVisibleSections, getEmiReport, getFamilyEnergySandbox, getEnergySandbox, getChildren, getVisibleFamilyMembers, getEnergyOverview, getContactList, getHealthAlerts, getMilestones, getDanReportByChild, getFamilyFortune } from '../../utils/api.js'
+import { getVisibleSections, getEmiReport, getFamilyEnergySandbox, getEnergySandbox, getChildren, getFamilyMemberList, getEnergyOverview, getContactList, getHealthAlerts, getMilestones, getDanReportByChild, getFamilyFortune } from '../../utils/api.js'
 import nav from '../../utils/nav.js'
 import config from '../../config.js'
 import { parseDate } from '../../utils/format.js'
@@ -788,7 +788,7 @@ var descList = descMap[this.dominantElement] || descMap.wood
     },
     loadFamilyMembersVisible: function() {
       var self = this
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         self.familyMembersVisible = res.data || []
       }).catch(function(e) {
         console.log('获取可见家庭成员失败', e)

+ 12 - 3
cfc-frontend/pages/problem/quick-survey.vue

@@ -12,17 +12,17 @@
       <!-- 模板选择(多模板时可切换) -->
       <view class="qs-template-row" v-if="templates.length > 1">
         <text class="qs-template-label">选择话题:</text>
-        <view class="qs-template-chip" v-for="(t, i) in templates" :key="'t' + i"
+        <view class="qs-template-chip" v-for="(t, i) in templates" :key="getTemplateKey(t, i)"
           :class="{ 'qs-template-chip-active': selectedTemplateId === t.id }"
           @click="switchTemplate(t)">
           <text>{{ t.title }}</text>
         </view>
       </view>
 
-      <view class="qs-q" v-for="(q, qi) in questions" :key="'q' + qi">
+      <view class="qs-q" v-for="(q, qi) in questions" :key="getQuestionKey(q, qi)">
         <text class="qs-q-label">{{ qi + 1 }}. {{ q.text }}</text>
         <view class="qs-options">
-          <view class="qs-opt" v-for="(o, oi) in q.options" :key="'o' + qi + '_' + oi"
+          <view class="qs-opt" v-for="(o, oi) in q.options" :key="getOptionKey(o, qi, oi)"
             :class="{ 'qs-opt-active': selectedIdx(qi) === oi }"
             @click="select(qi, oi)">
             <text class="qs-opt-text">{{ o.text }}</text>
@@ -61,6 +61,15 @@ export default {
     this.loadTemplates()
   },
   methods: {
+    getTemplateKey(t, i) {
+      return t && t.id ? 't' + t.id : 't' + i
+    },
+    getQuestionKey(q, qi) {
+      return q && q.id ? 'q' + q.id : 'q' + qi
+    },
+    getOptionKey(o, qi, oi) {
+      return o && o.id ? 'o' + qi + '_' + o.id : 'o' + qi + '_' + oi
+    },
     selectedIdx(qi) {
       var arr = this.answers[qi]
       return (arr && arr.length > 0) ? arr[0] : -1

+ 3 - 4
cfc-frontend/pages/profile-extra/family-members.vue

@@ -102,11 +102,10 @@
 
 <script>
 import {
-  listFamilyMembers,
+  getFamilyMemberList,
   switchToFamilyMember,
   kickFamilyMemberById,
   generateFamilyInvite,
-  getFamilyMembers,
   updateFamilyMember
 } from '../../utils/api.js'
 import store from '../../store/index.js'
@@ -166,7 +165,7 @@ export default {
     async loadMembers() {
       this.loading = true
       try {
-        var res = await listFamilyMembers()
+        var res = await getFamilyMemberList()
         if (res.code === 200 && res.data) {
           this.members = res.data || []
           this.buildScoreMap(res.data)
@@ -194,7 +193,7 @@ export default {
     },
     async loadFamilyInfo() {
       try {
-        var res = await getFamilyMembers()
+        var res = await getFamilyMemberList({ includeFamily: true })
         if (res.code === 200 && res.data) {
           var data = res.data
           this.familyId = data.familyId

+ 2 - 2
cfc-frontend/pages/profile-main/profile.vue

@@ -230,7 +230,7 @@
 <script>
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import {
-  getChildren, getVisibleFamilyMembers,
+  getChildren, getFamilyMemberList,
   getTodayTasksByCategory, getChallengeList, getMyMembership,
   verifyPassword, getPointsBalance, getUserInfo,
   cartCount, getOrderCounts, getReferralSummary,
@@ -347,7 +347,7 @@ export default {
     },
     loadFamily() {
       var self = this
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         var data = res && res.data ? res.data : []
         self.familyMembers = Array.isArray(data) ? data : []
       }).catch(function() {

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

@@ -100,7 +100,7 @@
 </template>
 
 <script>
-import { setPassword, verifyPassword, updateMemberVisibility, getVisibleFamilyMembers, vendorStatus, ensureFamily } from '../../../utils/api.js'
+import { setPassword, verifyPassword, updateMemberVisibility, getFamilyMemberList, vendorStatus, ensureFamily } from '../../../utils/api.js'
 
 export default {
   name: 'ProfileMenu',
@@ -135,7 +135,7 @@ export default {
     async loadShowToFamily() {
       try {
         const uid = this.$store.state.userId || uni.getStorageSync('userId')
-        const res = await getVisibleFamilyMembers()
+        const res = await getFamilyMemberList({ visibleOnly: true })
         const members = res.data || []
         const self = members.find(m => m.id == uid)
         if (self) {

+ 2 - 2
cfc-frontend/pages/promotion/index.vue

@@ -206,7 +206,7 @@
 </template>
 
 <script>
-import { getReferralSummary, getCommissionSummary, getFamilyEarningsSummary, getFamilyEarningsMembers, getFamilyEarningsRecords, distributeToMember, toggleMemberBalance, withdrawFamilyEarnings, getInviteQrCode, getFamilyMembers, getReferralCode } from '../../utils/api.js'
+import { getReferralSummary, getCommissionSummary, getFamilyEarningsSummary, getFamilyEarningsMembers, getFamilyEarningsRecords, distributeToMember, toggleMemberBalance, withdrawFamilyEarnings, getInviteQrCode, getFamilyMemberList, getReferralCode } from '../../utils/api.js'
 import SharePoster from '@/components/SharePoster.vue'
 
 export default {
@@ -499,7 +499,7 @@ export default {
         }
         if (!this.familyName) {
           try {
-            var famRes = await getFamilyMembers()
+            var famRes = await getFamilyMemberList({ includeFamily: true })
             if (famRes.code === 200 && famRes.data && famRes.data.familyName) {
               this.familyName = famRes.data.familyName
             }

+ 2 - 2
cfc-frontend/pages/tasks/tasks.vue

@@ -155,7 +155,7 @@
 </template>
 
 <script>
-import { getMyPendingTasks, acceptTask as acceptTaskApi, rejectTask as rejectTaskApi, getTodayTasks, completeTask as completeTaskApi, createTask as createTaskApi, getVisibleFamilyMembers, getMyMembership } from '../../utils/api.js'
+import { getMyPendingTasks, acceptTask as acceptTaskApi, rejectTask as rejectTaskApi, getTodayTasks, completeTask as completeTaskApi, createTask as createTaskApi, getFamilyMemberList, getMyMembership } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 
 export default {
@@ -209,7 +209,7 @@ export default {
     async loadInitialData() {
       this.loading = true
       try {
-        const membersRes = await getVisibleFamilyMembers()
+        const membersRes = await getFamilyMemberList({ visibleOnly: true })
         this.familyMembers = membersRes.data || []
         this.loadTodayTasks()
         this.loadPendingTasks()

+ 2 - 2
cfc-frontend/pages/wealth/index.vue

@@ -209,7 +209,7 @@ import {
   getReferralCode,
   getReferralSummary,
   getCommissionSummary,
-  getVisibleFamilyMembers,
+  getFamilyMemberList,
   productList
 } from '../../utils/api.js'
 import nav from '../../utils/nav.js'
@@ -345,7 +345,7 @@ export default {
     },
     loadFamilyMembersVisible() {
       var self = this
-      getVisibleFamilyMembers().then(function(res) {
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
         self.familyMembersVisible = res.data || []
       }).catch(function(e) {
         console.log('获取可见家庭成员失败', e)

+ 4 - 12
cfc-frontend/utils/api.js

@@ -332,14 +332,10 @@ export const ensureFamily = () => {
 	})
 }
 
-// 获取家庭所有成员(家长+孩子)— 统一入口 /api/family/member/list
-export const getFamilyMembers = (params) => {
-	return request('/api/family/member/list', 'POST', Object.assign({ includeFamily: true }, params || {}))
-}
-
-// 获取对家庭成员可见的成员列表 — 统一入口 /api/family/member/list
-export const getVisibleFamilyMembers = () => {
-	return request('/api/family/member/list', 'POST', { visibleOnly: true })
+// 统一家庭成员列表接口(唯一入口,替代 getFamilyMembers / getVisibleFamilyMembers / listFamilyMembers)
+// params: { visibleOnly: true } 仅返回 show_to_family=1 成员; { includeFamily: true } 附带 familyId/familyName/inviteCode/creatorId
+export const getFamilyMemberList = (params) => {
+	return request('/api/family/member/list', 'POST', params || {})
 }
 
 // 获取以指定成员为中心的所有成员关系(关系类型+信任/亲密度/沟通评分)
@@ -437,10 +433,6 @@ export const addFamilyMember = (data) => {
   return request('/api/family/member/add', 'POST', data)
 }
 
-export const listFamilyMembers = () => {
-  return request('/api/family/member/list', 'POST')
-}
-
 // 用户同意记录
 export const recordConsent = (data) => {
   return request('/api/consent/record', 'POST', data)