/** * 神东煤炭综采工作面数据管理与AI分析系统 * 前端 JavaScript 逻辑 */ // ==================== 全局变量 ==================== let units = []; let aiModels = []; let realtimeData = []; // ==================== 初始化 ==================== document.addEventListener('DOMContentLoaded', function() { initNavigation(); updateTime(); setInterval(updateTime, 1000); loadUnits(); loadAIModels(); loadAllStats(); initForms(); initTimeRange(); }); // ==================== 导航 ==================== function initNavigation() { const links = document.querySelectorAll('.navbar-menu a'); links.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const page = this.getAttribute('data-page'); switchPage(page); }); }); } function switchPage(pageName) { // 更新菜单激活状态 document.querySelectorAll('.navbar-menu a').forEach(a => a.classList.remove('active')); const activeLink = document.querySelector(`.navbar-menu a[data-page="${pageName}"]`); if (activeLink) activeLink.classList.add('active'); // 切换页面 document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); const page = document.getElementById(pageName); if (page) page.classList.add('active'); } // ==================== 时间更新 ==================== function updateTime() { const now = new Date(); const timeStr = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); const el = document.getElementById('current-time'); if (el) el.textContent = timeStr; } // ==================== API 请求 ==================== async function apiRequest(endpoint, options = {}) { try { // AI分析请求设置更长的超时时间(10分钟) const isAiRequest = endpoint.startsWith('/ai/'); const timeoutMs = isAiRequest ? (options.timeout || 600000) : (options.timeout || 30000); // 创建带超时的AbortController const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); const response = await fetch(`/api${endpoint}`, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers }, body: options.body ? JSON.stringify(options.body) : undefined, signal: controller.signal }); clearTimeout(timeoutId); const data = await response.json(); if (!response.ok || !data.success) { // 优先使用 error 字段,其次使用 data.content(AI分析错误),最后使用默认提示 let errorMsg = data.error || '请求失败'; if (data.data && data.data.status === 'error' && data.data.content) { errorMsg = data.data.content; } throw new Error(errorMsg); } return data; } catch (error) { console.error('API请求错误:', error); if (error.name === 'AbortError') { throw new Error(`请求超时(等待时间过长),请检查网络连接或稍后重试`); } throw error; } } // ==================== Toast 提示 ==================== function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } // ==================== 加载总体统计 ==================== async function loadAllStats() { try { const data = await apiRequest('/stats/all'); const stats = data.data || {}; // 更新顶部统计卡片 const unitCountEl = document.getElementById('unit-count'); const tagCountEl = document.getElementById('tag-count'); const totalCollectsEl = document.getElementById('total-collects'); const todayCollectsEl = document.getElementById('today-collects'); const dataTotalEl = document.getElementById('data-total'); if (unitCountEl) unitCountEl.textContent = stats.unit_count || 0; if (tagCountEl) tagCountEl.textContent = stats.tag_count || 0; if (totalCollectsEl) totalCollectsEl.textContent = stats.total_collects || 0; if (todayCollectsEl) todayCollectsEl.textContent = stats.today_collects || 0; if (dataTotalEl) dataTotalEl.textContent = formatNumber(stats.data_total || 0); // 渲染工作面统计表格 renderUnitStatsTable(stats.unit_stats || []); } catch (error) { console.error('加载总体统计失败:', error); } } function formatNumber(num) { if (num >= 1000000) { return (num / 1000000).toFixed(1) + 'M'; } else if (num >= 1000) { return (num / 1000).toFixed(1) + 'K'; } return num.toString(); } function renderUnitStatsTable(unitStats) { const container = document.getElementById('unit-stats-table'); if (!container) return; if (!unitStats || unitStats.length === 0) { container.innerHTML = '

暂无工作面数据

'; return; } let html = ` `; unitStats.forEach(unit => { html += ` `; }); html += '
工作面名称 工作面代码 采集次数 监测点数 最近采集时间
${unit.unit_name || '-'} ${unit.unit_code} ${unit.collect_count || 0} ${unit.tag_count || 0} ${unit.last_time || '暂无'}
'; container.innerHTML = html; } // ==================== 加载工作面列表 ==================== async function loadUnits() { try { const data = await apiRequest('/units'); units = data.data || []; // 更新下拉框 updateUnitSelects(); // 更新列表 renderUnitList(); // 加载采集状态 loadCollectStatus(); // 加载总体统计 loadAllStats(); } catch (error) { showToast('加载工作面列表失败: ' + error.message, 'error'); } } function updateUnitSelects() { const selects = ['collect-unit', 'monitor-unit', 'fault-unit', 'trend-unit', 'report-unit']; selects.forEach(id => { const select = document.getElementById(id); if (select) { const current = select.value; select.innerHTML = ''; units.forEach(unit => { const option = document.createElement('option'); option.value = unit.unit_code; option.textContent = `${unit.unit_name} (${unit.unit_code})`; select.appendChild(option); }); if (current) select.value = current; } }); } function renderUnitList() { const container = document.getElementById('unit-list'); if (!container) return; if (units.length === 0) { container.innerHTML = '

暂无工作面数据

'; return; } container.innerHTML = units.map(unit => `
${unit.unit_name}
${unit.unit_code}
${unit.has_data ? '有数据' : '暂无数据'}
`).join(''); } function switchToMonitor(unitCode) { const monitorSelect = document.getElementById('monitor-unit'); if (monitorSelect) { monitorSelect.value = unitCode; switchPage('monitor'); loadRealtimeData(); } } // ==================== 加载采集状态 ==================== async function loadCollectStatus() { const container = document.getElementById('collect-status'); if (!container) return; try { const promises = units.map(async (unit) => { try { const data = await apiRequest(`/stats/${unit.unit_code}`); return { ...unit, stats: data.data }; } catch { return { ...unit, stats: null }; } }); const results = await Promise.all(promises); if (results.length === 0) { container.innerHTML = '

暂无采集记录

'; return; } container.innerHTML = results.map(item => `
${item.unit_name} ${item.stats ? ` 采集${item.stats.today_count}次 · ${item.stats.last_time || '未知'} ` : '暂无数据'}
`).join(''); } catch (error) { console.error('加载采集状态失败:', error); } } // ==================== 加载AI模型列表 ==================== async function loadAIModels() { try { const data = await apiRequest('/ai/models'); aiModels = data.data || []; const selects = ['fault-model', 'trend-model']; selects.forEach(id => { const select = document.getElementById(id); if (select) { select.innerHTML = ''; aiModels.forEach(model => { const option = document.createElement('option'); option.value = model.key; option.textContent = `${model.name}${model.available ? '' : ' (未配置)'}`; if (!model.available) option.disabled = true; select.appendChild(option); }); } }); } catch (error) { console.error('加载AI模型列表失败:', error); } } // ==================== 表单初始化 ==================== function initForms() { // 数据采集表单 const collectForm = document.getElementById('collect-form'); if (collectForm) { collectForm.addEventListener('submit', async function(e) { e.preventDefault(); await startCollect(); }); } // 故障诊断表单 const faultForm = document.getElementById('fault-form'); if (faultForm) { faultForm.addEventListener('submit', async function(e) { e.preventDefault(); await startFaultAnalysis(); }); } // 趋势分析表单 const trendForm = document.getElementById('trend-form'); if (trendForm) { trendForm.addEventListener('submit', async function(e) { e.preventDefault(); await startTrendAnalysis(); }); } // 报告生成表单 const reportForm = document.getElementById('report-form'); if (reportForm) { reportForm.addEventListener('submit', async function(e) { e.preventDefault(); await generateReport(); }); } } // ==================== 时间范围初始化 ==================== function initTimeRange() { const now = new Date(); const oneHourAgo = new Date(now.getTime() - 1 * 60 * 60 * 1000); const formatDateTime = (date) => { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); const h = String(date.getHours()).padStart(2, '0'); const min = String(date.getMinutes()).padStart(2, '0'); return `${y}-${m}-${d}T${h}:${min}`; }; const startEl = document.getElementById('trend-start'); const endEl = document.getElementById('trend-end'); if (startEl) startEl.value = formatDateTime(oneHourAgo); if (endEl) endEl.value = formatDateTime(now); } // ==================== 数据采集 ==================== async function startCollect() { const unitCode = document.getElementById('collect-unit').value; const deviceType = document.getElementById('collect-device').value; if (!unitCode) { showToast('请选择工作面', 'warning'); return; } const btn = document.getElementById('collect-btn'); const btnText = btn.querySelector('.btn-text'); const btnLoading = btn.querySelector('.btn-loading'); btn.disabled = true; btnText.style.display = 'none'; btnLoading.style.display = 'inline'; addLog('开始采集数据...', 'info'); try { const body = { unit_code: unitCode }; if (deviceType) body.device_type = deviceType; const data = await apiRequest('/collect', { method: 'POST', body: body }); const result = data.data; addLog(`采集完成: ${result.message || '成功'}`, 'success'); if (result.total_collected) { addLog(`共采集 ${result.total_collected} 条数据`, 'success'); } showToast('数据采集成功'); // 刷新状态 loadCollectStatus(); updateUnitDataStatus(); } catch (error) { addLog(`采集失败: ${error.message}`, 'error'); showToast('数据采集失败: ' + error.message, 'error'); } finally { btn.disabled = false; btnText.style.display = 'inline'; btnLoading.style.display = 'none'; } } async function collectAll() { const btn = document.getElementById('collect-all-btn'); btn.disabled = true; btn.textContent = '采集中...'; addLog('开始全量采集...', 'info'); try { const data = await apiRequest('/collect/all', { method: 'POST' }); addLog('全量采集完成', 'success'); showToast('全量采集完成'); loadCollectStatus(); updateUnitDataStatus(); } catch (error) { addLog(`全量采集失败: ${error.message}`, 'error'); showToast('全量采集失败', 'error'); } finally { btn.disabled = false; btn.textContent = '全量采集'; } } function updateUnitDataStatus() { // 重新加载units以更新状态 loadUnits(); } // ==================== 日志 ==================== function addLog(message, type = 'info') { const container = document.getElementById('collect-log'); if (!container) return; const time = new Date().toLocaleTimeString('zh-CN'); const logEl = document.createElement('p'); logEl.className = `log-${type}`; logEl.textContent = `[${time}] ${message}`; container.appendChild(logEl); container.scrollTop = container.scrollHeight; } // ==================== 实时数据 ==================== async function loadRealtimeData() { const unitCode = document.getElementById('monitor-unit').value; if (!unitCode) { document.getElementById('realtime-data').innerHTML = '

请选择工作面查看实时数据

'; return; } try { const data = await apiRequest(`/realtime/${unitCode}`); realtimeData = data.data || []; // 更新时间 const timeEl = document.getElementById('monitor-time'); if (timeEl && data.snapshot_time) { timeEl.textContent = `数据时间: ${data.snapshot_time}`; } // 更新统计 updateStats(realtimeData); // 渲染表格 renderRealtimeTable(realtimeData); } catch (error) { showToast('加载实时数据失败: ' + error.message, 'error'); } } function updateStats(data) { // 更新首页的监测点总数(仅当在首页时才有意义) const tagCountEl = document.getElementById('tag-count'); if (tagCountEl) { tagCountEl.textContent = data.length; } } function renderRealtimeTable(data) { const container = document.getElementById('realtime-data'); if (!data || data.length === 0) { container.innerHTML = '

暂无数据

'; return; } // 按设备分类 const categories = categorizeData(data); let html = ''; for (const [cat, items] of Object.entries(categories)) { html += `

${cat}

`; items.forEach(item => { const tag = item.tag_short_name || item.tag_name || ''; // 优先使用后端返回的中文名称,否则显示英文标签 const displayName = item.chinese_name && item.chinese_name !== tag ? `${item.chinese_name} (${tag})` : tag; const value = item.tag_value != null ? item.tag_value : (item.tag_value_bool != null ? (item.tag_value_bool ? 'ON' : 'OFF') : 'N/A'); const unit = item.tag_unit || ''; const quality = item.quality || 0; const valueClass = quality === 0 ? 'value-normal' : 'value-warning'; const statusBadge = quality === 0 ? '正常' : '异常'; html += ` `; }); html += '
参数名称 数值 单位 状态
${displayName} ${value} ${unit} ${statusBadge}
'; } container.innerHTML = html; } function categorizeData(data) { const categories = {}; data.forEach(item => { const tag = item.tag_short_name || item.tag_name || ''; let cat = '其他'; if (tag.includes('Shearer') || tag.includes('shearer')) { cat = '采煤机'; } else if (tag.includes('AFC') || tag.includes('LOA') || tag.includes('CRU')) { cat = '三机'; } else if (tag.includes('Emulsion') || tag.includes('Spray')) { cat = '泵站'; } else if (tag.includes('Loop')) { cat = '开关'; } if (!categories[cat]) categories[cat] = []; categories[cat].push(item); }); return categories; } function filterRealtimeData() { const filter = document.getElementById('monitor-filter').value; const search = document.getElementById('monitor-search').value.toLowerCase(); let filtered = realtimeData; if (filter) { filtered = filtered.filter(item => { const tag = item.tag_short_name || item.tag_name || ''; const chinese = (item.chinese_name || '').toLowerCase(); return tag.includes(filter) || chinese.includes(filter); }); } if (search) { filtered = filtered.filter(item => { const tag = (item.tag_short_name || item.tag_name || '').toLowerCase(); const chinese = (item.chinese_name || '').toLowerCase(); return tag.includes(search) || chinese.includes(search); }); } renderRealtimeTable(filtered); } // ==================== AI分析 ==================== async function startFaultAnalysis() { const unitCode = document.getElementById('fault-unit').value; const modelKey = document.getElementById('fault-model').value; if (!unitCode) { showToast('请选择工作面', 'warning'); return; } const btn = document.getElementById('fault-btn'); btn.disabled = true; btn.textContent = '分析中...'; const resultEl = document.getElementById('analysis-result'); resultEl.innerHTML = '

AI正在分析中,请稍候...

'; try { const body = { unit_code: unitCode }; if (modelKey) body.model_key = modelKey; const data = await apiRequest('/ai/fault', { method: 'POST', body: body }); displayAnalysisResult(data.data); showToast('故障诊断完成'); } catch (error) { resultEl.innerHTML = `

分析失败: ${error.message}

`; showToast('故障诊断失败', 'error'); } finally { btn.disabled = false; btn.textContent = '开始诊断'; } } async function startTrendAnalysis() { const unitCode = document.getElementById('trend-unit').value; const startTime = document.getElementById('trend-start').value; const endTime = document.getElementById('trend-end').value; const modelKey = document.getElementById('trend-model').value; if (!unitCode || !startTime || !endTime) { showToast('请填写完整信息', 'warning'); return; } const btn = document.getElementById('trend-btn'); btn.disabled = true; btn.textContent = '分析中...'; const resultEl = document.getElementById('analysis-result'); resultEl.innerHTML = '

AI正在分析趋势,请稍候...

'; try { const body = { unit_code: unitCode, start_time: startTime, end_time: endTime }; if (modelKey) body.model_key = modelKey; const data = await apiRequest('/ai/trend', { method: 'POST', body: body }); displayAnalysisResult(data.data); showToast('趋势分析完成'); } catch (error) { resultEl.innerHTML = `

分析失败: ${error.message}

`; showToast('趋势分析失败', 'error'); } finally { btn.disabled = false; btn.textContent = '开始分析'; } } function displayAnalysisResult(data) { const resultEl = document.getElementById('analysis-result'); const summaryCard = document.getElementById('data-summary-card'); const summaryContent = document.getElementById('data-summary-content'); const summaryDataCount = document.getElementById('summary-data-count'); // 显示数据摘要 if (data.data_summary) { if (summaryCard) summaryCard.style.display = 'block'; if (summaryDataCount) { const count = data.data_count || 0; summaryDataCount.textContent = `共 ${count} 个监测点`; } if (summaryContent) { summaryContent.innerHTML = formatMarkdown(data.data_summary); } } else { if (summaryCard) summaryCard.style.display = 'none'; } if (data.status === 'success') { resultEl.innerHTML = `
分析成功 ${data.model_name ? `${data.model_name}` : ''}
${formatMarkdown(data.content)}
`; } else { resultEl.innerHTML = `
分析失败

${data.content || '未知错误'}

`; if (summaryCard) summaryCard.style.display = 'none'; } } function formatMarkdown(text) { // 简单Markdown格式化 let html = text // 标题 .replace(/^#### (.+)$/gm, '

$1

') .replace(/^### (.+)$/gm, '

$1

') .replace(/^## (.+)$/gm, '

$1

') .replace(/^# (.+)$/gm, '

$1

') // 加粗 .replace(/\*\*(.+?)\*\*/g, '$1') // 斜体 .replace(/\*(.+?)\*/g, '$1') // 无序列表 .replace(/^- (.+)$/gm, '
  • $1
  • ') // 分割线 .replace(/^---$/gm, '
    ') // 换行 .replace(/\n/g, '
    '); // 包裹连续的li html = html.replace(/(
  • .*?<\/li>(?:
    )?)+/g, function(match) { return ''; }); return html; } function copyResult() { const resultEl = document.getElementById('analysis-result'); const text = resultEl.innerText; navigator.clipboard.writeText(text).then(() => { showToast('已复制到剪贴板'); }).catch(() => { showToast('复制失败', 'error'); }); } // ==================== 报告生成 ==================== async function generateReport() { const unitCode = document.getElementById('report-unit').value; const reportType = document.getElementById('report-type').value; if (!unitCode) { showToast('请选择工作面', 'warning'); return; } const btn = document.getElementById('report-btn'); btn.disabled = true; btn.textContent = '生成中...'; const previewEl = document.getElementById('report-preview'); previewEl.innerHTML = '

    正在生成报告...

    '; try { const data = await apiRequest('/report/word', { method: 'POST', body: { unit_code: unitCode, type: reportType } }); const fileInfo = data.data; previewEl.innerHTML = `
    📄

    报告生成成功

    文件名: ${fileInfo.filename}

    下载报告
    `; showToast('报告生成成功'); } catch (error) { previewEl.innerHTML = `

    报告生成失败: ${error.message}

    `; showToast('报告生成失败', 'error'); } finally { btn.disabled = false; btn.textContent = '生成报告'; } }