资讯中心

图扑智慧机房:二三维表单如何实现机柜拖拽上下架?

📅 2026/7/2 11:01:11
图扑智慧机房:二三维表单如何实现机柜拖拽上下架?
在数据中心运维的日常中设备上下架管理一直是个“经典难题”运维人员需要反复在 Excel 表格、U 位图纸和物理机柜之间来回切换。整个流程不仅耗时耗力运维效率低下还易出现机位错放、资产账实不符等问题。例如定位 A01423 服务器时需先后核对表格、机柜图纸确认其 B7 机柜 14U-16U 机位后再去机房现场精确查找。本文提出二维表单联动三维机柜可视化解决方案通过图扑创新式可视化管控系统让设备管理从“表格查找”升级为“空间操作”。该案例依托拖拽交互模式实现表格设备直接上架、三维场景设备反向下架。系统搭建一体化交互架构基于共享数据模型实现二三维视图数据实时同步通过事件驱动保障双向拖拽精准定位搭配可自适应视角与窗口变化的动态连接线、空闲 U 位智能推荐算法以及多层交互动画优化整体操作体验。下文将对系统的设计思路与具体实现方式展开详细阐述。设备上下架交互设备上架流程在传统 Excel 式管理中一台服务器仅表现为一行数字与文字。而在本系统中设备具备完整的“可拖拽”实体属性上架过程如下01.权限校验系统首先检查用户是否处于编辑模式——防止误操作影响生产环境。该机制类似于物理机房需凭工单操作确保安全。// 检查编辑权限 if (!isEditMode event.kind ! sortChange) return;02.触发拖拽与机位高亮用户从表格中拖拽一台“未上架”设备时系统标记拖拽方向并生成自定义拖拽图标。同时在 3D 机柜中高亮所有符合尺寸要求的空闲 U 位。拖拽图标通过独立 JSON 文件配置支持按需自定义。// 标记拖拽方向为2D→3D isDraggingFromTable true; const rowData event.info.data; // 创建拖拽视觉元素 event.elementInfo { element: ht.Default.toCanvas(device-drag-icon.json, 24, 24), offsetX: 0, offsetY: 0 }; // 通知3D视图创建高亮块 event.fire(createHighlightBlock,rowData.a(unit) );拖拽过程中目标机柜上所有可用的连续 U 位会以高亮块形式呈现类似影院空座位标识。03实时位置计算与悬停高亮鼠标移动时系统实时计算当前悬停的 U 位槽位并动态调整高亮状态实现精准点位指引。const targetSlot rack3DView.getDataAt(event.nativeEvent); if (targetSlot targetSlot.getParent() highlightBlock) { highlightBlock.eachChild(slot { // 高亮当前悬停的槽位 slot.s(highlight.group, slot targetSlot ? 1 : undefined); slot.s(highlight.mode, slot targetSlot); }); }针对服务器、交换机等占用多联 U 位的设备系统内置算法可根据设备尺寸、机柜总容量及已占用机位快速筛选全部可用连续空闲 U 位。/** * 寻找机柜中可用的连续U位 * param {number}deviceUnit - 设备所需U位数必须大于0 * param {number}maxUnits - 机柜总U位数必须大于0 * param {Arraynumber}occupiedUnits - 已占用U位数组范围应在[1, maxUnits]之间 * returns {Arraynumber}可用的起始U位数组按升序排列 * example * // 返回 [1, 7, 12] * findAvailablePositions(2, 24, [3, 4, 5, 6, 9, 10]); */ functionfindAvailablePositions(deviceUnit, maxUnits, occupiedUnits) { // 1. 参数验证 if (typeof deviceUnit ! number || deviceUnit 0 || typeof maxUnits ! number || maxUnits 0 || !Array.isArray(occupiedUnits)) { console.warn(findAvailablePositions: 参数无效, { deviceUnit, maxUnits, occupiedUnits }); return []; } // 设备需求超过机柜总容量直接返回空数组 if (deviceUnit maxUnits) { return []; } // 2. 准备数据过滤无效的占用位置并创建查找集合 const validOccupiedUnits occupiedUnits.filter( position Number.isInteger(position) position 1 position maxUnits ); const occupiedSet newSet(validOccupiedUnits); // 3. 查找可用位置 const availableStartPositions []; let consecutiveFreeSlots 0; for (let position 1; position maxUnits; position) { if (occupiedSet.has(position)) { // 当前位置被占用重置连续空闲计数 consecutiveFreeSlots 0; continue; } consecutiveFreeSlots; // 找到满足设备需求的连续空位 if (consecutiveFreeSlots deviceUnit) { const startPosition position - deviceUnit 1; availableStartPositions.push(startPosition); } } // 4. 按约定返回结果已经是升序 return availableStartPositions; }04.放置确认与数据同步用户松开鼠标时系统执行原子性数据更新在 3D 机柜创建设备模型、修改 2D 表格设备状态已上架/未上架、记录机位信息并同步至上架设备清单。所有操作要么全部成功要么全部回滚保证数据一致性。if (slot targetSlot) { const deviceInfo rowData.getAttrObject(); const startU slot.a(slotIndexes)[0]; // 更新设备占用信息 deviceInfo.occupy [startU]; // 在3D视图中创建设备模型 mountDevice(deviceInfo); // 更新设备状态为已上架 event.info.data.a(status, 已上架); // 记录到已上架设备列表 mountedDeviceList.push({ ...deviceInfo, startU: startU, position: ${startU}U-${startU deviceInfo.unit}U }); }05.界面状态收尾上架完成后系统自动触发统计面板更新、当前选中设备高亮刷新、连接线重绘以及临时高亮块清理。// 更新统计面板 updateStatisticsPanel(); // 更新当前选中信息 rowInfo deviceInfo; // 让3D视图高亮显示新设备 highlightSelectedDevice(); // 重新绘制连接线 updateConnectionLine(); // 恢复鼠标指针 view.setCursor(null); // 清理高亮块 rack3DView.dm().remove(highlightBlock);06.表格排序与机内迁移表格排序适配运维人员可按设备类型、负载、上架时间等维度排序表格。系统等待表格渲染完成后自动重新计算并刷新连接线保证 2D 与 3D 设备对应关系不变。if (event.kind sortChange) { // 等待表格渲染完成后重新计算连接线 ht.Default.callLater(() { calculateLineStartPoint(); updateConnectionLine(); }); }机柜内设备迁移对于已在机柜中的设备系统同样支持拖拽重定位。拖拽开始时保存原始位置并临时创建可用 U 位高亮块拖拽结束后更新占用信息拖拽取消则恢复原位。/** * 处理设备拖拽开始事件 * param {Object}event - 拖拽事件对象 */ functiononBeginDrag(event) { // 检查编辑权限 if (!isEditMode) return; // 验证拖拽事件类型 if (event.type ! data) return; const deviceData event.data; const deviceShape deviceData.s(shape3d); // 验证是否为可拖拽的设备模型 if (!deviceModels.includes(deviceShape)) return; // 记录拖拽信息 dragInfo { draggingElementInfo: { element: ht.Default.toCanvas(device-drag-icon.json, 24, 24), }, view: view, data: deviceData }; // 保存原始位置以便取消操作 const originalPosition deviceData.p3(); deviceData.a(originalPosition, originalPosition); try { // 启动拖拽 ht.drawing.Dragger.startDragging(dragInfo, event.event); // 获取设备信息用于高亮提示 const unitSize deviceData.a(unit); const occupyArray deviceData.a(occupy); // 创建高亮块显示可用位置 if (unitSize occupyArray occupyArray.length 0) { const minOccupy Math.min(...occupyArray); createHighlightBlock(unitSize, minOccupy); } // 临时取消设备高亮 deviceData.s(highlight.group, undefined); deviceData.s(highlight.mode, false); } catch (error) { console.error(拖拽操作失败:, error); // 清理拖拽状态 dragInfo null; deviceData.a(originalPosition, null); } }07.拖拽状态重置拖拽动作结束后清空方向标记区分上下架两类拖拽逻辑。isDraggingFromTable 标志位很关键用于区分拖拽来源确保后续逻辑如下架正确处理。if (event.kind endDrag) { isDraggingFromTable false; }技术亮点■状态驱动交互通过 isEditMode 控制整个系统的可操作性确保生产安全■实时视觉反馈拖拽过程中的高亮变化、放置时的状态更新都提供了清晰的交互引导■全域数据同步2D 表格、3D 模型、统计数据通过统一的数据模型保持同步■完善异常兜底无效拖拽、放置失败等场景支持自动回退避免界面异常■高性能设计结合事件委托、异步执行机制海量设备场景下仍可流畅运行。设备下架流程系统支持反向拖拽实现从 3D 机柜拖回表格采用状态标记替代物理删除的设计保留设备全量数据支持操作追溯与复原。if (event.kind crossDrop) { const deviceData event.info.data; const deviceName deviceData.a(name); // 1. 修改设备状态 deviceData.a(status, 未上架); // 2. 从已上架设备列表中移除 const deviceIndex mountedDeviceList.findIndex(device device.name deviceName); if (deviceIndex -1) { console.warn(设备 ${deviceName} 未在上架列表中找到); return; // 或进行相应处理 } else { // 如果当前选中的设备是正在下架的设备清除选中状态 if (selectedEdge selectedEdge.a(name) deviceName) { selectedEdge null; hideConnectionLine(); } // 从已上架列表移除 mountedDeviceList.splice(deviceIndex, 1); } // 3. 如果是从3D拖到2D从表格数据模型中移除对应行 if (!isDraggingFromTable) { const tableDataModel data.a(dataModel); const tableRows tableDataModel.toDatas().toArray(); for (let i 0; i tableRows.length; i) { const tableRow tableRows[i]; if (tableRow.a(name) deviceName) { tableDataModel.remove(tableRow); break; // 找到后立即跳出循环 } } } // 4. 更新统计面板 updateStatisticsPanel(); }流程遵循■状态驱动而非删除通过状态变更而非物理删除保留了操作的历史痕迹■数据一致性保障更新状态的同时同步更新相关的统计数据面板■智能状态清理自动清理冗余界面元素优化视觉体验■拖拽方向感知通过 isDraggingFromTable 标志区分拖拽方向确保逻辑正确处理。关于动态连接线连接线生成逻辑选中表格内已上架设备时系统会自动生成一条动态连接线作为连接 2D 表单与 3D 机柜的视觉桥梁。连接线的生成依托精准的坐标换算与路径计算适配表格滚动、视图旋转等各类界面变化。01.计算连接线起点起点定位需综合表格表头高度、行高、行间距、纵向滚动偏移、表格排序结果等多重因素精准定位选中表格行的中心坐标。/** * 计算连接线起点位置表格行中心 * returns {Object|null}返回起点坐标对象 {x, y}如果无法计算则返回null */ functioncalculateLineStartPoint() { // 1. 获取表格节点 const tableNode dm.getDataByTag(table); // 2. 检查是否有选中的行信息 if (!rowInfo || !rowInfo.name) { console.warn(未选中表格行无法计算连接线起点); returnnull; } // 3. 获取表格相关属性 const tableWidth tableNode.getWidth(); const tableHeight tableNode.getHeight(); const tableCenterX tableNode.p().x tableWidth / 2; const headRowHeight tableNode.a(headRowHeight) || 0; const rowHeight tableNode.a(rowHeight) || 0; const rowLineWidth (tableNode.a(rowLineStyle) || {}).width || 0; const verticalScrollOffset tableNode.a(table.translateY) || 0; // 4. 计算表格数据区域第一行的起始Y坐标 const tableTopY tableNode.p().y - tableHeight / 2; const firstRowCenterY tableTopY headRowHeight rowHeight / 2; // 5. 获取设备在当前排序后的行索引 const rowUIs getTableRowUIs(view, tableNode); if (!rowUIs || rowUIs.length 0) { console.warn(无法获取表格行UI信息); returnnull; } const rowIndex getRowIndexByName(rowUIs, rowInfo.name); if (rowIndex -1) { console.warn(未找到对应的表格行:, rowInfo.name); returnnull; } // 6. 计算该行的Y坐标考虑行高和行间距 const rowCenterY firstRowCenterY rowIndex * (rowHeight rowLineWidth) verticalScrollOffset; return { x: tableCenterX, y: rowCenterY }; } /** * 获取表格行UI实例 * param {Object}view - 视图对象 * param {Object}tableNode - 表格节点 * returns {Array}表格行UI数组 */ functiongetTableRowUIs(view, tableNode) { const drawingInstances ht.drawing.getDrawingInstances(view, tableNode); return drawingInstances.length 0 ? drawingInstances[0].ui.rowUIs : []; } /** * 根据设备名称获取行索引 * param {Array}rowUIs - 行UI数组 * param {string}deviceName - 设备名称 * returns {number}行索引未找到返回-1 */ functiongetRowIndexByName(rowUIs, deviceName) { for (let i 0; i rowUIs.length; i) { const rowData rowUIs[i].data; if (rowData rowData.a(name) deviceName) { return i; } } return -1; }02.计算连接线终点终点需要完成 3D 世界坐标→屏幕坐标→2D 逻辑坐标两层转换结合 3D 视图当前视角、相机位置等因素将三维设备位置映射到二维界面中。/** * 计算连接线终点3D设备二维投影 * returns {Object|null} 坐标对象 {x, y}计算失败返回null */ function calculateLineEndPoint() { if (!selectedEdge) { console.warn(未选中3D设备无法计算终点); return null; } const rack3DView ht.Default.findView(3d); const device3DPosition selectedEdge.p3(); try { // 3D世界坐标转屏幕坐标 const screenPosition rack3DView.toViewPosition(device3DPosition); if (!screenPosition) { console.warn(3D坐标转屏幕坐标失败); return null; } // 屏幕坐标转2D逻辑坐标 const logicalPosition view.getLogicalPoint(screenPosition); if (!logicalPosition) { console.warn(屏幕坐标转逻辑坐标失败); return null; } return { x: logicalPosition.x, y: logicalPosition.y }; } catch (error) { console.error(计算连接线终点异常:, error); return null; } }03.生成自适应贝塞尔曲线获取起止坐标后系统不使用生硬直线而是动态生成贝塞尔曲线。控制点会根据两点水平距离自动适配兼顾短距离不尖锐、长距离比例协调的视觉效果。/** * 更新连接线显示状态 * param {boolean}shouldShow - 是否显示连接线 */ functionupdateConnectionLine(shouldShow) { if (!shouldShow) { // 隐藏所有连接线元素 connectionLine.s(2d.visible, false); startMarker.s(2d.visible, false); endMarker.s(2d.visible, false); return; } // 显示起点和终点标记 startMarker.s(2d.visible, true); endMarker.s(2d.visible, true); const startPoint calculateLineStartPoint(); // 表格行中心 const endPoint calculateLineEndPoint(); // 3D设备投影位置 // 创建贝塞尔曲线控制点数组 let controlPoints []; const horizontalDistance Math.abs(endPoint.x - startPoint.x); // 6个控制点构成的贝塞尔曲线 controlPoints.push(startPoint); // 起点 controlPoints.push({ x: startPoint.x 12, y: startPoint.y }); // 水平右移12px controlPoints.push({ x: startPoint.x Math.max(40, horizontalDistance / 2), y: startPoint.y }); // 动态控制点 controlPoints.push({ x: endPoint.x - Math.max(40, horizontalDistance / 2), y: endPoint.y }); // 接近终点 controlPoints.push({ x: endPoint.x - 12, y: endPoint.y }); // 水平左移12px controlPoints.push(endPoint); // 终点 // 更新连接线显示 connectionLine.s(2d.visible, true); connectionLine.setPoints(controlPoints); connectionLine.setSegments([1, 2, 4, 2]); // 控制曲线分段直线-曲线-直线 }技术亮点■动态控制点根据起点和终点的水平距离动态调整控制点位置确保无论距离远近曲线都保持优雅的比例■最小水平偏移使用 Math.max(40, horizontalDistance / 2) 确保连接线有足够的水平延伸避免短距离时曲线过于尖锐■平滑过渡Segments 参数[1, 2, 4, 2]控制曲线分段方式创建自然的S形过渡■视觉美感通过在起点和终点附近设置 12 像素的水平延伸创建优雅的曲线入口和出口。跨维度快速检索依托连接线实现表格与 3D 设备双向联动查询运维人员可从任意一端发起查找快速定位对应目标彻底摆脱人工脑补换算的繁琐。01.选中表格条目定位 3D 设备位置点击表格中的设备行系统自动记录设备信息同步在 3D 视图中匹配对应设备并高亮同时拉起连接线完成关联展示。若设备处于未上架状态则不触发查找逻辑。// 监听表格选中状态变化 let tableDm getDataAttr(table, dataModel); let tableSm tableDm.sm(); tableSm.ms((event) { const selectedRowData tableSm.ld(); if (selectedRowData) { // 存储选中行信息 rowInfo selectedRowData.getAttrObject(); // 如果设备未上架则清除选中边界 if (rowInfo.status 未上架) { selectedEdge null; } } else { // 清除选中信息 rowInfo {}; selectedEdge null; } // 更新3D选中状态并重绘连接线 getSelectedEdge(); updateConnectionLine(); });该模式优势显著无需人工对照编号查找视觉引导一目了然同时自动识别设备上架状态规避无效查找。02.点击 3D 设备定位表格数据反向操作同样顺畅点击机柜内的 3D 设备系统根据设备名称反向匹配表格数据自动将对应行滚动至视野中心并高亮两端同步标记实现双向互通。functiongetSelectedEdge() { // 设置当前选中的3d设备高亮效果 var g3d ht.Default.findView(3d); var upDevices g3d.dm().getDataByTag(upDevices); upDevices.eachChild((data) { const upInfo data.getAttrObject(); const selected upInfo.name rowInfo.name; if (selected) { selectedEdge data; } data.s(highlight.group, selected ? 1 : undefined); data.s(highlight.mode, selected); }); }双向查找机制真正建立起数据记录与物理设备的强关联让抽象数据拥有直观的空间形态降低运维查找成本。视角与窗口变化实时处理连接线需要实时跟随两种用户操作3D 视角旋转旋转/平移/缩放和浏览器窗口尺寸变化。通过精心设计的监听与调度机制系统确保了 2D 表格与 3D 设备之间的连接线始终准确、流畅。双重监听与性能优化系统建立了两个关键的事件监听器覆盖可能影响连接线位置的变化。采用基于 requestAnimationFrame 的智能节流机制// 性能优化相关变量 let animationFrameId null; let isUpdating false; /** * 调度连接线更新任务 */ functionscheduleConnectionLineUpdate() { // 如果已有更新任务在执行跳过 if (isUpdating) return; // 取消未执行的更新任务 if (animationFrameId) { cancelAnimationFrame(animationFrameId); } // 使用requestAnimationFrame调度更新 animationFrameId requestAnimationFrame(() { isUpdating true; try { updateConnectionLine(); // 重新计算并绘制连接线 } finally { isUpdating false; animationFrameId null; } }); } // 监听3D视角变化旋转、平移、缩放 view.mp((event) { // 仅响应视角相关的变化 if (event.property ! eye event.property ! center) return; scheduleConnectionLineUpdate(); }); // 监听窗口大小变化 addEventListener(resize, () { scheduleConnectionLineUpdate(); });采用智能节流策略■防抖设计通过 isUpdating 标志防止重复计算确保同一时间只有一个更新任务在执行■请求合并使用 cancelAnimationFrame 取消未执行的更新请求避免任务堆积■帧率同步借助 requestAnimationFrame 确保更新与浏览器刷新同步保持 60fps 流畅体验。实际效果■快速旋转 3D 视图 → 连接线平滑跟随无卡顿或闪烁■拖动窗口边界 → 连接线立即重新计算并精准对齐■高频率触发事件 → 系统资源不浪费避免不必要的重绘。本文所述的可视化方案本质上是将抽象的设备记录与具体的物理位置通过直观的交互和视觉反馈连接起来。它解决了传统运维中的几个关键痛点■位置感知不再需要脑补设备在机柜里的位置■操作直观拖拽比填表格更符合直觉■实时反馈任何操作都能立即看到结果■降低门槛新员工也能快速上手。成熟的方案绝非单纯代码堆叠而是兼顾使用体验的人性化设计动画不追求视觉特效而是引导用户视线拖拽交互并非简化开发而是模拟真实物理操作逻辑连线图形不以装饰为目的搭建多视图间的数据认知关联。图扑已落地海量智慧机房、数据中心运维管控可视化项目覆盖园区、机房、机柜、设备多层级场景积累了丰富的资产、动环、能耗一体化开发经验。。