feat(reportmanage): T11.3 可视化仪表盘 - AppService + Controller + Frontend

This commit is contained in:
2026-06-18 15:11:30 +08:00
parent c004badf30
commit 632d0828b4
5 changed files with 220 additions and 2 deletions

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.web.reportmanage.appservice;
import com.core.common.core.domain.R;
import java.util.Map;
public interface IDashboardAppService {
R<?> getDashboardData(Map<String, Object> params);
R<?> getCharts(Map<String, Object> params);
}

View File

@@ -0,0 +1,129 @@
package com.healthlink.his.web.reportmanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.healthlink.his.basicmanage.domain.DashboardConfig;
import com.healthlink.his.basicmanage.service.IDashboardConfigService;
import com.healthlink.his.quality.domain.BusinessAnalytics;
import com.healthlink.his.quality.service.IBusinessAnalyticsService;
import com.healthlink.his.crossmodule.domain.DrgPerformance;
import com.healthlink.his.crossmodule.service.IDrgPerformanceService;
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
import com.healthlink.his.mrhomepage.service.IMrDrgGroupingService;
import com.healthlink.his.web.reportmanage.appservice.IDashboardAppService;
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 DashboardAppServiceImpl implements IDashboardAppService {
private final IDashboardConfigService dashboardConfigService;
private final IBusinessAnalyticsService analyticsService;
private final IDrgPerformanceService drgPerformanceService;
private final IMrDrgGroupingService drgGroupingService;
@Override
public R<?> getDashboardData(Map<String, Object> params) {
Map<String, Object> data = new HashMap<>();
List<BusinessAnalytics> analyticsList = analyticsService.list();
BigDecimal totalRevenue = BigDecimal.ZERO;
BigDecimal totalCost = BigDecimal.ZERO;
int totalPatients = 0;
for (BusinessAnalytics ba : analyticsList) {
if (ba.getRevenue() != null) totalRevenue = totalRevenue.add(ba.getRevenue());
if (ba.getCost() != null) totalCost = totalCost.add(ba.getCost());
if (ba.getPatientCount() != null) totalPatients += ba.getPatientCount();
}
data.put("totalRevenue", totalRevenue);
data.put("totalCost", totalCost);
data.put("totalProfit", totalRevenue.subtract(totalCost));
data.put("totalPatients", totalPatients);
data.put("totalRecords", analyticsList.size());
LambdaQueryWrapper<DashboardConfig> configW = new LambdaQueryWrapper<>();
configW.eq(DashboardConfig::getIsDefault, true);
List<DashboardConfig> defaultConfigs = dashboardConfigService.list(configW);
data.put("defaultDashboard", defaultConfigs.isEmpty() ? null : defaultConfigs.get(0));
LambdaQueryWrapper<DrgPerformance> perfW = new LambdaQueryWrapper<>();
perfW.orderByDesc(DrgPerformance::getStatMonth).last("LIMIT 1");
List<DrgPerformance> latestPerf = drgPerformanceService.list(perfW);
if (!latestPerf.isEmpty()) {
DrgPerformance p = latestPerf.get(0);
data.put("latestDrgCases", p.getTotalCases());
data.put("latestCmiValue", p.getCmiValue());
data.put("latestCostControlRate", p.getCostControlRate());
}
long totalDrgCases = drgGroupingService.count();
data.put("totalDrgCases", totalDrgCases);
return R.ok(data);
}
@Override
public R<?> getCharts(Map<String, Object> params) {
Map<String, Object> charts = new HashMap<>();
List<BusinessAnalytics> analyticsList = analyticsService.list();
Map<String, BigDecimal> monthlyRevenue = new LinkedHashMap<>();
Map<String, BigDecimal> monthlyCost = new LinkedHashMap<>();
for (BusinessAnalytics ba : analyticsList) {
String month = ba.getStatDate();
if (StringUtils.hasText(month) && month.length() >= 7) {
month = month.substring(0, 7);
} else {
month = "未知";
}
monthlyRevenue.merge(month, ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add);
monthlyCost.merge(month, ba.getCost() != null ? ba.getCost() : BigDecimal.ZERO, BigDecimal::add);
}
List<Map<String, Object>> revenueChart = new ArrayList<>();
monthlyRevenue.forEach((k, v) -> {
Map<String, Object> item = new HashMap<>();
item.put("month", k);
item.put("revenue", v);
item.put("cost", monthlyCost.getOrDefault(k, BigDecimal.ZERO));
revenueChart.add(item);
});
charts.put("revenueChart", revenueChart);
Map<String, BigDecimal> deptRevenue = analyticsList.stream()
.filter(ba -> StringUtils.hasText(ba.getDepartmentName()))
.collect(Collectors.groupingBy(
BusinessAnalytics::getDepartmentName,
Collectors.reducing(BigDecimal.ZERO, ba -> ba.getRevenue() != null ? ba.getRevenue() : BigDecimal.ZERO, BigDecimal::add)));
List<Map<String, Object>> deptChart = new ArrayList<>();
deptRevenue.forEach((k, v) -> {
Map<String, Object> item = new HashMap<>();
item.put("department", k);
item.put("revenue", v);
deptChart.add(item);
});
charts.put("departmentChart", deptChart);
LambdaQueryWrapper<DrgPerformance> perfW = new LambdaQueryWrapper<>();
perfW.orderByAsc(DrgPerformance::getStatMonth);
List<DrgPerformance> perfList = drgPerformanceService.list(perfW);
List<Map<String, Object>> cmiChart = new ArrayList<>();
for (DrgPerformance p : perfList) {
Map<String, Object> item = new HashMap<>();
item.put("month", p.getStatMonth());
item.put("cmiValue", p.getCmiValue());
item.put("costControlRate", p.getCostControlRate());
item.put("totalCases", p.getTotalCases());
cmiChart.add(item);
}
charts.put("cmiChart", cmiChart);
return R.ok(charts);
}
}

View File

@@ -0,0 +1,35 @@
package com.healthlink.his.web.reportmanage.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.web.reportmanage.appservice.IDashboardAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/dashboard")
@Slf4j
@AllArgsConstructor
public class DashboardDataController {
private final IDashboardAppService dashboardAppService;
@GetMapping("/data")
@PreAuthorize("hasAuthority('infection:report:list')")
public R<?> getDashboardData(@RequestParam(required = false) String dashboardType) {
Map<String, Object> params = new java.util.HashMap<>();
params.put("dashboardType", dashboardType);
return dashboardAppService.getDashboardData(params);
}
@GetMapping("/charts")
@PreAuthorize("hasAuthority('infection:report:list')")
public R<?> getCharts(@RequestParam(required = false) String dashboardType) {
Map<String, Object> params = new java.util.HashMap<>();
params.put("dashboardType", dashboardType);
return dashboardAppService.getCharts(params);
}
}

View File

@@ -1,3 +1,5 @@
import request from '@/utils/request'
export function getDashboardOverview(){return request({url:'/dashboard/overview',method:'get'})}
export function getDashboardList(p){return request({url:'/dashboard/list',method:'get',params:p})}
export function getDashboardData(p){return request({url:'/dashboard/data',method:'get',params:p})}
export function getDashboardCharts(p){return request({url:'/dashboard/charts',method:'get',params:p})}

View File

@@ -62,6 +62,42 @@
</el-col>
</el-row>
<!-- 数据仪表盘 -->
<el-row :gutter="16" style="margin-bottom:16px" v-if="dashData.totalRecords > 0">
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#409eff">{{ formatMoney(dashData.totalRevenue) }}</div>
<div style="font-size:12px;color:#999">总收入()</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#67c23a">{{ formatMoney(dashData.totalProfit) }}</div>
<div style="font-size:12px;color:#999">总利润()</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#e6a23c">{{ dashData.totalPatients || 0 }}</div>
<div style="font-size:12px;color:#999">总患者数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold;color:#f56c6c">{{ dashData.totalDrgCases || 0 }}</div>
<div style="font-size:12px;color:#999">DRG病例数</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 功能模块 -->
<el-card
shadow="never"
@@ -185,9 +221,10 @@
<script setup>
import {ref, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getDashboardOverview} from './api'
import {getDashboardOverview, getDashboardData, getDashboardCharts} from './api'
const overview = ref({})
const dashData = ref({})
const showSystemInfo = ref(false)
const statCards = ref([
@@ -231,10 +268,16 @@ const recentLogs = ref([
{content:'危急值处理完成', time:'20分钟前', type:'danger'}
])
function formatMoney(val) {
if (!val) return '0.00'
return (val / 10000).toFixed(2)
}
async function loadData() {
try {
const r = await getDashboardOverview()
const [r, d] = await Promise.all([getDashboardOverview(), getDashboardData()])
overview.value = r.data || {}
dashData.value = d.data || {}
statCards.value[0].value = overview.value.totalTables || 0
statCards.value[1].value = overview.value.totalApis || 0
statCards.value[2].value = modules.value.length