feat(reportmanage): 报表维度扩展 — 多维度报表查询
- 新增 IReportDimensionAppService + ReportDimensionAppServiceImpl - 新增 ReportDimensionController (GET /query) - 支持按状态/DRG/诊断维度统计 - 前端 ReportDimension.vue 维度切换+明细表格
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
package com.healthlink.his.web.reportmanage.appservice;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IReportDimensionAppService {
|
||||
|
||||
Map<String, Object> getReportByDimension(String dimension, Map<String, String> filters);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.healthlink.his.web.reportmanage.appservice.impl;
|
||||
|
||||
import com.healthlink.his.mrhomepage.domain.MrHomepage;
|
||||
import com.healthlink.his.mrhomepage.service.IMrHomepageService;
|
||||
import com.healthlink.his.web.reportmanage.appservice.IReportDimensionAppService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ReportDimensionAppServiceImpl implements IReportDimensionAppService {
|
||||
|
||||
private final IMrHomepageService mrHomepageService;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getReportByDimension(String dimension, Map<String, String> filters) {
|
||||
List<MrHomepage> allData = mrHomepageService.list();
|
||||
|
||||
String startDate = filters != null ? filters.get("startDate") : null;
|
||||
String endDate = filters != null ? filters.get("endDate") : null;
|
||||
|
||||
if (StringUtils.hasText(startDate)) {
|
||||
allData = allData.stream()
|
||||
.filter(h -> h.getDischargeDate() != null &&
|
||||
h.getDischargeDate().toString().compareTo(startDate) >= 0)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
if (StringUtils.hasText(endDate)) {
|
||||
allData = allData.stream()
|
||||
.filter(h -> h.getDischargeDate() != null &&
|
||||
h.getDischargeDate().toString().compareTo(endDate) <= 0)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalCount", allData.size());
|
||||
|
||||
BigDecimal totalCost = allData.stream()
|
||||
.map(MrHomepage::getTotalCost)
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
result.put("totalCost", totalCost);
|
||||
result.put("avgCost", allData.isEmpty() ? BigDecimal.ZERO :
|
||||
totalCost.divide(BigDecimal.valueOf(allData.size()), 2, RoundingMode.HALF_UP));
|
||||
|
||||
Map<String, List<MrHomepage>> grouped;
|
||||
switch (dimension != null ? dimension : "status") {
|
||||
case "drg":
|
||||
grouped = allData.stream()
|
||||
.filter(h -> h.getDrgGroup() != null)
|
||||
.collect(Collectors.groupingBy(MrHomepage::getDrgGroup));
|
||||
break;
|
||||
case "diagnosis":
|
||||
grouped = allData.stream()
|
||||
.filter(h -> h.getPrimaryDiagnosisName() != null)
|
||||
.collect(Collectors.groupingBy(MrHomepage::getPrimaryDiagnosisName));
|
||||
break;
|
||||
case "status":
|
||||
default:
|
||||
grouped = allData.stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
h -> h.getQualityStatus() != null ? h.getQualityStatus() : "UNKNOWN"));
|
||||
break;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> dimensionData = new ArrayList<>();
|
||||
grouped.forEach((key, items) -> {
|
||||
Map<String, Object> entry = new HashMap<>();
|
||||
entry.put("dimension", key);
|
||||
entry.put("count", items.size());
|
||||
BigDecimal cost = items.stream()
|
||||
.map(MrHomepage::getTotalCost)
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
entry.put("totalCost", cost);
|
||||
entry.put("avgCost", items.isEmpty() ? BigDecimal.ZERO :
|
||||
cost.divide(BigDecimal.valueOf(items.size()), 2, RoundingMode.HALF_UP));
|
||||
long totalLos = items.stream()
|
||||
.mapToInt(h -> h.getLosDays() != null ? h.getLosDays() : 0)
|
||||
.sum();
|
||||
entry.put("avgLosDays", items.isEmpty() ? 0 :
|
||||
Math.round(totalLos * 10.0 / items.size()) / 10.0);
|
||||
dimensionData.add(entry);
|
||||
});
|
||||
|
||||
result.put("dimension", dimension);
|
||||
result.put("data", dimensionData);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.healthlink.his.web.reportmanage.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.web.reportmanage.appservice.IReportDimensionAppService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "多维度报表")
|
||||
@RestController
|
||||
@RequestMapping("/report-manage/report-dimension")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class ReportDimensionController {
|
||||
|
||||
private final IReportDimensionAppService reportDimensionAppService;
|
||||
|
||||
@Operation(summary = "多维度报表查询")
|
||||
@PreAuthorize("@ss.hasPermi('reportmanage:report:list')")
|
||||
@GetMapping("/query")
|
||||
public R<Map<String, Object>> getReportByDimension(
|
||||
@RequestParam(value = "dimension", defaultValue = "status") String dimension,
|
||||
@RequestParam(value = "startDate", required = false) String startDate,
|
||||
@RequestParam(value = "endDate", required = false) String endDate) {
|
||||
Map<String, String> filters = new HashMap<>();
|
||||
if (startDate != null) filters.put("startDate", startDate);
|
||||
if (endDate != null) filters.put("endDate", endDate);
|
||||
return R.ok(reportDimensionAppService.getReportByDimension(dimension, filters));
|
||||
}
|
||||
}
|
||||
5
healthlink-his-ui/src/api/reportmanage/dimension.js
Normal file
5
healthlink-his-ui/src/api/reportmanage/dimension.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getReportByDimension(params) {
|
||||
return request({ url: '/report-manage/report-dimension/query', method: 'get', params })
|
||||
}
|
||||
125
healthlink-his-ui/src/views/reportmanage/ReportDimension.vue
Normal file
125
healthlink-his-ui/src/views/reportmanage/ReportDimension.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="report-dimension-container">
|
||||
<div class="page-header">
|
||||
<span class="tab-title">多维度报表</span>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>查询条件</span>
|
||||
</template>
|
||||
<el-form :model="queryParams" inline>
|
||||
<el-form-item label="统计维度">
|
||||
<el-select v-model="queryParams.dimension" style="width: 140px">
|
||||
<el-option label="按质控状态" value="status" />
|
||||
<el-option label="按DRG分组" value="drg" />
|
||||
<el-option label="按主要诊断" value="diagnosis" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="queryParams.startDate" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="queryParams.endDate" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadData" :loading="loading">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom: 16px">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #409eff">{{ reportData.totalCount || 0 }}</div>
|
||||
<div class="stat-label">总病案数</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #e6a23c">{{ formatCost(reportData.totalCost) }}</div>
|
||||
<div class="stat-label">总费用</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #67c23a">{{ formatCost(reportData.avgCost) }}</div>
|
||||
<div class="stat-label">平均费用</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>{{ dimensionLabel }}明细</span>
|
||||
</template>
|
||||
<el-table :data="reportData.data || []" v-loading="loading" border stripe style="width: 100%">
|
||||
<el-table-column prop="dimension" :label="dimensionLabel" min-width="160" />
|
||||
<el-table-column prop="count" label="病案数" width="100" />
|
||||
<el-table-column prop="totalCost" label="总费用" width="140">
|
||||
<template #default="{ row }">{{ formatCost(row.totalCost) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="avgCost" label="平均费用" width="140">
|
||||
<template #default="{ row }">{{ formatCost(row.avgCost) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="avgLosDays" label="平均住院日" width="120">
|
||||
<template #default="{ row }">{{ row.avgLosDays }}天</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getReportByDimension } from '@/api/reportmanage/dimension'
|
||||
|
||||
const loading = ref(false)
|
||||
const queryParams = reactive({
|
||||
dimension: 'status',
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
})
|
||||
const reportData = ref({})
|
||||
|
||||
const DIMENSION_LABEL = { status: '质控状态', drg: 'DRG分组', diagnosis: '主要诊断' }
|
||||
const dimensionLabel = computed(() => DIMENSION_LABEL[queryParams.dimension] || '维度')
|
||||
|
||||
const formatCost = (val) => {
|
||||
if (!val || val === '0') return '¥0'
|
||||
return '¥' + Number(val).toLocaleString('zh-CN', { minimumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = { dimension: queryParams.dimension }
|
||||
if (queryParams.startDate) params.startDate = queryParams.startDate
|
||||
if (queryParams.endDate) params.endDate = queryParams.endDate
|
||||
const res = await getReportByDimension(params)
|
||||
reportData.value = res.data || {}
|
||||
} catch (e) {
|
||||
ElMessage.error('加载失败: ' + (e.message || '未知错误'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.report-dimension-container { padding: 16px; }
|
||||
.page-header { margin-bottom: 16px; }
|
||||
.tab-title { font-size: 18px; font-weight: bold; }
|
||||
.stat-card { text-align: center; padding: 12px 0; }
|
||||
.stat-value { font-size: 28px; font-weight: bold; }
|
||||
.stat-label { font-size: 13px; color: #909399; margin-top: 4px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user