From abafd4b2a9e816b9eb14596c60e43c2d3d0e4476 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 18 Jun 2026 15:07:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(reportmanage):=20T11.2=20=E7=BB=8F?= =?UTF-8?q?=E8=90=A5=E5=88=86=E6=9E=90+=E6=95=B0=E6=8D=AE=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=20-=20AppService=20+=20Controller=20+=20Frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IBusinessAnalyticsAppService.java | 9 ++ .../impl/BusinessAnalyticsAppServiceImpl.java | 116 ++++++++++++++++++ .../BusinessAnalyticsController.java | 38 ++++++ .../src/views/businessanalytics/api.js | 2 + .../src/views/businessanalytics/index.vue | 51 +++++++- 5 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IBusinessAnalyticsAppService.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/BusinessAnalyticsAppServiceImpl.java create mode 100644 healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/BusinessAnalyticsController.java diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IBusinessAnalyticsAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IBusinessAnalyticsAppService.java new file mode 100644 index 000000000..a40c58d4b --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/IBusinessAnalyticsAppService.java @@ -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 params); + R exportToExcel(Map params); +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/BusinessAnalyticsAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/BusinessAnalyticsAppServiceImpl.java new file mode 100644 index 000000000..7939b3bab --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/appservice/impl/BusinessAnalyticsAppServiceImpl.java @@ -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 params) { + String departmentName = (String) params.get("departmentName"); + String startDate = (String) params.get("startDate"); + String endDate = (String) params.get("endDate"); + + LambdaQueryWrapper 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 list = analyticsService.list(w); + + Map 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 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 params) { + String departmentName = (String) params.get("departmentName"); + String startDate = (String) params.get("startDate"); + String endDate = (String) params.get("endDate"); + + LambdaQueryWrapper 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 list = analyticsService.list(w); + + List> 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 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); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/BusinessAnalyticsController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/BusinessAnalyticsController.java new file mode 100644 index 000000000..fd9e4a209 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/reportmanage/controller/BusinessAnalyticsController.java @@ -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 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 params = new java.util.HashMap<>(); + params.put("departmentName", departmentName); + params.put("startDate", startDate); + params.put("endDate", endDate); + return analyticsAppService.exportToExcel(params); + } +} diff --git a/healthlink-his-ui/src/views/businessanalytics/api.js b/healthlink-his-ui/src/views/businessanalytics/api.js index 913bc40ab..7558ed052 100644 --- a/healthlink-his-ui/src/views/businessanalytics/api.js +++ b/healthlink-his-ui/src/views/businessanalytics/api.js @@ -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'})} diff --git a/healthlink-his-ui/src/views/businessanalytics/index.vue b/healthlink-his-ui/src/views/businessanalytics/index.vue index a1d116ca8..e9e9e88d2 100644 --- a/healthlink-his-ui/src/views/businessanalytics/index.vue +++ b/healthlink-his-ui/src/views/businessanalytics/index.vue @@ -9,6 +9,12 @@ > 刷新 + + 生成报告 + 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())