1965 lines
62 KiB
JavaScript
1965 lines
62 KiB
JavaScript
|
||
// 查询堆垛机数据列表
|
||
function listDdj() {
|
||
request({
|
||
url: '/dev-api/dynamicMonitoring/list/ddjList',
|
||
method: 'get'
|
||
}).then(response => {
|
||
handleStackerDataSuccess(response)
|
||
}).catch(error => {
|
||
console.error('堆垛机数据获取失败:', error);
|
||
});
|
||
}
|
||
|
||
|
||
// 查询输送机数据列表
|
||
function listSsj() {
|
||
request({
|
||
url: '/dev-api/dynamicMonitoring/list/ssjList',
|
||
method: 'get'
|
||
}).then(response => {
|
||
handleConveyorDataSuccess(response)
|
||
}).catch(error => {
|
||
console.error('输送机数据获取失败:', error)
|
||
});
|
||
}
|
||
// 查询报警信息数据列表
|
||
function listAlarm() {
|
||
request({
|
||
url: '/dev-api/dynamicMonitoring/list/alarmList',
|
||
method: 'get'
|
||
}).then(response => {
|
||
console.log("报警信息:",response)
|
||
handleAlarmDataSuccess(response)
|
||
}).catch(error => {
|
||
handleAlarmDataError(response)
|
||
});
|
||
}
|
||
/**
|
||
* 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;
|
||
}
|
||
|
||
listSsj()
|
||
}
|
||
|
||
/**
|
||
* 处理输送机数据获取成功
|
||
* @param {Object} response 服务器响应数据
|
||
*/
|
||
function handleConveyorDataSuccess(response) {
|
||
if (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) > 3;
|
||
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 (response.data.length==0 || configEditMode != 2) {
|
||
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
|
||
request({
|
||
url: '/dev-api/dynamicMonitoring/list/ddjList',
|
||
method: 'get'
|
||
}).then(response => {
|
||
if (response && response.data) {
|
||
const deviceData = response.data.find(item => item.id === deviceId);
|
||
resolve(deviceData || null);
|
||
} else {
|
||
resolve(null);
|
||
}
|
||
}).catch(error => {
|
||
console.error('获取堆垛机数据失败:', error);
|
||
reject('获取堆垛机数据失败');
|
||
});
|
||
} else if (deviceType === 'conveyor' || deviceType === 'rollerConveyor') {
|
||
// 获取输送机实时数据 - 使用配置的URL
|
||
request({
|
||
url: '/dev-api/dynamicMonitoring/list/ssjList',
|
||
method: 'get'
|
||
}).then(response => {
|
||
if (response && response.data) {
|
||
const deviceData = response.data.find(item => item.id === deviceId);
|
||
resolve(deviceData || null);
|
||
} else {
|
||
resolve(null);
|
||
}
|
||
}).catch(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);
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
|
||
function ssjSbzt(a){
|
||
if (a==0){
|
||
return"未知"
|
||
}else if(a==1){
|
||
return "手动"
|
||
}else if(a==2){
|
||
return "自动"
|
||
}else if (a==3){
|
||
return "运行"
|
||
}else if (a==4){
|
||
return "输送超时"
|
||
}else if(a==5){
|
||
return "断路器跳闸"
|
||
}else if (a==6){
|
||
return "变频器报警"
|
||
}else if (a==7){
|
||
return "开关任务异常"
|
||
}else if (a==8){
|
||
return "急停"
|
||
}else if (a==9){
|
||
return "超界"
|
||
}else if(a==10){
|
||
return "外型异常"
|
||
}else if(a==11){
|
||
return "称重异常"
|
||
}else if(a==12){
|
||
return "扫码异常"
|
||
}else {
|
||
return a;
|
||
}
|
||
}
|
||
function ddjSbzt(a){
|
||
if (a<1000){
|
||
return "正常"
|
||
}else if(a==1031){
|
||
return "急停按下"
|
||
}else if (a==1032){
|
||
return "货叉不在中位"
|
||
}else if (a==1033){
|
||
return "运行变频器报警"
|
||
}else if (a==1034){
|
||
return "升降变频器报警"
|
||
}else if (a==1035){
|
||
return "货叉变频器报警"
|
||
}else if (a==1037){
|
||
return "运行限位故障"
|
||
}else if(a==1038){
|
||
return "升降限位故障"
|
||
}else if (a==1039){
|
||
return "运行不能纠偏"
|
||
}else if (a==1040){
|
||
return "升降不能纠偏"
|
||
}else if (a==1041){
|
||
return "水平测距出错"
|
||
}else if (a==1042){
|
||
return "垂直BPS出错"
|
||
}else if (a==1043){
|
||
return "超速保护"
|
||
}else if (a==1044){
|
||
return "前边超宽"
|
||
}else if (a==1045){
|
||
return "后边超宽"
|
||
}else if (a==1046){
|
||
return "左边歪斜"
|
||
}else if (a==1047){
|
||
return "右边歪斜"
|
||
}else if (a==1048){
|
||
return "超高1"
|
||
}else if (a==1049){
|
||
return "超高2"
|
||
}else if (a==1050){
|
||
return "放货完毕货叉有货"
|
||
}else if (a==1051){
|
||
return "取货时货叉有货"
|
||
}else if (a==1052){
|
||
return "放货目标有货"
|
||
}else if (a==1053){
|
||
return "取货完毕无货"
|
||
}else if (a==1054){
|
||
return "放货时货叉无货"
|
||
}else if (a==1055){
|
||
return "货叉动作超时"
|
||
}else if (a==1056){
|
||
return "货叉不能纠偏"
|
||
}else if (a==1057){
|
||
return "微升降超时"
|
||
}else if (a==1058){
|
||
return "松绳过载"
|
||
}else if (a==1059){
|
||
return "设定超限"
|
||
}else if (a==1062){
|
||
return "入库输送机任务号不符"
|
||
}else if (a==1063){
|
||
return "安全门打开"
|
||
}else if (a==1064){
|
||
return "与安全门通讯中断"
|
||
}else {
|
||
return a
|
||
}
|
||
}
|
||
/**
|
||
* 构建设备信息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': '<img src="img/Lssj.png" style="width: 60px; height: 32px; vertical-align: middle;">',
|
||
'rollerConveyor': '<img src="img/Gssj.png" style="width: 30px; height: 60px; vertical-align: middle; transform: rotate(90deg);">',
|
||
'stacker': '<img src="img/ddj.png" style="width: 60px; height: 30px; vertical-align: middle;">',
|
||
'rgv': '<img src="img/rgv.png" style="width: 35px; height: 30px; vertical-align: middle;">' };
|
||
const deviceIcon = deviceIcons[deviceType] || '⚙️';
|
||
|
||
let html = `
|
||
<div style="
|
||
background: linear-gradient(135deg, #f8fffe 0%, #e8f5f4 100%);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin: -20px;
|
||
box-shadow: inset 0 1px 3px rgba(0,102,102,0.1);
|
||
">
|
||
<!-- 设备标题区域 -->
|
||
<div style="
|
||
text-align: center;
|
||
margin-bottom: 25px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 2px solid rgba(0,102,102,0.1);
|
||
">
|
||
<div style="
|
||
font-size: 32px;
|
||
margin-bottom: 8px;
|
||
filter: drop-shadow(0 2px 4px rgba(0,102,102,0.2));
|
||
">${deviceIcon}</div>
|
||
<h3 style="
|
||
color: #006666;
|
||
font-size: 20px;
|
||
margin: 0 0 8px 0;
|
||
font-weight: 600;
|
||
text-shadow: 0 1px 2px rgba(0,102,102,0.1);
|
||
">${typeName}</h3>
|
||
<div style="
|
||
background: linear-gradient(90deg, #006666, #008080);
|
||
color: white;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
display: inline-block;
|
||
box-shadow: 0 2px 4px rgba(0,102,102,0.3);
|
||
">ID: ${displayId}</div>
|
||
</div>
|
||
`;
|
||
|
||
if (realTimeData) {
|
||
html += `
|
||
<!-- 设备信息网格 -->
|
||
<div style="
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
">
|
||
`;
|
||
|
||
if (deviceType === 'stacker') {
|
||
// 堆垛机字段:基于ddj.json
|
||
const statusValue = parseInt(realTimeData.sbzt);
|
||
const statusColor = statusValue > 1000 ? '#ff4444' : '#00aa00';
|
||
const statusText = ddjSbzt(statusValue);
|
||
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 += `
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid #006666;
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">设备信息</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;"><strong>名称:</strong> ${realTimeData.name}</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;"><strong>执行阶段:</strong> ${phaseText}</div>
|
||
<div style="font-size: 14px;"><strong>任务号:</strong> ${realTimeData.rwh}</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: ${statusBgColor};
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid ${statusColor};
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">运行状态</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;">
|
||
<strong>状态:</strong>
|
||
<span style="
|
||
color: ${statusColor};
|
||
font-weight: bold;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
background: white;
|
||
font-size: 12px;
|
||
">${statusText}</span>
|
||
</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;"><strong>状态值:</strong> ${realTimeData.sbzt}</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid #4CAF50;
|
||
grid-column: 1 / -1;
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 10px;">位置信息</div>
|
||
<div style="display: flex; justify-content: space-around; text-align: center;">
|
||
<div>
|
||
<div style="font-size: 18px; font-weight: bold; color: #4CAF50;">${realTimeData.yp}排${realTimeData.ywz}列${realTimeData.yc}层</div>
|
||
<div style="font-size: 12px; color: #666;">原位置</div>
|
||
</div>
|
||
<div style="align-self: center; color: #ccc; font-size: 20px;">→</div>
|
||
<div>
|
||
<div style="font-size: 18px; font-weight: bold; color: #2196F3;">${realTimeData.mbp}排${realTimeData.mbwz}列${realTimeData.mbc}层</div>
|
||
<div style="font-size: 12px; color: #666;">目标位置</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else if (deviceType === 'conveyor' || deviceType === 'rollerConveyor') {
|
||
// 输送机字段:基于Lssj.json
|
||
const statusValue = parseInt(realTimeData.sbzt);
|
||
const statusColor = statusValue > 3 ? '#ff4444' : '#00aa00';
|
||
const statusText = ssjSbzt(statusValue)
|
||
const statusBgColor = statusValue > 3 ? '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 += `
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid #006666;
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">设备信息</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;"><strong>名称:</strong> ${realTimeData.name}</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;"><strong>任务号:</strong> ${realTimeData.rwh}</div>
|
||
<div style="font-size: 14px;"><strong>托盘编码:</strong> ${realTimeData.trayCode}</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: ${statusBgColor};
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid ${statusColor};
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">运行状态</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;">
|
||
<strong>状态:</strong>
|
||
<span style="
|
||
color: ${statusColor};
|
||
font-weight: bold;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
background: white;
|
||
font-size: 12px;
|
||
">${statusText}</span>
|
||
</div>
|
||
<div style="font-size: 14px;"><strong>状态值:</strong> ${realTimeData.sbzt}</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid ${switchColor};
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">开关状态</div>
|
||
<div style="
|
||
text-align: center;
|
||
padding: 10px;
|
||
background: rgba(76,175,80,0.1);
|
||
border-radius: 6px;
|
||
">
|
||
<div style="
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: ${switchColor};
|
||
">${kxText}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid ${cargoColor};
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">货物状态</div>
|
||
<div style="
|
||
text-align: center;
|
||
padding: 10px;
|
||
background: rgba(255,152,0,0.1);
|
||
border-radius: 6px;
|
||
">
|
||
<div style="
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: ${cargoColor};
|
||
">${hasCargoText}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} 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 += `
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid #006666;
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">设备信息</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;"><strong>名称:</strong> ${realTimeData.name}</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;"><strong>任务号:</strong> ${realTimeData.rwh}</div>
|
||
<div style="font-size: 14px;"><strong>执行阶段:</strong> ${phaseTextRgv}</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: ${statusBgColor};
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid ${statusColor};
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">运行状态</div>
|
||
<div style="margin-bottom: 8px; font-size: 14px;">
|
||
<strong>状态:</strong>
|
||
<span style="
|
||
color: ${statusColor};
|
||
font-weight: bold;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
background: white;
|
||
font-size: 12px;
|
||
">${statusText}</span>
|
||
</div>
|
||
<div style="font-size: 14px;"><strong>状态值:</strong> ${realTimeData.sbzt}</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid ${phaseColor};
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 5px;">执行阶段</div>
|
||
<div style="
|
||
text-align: center;
|
||
padding: 10px;
|
||
background: rgba(255,152,0,0.1);
|
||
border-radius: 6px;
|
||
">
|
||
<div style="
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: ${phaseColor};
|
||
">${phaseText}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid #2196F3;
|
||
">
|
||
<div style="color: #666; font-size: 12px; margin-bottom: 10px;">位置信息</div>
|
||
<div style="
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 15px;
|
||
">
|
||
<div style="
|
||
text-align: center;
|
||
padding: 12px;
|
||
background: rgba(33,150,243,0.1);
|
||
border-radius: 6px;
|
||
">
|
||
<div style="
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #2196F3;
|
||
margin-bottom: 4px;
|
||
">${realTimeData.ywz}</div>
|
||
<div style="font-size: 12px; color: #666;">原位置(输送机ID)</div>
|
||
</div>
|
||
<div style="
|
||
text-align: center;
|
||
padding: 12px;
|
||
background: rgba(76,175,80,0.1);
|
||
border-radius: 6px;
|
||
">
|
||
<div style="
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #4CAF50;
|
||
margin-bottom: 4px;
|
||
">${realTimeData.mbwz}</div>
|
||
<div style="font-size: 12px; color: #666;">目标位置(输送机ID)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
html += `</div>`;
|
||
} else {
|
||
html += `
|
||
<div style="
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
margin-bottom: 20px;
|
||
">
|
||
<div style="font-size: 48px; margin-bottom: 15px; opacity: 0.3;">📊</div>
|
||
<div style="color: #999; font-size: 16px; font-weight: 500;">暂无设备数据</div>
|
||
<div style="color: #ccc; font-size: 12px; margin-top: 5px;">请检查设备连接状态</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
html += `
|
||
<!-- 关闭按钮 -->
|
||
<div style="text-align: center; margin-top: 25px;">
|
||
<button onclick="closeBox()" style="
|
||
background: linear-gradient(135deg, #006666 0%, #008080 100%);
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 50px;
|
||
border-radius: 25px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 12px rgba(0,102,102,0.3);
|
||
letter-spacing: 0.5px;
|
||
min-width: 120px;
|
||
"
|
||
onmouseover="
|
||
this.style.transform='translateY(-2px)';
|
||
this.style.boxShadow='0 6px 16px rgba(0,102,102,0.4)';
|
||
"
|
||
onmouseout="
|
||
this.style.transform='translateY(0)';
|
||
this.style.boxShadow='0 4px 12px rgba(0,102,102,0.3)';
|
||
"
|
||
onmousedown="this.style.transform='translateY(0)';">
|
||
✕ 关闭
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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;
|
||
}
|
||
|
||
listAlarm()
|
||
}
|
||
|
||
/**
|
||
* 处理报警数据获取成功的回调
|
||
* @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 || '未知';
|
||
if(alarm.name=="堆垛机"){
|
||
return `【${alarm.id}${alarm.name}】错误代码: ${errorCode},报警信息:${ddjSbzt(errorCode)}`;
|
||
}else if(alarm.name=="输送机"){
|
||
return `【${alarm.id}${alarm.name}】错误代码: ${errorCode},报警信息:${ssjSbzt(errorCode)}`;
|
||
}else {
|
||
return `【${alarm.id}${alarm.name}】错误代码: ${errorCode},报警信息:未知`;
|
||
}
|
||
});
|
||
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();
|
||
});
|