Files
lanzhouhailiang_one/lms/nladmin-system/doc/套轴点位日志记录实时看板v4.html
2025-09-26 13:32:50 +08:00

619 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>套轴点位日志记录实时看板</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #183a5c 0%, #274b7a 100%); /* 深科技蓝渐变背景 */
color: #cfd8e3; /* 浅灰蓝文本 */
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
color: #ffffff; /* 亮蓝标题40a9ff */
text-align: center;
font-size: 2.5em;
margin-bottom: 30px;
text-shadow: 0 2px 12px #274b7a;
}
.dashboard-container {
display: flex;
justify-content: space-around;
width: 100%;
max-width: 1800px;
margin-bottom: 30px;
}
.device-card {
background: #223a5c; /* 中深蓝卡片背景 */
border: 1px solid #40a9ff; /* 亮蓝边框 */
border-radius: 10px;
padding: 20px;
width: 45%;
min-height: 300px;
height: 350px; /* 新增:固定高度 */
box-shadow: 0 2px 16px rgba(64,169,255,0.12);
transition: transform 0.3s, box-shadow 0.3s;
display: flex;
flex-direction: column;
}
.log-entries {
flex: 1;
overflow-y: auto;
max-height: 250px;
/* 美化滚动条 */
scrollbar-width: thin;
scrollbar-color: #40a9ff #223a5c;
}
.log-entries::-webkit-scrollbar {
width: 8px;
}
.log-entries::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #40a9ff 0%, #274b7a 100%);
border-radius: 6px;
}
.log-entries::-webkit-scrollbar-track {
background: #223a5c;
border-radius: 6px;
}
/* .device-card:hover {
transform: translateY(-5px) scale(1.02);
box-shadow: 0 0 30px #40a9ff;
} */
.device-card h2 {
color: #ffffff;
border-bottom: 1px solid #274b7a;
padding-bottom: 10px;
margin-top: 0;
font-size: 1.5em;
}
.log-entry {
background: #274b7a;
color: #cfd8e3;
padding: 8px;
margin-bottom: 8px;
border-radius: 4px;
border-left: 3px solid #40a9ff;
font-size: 0.95em;
word-wrap: break-word;
}
.table-container {
width: 95%;
max-width: 1800px;
background: #223a5c;
border: 1px solid #40a9ff;
border-radius: 10px;
padding: 0 20px 20px 20px;
margin-top: 20px;
box-shadow: 0 2px 16px rgba(64,169,255,0.12);
}
.table-container h2 {
color: #ffffff;
text-align: center;
font-size: 1.8em;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
color: #cfd8e3;
background: #274b7a;
}
th, td {
border: 1px solid #3b6ea5;
padding: 10px;
text-align: left;
font-size: 0.95em;
}
th {
background: linear-gradient(90deg, #274b7a 0%, #3b6ea5 100%);
color: #40a9ff;
}
tbody tr:nth-child(odd) {
background: #223a5c;
}
tbody tr:hover {
background: #3b6ea5;
}
.timeout-row-warning {
background: #8B0000 !important; /* 红色背景 */
color: #ffffff;
}
.timeout-row-warning td {
color: #ffffff !important;
}
.timeout-row-error {
background: #FFC107 !important; /* 黄色背景 */
color: #000000;
}
.timeout-row-error td {
color: #000000 !important;
}
/* 添加颜色图例样式 */
.color-legend {
display: flex;
justify-content: center;
margin-bottom: 15px;
gap: 20px;
}
.legend-item {
display: flex;
align-items: center;
font-size: 0.9em;
}
.color-box {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 8px;
border-radius: 3px;
}
.red-box {
background-color: #8B0000;
}
.yellow-box {
background-color: #FFC107;
}
.grayed-box {
background-color: #807132;
}
/* 命中当前设备日志中的设备号时置灰 */
.grayed-row {
background: #807132 !important;
color: #ffffff !important;
}
.grayed-row td {
color: #ffffff !important;
}
</style>
</head>
<body>
<h1>套轴点位日志记录实时看板</h1>
<div class="dashboard-container">
<div class="device-card" id="device-B_CBJ01">
<h2>B_CBJ01<span id="tip-B_CBJ01" style="font-size:0.7em;color:#ffec3d;margin-left:12px;"></span></h2>
<!-- 添加tip2显示区域 -->
<div id="tip2-B_CBJ01" style="color:#ffec3d;font-size:0.9em;margin-bottom:10px;background:rgba(64,169,255,0.1);padding:5px;border-radius:4px;"></div>
<div class="log-entries">
<!-- 日志条目将在这里动态添加 -->
</div>
</div>
<div class="device-card" id="device-B_CBJ02">
<h2>B_CBJ02<span id="tip-B_CBJ02" style="font-size:0.7em;color:#ffec3d;margin-left:12px;"></span></h2>
<!-- 添加tip2显示区域 -->
<div id="tip2-B_CBJ02" style="color:#ffec3d;font-size:0.9em;margin-bottom:10px;background:rgba(64,169,255,0.1);padding:5px;border-radius:4px;"></div>
<div class="log-entries">
<!-- 日志条目将在这里动态添加 -->
</div>
</div>
</div>
<!-- 新增的表格容器 -->
<div class="table-container">
<h2>套轴监控系统实时数据</h2>
<!-- 添加颜色图例 -->
<div class="color-legend">
<span class="legend-item"><span class="color-box red-box"></span>呼叫超过2小时</span>
<span class="legend-item"><span class="color-box yellow-box"></span>存在异常</span>
<span class="legend-item"><span class="color-box grayed-box"></span>暂时不套轴</span>
</div>
<table id="slitter-table">
<thead>
<tr>
<th>设备</th>
<th>子卷号</th>
<th>轴位置</th>
<th>气胀轴尺寸</th>
<th>气胀轴代数</th>
<th>气胀轴状态</th>
<th>呼叫时间</th>
<th>管芯规格</th>
<th>套轴标记</th>
<th>是否加急</th>
<th>一键恢复</th>
</tr>
</thead>
<tbody id="slitter-table-body">
<!-- 表格数据将在这里动态添加 -->
</tbody>
</table>
</div>
<script>
// API URL 配置
const BASE_URL = 'http://10.1.3.91:8013';
const API_CONFIG = {
logApiUrl: `${BASE_URL}/api/wms/apply/v2/tzInfo`,
tableApiUrl: `${BASE_URL}/api/pda/slitter/showManualView`,
recoverApiUrl: `${BASE_URL}/api/wms/apply/recover`,
tipApiUrl: `${BASE_URL}/api/wms/apply/v2/tzTaskINfo`
};
const refreshInterval = 5000; // 刷新间隔统一为5秒
const devices = ['B_CBJ01', 'B_CBJ02'];
// 保存从两个设备日志中提取去重后的设备号集合
let latestDeviceIds = new Set();
async function fetchLogData(deviceCode) {
try {
const response = await fetch(API_CONFIG.logApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ device_code: deviceCode }),
});
if (!response.ok) {
const errorText = await response.text();
console.error(`HTTP error response for ${deviceCode} (log):`, errorText);
throw new Error(`HTTP error! status: ${response.status} for ${deviceCode} (log). Response: ${errorText}`);
}
const responseText = await response.text();
if (!responseText) {
console.warn(`Empty response received for ${deviceCode} (log)`);
return [];
}
try {
const data = JSON.parse(responseText);
return data;
} catch (e) {
console.error(`Error parsing JSON for ${deviceCode} (log):`, e);
console.error(`Raw response text for ${deviceCode} (log):`, responseText);
throw new Error(`Failed to parse JSON response for ${deviceCode} (log). Raw text: ${responseText.substring(0, 100)}...`);
}
} catch (error) {
console.error(`Error fetching log data for ${deviceCode}:`, error);
return [`获取 ${deviceCode} 日志数据失败: ${error.message}`];
}
}
function displayLogData(deviceCode, data) {
const deviceElement = document.getElementById(`device-${deviceCode}`);
if (!deviceElement) {
console.error(`Element with ID 'device-${deviceCode}' not found.`);
return;
}
const logEntriesContainer = deviceElement.querySelector('.log-entries');
logEntriesContainer.innerHTML = '';
if (Array.isArray(data) && data.length > 0) {
data.forEach(entry => {
const logEntryDiv = document.createElement('div');
logEntryDiv.classList.add('log-entry');
logEntryDiv.textContent = entry;
logEntriesContainer.appendChild(logEntryDiv);
});
} else {
const noDataEntry = document.createElement('div');
noDataEntry.classList.add('log-entry');
noDataEntry.textContent = '暂无日志信息。';
logEntriesContainer.appendChild(noDataEntry);
}
}
async function updateLogDashboard() {
const aggregatedIds = new Set();
for (const device of devices) {
const data = await fetchLogData(device);
displayLogData(device, data);
if (Array.isArray(data)) {
data.forEach(line => {
if (typeof line === 'string' && line.length > 0) {
const ids = extractDeviceIds(line);
if (Array.isArray(ids)) {
ids.forEach(id => aggregatedIds.add(id));
}
}
});
}
}
latestDeviceIds = aggregatedIds;
}
// 新增表格数据相关逻辑
async function fetchTableData() {
try {
const response = await fetch(API_CONFIG.tableApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
project: "套轴监控系统",
timestamp: Date.now() // 使用当前时间戳
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error('HTTP error response for table data:', errorText);
throw new Error(`HTTP error! status: ${response.status} for table data. Response: ${errorText}`);
}
const responseText = await response.text();
if (!responseText) {
console.warn('Empty response received for table data');
return [];
}
try {
const data = JSON.parse(responseText);
return data;
} catch (e) {
console.error('Error parsing JSON for table data:', e);
console.error('Raw response text for table data:', responseText);
throw new Error(`Failed to parse JSON response for table data. Raw text: ${responseText.substring(0, 100)}...`);
}
} catch (error) {
console.error('Error fetching table data:', error);
// 可以选择在表格中显示错误信息
const tableBody = document.getElementById('slitter-table-body');
if (tableBody) {
tableBody.innerHTML = `<tr><td colspan="7">获取表格数据失败: ${error.message}</td></tr>`;
}
return []; // 返回空数组以避免后续处理错误
}
}
function displayTableData(data) {
const tableBody = document.getElementById('slitter-table-body');
if (!tableBody) {
console.error("Element with ID 'slitter-table-body' not found.");
return;
}
tableBody.innerHTML = ''; // 清空旧数据
if (Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const row = tableBody.insertRow();
row.insertCell().textContent = item.resource_name || 'N/A';
row.insertCell().textContent = item.container_name || 'N/A';
row.insertCell().textContent = item.up_or_down === '1' ? '上' : (item.up_or_down === '2' ? '下' : item.up_or_down || 'N/A');
row.insertCell().textContent = item.qzz_size || 'N/A';
row.insertCell().textContent = item.qzz_generation || 'N/A';
let statusText = '已完成';
if (item.status === '01') {
if (item.is_paper_ok === '2') {
statusText = '等待配送';
} else if(item.is_paper_ok === '3') {
statusText = '套轴执行中';
} else {
statusText = '准备套轴';
}
} else if (item.status === '02') {
statusText = '正在配送';
} else if (item.status === '03') {
statusText = '配送完成';
} else if (item.status) {
statusText = '已完成';
}
row.insertCell().textContent = statusText;
const startTimeCell = row.insertCell();
startTimeCell.textContent = item.start_time || 'N/A';
row.insertCell().textContent = item.tube || 'N/A';
let tzText = '未套轴';
if (item.is_paper_ok === '2') {
tzText = '已套轴';
}
if (item.is_paper_ok === '3') {
tzText = '正在套轴';
}
if (item.is_paper_ok === '4') {
tzText = '已下发套轴';
}
if (item.is_paper_ok === '95') {
tzText = '等待更换管芯托盘';
}
if (item.is_paper_ok === '99') {
tzText = '套轴异常';
}
row.insertCell().textContent = tzText || 'N/A';
// 是否加急
let urgentText = '未加急';
if (item.manufacture_sort === 'P2') {
urgentText = '已加急';
}
row.insertCell().textContent = urgentText || 'N/A';
// 添加恢复按钮
const recoverCell = row.insertCell();
const recoverBtn = document.createElement('button');
recoverBtn.className = 'recover-btn';
recoverBtn.textContent = '恢复';
recoverBtn.disabled = item.is_paper_ok !== '99' && item.is_paper_ok !== '95';
recoverBtn.onclick = async () => {
try {
const response = await fetch(API_CONFIG.recoverApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
container_name: item.container_name
})
});
if (response.ok) {
const alertDiv = document.createElement('div');
alertDiv.style.position = 'fixed';
alertDiv.style.top = '50%';
alertDiv.style.left = '50%';
alertDiv.style.transform = 'translate(-50%, -50%)';
alertDiv.style.padding = '20px 40px';
alertDiv.style.background = 'linear-gradient(135deg, #40a9ff 0%, #096dd9 100%)';
alertDiv.style.color = 'white';
alertDiv.style.borderRadius = '8px';
alertDiv.style.boxShadow = '0 4px 16px rgba(0,0,0,0.3)';
alertDiv.style.zIndex = '1000';
alertDiv.textContent = '恢复成功';
document.body.appendChild(alertDiv);
setTimeout(() => alertDiv.remove(), 2000);
// 刷新数据
updateTableDashboard();
} else {
throw new Error('恢复失败');
}
} catch (error) {
console.error('Error:', error);
alert('恢复失败:' + error.message);
}
};
recoverCell.appendChild(recoverBtn);
// 检查start_time是否超过2小时并设置行样式
if (item.start_time) {
const startTime = new Date(item.start_time.replace(/-/g, '/'));
const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000);
if (startTime < twoHoursAgo) {
row.classList.add('timeout-row-warning'); // 应用于整行
}
}
if (item.is_paper_ok === '99') {
row.classList.add('timeout-row-error'); // 应用于整行
}
// 若该记录的resource_name包含在两个设备日志提取的设备号集合中则置灰显示
if (item.status === '01' && tzText === '未套轴' && item.resource_name && latestDeviceIds && latestDeviceIds.has(item.resource_name)) {
row.classList.add('grayed-row');
}
});
} else {
const row = tableBody.insertRow();
const cell = row.insertCell();
cell.colSpan = 11;
cell.textContent = '暂无数据。';
cell.style.textAlign = 'center';
}
}
async function updateTableDashboard() {
const data = await fetchTableData();
displayTableData(data);
}
// 新增获取tip并显示在标题后
async function fetchAndDisplayTip(deviceCode) {
try {
const response = await fetch(API_CONFIG.tipApiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ device_code: deviceCode })
});
let tip = '';
let tip2 = '';
if (response.ok) {
const data = await response.json();
tip = data.tip || '';
tip2 = data.tip2 || '';
// 显示tip2在对应设备标题下方
document.getElementById('tip2-' + deviceCode).textContent = tip2;
}
document.getElementById('tip-' + deviceCode).textContent = tip;
} catch (e) {
document.getElementById('tip-' + deviceCode).textContent = '';
document.getElementById('tip2-' + deviceCode).textContent = '';
}
}
function updateAllTips() {
devices.forEach(device => fetchAndDisplayTip(device));
}
// 提取设备号
function extractDeviceIds(text) {
// 正则表达式匹配10位字符第一个是字母第四第五是FQ
const regex = /\b[A-Za-z][A-Za-z0-9]{2}FQ[A-Za-z0-9]{5}\b/g;
const matches = text.match(regex);
// 去除重复的设备ID
return matches ? [...new Set(matches)] : [];
}
// 顺序刷新:先日志,再表格
async function refreshAll() {
await updateLogDashboard();
await updateTableDashboard();
}
let isRefreshing = false;
// 初始加载tip
updateAllTips();
// 定时刷新tip
setInterval(updateAllTips, refreshInterval);
// 初始加载数据
refreshAll();
// 定时刷新数据
setInterval(async () => {
if (isRefreshing) return;
isRefreshing = true;
try {
await refreshAll();
} finally {
isRefreshing = false;
}
}, refreshInterval);
</script>
</body>
</html>
<style>
.recover-btn {
background: linear-gradient(135deg, #40a9ff 0%, #096dd9 100%);
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(64,169,255,0.2);
}
.recover-btn:hover {
background: linear-gradient(135deg, #69c0ff 0%, #40a9ff 100%);
box-shadow: 0 4px 12px rgba(64,169,255,0.3);
}
.recover-btn:disabled {
background: #274b7a;
cursor: not-allowed;
opacity: 0.6;
}
</style>