Vue3 储能 EMS 前端实战(上):配置化表单与表格体系
Vue3 储能 EMS 前端实战(上):配置化表单与表格体系
系列说明:本文是《Vue3 储能 EMS 前端实战》第 1 篇 / 共 3 篇
- 本篇:HForm + HTable 配置化组件
- 第 2 篇:MQTT 实时数据与组态联动
- 第 3 篇:Element Plus 深度定制与工程总结
适用读者:Vue3 中高级开发者、工业物联网 / 能源管理系统前端工程师
技术栈:Vue 3.5 · Vite 8 · Element Plus · Pinia · Decimal.js
项目类型:储能云平台 EMS UI
一、为什么 EMS 前端需要配置化?
储能能源管理系统(EMS)前端和普通后台管理系统有本质区别:
| 维度 | 普通 Admin | EMS 前端 |
|---|---|---|
| 数据特征 | 请求-响应,静态为主 | 秒级实时推送 |
| 页面形态 | 表格 + 表单 | 组态拓扑图 + 实时曲线 + 3D 模型 |
| 页面数量 | 几十页 | 上百页,高度重复 |
上百个 CRUD 页面如果每个都手写 <el-form> + <el-table>,维护成本会爆炸。本项目的第一层抽象是:用 JSON 配置描述 UI,用 Slot 处理 20% 的特殊场景。
本篇聚焦:HForm 表单引擎 + HTable 表格引擎 + useHTable Composable。
二、配置化表单 HForm —— 用 JSON 描述 UI
2.1 设计思路
传统写法:每个页面手写 <el-form> + 十几个 <el-form-item>,字段一多就失控。
本项目抽象出全局类型 HFormItem(定义在 custom-declare.d.ts),用数组配置描述整个表单:
// 典型用法:查询表单
const queryFormItems = shallowRef<HFormItem[]>([
{ label: '设备类型', type: 'select', prop: 'deviceType', options: deviceTypes, span: 6 },
{ label: '设备名称', type: 'input', prop: 'deviceName', span: 6 },
{ label: '时间范围', type: 'daterange', prop: 'dateRange', span: 6 },
])
模板侧只需一行:
<h-form :form-items="queryFormItems" :model="queryFormData" isquery @search="handleSearch" />
2.2 核心实现:layoutItems 自动换行算法
HForm 不是简单 v-for,而是内置栅格换行布局引擎:
// 核心逻辑(简化版)
const layoutItems = computed(() => {
const result = []
let i = 0
while (i < items.length) {
const rowItems = []
let rowSpan = 0
// 按 span 累加,满 24 换行;span=24 独占一行
while (i < items.length) {
const span = item.span || (isquery ? 6 : 12)
if (span === 24) { /* 独占一行 */ break }
if (rowSpan + span > 24) break
rowItems.push(item)
rowSpan += span
i++
if (rowSpan === 24) break
}
// 为每列计算对齐类:set-left / set-center / set-right
rowItems.forEach((item, index) => {
result.push({ item, span, alignClass: getAlignClass(index, spans, rowSpan) })
})
}
return result
})
三种模式:
| 模式 | 触发条件 | 行为 |
|---|---|---|
isquery |
查询表单 | 默认 span=6,前 3 项 + 搜索按钮,超出可展开 |
| 编辑表单 | 默认 | span=12,左/中/右对齐,固定 340px 输入宽度 |
disabled |
查看详情 | 禁用态样式,空值隐藏 placeholder |
2.3 校验体系:函数式 Rule + Decimal.js
数字字段支持 min / max / step 校验,步长用 Decimal.js 避免浮点误差:
// step 校验:value 必须是 step 的整数倍
rules.push(createRule((value) =>
new Decimal(value).mod(item.attrs!.step!).toNumber() !== 0
? `请输入 ${item.attrs!.step} 的倍数`
: undefined
))
自定义校验支持函数数组,比 Element Plus 原生 rules 更简洁:
rules: [
(val) => val < 0 ? '不能为负数' : undefined,
(val) => val > 100 ? '不能超过100' : undefined,
]
2.4 扩展点:Slot 插槽
配置覆盖不了的场景,用 #prop名 插槽:
<h-form :form-items="formItems" :model="formData">
<template #customField>
<my-special-component v-model="formData.customField" />
</template>
</h-form>
源码位置:src/components/HForm/index.vue、custom-declare.d.ts
三、配置化表格 HTable —— 自适应高度 + 列显隐
3.1 useHTable Composable 设计
表格逻辑从组件中抽离到 useHTable.ts,实现逻辑复用(HTableClean、HTableBorder 共用同一套逻辑)。
亮点 1:ResizeObserver 自动计算表格高度
EMS 页面常见布局:顶部 Tab + 查询表单 + 表格撑满剩余空间。手动算高度很痛苦,项目用 ResizeObserver 监听父容器:
const getTableHeight = () => {
// 遍历兄弟节点,累加高度
for (let el of siblingNodes) {
if (el !== tableContainer.value) {
sum += el.getBoundingClientRect().height + marginTop + marginBottom
}
}
// 父容器高度 - padding - 兄弟高度 = 表格可用高度
height.value = Math.max(parentHeight - sum, minHeight ?? 280)
}
亮点 2:列宽智能计算
支持 "10z" 这种写法,表示 10 个字符宽度,内部用 Canvas measureText 精确测量:
if (/\d+[zZ]{1}/.test(col.width)) {
col.width = calculateTextWidth("Z".repeat(parseInt(col.width))) + 24
}
亮点 3:列显隐设置
setting=true 时弹出 Popover,用户勾选可见列,存入 selectedColumns,持久化可扩展 localStorage。
3.2 HTableClean:Element Plus 表格边框的深度覆盖
Element Plus 默认 border 表格四边都有线,设计稿要求:
- 去掉单元格右边框
- 表头用伪元素做列间分隔线(高度 26px,垂直居中)
- 固定列在所有滚动状态下都显示分隔线
.htable-clean {
:deep(td.ep-table__cell) {
border-right: none !important;
border-bottom: 1px solid var(--ep-table-border-color) !important;
}
:deep(th.ep-table__cell) {
border-right: none !important;
position: relative;
&::after {
content: " ";
width: 1px;
height: 26px;
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
background-color: var(--ep-table-border-color);
}
}
// 关键:覆盖 EP 在不同 is-scrolling-xxx 状态下的固定列边框
:deep(.ep-table.is-scrolling-none),
:deep(.ep-table.is-scrolling-left),
:deep(.ep-table.is-scrolling-right),
:deep(.ep-table.is-scrolling-middle) {
td.ep-table-fixed-column--left.is-last-column,
th.ep-table-fixed-column--left.is-last-column {
border-right: 1px solid var(--ep-table-border-color) !important;
}
}
}
踩坑:is-scrolling-xxx 类名在 .ep-table 上,不在 .htable-clean 上,选择器层级要写对。
源码位置:src/components/HTable/useHTable.ts、src/components/HTable/HTableClean.vue
四、本篇小结
| 组件 | 解决的问题 | 关键技术 |
|---|---|---|
| HForm | 上百页表单重复劳动 | 栅格换行算法、函数式校验、Slot 扩展 |
| HTable | 表格高度自适应、列配置 | ResizeObserver、Canvas 测宽、Composable 复用 |
| HTableClean | 设计稿与 EP 默认样式差异 | :deep() 覆盖、伪元素分隔线 |
配置化解决了 EMS 项目 80% 页面的开发效率问题。下一篇进入工业场景的核心难点:MQTT 实时推送 + Maotu 组态联动。
更多推荐


所有评论(0)