diff --git a/zhandianxinxi/光谱数据监控/src/router/index.js b/zhandianxinxi/光谱数据监控/src/router/index.js new file mode 100644 index 0000000..9085495 --- /dev/null +++ b/zhandianxinxi/光谱数据监控/src/router/index.js @@ -0,0 +1,42 @@ +import { createRouter, createWebHistory } from 'vue-router' + +// 假设你的组件都在 views 目录下 +// Dashboard 作为首页,通常直接引入 +import Dashboard from '../views/Dashboard.vue' + +const routes = [ + { + path: '/', + name: 'Dashboard', + component: Dashboard, + meta: { title: '设备监控总览' } + }, + { + path: '/data-monitor', + name: 'CrawledData', + // 路由懒加载:只有访问该页面时才加载组件,提升首页加载速度 + component: () => import('../views/DataMonitor.vue'), + meta: { title: '数据爬取监控' } + }, + { + path: '/logs', + name: 'MaintenanceLogs', + component: () => import('../views/MaintenanceLogs.vue'), + meta: { title: '维修日志中心' } + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +// 全局路由守卫:自动修改浏览器标签页标题 +router.beforeEach((to, from, next) => { + if (to.meta.title) { + document.title = to.meta.title + } + next() +}) + +export default router diff --git a/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue b/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue index 3e88c71..8692b16 100644 --- a/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue +++ b/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue @@ -30,7 +30,7 @@
- 红色:离线 / 中断 - 橙色:滞后 > 2天 - 蓝色:维修中 - 绿色:正常运行 + 蓝色:维修中 (置顶) + 红色:离线 / 滞后 > 7天 + 橙色:滞后 1 - 7天 + 黄色:滞后 < 24小时 + 绿色:当天数据 (正常)
@@ -81,13 +82,20 @@ style="width: 100%" :row-class-name="tableRowClassName" height="calc(100vh - 380px)" + :default-sort="{ prop: 'sortHours', order: 'descending' }" > - + @@ -120,15 +128,35 @@ - + @@ -213,7 +241,6 @@ const logDialog = reactive({ form: { content: '' } }) -// 计算统计信息 (注意:错误和警告只统计未隐藏的设备) const summary = computed(() => { const activeDevices = rawData.value.filter(r => !r.is_hidden) const errors = activeDevices.filter(r => r.statusType === 'error').length @@ -229,15 +256,6 @@ const summary = computed(() => { } }) -const getDaysDiff = (dateStr, nowObj) => { - if (!dateStr || dateStr === 'N/A') return 999 - let cleanDateStr = dateStr.toString().replace(/_/g, '-') - const d = new Date(cleanDateStr) - if (isNaN(d.getTime())) return 999 - const diffTime = Math.abs(nowObj - d) - return diffTime / (1000 * 60 * 60 * 24) -} - const fetchData = async () => { loading.value = true try { @@ -246,15 +264,52 @@ const fetchData = async () => { const now = new Date() let processedData = backendList.map(item => { - const diff = getDaysDiff(item.latest_time, now) - - // 默认是否隐藏 (如果后端没传,默认为 false) const isHidden = item.is_hidden === true || item.is_hidden === 1 + let diffDays = 0 + let diffHours = 0 + let isToday = false + let validTime = false + + // 计算真实时间 + if (item.latest_time && item.latest_time !== 'N/A') { + const cleanDateStr = item.latest_time.toString().replace(/_/g, '-') + const d = new Date(cleanDateStr) + if (!isNaN(d.getTime())) { + validTime = true + const diffTime = now - d + const safeDiff = diffTime > 0 ? diffTime : 0 + + diffHours = safeDiff / (1000 * 60 * 60) + diffDays = safeDiff / (1000 * 60 * 60 * 24) + isToday = d.toDateString() === now.toDateString() + } + } + + // --- 核心排序逻辑修正 --- + // 目标顺序:维修 > 离线 > 无数据 > 滞后时间长 > 滞后时间短 + // 我们用 sortHours 来控制这个顺序,数值越大越靠前 + let sortHours = diffHours; + + // 1. 维修中:给一个最大的安全整数,保证在所有排序中都是第一 + if (item.is_maintaining) { + sortHours = Number.MAX_SAFE_INTEGER; // 9007199254740991,绝对第一 + } + // 2. 离线:给一个次大的数 (10亿) + else if (item.status === 'offline' || item.status === '已离线') { + sortHours = 1000000000; + } + // 3. 无数据但没报离线:给一个第三大的数 (5亿) + else if (!validTime) { + sortHours = 500000000; + } + // 4. 其他情况 sortHours 就是真实的 diffHours + let statusColor = '#67C23A' let statusLabel = '正常在线' let statusType = 'normal' - let sortWeight = 4 + let sortWeight = 6 + let statusLabelColor = '#fff' if (item.is_maintaining) { statusColor = '#409EFF' @@ -262,47 +317,57 @@ const fetchData = async () => { statusType = 'maintenance' sortWeight = 1 } - else if (item.status === 'offline' || item.status === '已离线') { + else if ((item.status === 'offline' || item.status === '已离线') || (!validTime || diffDays > 7)) { + if (item.status === 'offline' || item.status === '已离线') { + statusLabel = '🔴 设备离线' + } else { + statusLabel = '严重滞后' + } statusColor = '#F56C6C' - statusLabel = '🔴 设备离线' statusType = 'error' sortWeight = 2 } - else if (!item.latest_time || diff > 7) { - statusColor = '#F56C6C' - statusLabel = '💾 数据中断' - statusType = 'error' - sortWeight = 2 - } - else if (diff > 2) { + else if (diffHours > 24) { statusColor = '#E6A23C' - statusLabel = '⚠️ 数据滞后' + statusLabel = '数据滞后' statusType = 'warning' sortWeight = 3 } + else if (!isToday) { + statusColor = '#FAC858' + statusLabel = '昨日数据' + statusType = 'slight-warning' + statusLabelColor = '#333' + sortWeight = 4 + } else { statusColor = '#67C23A' statusLabel = '🟢 运行正常' statusType = 'normal' - sortWeight = 4 + sortWeight = 5 } return { ...item, - is_hidden: isHidden, // 绑定隐藏状态 - diffDays: diff, + is_hidden: isHidden, + diffDays, + diffHours, + sortHours, // 排序专用字段 + isToday, statusColor, statusLabel, statusType, sortWeight, + statusLabelColor, isEditingSite: false, tempSite: '' } }) + // 默认排序逻辑(即便没有 el-table 排序,数组本身也应该是这个顺序) processedData.sort((a, b) => { - if (a.sortWeight !== b.sortWeight) return a.sortWeight - b.sortWeight - return (a.name || '').localeCompare(b.name || '') + // 数值越大越靠前 (Maintenance > Offline > NoData > Old > New) + return b.sortHours - a.sortHours }) rawData.value = processedData @@ -329,24 +394,16 @@ const runManualMonitor = async () => { } } -// --- 过滤逻辑 (核心修改) --- const filteredData = computed(() => { return rawData.value.filter(item => { - // 1. 隐藏状态筛选 if (filters.status === 'hidden') { - // 只有在选中“回收站”时,才显示已隐藏的设备 if (!item.is_hidden) return false } else { - // 在其他模式(全部、异常)下,必须隐藏已隐藏的设备 if (item.is_hidden) return false - - // 处理异常筛选 if (filters.status === 'abnormal') { - if (item.statusType !== 'error' && item.statusType !== 'warning') return false + if (item.statusType !== 'error' && item.statusType !== 'warning' && item.statusType !== 'slight-warning') return false } } - - // 2. 关键词搜索 const keyMatch = !filters.keyword || (item.name && item.name.toLowerCase().includes(filters.keyword.toLowerCase())) return keyMatch }) @@ -395,20 +452,14 @@ const handleMaintenanceBeforeChange = (row) => { }) } -// --- 新增:隐藏/恢复设备 --- const toggleHidden = async (row, targetState) => { try { await axios.post(`${API_BASE}/api/toggle_hidden`, { name: row.name, is_hidden: targetState }) - - // 更新本地状态,这样不需要刷新页面也能立即消失/出现 row.is_hidden = targetState - ElMessage.success(targetState ? '设备已隐藏(移至回收站)' : '设备已恢复显示') - - // 自动刷新数据以确保排序和统计正确 fetchData() } catch (e) { console.error(e) @@ -443,10 +494,11 @@ const submitLog = async () => { const formatDisplayName = (name) => name ? name.toUpperCase().replace(/_/g, ' ') : '' const tableRowClassName = ({ row }) => { - if (row.is_hidden) return 'hidden-row' // 灰色行 + if (row.is_hidden) return 'hidden-row' + if (row.statusType === 'maintenance') return 'maintenance-row' if (row.statusType === 'error') return 'error-row' if (row.statusType === 'warning') return 'warning-row' - if (row.statusType === 'maintenance') return 'maintenance-row' + if (row.statusType === 'slight-warning') return 'slight-warning-row' return '' } @@ -522,7 +574,6 @@ onBeforeUnmount(() => window.removeEventListener('resize', () => windowWidth.val color: #909399; } -/* 回收站文字颜色 */ .search-input { width: 250px; } @@ -538,15 +589,20 @@ onBeforeUnmount(() => window.removeEventListener('resize', () => windowWidth.val color: #999; } -/* 删除线样式 */ - -.warning-text { - color: #F56C6C; - font-size: 12px; +.status-text { + font-size: 13px; margin-top: 4px; font-weight: bold; + display: flex; + align-items: center; } +.maintenance-text { color: #409EFF; } +.error-text { color: #F56C6C; } +.warning-text { color: #E6A23C; } +.slight-warning-text { color: #dcb041; } +.success-text { color: #67C23A; } + .display-cell { cursor: pointer; padding: 5px; @@ -602,14 +658,13 @@ onBeforeUnmount(() => window.removeEventListener('resize', () => windowWidth.val align-items: flex-start; gap: 10px; } - .filter-section { flex-direction: column; align-items: stretch; } - .search-input { width: 100%; + min-height: 300px; } }