liu
3 天以前 ee7ac8df8bce38add1f75baf876b49de94b9e00a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
<script lang="ts" setup>
import type { ManagementCockpitApi } from '#/api/mes/management-cockpit';
 
import { ref } from 'vue';
 
import { Page } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { downloadFileFromBlobPart } from '@vben/utils';
 
import { Button, message, Space } from 'ant-design-vue';
// 变量名 vPrint(v 开头)在 <script setup> 中会被 Vue 自动注册为局部指令 v-print
// @ts-expect-error - vue3-print-nb 未提供类型声明
import vPrint from 'vue3-print-nb';
 
import {
  exportManagementCockpitExcel,
  getManagementCockpitSummary,
} from '#/api/mes/management-cockpit';
 
import KpiCards from './modules/kpi-cards.vue';
import InventoryChart from './modules/inventory-chart.vue';
import OutboundChart from './modules/outbound-chart.vue';
import ProductionTrend from './modules/production-trend.vue';
import QualityChart from './modules/quality-chart.vue';
import TraceChart from './modules/trace-chart.vue';
 
defineOptions({ name: 'ManagementCockpit' });
 
// 打印配置(打印报表容器)
const printObj = ref({
  id: 'management-cockpit-report',
  popTitle: '&nbsp;',
  extraCss: '/print.css',
  zIndex: 20_003,
});
 
const summary = ref<ManagementCockpitApi.Summary>({
  batchTotal: 0,
  batchDraft: 0,
  batchProducing: 0,
  batchFinished: 0,
  batchCanceled: 0,
  bagCodeTotal: 0,
  bagCodeGenerated: 0,
  bagCodePrinted: 0,
  bagCodeCanceled: 0,
  palletTotal: 0,
  todayInboundBagCount: 0,
  qualityTotal: 0,
  qualityPending: 0,
  qualityPass: 0,
  qualityFail: 0,
  qualityPassRate: 0,
  stockTotal: 0,
  stockTemporary: 0,
  stockQualified: 0,
  stockOutbound: 0,
  lowStockCount: 0,
  outboundOrderTotal: 0,
  outboundQuantityTotal: 0,
  todayOutboundQuantity: 0,
  scanTotal: 0,
  todayScanCount: 0,
  scanSuccessCount: 0,
  crossRegionWarningTotal: 0,
  crossRegionPendingCount: 0,
});
 
// 通过递增 key 触发子组件重新挂载实现整体刷新
const reloadKey = ref(0);
 
const loading = ref(false);
 
/** 加载汇总统计 */
async function loadSummary() {
  try {
    summary.value = await getManagementCockpitSummary();
  } catch {
    // 接口异常时保留默认值,避免页面崩溃
  }
}
 
/** 刷新全部数据 */
async function handleRefresh() {
  loading.value = true;
  try {
    await Promise.all([loadSummary()]);
    reloadKey.value += 1;
    message.success('已刷新');
  } finally {
    loading.value = false;
  }
}
 
/** 导出 Excel 报表(后端生成多 sheet 工作簿) */
async function handleExportExcel() {
  try {
    const data = await exportManagementCockpitExcel();
    downloadFileFromBlobPart({
      fileName: `管理驾驶舱统计报表-${new Date().toLocaleDateString()}.xls`,
      source: data,
    });
    message.success('Excel 报表已导出');
  } catch {
    // 错误已由 requestClient 统一提示
  }
}
 
/** 导出 PDF 报表(前端 html2canvas + jspdf 生成可下载文件) */
async function handleExportPdf() {
  const target = document.getElementById('management-cockpit-report');
  if (!target) {
    message.warning('报表区域未就绪');
    return;
  }
  const hide = message.loading('正在生成 PDF,请稍候…', 0);
  try {
    // 动态加载,避免拖慢首屏(html-to-image 通过 SVG foreignObject 让浏览器原生渲染,
    // 规避 html2canvas 不支持 oklch 等现代 CSS 颜色函数的问题)
    const [{ toPng }, { jsPDF }] = await Promise.all([
      import('html-to-image'),
      import('jspdf'),
    ]);
    const dataUrl = await toPng(target, {
      backgroundColor: '#ffffff',
      cacheBust: true,
      pixelRatio: 2,
    });
    const pdf = new jsPDF({ compress: true, format: 'a4', orientation: 'p', unit: 'mm' });
    const pageWidth = pdf.internal.pageSize.getWidth();
    const pageHeight = pdf.internal.pageSize.getHeight();
    const imgWidth = pageWidth;
    const imgHeight = (target.offsetHeight * imgWidth) / target.offsetWidth;
 
    let heightLeft = imgHeight;
    let position = 0;
    pdf.addImage(dataUrl, 'PNG', 0, position, imgWidth, imgHeight);
    heightLeft -= pageHeight;
    while (heightLeft > 0) {
      position -= pageHeight;
      pdf.addPage();
      pdf.addImage(dataUrl, 'PNG', 0, position, imgWidth, imgHeight);
      heightLeft -= pageHeight;
    }
    pdf.save(`管理驾驶舱统计报表-${new Date().toLocaleDateString()}.pdf`);
    message.success('PDF 报表已导出');
  } catch (error) {
    console.error('[management-cockpit] PDF 导出失败:', error);
    message.error(
      `PDF 导出失败:${error instanceof Error ? error.message : '未知错误'},请打开浏览器控制台(F12)查看详情`,
    );
  } finally {
    hide();
  }
}
 
/** 加载汇总统计 */
async function loadInitial() {
  await loadSummary();
}
loadInitial();
</script>
 
<template>
  <Page>
    <!-- 工具栏 -->
    <div class="mb-4 flex flex-wrap items-center justify-between gap-3">
      <div class="flex items-center gap-2">
        <IconifyIcon class="text-xl text-blue-500" icon="lucide:gauge" />
        <span class="text-base font-semibold">管理驾驶舱与统计分析</span>
      </div>
      <Space>
        <Button :loading="loading" @click="handleRefresh">
          <template #icon><IconifyIcon icon="lucide:refresh-cw" /></template>
          刷新
        </Button>
        <Button v-access:code="['mes:dashboard:export']" type="primary" @click="handleExportExcel">
          <template #icon><IconifyIcon icon="lucide:file-spreadsheet" /></template>
          导出 Excel
        </Button>
        <Button v-access:code="['mes:dashboard:export']" type="primary" ghost @click="handleExportPdf">
          <template #icon><IconifyIcon icon="lucide:file-down" /></template>
          导出 PDF
        </Button>
        <Button v-print="printObj">
          <template #icon><IconifyIcon icon="lucide:printer" /></template>
          打印
        </Button>
      </Space>
    </div>
 
    <!-- 报表内容(Excel/PDF/打印 均基于此区域) -->
    <div id="management-cockpit-report" class="rounded-lg bg-white p-4 dark:bg-gray-800">
      <!-- 五维度 KPI -->
      <KpiCards :key="`kpi-${reloadKey}`" :summary="summary" class="mb-2" />
 
      <!-- 生产趋势 -->
      <ProductionTrend :key="`pt-${reloadKey}`" class="mb-4" />
 
      <QualityChart :key="`qc-${reloadKey}`" class="mb-4" />
 
      <InventoryChart :key="`iv-${reloadKey}`" class="mb-4" />
      <OutboundChart :key="`ob-${reloadKey}`" class="mb-4" />
      <TraceChart :key="`tr-${reloadKey}`" class="mb-4" />
    </div>
  </Page>
</template>