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 并支持手动分页

本项目分别用 HDataItemNativeTablePDFExport 解决。


二、HDataItem:Label + 数值 + 单位 + 趋势 四合一

2.1 组件职责

整合三个能力:

  1. HText 标签
  2. HVal 趋势对比(rise / down 箭头)
  3. 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,
}

内部维护 mergeStaterowspan + 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 分页策略

  1. 扫描 slot 内直接子元素
  2. 遇到 .pdf-split-line 切分为多个 group
  3. 每组单独 html2canvas 截图
  4. 按 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 实时页)

HDataItem Page MQTT HDataItem Page MQTT handleMqttData(topic, data) oldList[index] = copy(bmsList[index]) bmsList[index] = merge(data) value + pre-value compareFun → rise/down

踩坑

  1. MQTT 更新前必须先保存 old 值,否则 trend 永远为空
  2. NativeTable mergeFn 要保证排序稳定,否则合并错乱
  3. 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

更多推荐