Vue3 储能 EMS 前端实战(下):Element Plus 深度定制与工程总结
Vue3 储能 EMS 前端实战(下):Element Plus 深度定制与工程总结
系列说明:本文是《Vue3 储能 EMS 前端实战》第 3 篇 / 共 3 篇
- 第 1 篇:配置化表单与表格体系
- 第 2 篇:MQTT 实时数据与组态联动
- 本篇:HTabs + UI 体系 + 工程踩坑总结
适用读者:Vue3 开发者、Element Plus 二次定制、大型 B 端项目架构
技术栈:Vue 3.5 · Element Plus · SCSS · UnoCSS · ECharts · Three.js
项目类型:储能云平台 EMS UI
一、EMS 前端的 UI 体系挑战
配置化组件(HForm / HTable)和实时组态(MQTT + Maotu)解决的是功能和数据问题。但 EMS 项目还有第三个挑战:
- 上百页面需要统一的视觉语言
- Tab 导航 + 表格撑满剩余高度,Layout 链路必须闭环
- Element Plus 默认样式与设计稿差距大,需要系统性覆盖
本篇聚焦 HTabs 自定义指示线、全局 EP 主题定制,以及全项目的工程经验汇总。
二、整体架构回顾
工程化配套:
- Vite 8 + unplugin-auto-import:
ref、computed等自动导入 - vue-i18n:
$t('1001142')形式的多语言 key - UnoCSS + SCSS:原子化 + 主题变量双层样式体系
- postcss-pxtorem + amfe-flexible:大屏适配
三、HTabs 自定义指示线 —— 突破 Element Plus 限制
Element Plus Tabs 的 active-bar 是内置动画条,设计稿要求下划线指示器且宽度跟随 Tab 文字。
3.1 方案:CSS 变量 + JS 同步
隐藏 EP 默认 active-bar,用 CSS 伪元素 + 变量驱动指示线位置:
// styles/element/index.scss
@mixin h-tabs-line-nav {
&::after {
content: '';
position: absolute;
bottom: 0;
left: var(--h-tabs-bar-x, 0);
width: var(--h-tabs-bar-width, 0);
height: 2px;
background: #2897ff;
transition: left 0.2s, width 0.2s;
}
}
.h-tabs .ep-tabs__active-bar {
display: none;
}
// HTabs/index.vue
const applyIndicator = (item: HTMLElement) => {
navEl.style.setProperty('--h-tabs-bar-x', `${item.offsetLeft}px`)
navEl.style.setProperty('--h-tabs-bar-width', `${item.offsetWidth}px`)
navEl.classList.add('has-indicator')
}
3.2 监听 DOM 变化
Tab 动态增删、权限过滤导致 Tab 数量变化、国际化切换文字宽度变化时,指示线会错位。用 MutationObserver + ResizeObserver + requestAnimationFrame 三重保障:
const scheduleSync = () => {
cancelAnimationFrame(rafId)
rafId = requestAnimationFrame(syncActiveIndicator)
}
const observer = new MutationObserver(() => scheduleSync())
observer.observe(navEl, { attributes: true, childList: true, subtree: true })
const resizeObserver = new ResizeObserver(() => {
clearTimeout(resizeTimer)
resizeTimer = setTimeout(scheduleSync, 80)
})
踩坑:同步读取 offsetWidth 会导致指示线闪烁,必须用 requestAnimationFrame 延迟同步。
3.3 页面模式:Tab 导航 + keep-alive 内容区
EMS 大量页面采用「Tab 切换子模块」模式:
<h-tabs v-model="activeTab">
<el-tab-pane v-for="tab in tabs" :label="tab.label" :name="tab.name" />
</h-tabs>
<div class="contentBox h-tabs-page-content">
<keep-alive>
<component :is="activeComponent" />
</keep-alive>
</div>
配合权限过滤:
const tabs = allTabs.filter(tab =>
userInfo.permissions.includes('*:*:*') ||
userInfo.permissions.includes(tab.permit)
)
样式上 h-tabs-page-content 撑满剩余高度:
.h-tabs-page-content {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
height: calc(100% - var(--h-tabs-header-height, 48px));
background-color: #fff;
}
这与 HTable 的 ResizeObserver 形成完整布局链:Layout flex → Tab 内容区 → 表格自适应高度。
源码位置:src/components/HTabs/index.vue、src/styles/element/index.scss
四、Element Plus 全局主题定制要点
项目在 src/styles/element/index.scss 中系统性覆盖 EP 组件,核心策略:
| 策略 | 示例 |
|---|---|
| CSS 变量覆盖 | --ep-table-border-color、--ep-checkbox-checked-bg-color |
:deep() 穿透 |
HTableClean 表格边框、HForm 查看态样式 |
| Mixin 复用 | h-tabs-line-nav、generate-custom-link-colors |
| 场景化类名 | .h-tabs--segment、.scene-tab、.look-form |
Dialog 统一风格:
.ep-dialog {
padding: 0;
.ep-dialog__header {
background: #f2f4f7;
height: 42px;
line-height: 42px;
}
.ep-dialog__body {
padding: 22px;
}
}
自定义 Loading(v-loadingh):
.ep-loading-mask {
background-color: rgba(252, 252, 252, 0.15) !important;
}
.ep-loading-spinner .path {
stroke: #0279e8 !important;
}
HLink 业务色扩展:通过 SCSS mixin 生成 b1~b4、add、look、warn 等业务按钮色。
五、其他特色模块速览
| 模块 | 特色 | 典型场景 |
|---|---|---|
| Echart 封装 | 统一主题、resize 监听 | 电量曲线、收益分析 |
| GlbViewer | Three.js 加载 3D 模型 | 储能设备 3D 展示 |
| HAmap / 天地图 | 站点地理分布 | 运营大屏 |
| Decimal.js | 避免 JS 浮点误差 | 电量、金额计算 |
| exceljs + html2canvas | 报表导出 | PDF / Excel 下载 |
| v-loadingh | 品牌色 Loading | 全局加载态 |
六、全系列工程经验总结
✅ 值得借鉴
- 配置驱动 + Slot 扩展:80% 页面用 HForm / HTable 配置,20% 特殊场景用插槽
- Composable 抽离:
useHTable、useDicts、useLoad让组件保持轻薄 - 全局类型声明:
HFormItem、HColumn在custom-declare.d.ts,IDE 自动补全体验好 - MQTT 生命周期闭环:connect → 申请权限 → subscribe → unsubscribe → disconnect
- 样式分层:UnoCSS 原子类 + SCSS 主题变量 +
:deep()覆盖 EP 组件 - Layout 链路:每层
flex:1; min-height:0,表格才能正确撑满
⚠️ 踩坑记录(全系列汇总)
| 问题 | 原因 | 解法 | 涉及篇目 |
|---|---|---|---|
| 固定列边框消失 | EP 滚动状态类名层级不对 | 选择器写到 .ep-table.is-scrolling-xxx |
第 1 篇 |
| 表格高度为 0 | 父容器无明确高度 | Layout 链路上每层 flex:1; min-height:0 |
第 1、3 篇 |
| MQTT 通配符不生效 | mqtt.js 不支持客户端 + 匹配 | 自写正则路由 | 第 2 篇 |
| Maotu 条件渲染后空白 | 挂载前调 API | nextTick 后再 setImportJson |
第 2 篇 |
| Tab 指示线闪烁 | 同步读 offsetWidth | requestAnimationFrame 延迟同步 |
第 3 篇 |
| 切换站点数据错乱 | 旧 topic 未 unsubscribe | 生命周期内显式清理订阅 | 第 2 篇 |
七、系列结语
这个 EMS 前端项目的核心价值,不在于用了多少库,而在于针对工业场景做了正确的抽象:
| 挑战 | 解法 | 系列篇目 |
|---|---|---|
| 上百 CRUD 页面 | HForm / HTable 配置化 | 第 1 篇 |
| 实时监控 | MQTT + 组态联动 | 第 2 篇 |
| 统一 UI 规范 | Element Plus 深度主题定制 | 第 3 篇 |
三篇文章构成完整链路:效率(配置化)→ 实时(MQTT + 组态)→ 体验(UI 体系)。
如果你也在做 IoT / 能源 / 工业互联网前端,希望本系列能帮你少踩几个坑。
系列目录:
更多推荐


所有评论(0)