package cn.iocoder.yudao.module.mes.controller.admin.dashboard; import cn.idev.excel.ExcelWriter; import cn.idev.excel.FastExcelFactory; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardDailyCountRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardGenericExcelVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardIndicatorAvgRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardInventoryRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardNameCountRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardOutboundRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardOverviewExcelVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionExcelVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionTrendRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardQualityRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardSummaryRespVO; import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardTraceRespVO; import cn.iocoder.yudao.module.mes.service.dashboard.MesDashboardService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; /** * 管理后台 - 管理驾驶舱与统计分析 *

* 整合生产、质检、库存、出库、追溯五大维度核心指标,提供可视化图表数据与 Excel 报表导出。 * * @author 超级管理员 */ @Tag(name = "管理后台 - MES 管理驾驶舱") @RestController @RequestMapping("/mes/dashboard") @Validated public class MesDashboardController { @Resource private MesDashboardService dashboardService; @GetMapping("/summary") @Operation(summary = "获得管理驾驶舱五维度汇总统计") @PreAuthorize("@ss.hasPermission('mes:dashboard:query')") public CommonResult getSummary() { return success(dashboardService.getSummary()); } @GetMapping("/production-trend") @Operation(summary = "获得生产趋势") @Parameter(name = "days", description = "天数", example = "30") @PreAuthorize("@ss.hasPermission('mes:dashboard:query')") public CommonResult> getProductionTrend( @RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) { return success(dashboardService.getProductionTrend(days)); } @GetMapping("/quality-statistics") @Operation(summary = "获得质检维度统计") @PreAuthorize("@ss.hasPermission('mes:dashboard:query')") public CommonResult getQualityStatistics() { return success(dashboardService.getQualityStatistics()); } @GetMapping("/inventory-statistics") @Operation(summary = "获得库存维度统计") @PreAuthorize("@ss.hasPermission('mes:dashboard:query')") public CommonResult getInventoryStatistics() { return success(dashboardService.getInventoryStatistics()); } @GetMapping("/outbound-statistics") @Operation(summary = "获得出库维度统计") @Parameter(name = "days", description = "天数", example = "30") @PreAuthorize("@ss.hasPermission('mes:dashboard:query')") public CommonResult getOutboundStatistics( @RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) { return success(dashboardService.getOutboundStatistics(days)); } @GetMapping("/trace-statistics") @Operation(summary = "获得追溯维度统计") @Parameter(name = "days", description = "天数", example = "30") @PreAuthorize("@ss.hasPermission('mes:dashboard:query')") public CommonResult getTraceStatistics( @RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) { return success(dashboardService.getTraceStatistics(days)); } @GetMapping("/export-excel") @Operation(summary = "导出管理驾驶舱 Excel 报表") @ApiAccessLog(operateType = EXPORT) @PreAuthorize("@ss.hasPermission('mes:dashboard:export')") public void exportExcel(HttpServletResponse response) throws IOException { // 1. 组装各维度报表数据 MesDashboardSummaryRespVO summary = dashboardService.getSummary(); List productionTrend = dashboardService.getProductionTrend(30); MesDashboardQualityRespVO quality = dashboardService.getQualityStatistics(); MesDashboardInventoryRespVO inventory = dashboardService.getInventoryStatistics(); MesDashboardOutboundRespVO outbound = dashboardService.getOutboundStatistics(30); MesDashboardTraceRespVO trace = dashboardService.getTraceStatistics(30); // 2. 多 sheet 写入 ExcelWriter excelWriter = FastExcelFactory.write(response.getOutputStream()) .autoCloseStream(false) .build(); try { excelWriter.write(buildOverviewRows(summary), FastExcelFactory.writerSheet("驾驶舱总览").build()); excelWriter.write(buildProductionRows(summary, productionTrend), FastExcelFactory.writerSheet("生产统计").build()); excelWriter.write(buildQualityRows(quality), FastExcelFactory.writerSheet("质检统计").build()); excelWriter.write(buildInventoryRows(inventory), FastExcelFactory.writerSheet("库存统计").build()); excelWriter.write(buildOutboundRows(outbound), FastExcelFactory.writerSheet("出库统计").build()); excelWriter.write(buildTraceRows(trace), FastExcelFactory.writerSheet("追溯统计").build()); } finally { excelWriter.finish(); excelWriter.close(); } response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8("管理驾驶舱统计报表.xls")); response.setContentType("application/vnd.ms-excel;charset=UTF-8"); } // ========== Excel 行组装 ========== private List buildOverviewRows(MesDashboardSummaryRespVO summary) { List rows = new ArrayList<>(); addOverview(rows, "批次总数", summary.getBatchTotal()); addOverview(rows, "生产中批次", summary.getBatchProducing()); addOverview(rows, "已完成批次", summary.getBatchFinished()); addOverview(rows, "已作废批次", summary.getBatchCanceled()); addOverview(rows, "袋码总数", summary.getBagCodeTotal()); addOverview(rows, "已导出印刷袋码", summary.getBagCodePrinted()); addOverview(rows, "已作废袋码", summary.getBagCodeCanceled()); addOverview(rows, "托盘码总数", summary.getPalletTotal()); addOverview(rows, "今日扫码入库袋数", summary.getTodayInboundBagCount()); addOverview(rows, "检验记录总数", summary.getQualityTotal()); addOverview(rows, "合格检验记录", summary.getQualityPass()); addOverview(rows, "不合格检验记录", summary.getQualityFail()); addOverview(rows, "检验合格率(%)", summary.getQualityPassRate()); addOverview(rows, "当前库存总量", summary.getStockTotal()); addOverview(rows, "暂存库存量", summary.getStockTemporary()); addOverview(rows, "合格库存量", summary.getStockQualified()); addOverview(rows, "低库存预警数", summary.getLowStockCount()); addOverview(rows, "出库单总数", summary.getOutboundOrderTotal()); addOverview(rows, "出库总量", summary.getOutboundQuantityTotal()); addOverview(rows, "今日出库量", summary.getTodayOutboundQuantity()); addOverview(rows, "扫码总数", summary.getScanTotal()); addOverview(rows, "今日扫码数", summary.getTodayScanCount()); addOverview(rows, "窜货预警总数", summary.getCrossRegionWarningTotal()); addOverview(rows, "待处理窜货预警", summary.getCrossRegionPendingCount()); return rows; } private List buildProductionRows(MesDashboardSummaryRespVO summary, List trend) { List rows = new ArrayList<>(); addProduction(rows, "批次", "草稿", summary.getBatchDraft()); addProduction(rows, "批次", "生产中", summary.getBatchProducing()); addProduction(rows, "批次", "已完成", summary.getBatchFinished()); addProduction(rows, "批次", "已作废", summary.getBatchCanceled()); addProduction(rows, "袋码", "已生成", summary.getBagCodeGenerated()); addProduction(rows, "袋码", "已导出印刷", summary.getBagCodePrinted()); addProduction(rows, "袋码", "已作废", summary.getBagCodeCanceled()); addProduction(rows, "托盘", "总数", summary.getPalletTotal()); addProduction(rows, "入库", "今日扫码入库袋数", summary.getTodayInboundBagCount()); for (MesDashboardProductionTrendRespVO item : trend) { addProduction(rows, "趋势-" + item.getDate(), "新增批次", item.getBatchCount()); addProduction(rows, "趋势-" + item.getDate(), "新增袋码", item.getBagCodeCount()); addProduction(rows, "趋势-" + item.getDate(), "新增托盘", item.getPalletCount()); addProduction(rows, "趋势-" + item.getDate(), "入库袋数", item.getInboundBagQuantity() == null ? 0L : item.getInboundBagQuantity().longValue()); } return rows; } private List buildQualityRows(MesDashboardQualityRespVO quality) { List rows = new ArrayList<>(); for (MesDashboardNameCountRespVO item : quality.getResultDistribution()) { addGeneric(rows, "检验结果", item.getName(), item.getCount()); } for (MesDashboardIndicatorAvgRespVO item : quality.getIndicatorList()) { addGeneric(rows, "指标均值", item.getIndicatorName() + "(平均值)", item.getAverageValue()); addGeneric(rows, "指标均值", item.getIndicatorName() + "(合格率%)", item.getPassRate()); } addEmptyGuard(rows); return rows; } private List buildInventoryRows(MesDashboardInventoryRespVO inventory) { List rows = new ArrayList<>(); for (MesDashboardNameCountRespVO item : inventory.getWarehouseDistribution()) { addGeneric(rows, "按仓库", item.getName(), item.getCount()); } for (MesDashboardNameCountRespVO item : inventory.getItemDistribution()) { addGeneric(rows, "按品种", item.getName(), item.getCount()); } for (MesDashboardNameCountRespVO item : inventory.getStateDistribution()) { addGeneric(rows, "按状态", item.getName(), item.getCount()); } addEmptyGuard(rows); return rows; } private List buildOutboundRows(MesDashboardOutboundRespVO outbound) { List rows = new ArrayList<>(); for (MesDashboardDailyCountRespVO item : outbound.getTrend()) { addGeneric(rows, "出库趋势", item.getDate(), item.getCount()); } for (MesDashboardNameCountRespVO item : outbound.getDealerDistribution()) { addGeneric(rows, "按经销商", item.getName(), item.getCount()); } addEmptyGuard(rows); return rows; } private List buildTraceRows(MesDashboardTraceRespVO trace) { List rows = new ArrayList<>(); for (MesDashboardDailyCountRespVO item : trace.getScanTrend()) { addGeneric(rows, "扫码趋势", item.getDate(), item.getCount()); } for (MesDashboardNameCountRespVO item : trace.getProvinceDistribution()) { addGeneric(rows, "扫码省份", item.getName(), item.getCount()); } for (MesDashboardNameCountRespVO item : trace.getChannelDistribution()) { addGeneric(rows, "扫码渠道", item.getName(), item.getCount()); } if (trace.getCrossRegionSummary() != null) { addGeneric(rows, "窜货预警", "总数", trace.getCrossRegionSummary().getTotal()); addGeneric(rows, "窜货预警", "待处理", trace.getCrossRegionSummary().getPending()); addGeneric(rows, "窜货预警", "已处理", trace.getCrossRegionSummary().getHandled()); addGeneric(rows, "窜货预警", "误报", trace.getCrossRegionSummary().getFalseAlarm()); addGeneric(rows, "窜货预警", "跨省", trace.getCrossRegionSummary().getCrossProvince()); addGeneric(rows, "窜货预警", "跨市", trace.getCrossRegionSummary().getCrossCity()); } addEmptyGuard(rows); return rows; } /** * 空数据兜底:保证每个 sheet 至少有一行,避免 FastExcel 无法推断表头 */ private void addEmptyGuard(List rows) { if (rows.isEmpty()) { rows.add(new MesDashboardGenericExcelVO("提示", "暂无数据", "0")); } } private void addOverview(List rows, String indicator, Object value) { rows.add(new MesDashboardOverviewExcelVO(indicator, value == null ? "0" : value.toString())); } private void addProduction(List rows, String category, String status, Object count) { rows.add(new MesDashboardProductionExcelVO(category, status, count == null ? 0L : ((Number) count).longValue())); } private void addGeneric(List rows, String category, String name, Object value) { rows.add(new MesDashboardGenericExcelVO(category, name, value == null ? "0" : value.toString())); } }