Files
FT577-WCS/ruoyi-ui/public/monitor/js/index.js
猫猫guowen 0f2721f479 2D监控
2025-09-30 15:33:28 +08:00

1959 lines
62 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 查询堆垛机数据列表
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 || '未知';
return `【${alarm.id}${alarm.name}】错误代码: ${errorCode},报警信息:${ddjSbzt(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();
});