ThingsBoard HTML 容器 Widget 实战:可调整大小的 Split 主从布局样式实现(angular_split_css)

发布时间:2026/10/4 15:17:49
ThingsBoard HTML 容器 Widget 实战:可调整大小的 Split 主从布局样式实现(angular_split_css) 物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载ThingsBoard 的 HTML 容器HTML ContainerWidget 允许开发者用自定义 HTML、CSS 与 JavaScript 函数构建任意复杂的仪表盘交互组件。本篇以官方帮助文档中提供的可调整大小resizableSplit 主从布局master–detail示例为线索完整剖析其配套样式文件angular_split_css的每一段规则并结合 container_angular_fn.md 与 html-container-widget.component.ts 源码讲清楚「CSS 如何与 Angular 模板、JavaScript 函数协作」让读者能直接复制这套样式到自己的 HTML 容器 Widget 中并理解其底层运行原理。一、示例背景三个文件共同构成一个完整 Widget官方提供的 Split 主从示例由三个帮助文档文件共同组成存放在 examples 目录 下文件职责angular_split_html.mdAngular 模板左右两栏 拖拽分隔条 内嵌tb-dashboard-stateangular_split_css.md本篇主题布局、视觉与拖拽状态的样式定义angular_split_js.mdJavaScript 函数数据订阅、选中逻辑与拖拽计算它们通过 container_angular_fn.md 中tb-help-popup机制串接起来——在 Widget 的「帮助」面板里分别以弹出层展示 HTML 模板、CSS 与 JavaScript 函数供开发者复制使用。在使用这套样式前需要先明确 HTML 容器 Widget 的两种运行模式。从 html-container-widget.models.ts 可以看到export enum HtmlContainerWidgetType { PLAIN PLAIN, ANGULAR ANGULAR }PLAIN 模式函数签名是(ctx, container)直接操作 DOM适用于简单页面ANGULAR 模式函数签名是(ctx)函数内通过给this赋值来暴露数据与方法供 Angular 模板绑定引用。本示例属于ANGULAR 模式。在 html-container-widget.component.ts 中Angular 模式通过DynamicComponentFactoryService.createDynamicComponent以「用户提供的模板 编译后的容器函数 样式」动态创建组件实例样式即本 CSS通过styles数组注入到动态组件的样式作用域中。这也是为什么示例中的 class 命名都带split__前缀——在动态组件环境中使用清晰、带模块前缀的命名能有效避免样式冲突。注意帮助文档代码块中的{:code-stylemax-height: 400px;}与{:copy-code}是 ThingsBoard 帮助面板的渲染标记前者限制代码预览高度后者提供「复制代码」按钮复制到 Widget 设置中时应去掉。二、根容器.splitFlex 布局与整体外观示例 CSS 从根容器开始.split { display: flex; height: 100%; box-sizing: border-box; font-family: Inter, Roboto, system-ui, sans-serif; color: #0f172a; background: #fff; border-radius: 16px; box-shadow: 0 4px 24px rgba(15, 23, 42, 0.06); overflow: hidden; }逐项说明display: flex让左列表split__list、分隔条split__divider、详情区split__detail三个子元素水平排布构成主从两栏height: 100%撑满 Widget 容器。HTML 容器 Widget 的根节点挂载在tb-html-container-widget模板的div #container classtb-absolute-fill内见 html-container-widget.component.tstb-absolute-fill保证容器绝对填充 Widget 区域因此子元素按100%高度继承时能自然贴合 Widget 尺寸box-sizing: border-box配合后续padding计算避免边框与内边距撑破布局字体栈Inter, Roboto, system-ui, sans-serif与颜色#0f172a深 slate 色、#fff背景构成现代感的卡片外观border-radius: 16px 轻柔投影0 4px 24px rgba(15, 23, 42, 0.06)让整个 Split 组件呈现卡片质感overflow: hidden配合圆角防止子元素溢出破坏圆角裁剪也避免内部滚动条溢出。这套视觉基调圆角、浅投影、淡边框贯穿整个示例后续所有子元素都围绕它统一设计。三、左侧主列表.split__list宽度与滚动.split__list { flex: 0 0 auto; min-width: 160px; overflow: auto; border-right: 1px solid #eef1f6; }flex: 0 0 auto关键规则。不参与 flex 伸缩其实际宽度由模板中的[style.width.px]listWidth绑定决定初始值280见 angular_split_js.md 的this.listWidth 280。这条 CSS 与 JS 中的拖拽计算共同实现了「可拖拽调整宽度」的核心能力min-width: 160px拖拽下限。JS 拖拽函数中的Math.max(160, ...)与这里的min-width双重保证列表不会被拖到消失overflow: auto设备条目较多时列表内部纵向滚动border-right: 1px solid #eef1f6分隔线与列表之间的浅色描边视觉上强化「主/从」分区。列表内的标题与条目样式.split__title { margin: 0; padding: 16px; font-family: Roboto, sans-serif; font-size: 20px; font-weight: 500; } .split__items { list-style: none; margin: 0; padding: 0; } .split__item { display: flex; justify-content: space-between; gap: 12px; padding: 12px 16px; cursor: pointer; border-bottom: 1px solid #f1f5f9; transition: background 0.15s ease; } .split__item:hover { background: #f8fafc; } .split__item.is-active { background: rgba(47, 107, 255, 0.08); box-shadow: inset 3px 0 0 #2f6bff; } .split__value { font-weight: 700; color: #475569; }要点split__title使用margin: 0重置浏览器默认 h3 外边距配合padding: 16px与 20px/500 字重形成清晰的列表标题split__item采用display: flex; justify-content: space-between让左侧设备名、右侧最新值两端对齐gap: 12px控制间距:hover时以#f8fafc浅灰反馈.is-active是「选中态」的核心模板中通过[class.is-active]d.entityId selectedId动态挂载见 angular_split_html.md。它用rgba(47, 107, 255, 0.08)淡蓝色背景 inset 3px 0 0 #2f6bff左侧 3px 主题色竖条与整个 ThingsBoard 蓝色主题主色#2f6bff保持一致split__value用font-weight: 700#475569强调最新遥测值与普通条目文字形成层级对比。四、拖拽分隔条.split__divider全示例的灵魂.split__divider { flex: 0 0 12px; display: flex; align-items: center; justify-content: center; cursor: col-resize; background: #eef1f6; transition: background 0.15s ease; } .split__divider:hover { background: #e2e8f0; } .split__divider::before { content: ; width: 4px; height: 32px; background-image: radial-gradient(circle, #94a3b8 1.2px, transparent 1.4px); background-position: center; background-size: 4px 6px; background-repeat: repeat-y; }分隔条固定12px宽flex: 0 0 12px不随两侧伸缩cursor: col-resize明确提示用户可水平拖拽中央的「抓手」图形完全用 CSS 生成::before伪元素内使用radial-gradient(circle, #94a3b8 1.2px, transparent 1.4px)画出一列圆点配合background-size: 4px 6px与repeat-y形成竖直排列的圆点列模拟拖拽手柄无需任何图片资源悬停时背景加深为#e2e8f0给用户即时的可交互反馈。与 JavaScript 拖拽逻辑的协作分隔条的可拖拽能力不止靠 CSS还需要 JS 配合。在 angular_split_js.md 中this.startDrag (event) { event.preventDefault(); const root event.currentTarget.closest(.split); const left root.getBoundingClientRect().left; root.classList.add(split--dragging); const onMove (e) { this.listWidth Math.max(160, Math.min(e.clientX - left, root.clientWidth - 220)); ctx.detectChanges(); }; ... };这里演示了 CSS 与 JS 的三个协作点模板中分隔条绑定(mousedown)startDrag($event)JS 通过event.currentTarget.closest(.split)定位根节点由于 Angular 模式没有container参数官方在 container_angular_fn.md 中明确建议用event.currentTarget.closest(...)从事件处理器内访问根元素拖拽过程中反复给this.listWidth赋值并调用ctx.detectChanges()Angular 变更检测会立即把新宽度写入[style.width.px]绑定——CSS 的flex: 0 0 auto与min-width: 160px在此刻成为约束条件。五、拖拽中的状态类.split--dragging.split--dragging { user-select: none; } .split--dragging .split__list, .split--dragging .split__detail { pointer-events: none; }user-select: none拖拽时禁止文本被选中避免产生模糊的选区破坏体验pointer-events: none作用于两侧栏拖拽过程中屏蔽列表与详情区的鼠标事件防止误触导致条目被选中或点击状态被破坏。这个类在 JS 的startDrag中被root.classList.add(split--dragging)加上在onUp中用root.classList.remove(split--dragging)移除是典型的「CSS 负责视觉状态、JS 负责状态切换」协作模式。六、右侧详情区.split__detail主从联动.split__detail { flex: 1 1 0; min-width: 220px; display: flex; flex-direction: column; gap: 16px; padding: 20px; overflow: auto; } .split__eyebrow { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: #94a3b8; } .split__detail-title { margin: 2px 0 0; font-size: 20px; font-weight: 800; } .split__state { flex: 1 1 auto; min-height: 260px; border: 1px solid #eef1f6; border-radius: 14px; overflow: hidden; } .split__state tb-dashboard-state { display: block; width: 100%; height: 100%; }flex: 1 1 0详情区吸收剩余全部宽度与flex: 0 0 auto的列表形成「固定宽度 弹性宽度」组合min-width: 220px对应 JS 拖拽上限root.clientWidth - 220保证拖到最右时详情区仍保有至少 220px 的可读宽度内部用flex-direction: columngap: 16px垂直排布标题与状态区域split__eyebrow11px 大写 字距放大的「小眉题」如示例中的 Device是典型的主从界面设计元素split__state承载内嵌子仪表盘的容器min-height: 260px保证空态下也有稳定高度其中的tb-dashboard-state元素被强制display: block; width/height: 100%确保内嵌仪表盘状态充满该卡片。与 tb-dashboard-state 的联动原理模板中用tb-dashboard-state [ctx]ctx stateIddeviceCharts [syncParentStateParams]true/tb-dashboard-state嵌入子仪表盘状态见 angular_split_html.md。从 dashboard-state.component.ts 可以看到该组件的实现逻辑ngOnInit时通过deepClone(this.ctx.stateController.dashboardCtrl.dashboardCtx.getDashboard())克隆当前仪表盘并校验stateId指向的状态是否存在syncParentStateParams true时订阅stateChanged事件以同步父级状态参数最终通过updateEntityParams(params, this.entityParamName, this.entityId)把选中实体写入状态参数再以objToBase64编码为当前状态字符串。这与 JS 中的选中回调形成闭环this.select (d) { this.selected d; this.selectedId d.entityId; ctx.stateController.updateState(null, { entityId: { entityType: d.entityType, id: d.entityId }, entityName: d.name, }); ctx.detectChanges(); };ctx.stateController.updateState(...)对应 widget-api.models.ts 中IStateController接口的updateState(id?, params?, openRightLayout?)方法——点击列表条目时更新仪表盘状态参数更新后的实体参数被tb-dashboard-state感知并渲染对应的子仪表盘从而实现「左侧点设备、右侧切图表」的主从联动。七、空态提示.split__empty.split__empty { margin: auto; color: #94a3b8; font-size: 14px; }模板中使用ng-template #emptydiv classsplit__emptySelect a device/div/ng-template在未选中任何设备时渲染提示。margin: auto在 flex 容器.split__detail是display: flex; flex-direction: column中让该元素在剩余空间内垂直与水平居中#94a3b8灰色弱化提示文字避免干扰整体视觉。八、数据从哪来CSS 之外还需要的订阅逻辑需要特别强调的是HTML 容器 Widget不会自动绑定 Widget 数据源。官方在 container_angular_fn.md 的 Notes 中明确说明This widget does not bind widget datasources automatically. There is noctx.datapopulated from the widget configuration. To read live data create an explicit subscription viactx.subscriptionApi.createSubscription(...)and release it fromctx.registerDestroyCallback(...).因此在 angular_split_js.md 中示例通过ctx.subscriptionApi.createSubscription(...)主动创建latest类型订阅按设备类型peopleCount查询最新遥测值并在回调中把结果映射为devices数组驱动列表渲染同时在ctx.registerDestroyCallback(() ...)里调用ctx.subscriptionApi.removeSubscription(subscriptionId)释放订阅避免 Widget 销毁后产生泄漏。这三处 API 分别对应 widget-api.models.ts 中WidgetSubscriptionApi接口的createSubscription/removeSubscription与 widget-component.models.ts 中 WidgetContext 的registerDestroyCallback方法。样式CSS负责让这些数据呈现得整洁美观但数据本身必须由 JavaScript 函数负责获取——这是搭建 HTML 容器 Widget 时最容易被忽略的一环。九、实战接入把示例跑起来1. 创建 HTML 容器 Widget在仪表盘编辑器中添加HTML 容器HTML ContainerWidget在「Widget 设置」中Type选择Angular对应 html-container-widget.models.ts 的HtmlContainerWidgetType.ANGULARHTML粘贴 angular_split_html.md 的模板内容去掉{:code-style...}与{:copy-code}标记CSS粘贴本篇分析的angular_split_css.md样式JavaScript function粘贴 angular_split_js.md 的函数体。2. 按需修改与调参列表宽度初始值280、拖拽下限160、详情区下限220同时出现在 CSSmin-width与 JSMath.max / Math.min中需要保持一致设备类型过滤在 JS 的entityFilter: { type: deviceType, deviceTypes: [peopleCount], ... }中配置dataKeys指定要订阅的遥测键名stateIddeviceCharts指向目标仪表盘中的状态 ID需在仪表盘状态管理中预先创建主题色#2f6bff与 ThingsBoard 品牌蓝色一致如需品牌定制可直接替换 CSS 中的颜色常量。3. 保存即生效HTML 容器 Widget 的设置是纯运行时配置保存并重新打开仪表盘后组件通过 html-container-widget.component.ts 的ngOnInit加载资源、解析 JS 函数并动态编译模板与样式。若配置有误Widget 会通过handleWidgetException捕获异常并显示错误提示便于快速定位问题。十、小结angular_split_css.md这套样式虽然只有不到 80 行却完整覆盖了「根容器卡片化、固定宽度列表、拖拽分隔条、拖拽状态隔离、弹性详情区、子仪表盘填充、空态提示」等主从布局所需的所有关键样式并与 HTML 模板、JavaScript 函数形成了严密的协作关系CSS负责布局、视觉与状态样式flex比例、min-width约束、.is-active选中态、.split--dragging拖拽态JS负责数据订阅、状态更新与宽度计算并通过ctx.detectChanges()驱动模板重绘HTML通过[style.width.px]、[class.is-active]、(mousedown)、(click)绑定把两者桥接起来。读者可在此基础上自由扩展替换数据源、调整配色、增加更多分栏或动画过渡形成自己的仪表盘交互组件。赞分享物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载相关推荐Minimal Mistakes 标题防溢出长标题与不换行文本的布局兼容实战Minimal Mistakes 标题防溢出长标题与不换行文本的布局兼容实战 导读 本文围绕 Minimal Mistakes Jekyll 主题仓库中的边界物联网后端数据可视化消息队列Visdom窗口大小调整响应式布局的实现方法Visdom窗口大小调整响应式布局的实现方法 痛点直击可视化面板的自适应难题 你是否曾在调整浏览器窗口大小时遭遇Visdom可视化面板重叠错乱是否因屏幕数据可视化前端Ratatui WidgetRef 容器实战用异构 Widget 集合构建可复用 TUI 布局Ratatui WidgetRef 容器实战用异构 Widget 集合构建可复用 TUI 布局 本指南以 Ratatui 仓库中的 widget ref coUI组件上一篇d3dxSkinManage 快速教程15 分钟整理好 3DMigoto 皮肤模组下一篇Switch 第三方手柄完整指南sys-con 让 Xbox 和 PS 手柄直连可用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考