feat(reportmanage): T11.2 经营分析+数据导出 - AppService + Controller + Frontend

This commit is contained in:
2026-06-18 15:07:09 +08:00
parent 965418dc45
commit abafd4b2a9
5 changed files with 214 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 IBusinessAnalyticsAppService {
R<?> generateReport(Map<String, Object> params);
R<?> exportToExcel(Map<String, Object> params);
}

View File

@@ -0,0 +1,116 @@
package com.healthlink.his.web.reportmanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.healthlink.his.quality.domain.BusinessAnalytics;
import com.healthlink.his.quality.service.IBusinessAnalyticsService;
import com.healthlink.his.web.reportmanage.appservice.IBusinessAnalyticsAppService;
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 BusinessAnalyticsAppServiceImpl implements IBusinessAnalyticsAppService {
private final IBusinessAnalyticsService analyticsService;
@Override
public R<?> generateReport(Map<String, Object> params) {
String departmentName = (String) params.get("departmentName");
String startDate = (String) params.get("startDate");
String endDate = (String) params.get("endDate");
LambdaQueryWrapper<BusinessAnalytics> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(departmentName), BusinessAnalytics::getDepartmentName, departmentName);
if (StringUtils.hasText(startDate)) {
w.ge(BusinessAnalytics::getStatDate, startDate);
}
if (StringUtils.hasText(endDate)) {
w.le(BusinessAnalytics::getStatDate, endDate);
}
w.orderByDesc(BusinessAnalytics::getStatDate);
List<BusinessAnalytics> list = analyticsService.list(w);
Map<String, Object> report = new HashMap<>();
report.put("totalRecords", list.size());
BigDecimal totalRevenue = BigDecimal.ZERO;
BigDecimal totalCost = BigDecimal.ZERO;
int totalPatients = 0;
int totalBeds = 0;
for (BusinessAnalytics ba : list) {
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();
if (ba.getBedCount() != null) totalBeds += ba.getBedCount();
}
report.put("totalRevenue", totalRevenue);
report.put("totalCost", totalCost);
report.put("totalProfit", totalRevenue.subtract(totalCost));
report.put("totalPatients", totalPatients);
report.put("totalBeds", totalBeds);
int size = list.size();
report.put("avgRevenue", size > 0 ? totalRevenue.divide(BigDecimal.valueOf(size), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
report.put("avgCost", size > 0 ? totalCost.divide(BigDecimal.valueOf(size), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
report.put("profitRate", totalRevenue.compareTo(BigDecimal.ZERO) > 0
? totalRevenue.subtract(totalCost).multiply(BigDecimal.valueOf(100)).divide(totalRevenue, 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
Map<String, BigDecimal> deptRevenue = list.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)));
report.put("departmentRevenue", deptRevenue);
report.put("records", list.stream().limit(50).collect(Collectors.toList()));
return R.ok(report);
}
@Override
public R<?> exportToExcel(Map<String, Object> params) {
String departmentName = (String) params.get("departmentName");
String startDate = (String) params.get("startDate");
String endDate = (String) params.get("endDate");
LambdaQueryWrapper<BusinessAnalytics> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(departmentName), BusinessAnalytics::getDepartmentName, departmentName);
if (StringUtils.hasText(startDate)) {
w.ge(BusinessAnalytics::getStatDate, startDate);
}
if (StringUtils.hasText(endDate)) {
w.le(BusinessAnalytics::getStatDate, endDate);
}
w.orderByDesc(BusinessAnalytics::getStatDate);
List<BusinessAnalytics> list = analyticsService.list(w);
List<List<Object>> rows = new ArrayList<>();
rows.add(List.of("日期", "科室", "收入(万元)", "成本(万元)", "利润(万元)", "患者数", "床位数", "床位率(%)", "平均住院日", "平均费用(万元)"));
for (BusinessAnalytics ba : list) {
rows.add(List.of(
ba.getStatDate() != null ? ba.getStatDate() : "",
ba.getDepartmentName() != null ? ba.getDepartmentName() : "",
ba.getRevenue() != null ? ba.getRevenue().divide(BigDecimal.valueOf(10000), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO,
ba.getCost() != null ? ba.getCost().divide(BigDecimal.valueOf(10000), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO,
ba.getProfit() != null ? ba.getProfit().divide(BigDecimal.valueOf(10000), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO,
ba.getPatientCount() != null ? ba.getPatientCount() : 0,
ba.getBedCount() != null ? ba.getBedCount() : 0,
ba.getBedOccupancyRate() != null ? ba.getBedOccupancyRate() : BigDecimal.ZERO,
ba.getAvgStayDays() != null ? ba.getAvgStayDays() : BigDecimal.ZERO,
ba.getAvgCost() != null ? ba.getAvgCost().divide(BigDecimal.valueOf(10000), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO
));
}
Map<String, Object> result = new HashMap<>();
result.put("headers", rows.get(0));
result.put("data", rows.subList(1, rows.size()));
result.put("totalRows", rows.size() - 1);
return R.ok(result);
}
}

View File

@@ -0,0 +1,38 @@
package com.healthlink.his.web.reportmanage.controller;
import com.core.common.core.domain.R;
import com.healthlink.his.web.reportmanage.appservice.IBusinessAnalyticsAppService;
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("/report/analytics")
@Slf4j
@AllArgsConstructor
public class BusinessAnalyticsController {
private final IBusinessAnalyticsAppService analyticsAppService;
@PostMapping("/generate")
@PreAuthorize("hasAuthority('infection:report:edit')")
public R<?> generateReport(@RequestBody Map<String, Object> params) {
return analyticsAppService.generateReport(params);
}
@GetMapping("/export")
@PreAuthorize("hasAuthority('infection:report:list')")
public R<?> exportToExcel(
@RequestParam(required = false) String departmentName,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
Map<String, Object> params = new java.util.HashMap<>();
params.put("departmentName", departmentName);
params.put("startDate", startDate);
params.put("endDate", endDate);
return analyticsAppService.exportToExcel(params);
}
}

View File

@@ -2,3 +2,5 @@ import request from '@/utils/request'
export function getAnalyticsPage(p){return request({url:'/business-analytics/page',method:'get',params:p})}
export function addAnalytics(d){return request({url:'/business-analytics/add',method:'post',data:d})}
export function getAnalyticsSummary(){return request({url:'/business-analytics/summary',method:'get'})}
export function generateReport(d){return request({url:'/report/analytics/generate',method:'post',data:d})}
export function exportToExcel(p){return request({url:'/report/analytics/export',method:'get',params:p,responseType:'blob'})}

View File

@@ -9,6 +9,12 @@
>
刷新
</el-button>
<el-button
type="success"
@click="generateReport"
>
生成报告
</el-button>
<el-button
type="warning"
@click="exportReport"
@@ -188,7 +194,7 @@
<script setup>
import {ref, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getAnalyticsPage, getAnalyticsSummary} from './api'
import {getAnalyticsPage, getAnalyticsSummary, generateReport as apiGenerateReport, exportToExcel} from './api'
const loading = ref(false)
const analyticsData = ref([])
@@ -233,7 +239,48 @@ function resetQuery() {
loadData()
}
function exportReport() { ElMessage.info('导出功能开发中') }
function exportReport() {
const params = {}
if (q.value.departmentName) params.departmentName = q.value.departmentName
if (q.value.dateRange && q.value.dateRange.length === 2) {
params.startDate = q.value.dateRange[0]
params.endDate = q.value.dateRange[1]
}
exportToExcel(params).then(r => {
if (r.data && r.data.headers) {
const csvRows = [r.data.headers.join(',')]
r.data.data.forEach(row => csvRows.push(row.join(',')))
const blob = new Blob(['\ufeff' + csvRows.join('\n')], {type:'text/csv;charset=utf-8'})
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = '经营分析报告.csv'
link.click()
ElMessage.success('导出成功')
} else {
ElMessage.warning('无数据可导出')
}
}).catch(() => ElMessage.error('导出失败'))
}
async function generateReport() {
loading.value = true
try {
const params = {}
if (q.value.departmentName) params.departmentName = q.value.departmentName
if (q.value.dateRange && q.value.dateRange.length === 2) {
params.startDate = q.value.dateRange[0]
params.endDate = q.value.dateRange[1]
}
const r = await apiGenerateReport(params)
if (r.data) {
statCards.value[0].value = formatMoney(r.data.totalRevenue)
statCards.value[1].value = formatMoney(r.data.totalCost)
statCards.value[2].value = formatMoney(r.data.totalProfit)
statCards.value[3].value = r.data.totalPatients || 0
}
ElMessage.success('报告生成完成')
} finally { loading.value = false }
}
onMounted(() => loadData())
</script>