Jelajahi Sumber

feat(health): 报告列表支持家庭维度查询+成员过滤,健康页不再传memberId

liaoxg 3 minggu lalu
induk
melakukan
32f8cd3a23

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

@@ -270,24 +270,24 @@ public class HealthReportController {
 
     /**
      * 获取孩子的所有健康报告列表
+     * 未传 memberId 时返回当前家庭的全部报告;传 memberId 时按成员过滤(兼容 users.id / family_members.id)
      */
     @Operation(summary = "获取健康报告列表")
     @PostMapping("/report/list")
     public Result<List<HealthReport>> listReports(
             @RequestBody Map<String, Object> params,
-            @RequestAttribute(value = "currentMemberId", required = false) Long currentMemberId,
             @RequestAttribute(value = "familyId", required = false) Long familyId) {
-        Long memberId = ParamUtils.getLong(params.get("memberId"), currentMemberId);
+        Long memberId = ParamUtils.getLong(params.get("memberId"));
         if (memberId != null) {
-            List<HealthReport> reports = healthReportService.getUserReports(memberId);
+            List<HealthReport> reports = healthReportService.getMemberReports(memberId);
             return Result.success(reports);
         }
-        // 未指定 memberId 时,回退到家庭报告列表
+        // 未指定 memberId 时,返回当前家庭的全部报告
         if (familyId != null) {
             List<HealthReport> reports = healthReportService.getFamilyReports(familyId);
             return Result.success(reports);
         }
-        return Result.error("memberId不能为空");
+        return Result.error("无法获取家庭信息,请先创建或加入家庭");
     }
 
     @Operation(summary = "获取全家健康报告列表(方案页选择用)")

+ 38 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/HealthReportService.java

@@ -206,6 +206,44 @@ public class HealthReportService {
         );
     }
 
+    /**
+     * 获取某家庭成员的所有健康报告
+     * 兼容 subject_id 的两种历史语义(users.id / family_members.id):
+     * 先通过 family_members 表归一该成员的两种 id,再用 IN 匹配 subject_id/user_id
+     */
+    public List<HealthReport> getMemberReports(Long memberId) {
+        if (memberId == null) {
+            return Collections.emptyList();
+        }
+        Set<Long> ids = new HashSet<>();
+        ids.add(memberId);
+        try {
+            FamilyMember fm = familyMemberMapper.selectById(memberId);
+            if (fm != null) {
+                ids.add(fm.getId());
+                if (fm.getUserId() != null) {
+                    ids.add(fm.getUserId());
+                }
+            }
+            // memberId 可能是 users.id,反查其关联的 family_members 行
+            List<FamilyMember> linked = familyMemberMapper.selectList(
+                    new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getUserId, memberId));
+            for (FamilyMember item : linked) {
+                ids.add(item.getId());
+            }
+        } catch (Exception e) {
+            log.warn("归一家庭成员ID失败 memberId={}: {}", memberId, e.getMessage());
+        }
+        return healthReportMapper.selectList(
+                new LambdaQueryWrapper<HealthReport>()
+                        .and(w -> w.in(HealthReport::getSubjectId, ids)
+                                .or(w2 -> w2.isNull(HealthReport::getSubjectId)
+                                        .in(HealthReport::getUserId, ids)))
+                        .eq(HealthReport::getStatus, "active")
+                        .orderByDesc(HealthReport::getReportDate)
+        );
+    }
+
     /**
      * 获取某份报告的所有指标
      */

+ 7 - 9
cfc-frontend/pages/health-main/index.vue

@@ -558,15 +558,13 @@ export default {
       try {
         var api = require('../../utils/api.js')
         var memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || uni.getStorageSync('userId') || ''
-        // 健康报告(memberId 为空时不调用,避免后端 400)
-        if (memberId) {
-          api.getReportList(memberId).then(function(res) {
-            if (res.code === 200 && res.data) {
-              var list = Array.isArray(res.data) ? res.data : (res.data.list || [])
-              self.reports = list
-            }
-          }).catch(function() {})
-        }
+        // 健康报告:不传 memberId,由后端返回当前家庭的全部报告
+        api.getReportList().then(function(res) {
+          if (res.code === 200 && res.data) {
+            var list = Array.isArray(res.data) ? res.data : (res.data.list || [])
+            self.reports = list
+          }
+        }).catch(function() {})
         // 身体七维概览
       if (memberId) {
         api.getDimensionOverview({ memberId: memberId }).then(function(res) {

+ 93 - 4
cfc-frontend/pages/health/report-list.vue

@@ -1,5 +1,25 @@
 <template>
   <view class="list-container">
+    <!-- 成员过滤 -->
+    <view class="member-filter">
+      <scroll-view class="filter-scroll" scroll-x="true" :show-scrollbar="false">
+        <view class="filter-chips">
+          <view class="chip" :class="{ 'chip-active': !selectedMemberId }" @click="selectAllMembers">
+            <text class="chip-text">全部</text>
+          </view>
+          <view
+            class="chip"
+            v-for="item in members"
+            :key="item.id"
+            :class="{ 'chip-active': selectedMemberId === item._filterId }"
+            @click="selectMember(item)"
+          >
+            <text class="chip-text">{{ item.nickname || '成员' }}</text>
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+
     <!-- 加载状态 -->
     <view class="loading-wrap" v-if="loading">
       <text class="loading-text">加载中...</text>
@@ -9,7 +29,7 @@
     <view class="report-list" v-else-if="reports.length > 0">
       <view
         class="report-card"
-        v-for="item in reports.slice(0, 3)"
+        v-for="item in reports"
         :key="item.id"
         @click="goToDetail(item.id, item.reportType)"
       >
@@ -43,27 +63,63 @@
 </template>
 
 <script>
-import { getReportList, deleteReport } from '../../utils/api.js'
+import { getReportList, deleteReport, getFamilyMemberList } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 
 export default {
   data() {
     return {
       reports: [],
+      members: [],
+      selectedMemberId: null,
       loading: false,
       memberId: null
     }
   },
   onLoad(options) {
-    // 优先用导航参数传入的 memberId;未传时后端回退到家庭报告列表
+    // 导航参数可带初始 memberId(预选对应成员);未传时默认"全部"
     this.memberId = options.memberId ? parseInt(options.memberId) : null
+    this.selectedMemberId = this.memberId
+    this.loadMembers()
     this.loadData()
   },
   methods: {
+    loadMembers() {
+      var self = this
+      getFamilyMemberList({}).then(function(res) {
+        if (res.code === 200 && Array.isArray(res.data)) {
+          // _filterId 预计算,供 :class 内联比较使用(小程序 :class 禁止方法调用)
+          var list = []
+          for (var i = 0; i < res.data.length; i++) {
+            var m = res.data[i]
+            list.push({
+              id: m.id,
+              nickname: m.nickname,
+              avatar: m.avatar,
+              _filterId: m.id || m.userId
+            })
+          }
+          self.members = list
+        }
+      }).catch(function(e) {
+        console.log('获取家庭成员失败', e)
+      })
+    },
+    selectAllMembers() {
+      if (this.selectedMemberId === null) return
+      this.selectedMemberId = null
+      this.loadData()
+    },
+    selectMember(item) {
+      var filterId = item._filterId
+      if (this.selectedMemberId === filterId) return
+      this.selectedMemberId = filterId
+      this.loadData()
+    },
     loadData() {
       var self = this
       self.loading = true
-      getReportList(self.memberId).then(function(res) {
+      getReportList(self.selectedMemberId).then(function(res) {
         self.reports = res.data || []
       }).catch(function(e) {
         console.log('获取报告列表失败', e)
@@ -135,6 +191,39 @@ export default {
   background: #F5F7FA;
 }
 
+/* ===== 成员过滤 ===== */
+.member-filter {
+  background: #F5F7FA;
+  padding: 20rpx 30rpx 4rpx;
+}
+.filter-scroll {
+  width: 100%;
+  white-space: nowrap;
+}
+.filter-chips {
+  display: flex;
+  flex-direction: row;
+}
+.chip {
+  flex-shrink: 0;
+  background: #fff;
+  border: 1rpx solid #E2E8F0;
+  border-radius: 28rpx;
+  padding: 10rpx 30rpx;
+  margin-right: 16rpx;
+}
+.chip-active {
+  background: #5B9BD5;
+  border-color: #5B9BD5;
+}
+.chip-text {
+  font-size: 26rpx;
+  color: #64748B;
+}
+.chip-active .chip-text {
+  color: #fff;
+}
+
 /* ===== 加载状态 ===== */
 .loading-wrap {
   display: flex;

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

@@ -1944,7 +1944,12 @@ export const getGutFloraIndicators = (data) => request('/api/health/gut-flora',
 
 
 export const createHealthReport = (data) => request('/api/health/report/create', 'POST', data)
-export const getReportList = (memberId) => request('/api/health/report/list', 'POST', { memberId })
+// 健康报告列表:memberId 可选,不传时后端返回当前家庭的全部报告
+export const getReportList = (memberId) => {
+  var data = {}
+  if (memberId) { data.memberId = memberId }
+  return request('/api/health/report/list', 'POST', data)
+}
 export const getFamilyReports = () => request('/api/health/reports/family', 'POST', {})
 export const getReportDetail = (reportId) => request('/api/health/report/detail', 'POST', { reportId })
 // DAN 报告详情(含 blocks)