Sfoglia il codice sorgente

fix: 后端+前端全面修复 - ArticleService/EnergyService/DimensionArticles/FamilyRelationshipSection及多页面完善
- ArticleService: getAdminList 返回值修复
- EnergyService: getEnergyOverview 能量概览补全
- DimensionArticles: 修复文章组件
- FamilyRelationshipSection: 完善关系管理组件
- 多页面: action/mind/wisdom-detail/promotion 等页面更新

User 4 mesi fa
parent
commit
f1c9f97679

+ 13 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java

@@ -10,12 +10,14 @@ import com.etotem.cfc.entity.ArticleReadingRecord;
 import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.Task;
+import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.ArticleMapper;
 import com.etotem.cfc.mapper.ArticleQuizRecordMapper;
 import com.etotem.cfc.mapper.ArticleReadingRecordMapper;
 import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.TaskMapper;
+import com.etotem.cfc.mapper.UserMapper;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -66,6 +68,9 @@ public class ArticleService {
     @Resource
     private ArticleQuizRecordMapper articleQuizRecordMapper;
 
+    @Resource
+    private UserMapper userMapper;
+
     private final ObjectMapper objectMapper = new ObjectMapper();
 
     public Page<Article> getPublicList(Long categoryId, String keyword, String dimensionCode, int page, int size,
@@ -586,6 +591,14 @@ public class ArticleService {
         // 关联维度
         article.setRelatedDimensions(relatedDimensions);
 
+        // 私有(家庭可见)→ 设置 visibleTo
+        if ("private".equals(visibility)) {
+            User user = userMapper.selectById(userId);
+            if (user != null && user.getFamilyId() != null) {
+                article.setVisibleTo("[{\"type\":\"family\",\"familyId\":" + user.getFamilyId() + "}]");
+            }
+        }
+
         // 公开文章需要审核,家庭内部可见直接发布
         if ("public".equals(visibility)) {
             article.setStatus("draft");

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java

@@ -67,6 +67,15 @@ public class EnergyService {
         if (familyId == null) {
             return buildEmptyDTO();
         }
+        try {
+            return doCalculateFamilyEnergy(familyId);
+        } catch (Exception e) {
+            log.error("计算家庭能量异常, familyId={}: {}", familyId, e.getMessage(), e);
+            return buildEmptyDTO();
+        }
+    }
+
+    private EnergySandboxDTO doCalculateFamilyEnergy(Long familyId) {
         EnergySandboxDTO dto = new EnergySandboxDTO();
         List<MemberEnergyDTO> members = new ArrayList<>();
 

+ 4 - 4
cfc-backend/src/main/resources/application.yml

@@ -16,9 +16,9 @@ spring:
     name: cfc-backend
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://CHANGE_ME_HOST:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
-    username: CHANGE_ME_USER
-    password: CHANGE_ME_PASS
+    url: jdbc:mysql://192.168.16.251:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
+    username: zxyj
+    password: zxyj@123
     hikari:
       maximum-pool-size: 50
       minimum-idle: 10
@@ -31,7 +31,7 @@ sfms:
   datasource:
     enabled: false  # 本地开发禁用 SFMS 数据源,需要迁移时改为 true
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://192.168.16.251:3306/ax?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
+    url: jdbc:mysql://192.168.16.251:3306/zxyj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true
     username: zxyj
     password: zxyj@123
 

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

@@ -1,8 +1,8 @@
 <template>
   <view class="section">
     <view class="section-header">
-      <text class="section-title">推荐阅读</text>
-      <text class="section-more" v-if="displayArticles && displayArticles.length > 0" @click="$emit('moreArticles')">更多 ›</text>
+      <text class="section-title">🔥 推荐阅读</text>
+      <text class="section-more" @click="$emit('moreArticles')">更多 ›</text>
     </view>
     <view v-if="!displayArticles || displayArticles.length === 0" class="empty-state">
       <text class="empty-tip">{{ isLoggedIn ? '暂无阅读推荐' : '登录后查看更多文章' }}</text>

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

@@ -72,6 +72,7 @@
       </view>
     </view>
   </view>
+  </view>
 </template>
 
 <script>

+ 9 - 74
cfc-frontend/pages/action/index.vue

@@ -71,83 +71,17 @@
     <!-- 推荐阅读 -->
     <DimensionArticles
       dimensionCode="action"
-      :articles="articles"
+      :articles="actionArticles"
       :isLoggedIn="isLoggedIn"
       @articleClick="goArticleDetail"
       @moreArticles="goMoreArticles" />
 
-    <!-- ===== 重要关系维护 ===== -->
-    <view class="contact-section" v-if="isLoggedIn">
-      <view class="contact-header">
-        <text class="contact-section-title">重要关系</text>
-        <view class="contact-header-right">
-          <text class="contact-more" @click="loadContacts">刷新</text>
-          <text class="contact-import-btn" @click="onShowImport">+ 导入</text>
-        </view>
-      </view>
-      <scroll-view class="contact-scroll" scroll-x enable-flex>
-        <view class="contact-scroll-inner">
-          <view class="contact-card-wrap" v-for="item in contactList" :key="item.id" @click="onContactClick(item)">
-            <ContactCard :contact="item" />
-          </view>
-          <view class="contact-card-wrap contact-add-card" @click="onShowImport">
-            <view class="add-card-inner">
-              <text class="add-card-icon">+</text>
-              <text class="add-card-label">导入通讯录</text>
-            </view>
-          </view>
-        </view>
-      </scroll-view>
-      <view class="contact-empty" v-if="contactList.length === 0">
-        <text class="contact-empty-text">暂无重要联系人,点击导入添加</text>
-      </view>
-    </view>
-
-    <!-- ===== 关系健康概览 ===== -->
-    <view class="rq-section" v-if="isLoggedIn && (healthAlerts.length > 0 || milestones.length > 0)">
-      <view class="rq-header">
-        <text class="rq-title">关系健康</text>
-        <text class="rq-more" @click="goRelationshipQuestionnaire">去做问卷</text>
-      </view>
-      <!-- 健康预警列表 -->
-      <view class="rq-alerts" v-if="healthAlerts.length > 0">
-        <view class="rq-alert-card" v-for="alert in healthAlerts" :key="alert.memberId" @click="goInteractionLog(alert.memberId)">
-          <view class="rq-alert-icon">
-            <text class="rq-alert-emoji">⚠️</text>
-          </view>
-          <view class="rq-alert-info">
-            <text class="rq-alert-name">{{ alert.memberName }}</text>
-            <text class="rq-alert-desc">已{{ alert.daysSinceInteraction }}天无互动</text>
-          </view>
-          <view class="rq-alert-action">
-            <text class="rq-alert-btn">去互动</text>
-          </view>
-        </view>
-      </view>
-      <!-- 里程碑列表 -->
-      <view class="rq-milestones" v-if="milestones.length > 0">
-        <view class="rq-milestone-card" v-for="m in milestones" :key="m.memberId">
-          <view class="rq-milestone-icon">
-            <text class="rq-milestone-emoji">🎂</text>
-          </view>
-          <view class="rq-milestone-info">
-            <text class="rq-milestone-name">{{ m.memberName }}</text>
-            <text class="rq-milestone-desc">{{ m.daysUntil > 0 ? m.daysUntil + '天后生日' : '今天生日' }}</text>
-          </view>
-        </view>
-      </view>
-      <!-- 快捷操作 -->
-      <view class="rq-quick-actions">
-        <view class="rq-action-btn" @click="goInteractionLog()">
-          <text class="rq-action-icon">📝</text>
-          <text class="rq-action-label">记录互动</text>
-        </view>
-        <view class="rq-action-btn" @click="goRelationshipQuestionnaire">
-          <text class="rq-action-icon">🤝</text>
-          <text class="rq-action-label">做关系问卷</text>
-        </view>
-      </view>
-    </view>
+    <!-- ===== 重要关系维护 + 关系健康概览 ===== -->
+    <FamilyRelationshipSection
+      :contactList="contactList"
+      :healthAlerts="healthAlerts"
+      :milestones="milestones"
+      :isLoggedIn="isLoggedIn" />
 
     <!-- 功能入口(4个:活动、商品、课程) -->
     <view class="func-section" v-if="sectionVisible('func_entries')">
@@ -184,11 +118,12 @@ import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionArticles from '../../components/DimensionArticles.vue'
 import ContactCard from '../../components/ContactCard.vue'
 import ContactImport from '../../components/ContactImport.vue'
+import FamilyRelationshipSection from '../../components/FamilyRelationshipSection.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import { getVisibleSections, getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getContactList, getVisibleFamilyMembers, getFeaturedArticles, getHealthAlerts, getMilestones } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, ContactCard, ContactImport, FamilyRelationGraph },
+  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, ContactCard, ContactImport, FamilyRelationGraph, FamilyRelationshipSection },
   data() {
     return {
       showTabTransition: true,

+ 1 - 1
cfc-frontend/pages/guide/training-plans/detail.vue

@@ -77,7 +77,7 @@
         </view>
 
         <view class="item-list" v-if="items.length > 0">
-          <view class="plan-item" v-for="(item, idx) in items" :key="item.id || idx">
+          <view class="plan-item" v-for="(item, idx) in items" :key="item.id">
             <view class="item-header">
               <view class="item-seq">{{ idx + 1 }}</view>
               <view class="item-dim-tag">{{ dimensionLabel(item.dimensionKey) }}</view>

+ 8 - 6
cfc-frontend/pages/mind-detail/articles.vue

@@ -13,7 +13,7 @@
             v-for="dim in dimensionList"
             :key="dim.code"
             :class="['filter-chip', currentDimension === dim.code ? 'active' : '']"
-            :style="currentDimension === dim.code ? { background: dim.color, color: '#fff' } : {}"
+            :style="currentDimension === dim.code ? 'background:' + dim.color + ';color:#fff' : ''"
             @click="onDimensionChange(dim.code)"
           >
             {{ dim.label }}
@@ -112,7 +112,7 @@ export default {
         return { code: k, label: dimensionConfig[k].label, color: dimensionConfig[k].color }
       }),
       currentDimension: 'all',
-      categoryList: [{ id: '', name: '全部' }],
+      categoryList: [{ value: '', label: '全部' }],
       categoryMap: {},
       currentCategory: '',
       articles: [],
@@ -159,13 +159,15 @@ export default {
           var map = {}
           cats.forEach(function(c) { map[c.id] = c.name })
           this.categoryMap = map
-          this.categoryList = [{ id: '', name: '全部' }].concat(cats)
+          this.categoryList = [{ value: '', label: '全部' }].concat(cats.map(function(c) {
+            return { value: c.id, label: c.name }
+          }))
         }
       } catch (e) {
         this.categoryList = [
-          { id: '', name: '全部' },
-          { id: 1, name: '心理健康' },
-          { id: 2, name: '情绪管理' }
+          { value: '', label: '全部' },
+          { value: 1, label: '心理健康' },
+          { value: 2, label: '情绪管理' }
         ]
       }
       this.loadArticles()

+ 1 - 1
cfc-frontend/pages/mind-detail/family-dashboard.vue

@@ -75,7 +75,7 @@
         <view
           class="relation-card"
           v-for="r in relationships"
-          :key="r.memberId1 + '-' + r.memberId2"
+          :key="r.memberId1"
           :class="'relation-level-' + relationLevel(r.overallScore)"
           @click="goDuoDetail(r.memberId1, r.memberId2)">
           <view class="relation-pair">

+ 54 - 3
cfc-frontend/pages/mind/index.vue

@@ -42,6 +42,13 @@
       :compatibilityMap="compatibilityMapForGraph"
       @memberTap="goMemberDetail" />
 
+    <!-- ===== 重要关系维护 + 关系健康概览 ===== -->
+    <FamilyRelationshipSection
+      :contactList="contactList"
+      :healthAlerts="healthAlerts"
+      :milestones="milestones"
+      :isLoggedIn="isLoggedIn" />
+
     <!-- 登录后:用户快捷入口 -->
     <UserQuickEntry
       v-if="isLoggedIn"
@@ -273,13 +280,14 @@ import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionArticles from '../../components/DimensionArticles.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
+import FamilyRelationshipSection from '../../components/FamilyRelationshipSection.vue'
 import PsychCrisisBanner from '../../components/PsychCrisisBanner.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
-import { getVisibleSections, getFeaturedArticles, getEmiReport, getDailyTip, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers, getEnergyOverview } from '../../utils/api.js'
+import { getVisibleSections, getFeaturedArticles, getEmiReport, getDailyTip, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers, getEnergyOverview, getContactList, getHealthAlerts, getMilestones } from '../../utils/api.js'
 import config from '../../config.js'
 
 export default {
-  components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, PsychCrisisBanner, AIFloatingAvatar },
+  components: { TabTransition, PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, FamilyRelationshipSection, PsychCrisisBanner, AIFloatingAvatar },
   data() {
     return {
       isLoggedIn: false,
@@ -348,7 +356,10 @@ export default {
       sandboxData: null,
       dimensionTasks: [],
       dimensionActivities: [],
-      dimensionProducts: []
+      dimensionProducts: [],
+      contactList: [],
+      healthAlerts: [],
+      milestones: []
     }
   },
   computed: {
@@ -456,6 +467,9 @@ export default {
       this.loadGutData()
       this.loadEnergyData()
       this.loadTraditionalCultureData()
+      this.loadContacts()
+      this.loadHealthAlerts()
+      this.loadMilestones()
     }
     // 游客也能浏览活动和商品
     this.loadDimensionActivities()
@@ -787,6 +801,43 @@ export default {
     goLogin: function() { uni.navigateTo({ url: '/pages/login/login' }) },
 
     // ===== 传统文化数据 =====
+    // ===== 联系人 / 关系健康 =====
+    loadContacts: function() {
+      var self = this
+      getContactList({}).then(function(res) {
+        if (res.code === 200) {
+          self.contactList = Array.isArray(res.data) ? res.data : []
+        } else {
+          self.contactList = []
+        }
+      }).catch(function() {
+        self.contactList = []
+      })
+    },
+    loadHealthAlerts: function() {
+      var self = this
+      getHealthAlerts({}).then(function(res) {
+        if (res.code === 200) {
+          self.healthAlerts = Array.isArray(res.data) ? res.data : []
+        } else {
+          self.healthAlerts = []
+        }
+      }).catch(function() {
+        self.healthAlerts = []
+      })
+    },
+    loadMilestones: function() {
+      var self = this
+      getMilestones({}, 30).then(function(res) {
+        if (res.code === 200) {
+          self.milestones = Array.isArray(res.data) ? res.data : []
+        } else {
+          self.milestones = []
+        }
+      }).catch(function() {
+        self.milestones = []
+      })
+    },
     loadTraditionalCultureData: function() {
       var self = this
       var memberId = this.currentChildId

+ 1 - 1
cfc-frontend/pages/promotion/invite.vue

@@ -24,7 +24,7 @@
     <!-- 邀请里程碑 -->
     <view class="milestone-card" v-if="milestones.length > 0">
       <text class="milestone-title">邀请里程碑</text>
-      <view class="milestone-item" v-for="item in milestones" :key="item.id || item.milestone">
+      <view class="milestone-item" v-for="item in milestones" :key="item.id">
         <view class="milestone-header">
           <text class="milestone-name">{{ milestoneName(item.milestone) }}</text>
           <text class="milestone-reward">+{{ item.rewardPoints || 0 }}积分</text>

+ 1 - 1
cfc-frontend/pages/promotion/leaderboard.vue

@@ -58,7 +58,7 @@
 
     <!-- 排名列表 #4-#20 -->
     <view class="rank-list" v-if="topList.length > 3">
-      <view class="rank-item" v-for="item in restList" :key="item.userId || item.rank">
+      <view class="rank-item" v-for="item in restList" :key="item.rank">
         <view class="rank-num-wrap">
           <text class="rank-num">{{ item.rank }}</text>
         </view>

+ 1 - 1
cfc-frontend/pages/wisdom-detail/cognitive-report.vue

@@ -145,7 +145,7 @@
           <text class="tc-new">本次</text>
           <text class="tc-change">变化</text>
         </view>
-        <view class="tc-dim-row" v-for="dim in dimensions" :key="dim.key + 'trend'">
+        <view class="tc-dim-row" v-for="dim in dimensions" :key="dim.key">
           <text class="tc-dim-name">{{ dim.label }}</text>
           <text class="tc-old">{{ prevScore(dim.key) }}</text>
           <text class="tc-new" :class="scoreUp(dim.key) ? 'sc-up' : (scoreDown(dim.key) ? 'sc-down' : '')">{{ latestScore(dim.key) }}</text>