// 查询堆垛机数据列表 function listDdj() { request({ url: '/largeScreen/ddj/list', method: 'get' }).then(response => { console.log('接口返回数据:', response); handleStackerDataSuccess(response) }).catch(error => { console.error('堆垛机数据获取失败:', error); }); } // $.ajax({ // url: API_URLS.stacker, // type: 'GET', // dataType: 'json', // success: handleStackerDataSuccess, // error: (xhr, status, error) => console.error('堆垛机数据获取失败:', error) // }); /** * 2D可视化监控系统 - 数据同步和状态管理 * 负责配置同步、设备状态更新、实时数据获取等功能 */ // ==================== 全局变量 ==================== /** * 配置编辑模式标识 * @type {number} 1-编辑模式, 2-演示模式 */ let configEditMode = 2; // ==================== 配置同步功能 ==================== /** * 获取系统配置并同步状态 * 从服务器获取最新配置,更新本地状态和UI */ function getConfig() { $.ajax({ url: API_URLS.config, type: 'GET', dataType: 'json', cache: false, success: handleConfigSuccess, error: handleConfigError }); } /** * 处理配置获取成功 * @param {Object} data 配置数据 */ function handleConfigSuccess(data) { // 更新编辑模式状态 updateEditMode(data.editMode); // 同步main.js中的全局配置 syncMainConfig(data); // 更新组件拖拽状态 updateDraggableStatus(data.deviceDraggable); } /** * 处理配置获取失败 * @param {Object} xhr XMLHttpRequest对象 * @param {string} status 状态信息 * @param {string} error 错误信息 */ function handleConfigError(xhr, status, error) { console.error('配置获取失败:', error); } /** * 更新编辑模式状态 * @param {boolean} editMode 是否为编辑模式 */ function updateEditMode(editMode) { configEditMode = editMode ? 1 : 2; const modeText = editMode ? '编辑模式' : '演示模式'; } /** * 同步main.js中的全局配置 * @param {Object} data 配置数据 */ function syncMainConfig(data) { if (!window.config) return; // 更新所有配置项 window.config.deviceDraggable = data.deviceDraggable; window.config.editMode = data.editMode; window.config.allowBackendAccess = data.allowBackendAccess; window.config.labels = data.labels || []; window.config.conveyors = data.conveyors || []; window.config.stackers = data.stackers || []; window.config.tracks = data.tracks || []; window.config.rollerConveyors = data.rollerConveyors || []; window.config.titleSettings = data.titleSettings; // 调用main.js中的更新函数 if (typeof window.updateDevicesDraggableState === 'function') { window.updateDevicesDraggableState(); } // 更新标签显示 if (typeof window.updateLabels === 'function') { window.updateLabels(); } // 更新页面标题样式 if (typeof window.updatePageTitleStyle === 'function' && data.titleSettings) { window.updatePageTitleStyle(data.titleSettings); } } // ==================== UI状态管理 ==================== /** * 更新所有组件的拖拽状态 * 根据配置更新设备元素的鼠标样式和拖拽能力 * @param {boolean} isDraggable 是否允许拖拽 */ function updateDraggableStatus(isDraggable) { const draggableElements = document.querySelectorAll('.conveyor, .roller-conveyor, .stacker, .label'); draggableElements.forEach(element => { // 根据元素类型设置不同的鼠标样式 const cursorStyle = isDraggable ? (element.classList.contains('stacker') ? 'grab' : 'move') : 'default'; element.style.cursor = cursorStyle; }); // 同步更新全局配置 if (window.config) { window.config.deviceDraggable = isDraggable; } } // ==================== 页面初始化 ==================== /** * 页面加载完成后的初始化 */ $(document).ready(function() { // 立即执行一次数据获取 initializeData(); // 设置实时配置监听 setupConfigListeners(); // 启动定时器 startTimers(); }); /** * 初始化数据获取 */ function initializeData() { getConfig(); updateConveyorStatus(); updateStackerStatus(); } /** * 设置配置变化监听器 */ function setupConfigListeners() { // 监听localStorage变化(about页面通知) setupLocalStorageListener(); // 监听BroadcastChannel消息(跨标签页通信) setupBroadcastChannelListener(); } /** * 设置localStorage监听器 */ function setupLocalStorageListener() { let lastRefreshTime = localStorage.getItem('refreshIndex'); setInterval(() => { const currentRefreshTime = localStorage.getItem('refreshIndex'); if (currentRefreshTime && currentRefreshTime !== lastRefreshTime) { lastRefreshTime = currentRefreshTime; getConfig(); } }, 100); } /** * 设置BroadcastChannel监听器 */ function setupBroadcastChannelListener() { if (!window.BroadcastChannel) return; const broadcastChannel = new BroadcastChannel('index_refresh_channel'); broadcastChannel.onmessage = (event) => { if (event.data.refresh) { getConfig(); } }; } // 报警更新定时器ID let alarmUpdateTimer = null; /** * 启动定时器 */ function startTimers() { // 设备状态更新定时器 setInterval(updateConveyorStatus, 5000); setInterval(updateStackerStatus, 5000); setInterval(updateRgvStatus, 5000); // 配置同步定时器 setInterval(getConfig, 1000); // 启动智能报警更新定时器 startSmartAlarmTimer(); } /** * 启动报警系统 */ function startSmartAlarmTimer() { // 立即执行一次 updateAlarmStatus(); console.log('报警系统已启动'); } /** * 安排下次报警更新 * @param {number} delay 延迟时间(毫秒) */ function scheduleNextAlarmUpdate(delay) { // 清除现有定时器 if (alarmUpdateTimer) { clearTimeout(alarmUpdateTimer); } alarmUpdateTimer = setTimeout(() => { updateAlarmStatus(); // 递归安排下次更新 scheduleNextAlarmUpdate(3000); }, delay); } // ==================== 设备状态更新 ==================== /** * 更新输送机状态 * 从服务器获取输送机实时数据并更新UI显示 */ function updateConveyorStatus() { // 检查是否允许访问后台数据 if (!canAccessBackendData()) { return; } $.ajax({ url: API_URLS.conveyor, type: 'GET', dataType: 'json', success: handleConveyorDataSuccess, error: (xhr, status, error) => console.error('输送机数据获取失败:', error) }); } /** * 处理输送机数据获取成功 * @param {Object} response 服务器响应数据 */ function handleConveyorDataSuccess(response) { if (!isValidResponse(response) || configEditMode !== 2) { return; } const conveyors = document.querySelectorAll('.conveyor, .roller-conveyor'); conveyors.forEach(conveyor => { const conveyorId = conveyor.getAttribute('id'); const deviceData = response.data.find(item => item.id === conveyorId); if (deviceData) { updateConveyorDisplay(conveyor, deviceData); } }); } /** * 更新单个输送机的显示状态 * @param {HTMLElement} conveyor 输送机DOM元素 * @param {Object} data 设备数据 */ function updateConveyorDisplay(conveyor, data) { // 更新中心方块显示状态 const centerBlock = conveyor.querySelector('.conveyor-center, .roller-conveyor-center'); if (centerBlock) { centerBlock.style.display = data.dw === '1' ? 'block' : 'none'; } // 更新设备状态指示(故障闪烁) const hasError = parseInt(data.sbzt) > 100; conveyor.classList.toggle('breathing-border', hasError); } // ==================== 辅助函数 ==================== /** * 检查是否允许访问后台数据 * @returns {boolean} 是否允许访问 */ function canAccessBackendData() { if (window.config && window.config.allowBackendAccess === false) { return false; } return true; } /** * 验证服务器响应数据是否有效 * @param {Object} response 服务器响应 * @returns {boolean} 数据是否有效 */ function isValidResponse(response) { return response && response.code === 0 && response.data; } /** * 更新堆垛机状态 * 从服务器获取堆垛机实时数据并更新UI显示 */ function updateStackerStatus() { // 检查是否允许访问后台数据 if (!canAccessBackendData()) { return; } listDdj() } /** * 处理堆垛机数据获取成功 * @param {Object} response 服务器响应数据 */ function handleStackerDataSuccess(response) { if (!isValidResponse(response) || configEditMode !== 2) { console.log(11111,response) return; } const stackers = document.querySelectorAll('.stacker'); stackers.forEach(stacker => { const stackerId = stacker.getAttribute('id'); const deviceData = response.data.find(item => item.id === stackerId); if (deviceData) { updateStackerDisplay(stacker, deviceData); } }); } /** * 更新单个堆垛机的显示状态 * @param {HTMLElement} stacker 堆垛机DOM元素 * @param {Object} data 设备数据 */ function updateStackerDisplay(stacker, data) { const centerBlock = stacker.querySelector('.stacker-center'); const executionPhase = data.zxjd; // 根据执行阶段更新堆垛机状态 switch(executionPhase) { case '1': // 移动到原位置,隐藏货物 updateStackerPosition(stacker, data.ywz, false); break; case '2': // 移动到原位置,显示货物 updateStackerPosition(stacker, data.ywz, true); break; case '3': // 移动到目标位置,显示货物 updateStackerPosition(stacker, data.mbwz, true); break; case '4': case '5': // 移动到目标位置,隐藏货物 updateStackerPosition(stacker, data.mbwz, false); break; } // 处理堆垛机报警状态(sbzt > 1000 时显示红光) updateStackerAlarmStatus(stacker, data.sbzt); } /** * 更新堆垛机报警状态 * @param {HTMLElement} stacker 堆垛机元素 * @param {string|number} sbzt 设备状态值 */ function updateStackerAlarmStatus(stacker, sbzt) { const statusValue = parseInt(sbzt); if (statusValue > 1000) { // 添加报警样式 stacker.classList.add('breathing-border'); console.log(`堆垛机 ${stacker.getAttribute('id')} 报警,状态值: ${statusValue}`); } else { // 移除报警样式 stacker.classList.remove('breathing-border'); } } /** * 更新堆垛机位置和货物显示 * @param {HTMLElement} stacker 堆垛机元素 * @param {string} position 目标位置 * @param {boolean} showCargo 是否显示货物 */ function updateStackerPosition(stacker, position, showCargo) { const centerBlock = stacker.querySelector('.stacker-center'); // 更新货物显示状态 if (centerBlock) { centerBlock.style.display = showCargo ? 'block' : 'none'; } // 计算并移动到目标位置 const targetPosition = parseInt(position); if (!isNaN(targetPosition)) { // 获取配置信息以确定小方块总数 const boxesPerRow = window.config ? window.config.boxesPerRow : 60; if (targetPosition === 0) { // 位置为0时,移动到第一个小方块左侧 moveStackerToColumn(stacker, -1); } else if (targetPosition === boxesPerRow + 1) { // 位置为小方块数量+1时,移动到最后一个小方块右侧 moveStackerToColumn(stacker, boxesPerRow); } else if (targetPosition > 0) { // 正常位置处理 moveStackerToColumn(stacker, targetPosition - 1); } } } /** * 移动堆垛机到指定列 * @param {HTMLElement} stacker 堆垛机元素 * @param {number} columnIndex 目标列索引 */ function moveStackerToColumn(stacker, columnIndex) { // 获取堆垛机的ID和对应的货架ID const stackerId = stacker.getAttribute('id'); const stackerConfig = window.config.stackers.find(s => s.id === stackerId); if (!stackerConfig || !stackerConfig.shelfId) { console.warn(`堆垛机 ${stackerId} 未找到对应的货架ID`); return; } // 获取对应行的配置 const rowConfig = window.config.boxRows.find(row => row.id === stackerConfig.shelfId); if (!rowConfig) { console.warn(`未找到货架ID ${stackerConfig.shelfId} 对应的方块行配置`); return; } // 获取该行的方块数量 const boxesPerRow = rowConfig.boxCount; let finalX; if (columnIndex === -1) { // 位置为0时,移动到第一个小方块左侧一个方块距离 const firstBox = document.querySelector(`.small-box[data-row="${stackerConfig.shelfId}"][data-column="0"]`); if (!firstBox) { console.warn(`未找到货架 ${stackerConfig.shelfId} 的第一个方块`); return; } const firstBoxX = parseInt(firstBox.style.left); const boxSize = parseInt(firstBox.style.width); const stackerWidth = parseInt(stacker.style.width); const centerOffset = (stackerWidth - boxSize) / 2; finalX = firstBoxX - boxSize - centerOffset; // 向左偏移一个方块距离 } else if (columnIndex === boxesPerRow) { // 位置为小方块数量+1时,移动到最后一个小方块右侧一个方块距离 const lastBox = document.querySelector(`.small-box[data-row="${stackerConfig.shelfId}"][data-column="${boxesPerRow - 1}"]`); if (!lastBox) { console.warn(`未找到货架 ${stackerConfig.shelfId} 的最后一个方块`); return; } const lastBoxX = parseInt(lastBox.style.left); const boxSize = parseInt(lastBox.style.width); const stackerWidth = parseInt(stacker.style.width); const centerOffset = (stackerWidth - boxSize) / 2; finalX = lastBoxX + boxSize - centerOffset; // 向右偏移一个方块距离 } else { // 正常位置处理 const targetBox = document.querySelector(`.small-box[data-row="${stackerConfig.shelfId}"][data-column="${columnIndex}"]`); if (!targetBox) { console.warn(`未找到货架 ${stackerConfig.shelfId} 的第 ${columnIndex} 个方块`); return; } const targetX = parseInt(targetBox.style.left); const stackerWidth = parseInt(stacker.style.width); const boxSize = parseInt(targetBox.style.width); const centerOffset = (stackerWidth - boxSize) / 2; finalX = targetX - centerOffset; } // 添加平滑移动动画 stacker.style.transition = 'left 2s ease-in-out'; stacker.style.left = finalX + 'px'; } // ==================== RGV状态管理 ==================== /** * 更新RGV状态 */ function updateRgvStatus() { // 检查是否允许访问后台数据 if (!canAccessBackendData()) { return; } $.ajax({ url: API_URLS.rgv, type: 'GET', dataType: 'json', success: handleRgvDataSuccess, error: (xhr, status, error) => console.error('RGV数据获取失败:', error) }); } /** * 处理RGV数据获取成功 * @param {Object} response 服务器响应数据 */ function handleRgvDataSuccess(response) { if (!isValidResponse(response) || configEditMode !== 2) { return; } const rgvs = document.querySelectorAll('.rgv'); rgvs.forEach(rgv => { const rgvId = rgv.getAttribute('id'); const deviceData = response.data.find(item => item.id === rgvId); if (deviceData) { updateRgvDisplay(rgv, deviceData); } }); } /** * 更新RGV显示状态 * @param {HTMLElement} rgv RGV元素 * @param {Object} data RGV数据 */ function updateRgvDisplay(rgv, data) { const centerBlock = rgv.querySelector('.rgv-center'); const executionPhase = data.zxjd; // 根据执行阶段更新RGV状态 switch(executionPhase) { case '1': // 移动到原位置,隐藏货物 updateRgvPosition(rgv, data.ywz, false); break; case '2': // 移动到原位置,显示货物 updateRgvPosition(rgv, data.ywz, true); break; case '3': // 移动到目标位置,显示货物 updateRgvPosition(rgv, data.mbwz, true); break; case '4': case '5': // 移动到目标位置,隐藏货物 updateRgvPosition(rgv, data.mbwz, false); break; } // 处理RGV报警状态(sbzt > 1000 时显示红光) updateRgvAlarmStatus(rgv, data.sbzt); } /** * 更新RGV报警状态 * @param {HTMLElement} rgv RGV元素 * @param {string|number} sbzt 设备状态值 */ function updateRgvAlarmStatus(rgv, sbzt) { const statusValue = parseInt(sbzt); if (statusValue > 1000) { // 添加报警样式 rgv.classList.add('breathing-border'); console.log(`RGV ${rgv.getAttribute('id')} 报警,状态值: ${statusValue}`); } else { // 移除报警样式 rgv.classList.remove('breathing-border'); } } /** * 更新RGV位置和货物显示 * @param {HTMLElement} rgv RGV元素 * @param {string} conveyorId 目标输送机ID * @param {boolean} showCargo 是否显示货物 */ function updateRgvPosition(rgv, conveyorId, showCargo) { const centerBlock = rgv.querySelector('.rgv-center'); // 更新货物显示状态 if (centerBlock) { centerBlock.style.display = showCargo ? 'block' : 'none'; } // 移动到目标输送机位置 if (conveyorId) { moveRgvToConveyor(rgv, conveyorId); } } /** * 移动RGV到指定输送机位置 * @param {HTMLElement} rgv RGV元素 * @param {string} conveyorId 目标输送机ID */ function moveRgvToConveyor(rgv, conveyorId) { const targetConveyor = document.getElementById(conveyorId); if (!targetConveyor) { console.warn(`未找到输送机: ${conveyorId}`); return; } // 获取输送机的位置和尺寸 const conveyorX = parseInt(targetConveyor.style.left); const conveyorY = parseInt(targetConveyor.style.top); const conveyorWidth = parseInt(targetConveyor.style.width); const conveyorHeight = parseInt(targetConveyor.style.height); // 获取RGV的尺寸 const rgvWidth = parseInt(rgv.style.width); const rgvHeight = parseInt(rgv.style.height); // 计算RGV的目标位置(只调整y轴与输送机对齐,x轴保持不变) const currentX = parseInt(rgv.style.left); // 保持当前x坐标 const targetY = conveyorY + (conveyorHeight - rgvHeight) / 2; // 添加平滑移动动画(只动y轴) rgv.style.transition = 'top 2s ease-in-out'; rgv.style.top = targetY + 'px'; console.log(`RGV ${rgv.getAttribute('id')} 移动到输送机 ${conveyorId} Y轴位置: ${targetY}px (X轴保持: ${currentX}px)`); } // 注意:此处的重复代码已被上面的优化函数替代 // 旧的堆垛机处理逻辑已移至 updateStackerDisplay 等函数中 // 定时器设置已移至 startTimers 函数中 // ==================== 设备信息弹框功能 ==================== /** * 显示设备信息弹框 * @param {string} deviceType 设备类型 * @param {string} deviceId 设备ID * @param {Object} deviceConfig 设备配置信息 */ function showDeviceInfo(deviceType, deviceId, deviceConfig) { const box = document.getElementById('box'); const detailsDiv = document.getElementById('device-details'); if (!box || !detailsDiv) { console.error('设备信息弹框元素未找到'); return; } // 获取设备实时数据 getDeviceRealTimeData(deviceType, deviceId).then(realTimeData => { // 构建设备信息HTML const deviceInfo = buildDeviceInfoHTML(deviceType, deviceId, deviceConfig, realTimeData); detailsDiv.innerHTML = deviceInfo; // 显示弹框 box.style.display = 'block'; }).catch(error => { console.error('获取设备实时数据失败:', error); // 即使获取实时数据失败,也显示基本配置信息 const deviceInfo = buildDeviceInfoHTML(deviceType, deviceId, deviceConfig, null); detailsDiv.innerHTML = deviceInfo; box.style.display = 'block'; }); } /** * 获取设备实时数据 * @param {string} deviceType 设备类型 * @param {string} deviceId 设备ID * @returns {Promise} 设备实时数据 */ function getDeviceRealTimeData(deviceType, deviceId) { return new Promise((resolve, reject) => { if (deviceType === 'stacker') { // 获取堆垛机实时数据 - 使用配置的URL $.ajax({ url: API_URLS.stacker, type: 'GET', dataType: 'json', success: function(response) { if (response && response.data) { const deviceData = response.data.find(item => item.id === deviceId); resolve(deviceData || null); } else { resolve(null); } }, error: function(xhr, status, error) { console.error('获取堆垛机数据失败:', error); reject('获取堆垛机数据失败'); } }); } else if (deviceType === 'conveyor' || deviceType === 'rollerConveyor') { // 获取输送机实时数据 - 使用配置的URL $.ajax({ url: API_URLS.conveyor, type: 'GET', dataType: 'json', success: function(response) { if (response && response.data) { const deviceData = response.data.find(item => item.id === deviceId); resolve(deviceData || null); } else { resolve(null); } }, error: function(xhr, status, error) { console.error('获取输送机数据失败:', error); reject('获取输送机数据失败'); } }); } else if (deviceType === 'rgv') { // 获取RGV实时数据 - 使用配置的URL $.ajax({ url: API_URLS.rgv, type: 'GET', dataType: 'json', success: function(response) { if (response && response.data) { const deviceData = response.data.find(item => item.id === deviceId); resolve(deviceData || null); } else { resolve(null); } }, error: function(xhr, status, error) { console.error('获取RGV数据失败:', error); reject('获取RGV数据失败'); } }); } else { // 其他设备类型暂无实时数据 resolve(null); } }); } /** * 构建设备信息HTML * @param {string} deviceType 设备类型 * @param {string} deviceId 设备ID * @param {Object} deviceConfig 设备配置信息 * @param {Object} realTimeData 实时数据 * @returns {string} HTML字符串 */ function buildDeviceInfoHTML(deviceType, deviceId, deviceConfig, realTimeData) { const deviceTypeNames = { 'conveyor': '链条输送机', 'rollerConveyor': '滚筒输送机', 'stacker': '堆垛机', 'rgv': 'RGV', 'track': '堆垛机轨道' }; const typeName = deviceTypeNames[deviceType] || '未知设备'; // 使用后台数据中的ID,如果没有实时数据则使用配置ID const displayId = realTimeData ? realTimeData.id : deviceId; // 设备类型图标 const deviceIcons = { 'conveyor': '', 'rollerConveyor': '', 'stacker': '', 'rgv': '' }; const deviceIcon = deviceIcons[deviceType] || '⚙️'; let html = `
${deviceIcon}

${typeName}

ID: ${displayId}
`; if (realTimeData) { html += `
`; if (deviceType === 'stacker') { // 堆垛机字段:基于ddj.json const statusValue = parseInt(realTimeData.sbzt); const statusColor = statusValue > 1000 ? '#ff4444' : '#00aa00'; const statusText = statusValue > 1000 ? '报警' : '正常'; const statusBgColor = statusValue > 1000 ? 'rgba(255,68,68,0.1)' : 'rgba(0,170,0,0.1)'; // 执行阶段文字描述 const executionPhases = { '1': '取货行驶', '2': '取货', '3': '放货行驶', '4': '放货', '5': '完成' }; const phaseText = executionPhases[realTimeData.zxjd] || `未知阶段(${realTimeData.zxjd})`; html += `
设备信息
名称: ${realTimeData.name}
执行阶段: ${phaseText}
任务号: ${realTimeData.rwh}
运行状态
状态: ${statusText}
状态值: ${realTimeData.sbzt}
位置信息
${realTimeData.yp}排${realTimeData.ywz}列${realTimeData.yc}层
原位置
→
${realTimeData.mbp}排${realTimeData.mbwz}列${realTimeData.mbc}层
目标位置
`; } else if (deviceType === 'conveyor' || deviceType === 'rollerConveyor') { // 输送机字段:基于Lssj.json const statusValue = parseInt(realTimeData.sbzt); const statusColor = statusValue > 100 ? '#ff4444' : '#00aa00'; const statusText = statusValue > 100 ? '故障' : '正常'; const statusBgColor = statusValue > 100 ? 'rgba(255,68,68,0.1)' : 'rgba(0,170,0,0.1)'; const hasCargoText = realTimeData.dw === '1' ? '有货物' : '无货物'; const kxText = realTimeData.kx === '1' ? '空闲' : '不空闲'; const cargoColor = realTimeData.dw === '1' ? '#FF9800' : '#9E9E9E'; const switchColor = realTimeData.kx === '1' ? '#4CAF50' : '#F44336'; html += `
设备信息
名称: ${realTimeData.name}
任务号: ${realTimeData.rwh}
托盘编码: ${realTimeData.trayCode}
运行状态
状态: ${statusText}
状态值: ${realTimeData.sbzt}
开关状态
${kxText}
货物状态
${hasCargoText}
`; } else if (deviceType === 'rgv') { // RGV字段:基于rgv.json const statusValue = parseInt(realTimeData.sbzt); const statusColor = statusValue > 1000 ? '#ff4444' : '#00aa00'; const statusText = statusValue > 1000 ? '报警' : '正常'; const statusBgColor = statusValue > 1000 ? 'rgba(255,68,68,0.1)' : 'rgba(0,170,0,0.1)'; // 执行阶段文字描述 const executionPhases = { '1': '取货行驶', '2': '取货', '3': '放货行驶', '4': '放货', '5': '完成' }; const phaseTextRgv = executionPhases[realTimeData.zxjd] || `未知阶段(${realTimeData.zxjd})`; // 执行阶段描述 const phaseDescriptions = { '1': '移动到原位置(无货物)', '2': '移动到原位置(有货物)', '3': '移动到目标位置(有货物)', '4': '移动到目标位置(无货物)', '5': '完成(无货物)' }; const phaseText = phaseDescriptions[realTimeData.zxjd] || '未知阶段'; const phaseColor = realTimeData.zxjd === '1' || realTimeData.zxjd === '4' || realTimeData.zxjd === '5' ? '#9E9E9E' : '#FF9800'; html += `
设备信息
名称: ${realTimeData.name}
任务号: ${realTimeData.rwh}
执行阶段: ${phaseTextRgv}
运行状态
状态: ${statusText}
状态值: ${realTimeData.sbzt}
执行阶段
${phaseText}
位置信息
${realTimeData.ywz}
原位置(输送机ID)
${realTimeData.mbwz}
目标位置(输送机ID)
`; } html += `
`; } else { html += `
📊
暂无设备数据
请检查设备连接状态
`; } html += `
`; return html; } /** * 关闭设备信息弹框 */ function closeBox() { const box = document.getElementById('box'); if (box) { box.style.display = 'none'; } } /** * 初始化弹框拖动功能 */ function initBoxDrag() { const box = document.getElementById('box'); const header = document.getElementById('box-header'); if (!box || !header) return; let isDragging = false; let startX = 0; let startY = 0; let initialLeft = 0; let initialTop = 0; // 鼠标按下事件 header.addEventListener('mousedown', function(e) { e.preventDefault(); isDragging = true; // 记录初始位置 startX = e.clientX; startY = e.clientY; // 获取弹框当前位置 const rect = box.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; // 改变光标样式 document.body.style.cursor = 'move'; header.style.cursor = 'move'; // 添加全局事件监听 document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }); function handleMouseMove(e) { if (!isDragging) return; e.preventDefault(); // 计算移动距离 const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; // 计算新位置 let newLeft = initialLeft + deltaX; let newTop = initialTop + deltaY; // 边界检查 const boxWidth = box.offsetWidth; const boxHeight = box.offsetHeight; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; // 限制在窗口范围内 newLeft = Math.max(0, Math.min(newLeft, windowWidth - boxWidth)); newTop = Math.max(0, Math.min(newTop, windowHeight - boxHeight)); // 应用新位置 box.style.left = newLeft + 'px'; box.style.top = newTop + 'px'; box.style.transform = 'none'; // 移除居中变换 } function handleMouseUp(e) { if (!isDragging) return; isDragging = false; // 恢复光标样式 document.body.style.cursor = ''; header.style.cursor = 'move'; // 移除全局事件监听 document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); } } // 将函数暴露到全局作用域 window.showDeviceInfo = showDeviceInfo; window.closeBox = closeBox; window.initBoxDrag = initBoxDrag; // 页面加载完成后初始化拖动功能 $(document).ready(function() { initBoxDrag(); // 初始化报警系统 initAlarmSystem(); }); // ==================== 报警信息系统 ==================== /** * 初始化报警系统 * 系统启动时调用,开始报警监控 */ function initAlarmSystem() { // 先加载报警设置,检查是否有效 loadAlarmSettings().then(() => { // 只有在报警设置有效时才开始监控 const container = document.getElementById('alarm-container'); if (container && container.style.display !== 'none') { updateAlarmStatus(); } }); } /** * 更新报警状态 * 从后台获取最新的报警数据并更新显示 */ function updateAlarmStatus() { // 检查是否允许访问后台数据 if (!canAccessBackendData()) { return; } $.ajax({ url: API_URLS.alarm, type: 'GET', dataType: 'json', success: handleAlarmDataSuccess, error: handleAlarmDataError }); } /** * 处理报警数据获取成功的回调 * @param {Object} response 服务器响应数据 */ function handleAlarmDataSuccess(response) { // 验证响应数据有效性 if (!isValidAlarmResponse(response)) { showNoAlarmState(); return; } const alarms = response.alarm; // 根据报警数量决定显示方式 if (alarms.length === 0) { showNoAlarmState(); } else { showAlarmContainer(alarms); } } /** * 处理报警数据获取失败的回调 * @param {Object} xhr XMLHttpRequest对象 * @param {string} status 状态文本 * @param {string} error 错误信息 */ function handleAlarmDataError(xhr, status, error) { console.error('报警数据获取失败:', { status: status, error: error, responseText: xhr.responseText }); // 网络错误时显示无报警状态,避免界面异常 showNoAlarmState(); } // ==================== 报警系统模块 ==================== // 报警系统配置(可通过设置页面修改) let ALARM_CONFIG = { // 样式配置 STYLES: { NORMAL: { background: 'linear-gradient(135deg, #999999, #777777)', boxShadow: '0 4px 15px rgba(153, 153, 153, 0.3)' }, ALARM: { background: 'linear-gradient(135deg, #ff6b6b, #ee5a24)', boxShadow: '0 4px 15px rgba(255, 107, 107, 0.3)' } }, // 时间配置 TIMING: { NO_ALARM_CHECK_INTERVAL: 3000, // 无报警时检查间隔(毫秒) SHORT_CONTENT_CHECK_INTERVAL: 3000, // 短内容检查间隔(毫秒) SCROLL_SPEED: 50 // 滚动速度(像素/秒) }, // 文本配置 TEXT: { NO_ALARM: '暂无报警', SEPARATOR: '    ' }, // 报警框设置 CONTAINER: { width: 400, height: 60, x: 20, y: 20, position: 'bottom-center' } }; // API接口配置(从config.json中的apiSettings读取) let API_URLS = { stacker: 'json/ddj.json', conveyor: 'json/Lssj.json', rgv: 'json/rgv.json', alarm: 'json/alarm.json', config: 'json/config.json' }; // 报警系统全局变量 let alarmScrollTimer = null; // 滚动动画定时器 let currentAlarmData = []; // 当前报警数据缓存 let isScrolling = false; // 滚动状态标记 /** * 显示报警容器并更新内容 * @param {Array} alarms 报警数据数组 */ function showAlarmContainer(alarms) { const elements = getAlarmElements(); if (!elements) return; const { container, countElement, textElement } = elements; if (alarms.length === 0) { setNoAlarmState(container, countElement, textElement); return; } setAlarmState(container, countElement, textElement, alarms); } /** * 显示无报警状态(灰色状态) */ function showNoAlarmState() { const elements = getAlarmElements(); if (!elements) return; const { container, countElement, textElement } = elements; setNoAlarmState(container, countElement, textElement); } /** * 隐藏报警容器(仅在配置无效时使用) */ function hideAlarmContainer() { const container = document.getElementById('alarm-container'); if (container) { // 完全隐藏报警框 container.style.display = 'none'; // 停止所有报警相关的定时器和动画 stopAlarmScroll(); // 清除报警更新定时器 if (alarmUpdateTimer) { clearTimeout(alarmUpdateTimer); alarmUpdateTimer = null; } console.log('报警框已隐藏'); } } /** * 获取报警相关DOM元素 * @returns {Object|null} 包含DOM元素的对象或null */ function getAlarmElements() { const container = document.getElementById('alarm-container'); const countElement = document.getElementById('alarm-count'); const textElement = document.getElementById('alarm-text'); if (!container || !countElement || !textElement) { console.error('报警显示元素未找到'); return null; } return { container, countElement, textElement }; } /** * 设置无报警状态的样式和内容 * @param {HTMLElement} container 容器元素 * @param {HTMLElement} countElement 计数元素 * @param {HTMLElement} textElement 文本元素 */ function setNoAlarmState(container, countElement, textElement) { // 更新内容 countElement.textContent = '0'; textElement.textContent = ALARM_CONFIG.TEXT.NO_ALARM; // 设置样式 applyContainerStyle(container, ALARM_CONFIG.STYLES.NORMAL); // 停止滚动 stopAlarmScroll(); // 显示容器 container.style.display = 'block'; // 定时重新检查 setTimeout(() => { updateAlarmStatus(); }, ALARM_CONFIG.TIMING.NO_ALARM_CHECK_INTERVAL); } /** * 设置有报警状态的样式和内容 * @param {HTMLElement} container 容器元素 * @param {HTMLElement} countElement 计数元素 * @param {HTMLElement} textElement 文本元素 * @param {Array} alarms 报警数据数组 */ function setAlarmState(container, countElement, textElement, alarms) { // 更新报警数量 countElement.textContent = alarms.length; // 设置报警样式 applyContainerStyle(container, ALARM_CONFIG.STYLES.ALARM); // 构建滚动文本 const scrollText = buildAlarmText(alarms); // 检查内容是否变化 const contentChanged = JSON.stringify(alarms) !== JSON.stringify(currentAlarmData); if (contentChanged || !isScrolling) { currentAlarmData = [...alarms]; textElement.textContent = scrollText; startAlarmScroll(textElement); } // 显示容器 container.style.display = 'block'; } /** * 应用容器样式 * @param {HTMLElement} container 容器元素 * @param {Object} style 样式对象 */ function applyContainerStyle(container, style) { container.style.background = style.background; container.style.boxShadow = style.boxShadow; } /** * 构建报警文本 * @param {Array} alarms 报警数据数组 * @returns {string} 格式化的报警文本 */ function buildAlarmText(alarms) { const alarmTexts = alarms.map(alarm => { const errorCode = alarm.code || alarm.type || '未知'; return `【${alarm.id}${alarm.name}】错误代码: ${errorCode},报警信息:${alarm.content}`; }); return alarmTexts.join(ALARM_CONFIG.TEXT.SEPARATOR); } /** * 开始报警文本滚动 * @param {HTMLElement} textElement 文本元素 */ function startAlarmScroll(textElement) { // 停止当前滚动 stopAlarmScroll(); // 获取滚动容器和测量尺寸 const scrollContainer = document.getElementById('alarm-scroll'); if (!scrollContainer) { console.error('滚动容器未找到'); return; } const containerWidth = scrollContainer.offsetWidth; const textWidth = textElement.scrollWidth; // 判断是否需要滚动 if (textWidth <= containerWidth) { // 短内容居中显示 const centerPosition = (containerWidth - textWidth) / 2; textElement.style.left = centerPosition + 'px'; isScrolling = false; // 短内容定时重新检查 setTimeout(() => { updateAlarmStatus(); }, ALARM_CONFIG.TIMING.SHORT_CONTENT_CHECK_INTERVAL); return; } // 计算居中起始位置,但不小于50px const centerStartPosition = Math.max(50, (containerWidth - textWidth) / 2); // 设置初始居中位置 textElement.style.left = centerStartPosition + 'px'; isScrolling = true; // 延迟2秒后开始滚动,让用户看清第一条信息 setTimeout(() => { if (isScrolling) { // 确保在延迟期间没有被停止 // 计算滚动参数 - 只需要滚动到文字完全离开容器即可 const scrollDistance = textWidth; const scrollDuration = scrollDistance / ALARM_CONFIG.TIMING.SCROLL_SPEED; // 从居中位置开始滚动到文字完全离开容器 animateScroll(textElement, centerStartPosition, -scrollDistance, scrollDuration * 1000); } }, 2000); // 2秒延迟 } /** * 执行滚动动画 * @param {HTMLElement} element 文本元素 * @param {number} startPos 开始位置(像素) * @param {number} endPos 结束位置(像素) * @param {number} duration 持续时间(毫秒) */ function animateScroll(element, startPos, endPos, duration) { const startTime = Date.now(); function step() { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); // 线性插值计算当前位置 const currentPos = startPos + (endPos - startPos) * progress; element.style.left = currentPos + 'px'; if (progress < 1) { // 继续动画 alarmScrollTimer = requestAnimationFrame(step); } else { // 滚动完成,重新获取数据 isScrolling = false; updateAlarmStatus(); } } alarmScrollTimer = requestAnimationFrame(step); } /** * 停止报警滚动动画 */ function stopAlarmScroll() { if (alarmScrollTimer) { cancelAnimationFrame(alarmScrollTimer); alarmScrollTimer = null; } isScrolling = false; } /** * 验证报警响应数据是否有效 * @param {Object} response 响应数据 * @returns {boolean} 数据是否有效 */ function isValidAlarmResponse(response) { return response && typeof response === 'object' && Array.isArray(response.alarm); } /** * 从配置文件加载API设置 */ async function loadApiSettings() { $.ajax({ url: API_URLS.config, type: 'GET', dataType: 'json', cache: false, success(res){ if (res.apiSettings){ const settings = res.apiSettings; // 更新API URL配置 API_URLS.stacker = settings.stackerApiUrl || 'json/ddj.json'; API_URLS.conveyor = settings.conveyorApiUrl || 'json/Lssj.json'; API_URLS.rgv = settings.rgvApiUrl || 'json/rgv.json'; API_URLS.alarm = settings.alarmApiUrl || 'json/alarm.json'; API_URLS.config = settings.configApiUrl || 'json/config.json'; console.log('API设置已更新:', API_URLS); } }, error(error){ console.error('加载API设置失败:', error); } }); } /** * 从配置文件加载报警设置 */ async function loadAlarmSettings() { $.ajax({ url: API_URLS.config, type: 'GET', dataType: 'json', cache: false, success(res) { if (res.alarmSettings) { const settings = res.alarmSettings; // 检查报警设置是否有效(非空且非零) const isValidSettings = settings.width > 0 && settings.height > 0 && settings.position && settings.position.trim() !== ''; if (isValidSettings) { // 更新配置 ALARM_CONFIG.CONTAINER.width = settings.width; ALARM_CONFIG.CONTAINER.height = settings.height; ALARM_CONFIG.CONTAINER.x = settings.x || 20; ALARM_CONFIG.CONTAINER.y = settings.y || 20; ALARM_CONFIG.CONTAINER.position = settings.position; ALARM_CONFIG.TIMING.SCROLL_SPEED = settings.scrollSpeed || 50; // 应用设置到报警框 applyAlarmContainerSettings(); console.log('报警设置已加载并应用'); } else { // 报警设置无效,隐藏报警框 hideAlarmContainer(); console.log('报警设置无效,已隐藏报警框'); } } else { // 没有报警设置,隐藏报警框 hideAlarmContainer(); console.log('未找到报警设置,已隐藏报警框'); } }, error(error){ console.error('加载报警设置失败:', error); // 加载失败时也隐藏报警框 hideAlarmContainer(); } }) } /** * 应用报警容器设置 */ function applyAlarmContainerSettings() { const container = document.getElementById('alarm-container'); if (!container) return; const settings = ALARM_CONFIG.CONTAINER; // 设置尺寸 container.style.width = settings.width + 'px'; container.style.height = settings.height + 'px'; // 设置位置 switch (settings.position) { case 'top-left': container.style.top = settings.y + 'px'; container.style.left = settings.x + 'px'; container.style.bottom = 'auto'; container.style.right = 'auto'; container.style.transform = 'none'; break; case 'top-right': container.style.top = settings.y + 'px'; container.style.right = settings.x + 'px'; container.style.bottom = 'auto'; container.style.left = 'auto'; container.style.transform = 'none'; break; case 'bottom-left': container.style.bottom = settings.y + 'px'; container.style.left = settings.x + 'px'; container.style.top = 'auto'; container.style.right = 'auto'; container.style.transform = 'none'; break; case 'bottom-right': container.style.bottom = settings.y + 'px'; container.style.right = settings.x + 'px'; container.style.top = 'auto'; container.style.left = 'auto'; container.style.transform = 'none'; break; case 'bottom-center': default: container.style.bottom = settings.y + 'px'; container.style.left = '50%'; container.style.top = 'auto'; container.style.right = 'auto'; container.style.transform = 'translateX(-50%)'; break; case 'custom': container.style.left = settings.x + 'px'; container.style.top = settings.y + 'px'; container.style.bottom = 'auto'; container.style.right = 'auto'; container.style.transform = 'none'; break; } } /** * 监听配置设置更新 */ function setupConfigSettingsListener() { if (window.BroadcastChannel) { const bc = new BroadcastChannel('config_update_channel'); bc.onmessage = (event) => { if (event.data.type === 'alarm_settings_updated') { console.log('收到报警设置更新通知'); loadAlarmSettings(); } else if (event.data.type === 'api_settings_updated') { console.log('收到API设置更新通知'); loadApiSettings(); } }; } } // 在页面加载时初始化配置设置 $(document).ready(function() { loadApiSettings(); loadAlarmSettings(); setupConfigSettingsListener(); });