Vue3 EMS 项目实战:实时数据展示、NativeTable 与 PDF 导出
·
Vue3 EMS 项目实战:实时数据展示、NativeTable 与 PDF 导出
系列: 组件与工具专题
本篇主题:HDataItem + Unit 单位换算 + NativeTable 合并 + PDFExport
源码:src/components/HDataItem/、src/components/NativeTable/、src/components/PDFExport/
一、工业监控页面的数据展示需求
BMS 实时监控、PCS 运行面板等页面需要:
- 展示 SOC、功率、电压等带单位的数值
- MQTT 推送后对比上一帧,显示涨跌趋势
- 复杂报表用多级表头 + 单元格合并(Element Plus Table 配置成本高)
- 报表导出 PDF 并支持手动分页
本项目分别用 HDataItem、NativeTable、PDFExport 解决。
二、HDataItem:Label + 数值 + 单位 + 趋势 四合一
2.1 组件职责
整合三个能力:
- HText 标签
- HVal 趋势对比(rise / down 箭头)
- Unit 单位自动换算(kW → MW 等)
<!-- 有单位 + 趋势 -->
<HDataItem
:label="$t('1001169')"
:value="item?.rateP"
:pre-value="oldList[index]?.rateP"
unit="kW"
/>
<!-- 无单位 -->
<HDataItem :label="$t('1000852')" :value="item?.totalFactor" :pre-value="oldList[index]?.totalFactor" />
2.2 趋势对比逻辑
const compareFun = (oldVal: any, newVal: any): 'rise' | 'down' | '' => {
if (oldVal == null || newVal == null) return ''
const oldNum = Number(oldVal)
const newNum = Number(newVal)
if (isNaN(oldNum) || isNaN(newNum)) return ''
if (oldNum > newNum) return 'down'
if (oldNum < newNum) return 'rise'
return ''
}
watch([() => props.value, () => props.preValue], () => {
trend.value = compareFun(props.preValue, props.value)
})
与 BMS 主页面 MQTT 更新模式配合:更新前先 oldBmsList[index] = { ...bmsList[index] },再 merge 新数据。
2.3 单位换算
传入 unit="kW" 时内部调用 Unit 组件逻辑,大数值自动转换为 MW/GW 并保留 fixedNum 小数位。
三、NativeTable:原生 table 多级表头与合并
Element Plus Table 对动态多级表头 + rowspan 合并支持有限,报表类页面用原生 <table> 自研。
3.1 列配置
interface TableColumn {
label?: string
prop?: string
children?: TableColumn[] // 多级表头
hidden?: boolean | (() => boolean)
isMerge?: boolean // 启用行合并
mergeFn?: (current, prev, currentRow?, prevRow?) => boolean
formatter?: (value, row) => any
cellStyle?: CSSProperties | ((row) => CSSProperties)
}
3.2 表头处理
processedHeaders 计算 colspan / rowspan:
- 有
children的列:第一行 colspan = 子列数 - 无
children的叶子列:rowspan = 表头层级数
3.3 行合并 mergeFn
// 相同站点名称合并单元格
{
prop: 'stationName',
isMerge: true,
mergeFn: (current, prev) => current === prev,
}
内部维护 mergeState:rowspan + display: false 隐藏被合并行。
3.4 适用场景
- 结算报表、抄表汇总
- 固定打印布局
- EP Table 配置超过 200 行的复杂表
四、PDFExport:html2canvas + jsPDF 分页导出
4.1 基本用法
<PDFExport ref="pdfRef" orientation="landscape">
<div class="report-section">...</div>
<div class="pdf-split-line"></div> <!-- 分页符 -->
<div class="report-section">...</div>
</PDFExport>
<script setup>
const pdfRef = ref()
const handleExport = () => pdfRef.value.exportToPdf('月度报表')
</script>
4.2 分页策略
- 扫描 slot 内直接子元素
- 遇到
.pdf-split-line切分为多个 group - 每组单独
html2canvas截图 - 按 A4 可用高度逐页
addImage写入 jsPDF
const groups: HTMLElement[][] = []
for (const child of allChildren) {
if (child.classList.contains('pdf-split-line')) {
if (currentGroup.length > 0) groups.push([...currentGroup])
currentGroup = []
} else {
currentGroup.push(child)
}
}
4.3 横竖版与留白
const pageFullWidth = isLandscape ? 297 : 210 // mm
const pageFullHeight = isLandscape ? 210 : 297
导出过程显示 loading 遮罩,防止重复点击。
五、FlexC / FlexR:布局微组件
EMS 页面大量 flex 布局,封装竖向/横向容器:
<FlexC> <!-- flex-direction: column, min-height: 0 -->
<FlexR>...</FlexR>
</FlexC>
避免每个页面重复写 display:flex; min-height:0(flex 子项滚动的常见坑)。
六、Print 组件
配合 print-js 实现表格/区域打印,与 PDFExport 互补:
- PDFExport → 存档、邮件附件
- Print → 现场快速打印
七、本篇小结
| 组件 | 场景 |
|---|---|
| HDataItem | MQTT 实时监控数值 + 趋势 + 单位 |
| NativeTable | 多级表头、单元格合并报表 |
| PDFExport | 复杂报表 PDF 分页导出 |
| FlexC / FlexR | 统一 flex 布局 |
数据流示例(BMS 实时页):
踩坑:
- MQTT 更新前必须先保存 old 值,否则 trend 永远为空
- NativeTable mergeFn 要保证排序稳定,否则合并错乱
- PDF 导出 html2canvas 对跨域图片需 CORS
源码索引:
| 模块 | 路径 |
|---|---|
| HDataItem | src/components/HDataItem/index.vue |
| NativeTable | src/components/NativeTable/index.vue |
| PDFExport | src/components/PDFExport/index.vue |
| compareFun | src/utils/common.ts |
更多推荐


所有评论(0)