فهرست منبع

feat(frontend+backend): 健康报告列表支持删除报告

- 后端 HealthReportController + HealthReportService: 新增 POST /api/health/report/delete 端点
- deleteReport 逻辑删除(status=deleted)+ 清理关联数据(indicators/gut_flora/disease_risks/permissions/edit_logs/blocks/food_suitability)
- 前端 utils/api.js: 新增 deleteReport 导出函数
- 前端 report-list.vue: 报告卡片增加删除按钮,点击确认弹窗后调用 API 并刷新列表

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
liaoxg 2 ماه پیش
والد
کامیت
ad846fa1b3

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

@@ -373,6 +373,21 @@ public class HealthReportController {
         return Result.success("已保存");
     }
 
+    /**
+     * 删除健康报告(逻辑删除 + 清理关联数据)
+     */
+    @Operation(summary = "删除健康报告")
+    @PostMapping("/report/delete")
+    public Result<String> deleteReport(
+            @RequestBody Map<String, Object> params) {
+        Long reportId = ParamUtils.getLong(params.get("reportId"));
+        if (reportId == null) {
+            return Result.error("reportId不能为空");
+        }
+        healthReportService.deleteReport(reportId);
+        return Result.success("已删除");
+    }
+
     /**
      * 上传并解析肠道菌群报告PDF(向后兼容 — 内部自动执行两阶段:解析→确认)
      */

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

@@ -15,6 +15,7 @@ import com.etotem.cfc.mapper.HealthReportMapper;
 import com.etotem.cfc.mapper.HealthReportPermissionMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.FoodMapper;
+import com.etotem.cfc.mapper.ReportBlockMapper;
 import com.etotem.cfc.mapper.ReportFoodSuitabilityMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -85,6 +86,9 @@ public class HealthReportService {
     @Resource
     private ReportFoodSuitabilityMapper reportFoodSuitabilityMapper;
 
+    @Resource
+    private ReportBlockMapper reportBlockMapper;
+
     @Resource
     private FoodMapper foodMapper;
 
@@ -1464,4 +1468,40 @@ public class HealthReportService {
             log.warn("保存AI解读失败: {}", e.getMessage());
         }
     }
+
+    /**
+     * 删除健康报告(逻辑删除: 设置 status=deleted,同时清理关联数据)
+     */
+    @Transactional
+    public void deleteReport(Long reportId) {
+        // 清理关联指标
+        healthIndicatorMapper.delete(new LambdaQueryWrapper<HealthIndicator>()
+                .eq(HealthIndicator::getReportId, reportId));
+        // 清理菌群数据
+        healthGutFloraMapper.delete(new LambdaQueryWrapper<HealthGutFlora>()
+                .eq(HealthGutFlora::getReportId, reportId));
+        // 清理疾病风险评估
+        healthDiseaseRiskMapper.delete(new LambdaQueryWrapper<HealthDiseaseRisk>()
+                .eq(HealthDiseaseRisk::getReportId, reportId));
+        // 清理查看权限
+        healthReportPermissionMapper.delete(new LambdaQueryWrapper<HealthReportPermission>()
+                .eq(HealthReportPermission::getReportId, reportId));
+        // 清理编辑日志
+        healthReportEditLogMapper.delete(new LambdaQueryWrapper<HealthReportEditLog>()
+                .eq(HealthReportEditLog::getReportId, reportId));
+        // 清理展示块
+        reportBlockMapper.delete(new LambdaQueryWrapper<ReportBlock>()
+                .eq(ReportBlock::getReportId, reportId));
+        // 清理食材适合性
+        reportFoodSuitabilityMapper.delete(new LambdaQueryWrapper<ReportFoodSuitability>()
+                .eq(ReportFoodSuitability::getReportId, reportId));
+        // 逻辑删除报告本身
+        HealthReport report = healthReportMapper.selectById(reportId);
+        if (report != null) {
+            report.setStatus("deleted");
+            report.setUpdatedAt(new Date());
+            healthReportMapper.updateById(report);
+            log.info("健康报告已删除: id={}", reportId);
+        }
+    }
 }

+ 40 - 1
cfc-frontend/pages/health/report-list.vue

@@ -21,6 +21,7 @@
         </view>
         <view class="report-card-right">
           <text class="report-score">{{ item.overallScore || '--' }}<text class="score-unit">分</text></text>
+          <view class="report-del" @click.stop="confirmDelete(item)"><text class="report-del-text">删除</text></view>
           <text class="report-arrow">›</text>
         </view>
       </view>
@@ -40,7 +41,7 @@
 </template>
 
 <script>
-import { getReportList } from '../../utils/api.js'
+import { getReportList, deleteReport } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 
 export default {
@@ -87,6 +88,32 @@ export default {
     goToUpload() {
       uni.navigateTo({ url: '/pages/health/report-upload' })
     },
+    confirmDelete(item) {
+      var self = this
+      uni.showModal({
+        title: '删除报告',
+        content: '确定删除该健康报告吗?删除后不可恢复。',
+        confirmColor: '#F56C6C',
+        success: function(res) {
+          if (!res.confirm) return
+          self.doDelete(item)
+        }
+      })
+    },
+    doDelete(item) {
+      var self = this
+      deleteReport(item.id).then(function(res) {
+        if (res.code === 200) {
+          uni.showToast({ title: '已删除', icon: 'success' })
+          self.loadData()
+        } else {
+          uni.showToast({ title: res.message || '删除失败', icon: 'none' })
+        }
+      }).catch(function(e) {
+        console.log('删除报告失败', e)
+        uni.showToast({ title: '删除失败', icon: 'none' })
+      })
+    },
     formatDate(dateStr) {
       if (!dateStr) return '--'
       var d = parseDate(dateStr)
@@ -169,6 +196,18 @@ export default {
   align-items: center;
   gap: 12rpx;
 }
+.report-del {
+  padding: 8rpx 20rpx;
+  border: 1rpx solid #F56C6C;
+  border-radius: 24rpx;
+}
+.report-del:active {
+  background: #FEF0F0;
+}
+.report-del-text {
+  font-size: 22rpx;
+  color: #F56C6C;
+}
 .report-score {
   font-size: 36rpx;
   font-weight: bold;

+ 2 - 0
cfc-frontend/utils/api.js

@@ -2136,6 +2136,8 @@ export const getDanReportDetail = (id) => request('/api/dan-report/' + id, 'POST
 
 export const editHealthReport = (reportId, payload, subjectId) => request('/api/health/report/edit', 'POST', { reportId, payload, subjectId })
 
+export const deleteReport = (reportId) => request('/api/health/report/delete', 'POST', { reportId })
+
 export const queryKnowledgeBase = (itemType, itemName) => request('/api/health/knowledge/query', 'POST', { itemType, itemName })
 
 export const batchQueryKnowledgeBase = (queries) => request('/api/health/knowledge/batch-query', 'POST', { queries })