From fd82105a3dfe347c4c9acb0410c117d8d67c9339 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 18 三月 2026 10:40:16 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/deviceLogs/deviceLogs.html | 1504 +++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 1,329 insertions(+), 175 deletions(-)

diff --git a/src/main/webapp/views/deviceLogs/deviceLogs.html b/src/main/webapp/views/deviceLogs/deviceLogs.html
index 2af2915..5d2d451 100644
--- a/src/main/webapp/views/deviceLogs/deviceLogs.html
+++ b/src/main/webapp/views/deviceLogs/deviceLogs.html
@@ -6,192 +6,1346 @@
     <meta name="renderer" content="webkit">
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
-    <!-- CSS -->
     <link rel="stylesheet" href="../../static/vue/element/element.css">
     <link rel="stylesheet" href="../../static/css/common.css">
     <style>
-        body { margin: 0; padding: 0; background-color: #f0f2f5; height: 100vh; overflow: hidden; }
-        #app { height: 100%; padding: 10px; box-sizing: border-box; display: flex; flex-direction: column; }
-        .main-container { flex: 1; display: flex; overflow: hidden; }
-        .sidebar { width: 260px; margin-right: 10px; display: flex; flex-direction: column; }
-        .content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
-        
-        .box-card { height: 100%; display: flex; flex-direction: column; border: none; box-shadow: 0 1px 4px rgba(0,21,41,.08); }
-        .box-card .el-card__header { padding: 10px 15px; border-bottom: 1px solid #ebeef5; background: #fff; font-weight: bold; font-size: 15px; }
-        .box-card .el-card__body { flex: 1; overflow: auto; padding: 15px; }
-        
-        .device-item { margin-bottom: 10px; }
-        .device-card { background-color: #fff; border: 1px solid #e6ebf5; border-radius: 4px; transition: all .3s; }
-        .device-card:hover { box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); }
-        .device-info { display: flex; justify-content: space-between; align-items: center; padding: 15px; }
-        .device-info .info-text { font-size: 14px; color: #606266; }
-        .device-info .info-text b { color: #303133; margin-right: 5px; }
-        .device-info .tag-group { margin-left: 15px; }
-        
-        .control-bar { margin-bottom: 15px; padding: 15px; background: #fff; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,21,41,.08); }
-        
-        /* Visualization styles */
-        .vis-control-panel { margin-bottom: 10px; display: flex; align-items: center; background: #f5f7fa; padding: 10px; border-radius: 4px; }
-        .vis-container { border: 1px solid #ebeef5; padding: 10px; border-radius: 4px; min-height: 400px; height: calc(80vh - 100px); overflow-y: auto; }
+        :root {
+            --dl-bg: linear-gradient(180deg, #edf3f7 0%, #e7edf4 100%);
+            --dl-panel-bg: rgba(248, 251, 253, 0.94);
+            --dl-panel-border: rgba(223, 232, 240, 0.96);
+            --dl-panel-shadow: 0 12px 26px rgba(114, 136, 164, 0.08);
+            --dl-text-main: #22384f;
+            --dl-text-sub: #708396;
+            --dl-accent: #6f95bd;
+            --dl-accent-strong: #557ca7;
+            --dl-success: #52b17e;
+            --dl-warning: #c78a3f;
+            --dl-danger: #c96660;
+        }
+
+        html, body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+            overflow: hidden;
+        }
+
+        body {
+            background: var(--dl-bg);
+            color: var(--dl-text-main);
+        }
+
+        #app {
+            width: 100%;
+            height: 100%;
+        }
+
+        .dl-shell {
+            width: 100%;
+            height: 100%;
+            padding: 16px;
+            box-sizing: border-box;
+            display: flex;
+            gap: 14px;
+        }
+
+        .dl-sidebar {
+            width: 320px;
+            min-width: 320px;
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+            min-height: 0;
+        }
+
+        .dl-workbench {
+            flex: 1;
+            min-width: 0;
+            min-height: 0;
+            display: grid;
+            grid-template-columns: minmax(520px, 1.28fr) minmax(340px, 0.92fr);
+            gap: 14px;
+        }
+
+        .dl-center,
+        .dl-visual {
+            min-width: 0;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+        }
+
+        .dl-center {
+            order: 2;
+        }
+
+        .dl-visual {
+            order: 1;
+        }
+
+        .dl-panel {
+            border-radius: 20px;
+            border: 1px solid var(--dl-panel-border);
+            background: var(--dl-panel-bg);
+            box-shadow: var(--dl-panel-shadow);
+            overflow: hidden;
+            min-height: 0;
+        }
+
+        .dl-panel-head {
+            padding: 14px 16px 10px;
+            border-bottom: 1px solid rgba(228, 236, 243, 0.92);
+            background: rgba(255, 255, 255, 0.26);
+        }
+
+        .dl-panel-title {
+            font-size: 15px;
+            font-weight: 700;
+            color: var(--dl-text-main);
+            line-height: 1.25;
+        }
+
+        .dl-panel-desc {
+            margin-top: 5px;
+            font-size: 12px;
+            color: var(--dl-text-sub);
+            line-height: 1.5;
+        }
+
+        .dl-panel-body {
+            padding: 14px 16px 16px;
+            box-sizing: border-box;
+            min-height: 0;
+            height: 100%;
+        }
+
+        .dl-hero {
+            padding: 16px;
+            background: linear-gradient(135deg, rgba(255, 255, 255, 0.88) 0%, rgba(240, 246, 251, 0.82) 100%);
+        }
+
+        .dl-hero-title {
+            font-size: 19px;
+            font-weight: 700;
+            color: var(--dl-text-main);
+            line-height: 1.2;
+        }
+
+        .dl-hero-desc {
+            margin-top: 8px;
+            font-size: 12px;
+            line-height: 1.6;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-hero-stats {
+            margin-top: 14px;
+            display: grid;
+            grid-template-columns: repeat(3, minmax(0, 1fr));
+            gap: 10px;
+        }
+
+        .dl-stat {
+            padding: 10px 12px;
+            border-radius: 14px;
+            border: 1px solid rgba(225, 233, 240, 0.94);
+            background: rgba(255, 255, 255, 0.7);
+        }
+
+        .dl-stat-value {
+            font-size: 18px;
+            font-weight: 700;
+            color: var(--dl-accent-strong);
+        }
+
+        .dl-stat-label {
+            margin-top: 4px;
+            font-size: 11px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-date-panel,
+        .dl-device-panel,
+        .dl-log-panel,
+        .dl-raw-panel,
+        .dl-visual-card,
+        .dl-device-summary,
+        .dl-timeline-panel {
+            display: flex;
+            flex-direction: column;
+            min-height: 0;
+        }
+
+        .dl-date-panel {
+            flex: 0 0 260px;
+        }
+
+        .dl-device-panel {
+            flex: 1;
+        }
+
+        .dl-quick-days {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+            margin-bottom: 14px;
+        }
+
+        .dl-day-chip {
+            padding: 0 10px;
+            height: 30px;
+            border-radius: 999px;
+            border: 1px solid rgba(219, 228, 236, 0.96);
+            background: rgba(255, 255, 255, 0.84);
+            color: #47627d;
+            cursor: pointer;
+            transition: all .16s ease;
+        }
+
+        .dl-day-chip.is-active {
+            border-color: rgba(111, 149, 189, 0.44);
+            background: rgba(111, 149, 189, 0.14);
+            color: var(--dl-accent-strong);
+            box-shadow: 0 8px 18px rgba(111, 149, 189, 0.16);
+        }
+
+        .dl-tree-wrap {
+            flex: 1;
+            min-height: 0;
+            overflow: auto;
+            padding-right: 4px;
+            scrollbar-gutter: stable;
+        }
+
+        .dl-type-tabs {
+            display: grid;
+            grid-template-columns: repeat(2, minmax(0, 1fr));
+            gap: 8px;
+            margin-bottom: 12px;
+        }
+
+        .dl-type-tab {
+            padding: 10px 12px;
+            border-radius: 14px;
+            border: 1px solid rgba(222, 231, 239, 0.96);
+            background: rgba(255, 255, 255, 0.72);
+            cursor: pointer;
+            text-align: left;
+            transition: all .16s ease;
+        }
+
+        .dl-type-tab.is-active {
+            border-color: rgba(111, 149, 189, 0.44);
+            background: rgba(111, 149, 189, 0.14);
+            box-shadow: 0 10px 20px rgba(111, 149, 189, 0.12);
+        }
+
+        .dl-type-tab-label {
+            font-size: 13px;
+            font-weight: 700;
+            color: var(--dl-text-main);
+        }
+
+        .dl-type-tab-meta {
+            margin-top: 4px;
+            font-size: 11px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-device-search {
+            margin-bottom: 12px;
+        }
+
+        .dl-device-list {
+            flex: 1;
+            min-height: 0;
+            overflow: auto;
+            padding-right: 4px;
+            display: flex;
+            flex-direction: column;
+            gap: 10px;
+            scrollbar-gutter: stable;
+        }
+
+        .dl-device-item {
+            padding: 12px 13px;
+            border-radius: 16px;
+            border: 1px solid rgba(223, 232, 240, 0.94);
+            background: rgba(255, 255, 255, 0.72);
+            cursor: pointer;
+            transition: all .16s ease;
+        }
+
+        .dl-device-item.is-active {
+            border-color: rgba(111, 149, 189, 0.48);
+            background: rgba(111, 149, 189, 0.12);
+            box-shadow: 0 12px 24px rgba(111, 149, 189, 0.14);
+        }
+
+        .dl-device-item:hover {
+            transform: translateY(-1px);
+        }
+
+        .dl-device-name {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 8px;
+            font-size: 14px;
+            font-weight: 700;
+            color: var(--dl-text-main);
+        }
+
+        .dl-device-badge {
+            padding: 3px 8px;
+            border-radius: 999px;
+            background: rgba(111, 149, 189, 0.12);
+            color: var(--dl-accent-strong);
+            font-size: 10px;
+            font-weight: 700;
+        }
+
+        .dl-device-meta {
+            margin-top: 8px;
+            display: flex;
+            flex-direction: column;
+            gap: 4px;
+            font-size: 11px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-date-panel .dl-panel-body,
+        .dl-device-panel .dl-panel-body {
+            display: flex;
+            flex-direction: column;
+            flex: 1;
+            min-height: 0;
+            height: auto;
+        }
+
+        .dl-device-summary .dl-panel-head {
+            padding: 12px 16px 8px;
+        }
+
+        .dl-device-summary .dl-panel-desc {
+            margin-top: 3px;
+        }
+
+        .dl-device-summary .dl-panel-body,
+        .dl-timeline-panel .dl-panel-body {
+            padding: 12px 16px 14px;
+        }
+
+        .dl-timeline-panel .dl-panel-head {
+            padding: 8px 14px 4px;
+        }
+
+        .dl-timeline-panel .dl-panel-desc {
+            display: none;
+        }
+
+        .dl-timeline-panel .dl-panel-body {
+            padding: 8px 14px 10px;
+        }
+
+        .dl-timeline-panel .dl-btn {
+            height: 30px;
+            padding: 0 12px;
+            border-radius: 10px;
+        }
+
+        .dl-raw-panel .dl-panel-body {
+            display: flex;
+            flex-direction: column;
+            flex: 1;
+            min-height: 0;
+            height: auto;
+        }
+
+        .dl-summary-toolbar {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 10px;
+            margin-bottom: 10px;
+        }
+
+        .dl-device-summary .dl-btn {
+            height: 30px;
+            padding: 0 12px;
+            border-radius: 10px;
+        }
+
+        .dl-summary-grid {
+            display: grid;
+            grid-template-columns: repeat(2, minmax(0, 1fr));
+            gap: 6px;
+        }
+
+        .dl-summary-cell {
+            padding: 9px 10px;
+            border-radius: 12px;
+            border: 1px solid rgba(224, 232, 239, 0.96);
+            background: rgba(255, 255, 255, 0.78);
+        }
+
+        .dl-summary-cell-label {
+            font-size: 10px;
+            line-height: 1.2;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-summary-cell-value {
+            margin-top: 4px;
+            font-size: 12px;
+            font-weight: 700;
+            color: var(--dl-text-main);
+            line-height: 1.35;
+            word-break: normal;
+            overflow-wrap: anywhere;
+        }
+
+        .dl-summary-cell-sub {
+            margin-top: 2px;
+            font-size: 11px;
+            line-height: 1.3;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-summary-cell.is-range,
+        .dl-summary-cell.is-current {
+            grid-column: 1 / -1;
+        }
+
+        .dl-summary-cell.is-range .dl-summary-cell-value,
+        .dl-summary-cell.is-range .dl-summary-cell-sub {
+            font-family: monospace;
+        }
+
+        .dl-inline-actions {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+        }
+
+        .dl-btn {
+            height: 34px;
+            padding: 0 14px;
+            border-radius: 12px;
+            border: none;
+            background: var(--dl-accent);
+            color: #fff;
+            font-size: 12px;
+            font-weight: 700;
+            cursor: pointer;
+            transition: transform .16s ease, box-shadow .16s ease;
+            box-shadow: 0 10px 18px rgba(111, 149, 189, 0.16);
+        }
+
+        .dl-btn:hover {
+            transform: translateY(-1px);
+        }
+
+        .dl-btn.is-ghost {
+            border: 1px solid rgba(219, 228, 236, 0.96);
+            background: rgba(255, 255, 255, 0.84);
+            color: #4b667f;
+            box-shadow: none;
+        }
+
+        .dl-btn[disabled] {
+            opacity: .5;
+            cursor: not-allowed;
+            transform: none;
+            box-shadow: none;
+        }
+
+        .dl-log-workspace {
+            flex: 1;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+        }
+
+        .dl-detail-panel {
+            flex: 1;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .dl-detail-toolbar {
+            padding: 14px 16px 10px;
+            border-bottom: 1px solid rgba(228, 236, 243, 0.92);
+            display: flex;
+            align-items: flex-start;
+            justify-content: space-between;
+            gap: 10px;
+        }
+
+        .dl-detail-tabs {
+            display: inline-flex;
+            gap: 8px;
+            flex-wrap: wrap;
+            justify-content: flex-end;
+        }
+
+        .dl-detail-body {
+            flex: 1;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .dl-log-status {
+            display: inline-flex;
+            align-items: center;
+            height: 24px;
+            padding: 0 9px;
+            border-radius: 999px;
+            font-size: 11px;
+            font-weight: 700;
+            margin-right: 8px;
+        }
+
+        .dl-tone-success { background: rgba(82, 177, 126, 0.12); color: #2d7650; }
+        .dl-tone-working { background: rgba(111, 149, 189, 0.12); color: #3f6286; }
+        .dl-tone-warning { background: rgba(214, 162, 94, 0.14); color: #9b6a24; }
+        .dl-tone-danger { background: rgba(201, 102, 96, 0.14); color: #a74d47; }
+        .dl-tone-muted { background: rgba(148, 163, 184, 0.14); color: #748397; }
+
+        .dl-log-list {
+            flex: 1;
+            min-height: 0;
+            overflow: auto;
+            padding: 12px 16px 16px;
+            display: flex;
+            flex-direction: column;
+            gap: 10px;
+            scrollbar-gutter: stable;
+        }
+
+        .dl-raw-content {
+            flex: 1;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            padding: 12px 16px 16px;
+            box-sizing: border-box;
+        }
+
+        .dl-log-row {
+            padding: 10px 12px;
+            border-radius: 14px;
+            border: 1px solid rgba(223, 232, 240, 0.94);
+            background: rgba(255, 255, 255, 0.72);
+            cursor: pointer;
+            transition: all .16s ease;
+        }
+
+        .dl-log-row.is-active {
+            border-color: rgba(111, 149, 189, 0.5);
+            background: rgba(111, 149, 189, 0.12);
+            box-shadow: 0 12px 22px rgba(111, 149, 189, 0.12);
+        }
+
+        .dl-log-row-head {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 8px;
+        }
+
+        .dl-log-time {
+            font-size: 12px;
+            font-weight: 700;
+            color: var(--dl-accent-strong);
+            font-family: monospace;
+        }
+
+        .dl-log-title {
+            margin-top: 6px;
+            font-size: 12px;
+            font-weight: 700;
+            color: var(--dl-text-main);
+            line-height: 1.35;
+        }
+
+        .dl-log-meta-line {
+            margin-top: 4px;
+            font-size: 11px;
+            line-height: 1.4;
+            color: var(--dl-text-sub);
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }
+
+        .dl-log-hint {
+            margin-top: 5px;
+            display: inline-flex;
+            align-items: center;
+            font-size: 11px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-picker-main {
+            flex: 1;
+            min-width: 0;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .dl-picker-panel {
+            flex: 1;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .dl-picker-panel .dl-panel-body {
+            display: flex;
+            flex-direction: column;
+            min-height: 0;
+            height: auto;
+        }
+
+        .dl-picker-toolbar {
+            display: grid;
+            grid-template-columns: minmax(0, 1fr) 280px;
+            gap: 12px;
+            align-items: start;
+            margin-bottom: 12px;
+        }
+
+        .dl-picker-side-tools {
+            display: flex;
+            flex-direction: column;
+            gap: 10px;
+        }
+
+        .dl-picker-current-day {
+            padding: 10px 12px;
+            border-radius: 14px;
+            border: 1px solid rgba(223, 232, 240, 0.94);
+            background: rgba(255, 255, 255, 0.72);
+            font-size: 12px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-picker-current-day strong {
+            display: block;
+            margin-top: 3px;
+            font-size: 13px;
+            color: var(--dl-text-main);
+        }
+
+        .dl-picker-device-grid {
+            flex: 1;
+            min-height: 0;
+            overflow: auto;
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+            gap: 12px;
+            padding-right: 4px;
+            scrollbar-gutter: stable;
+        }
+
+        .dl-picker-device-grid .dl-device-item {
+            min-height: 112px;
+            text-align: left;
+        }
+
+        .dl-picker-span-all {
+            grid-column: 1 / -1;
+        }
+
+        .dl-viewer-shell {
+            flex: 1;
+            min-width: 0;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+        }
+
+        .dl-viewer-header .dl-panel-body {
+            height: auto;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 12px;
+            padding: 12px 16px;
+        }
+
+        .dl-viewer-header-main {
+            min-width: 0;
+            display: flex;
+            align-items: flex-start;
+            gap: 12px;
+        }
+
+        .dl-viewer-copy {
+            min-width: 0;
+            display: flex;
+            flex-direction: column;
+            gap: 6px;
+        }
+
+        .dl-viewer-meta {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+        }
+
+        .dl-viewer-meta-item {
+            display: inline-flex;
+            align-items: center;
+            gap: 6px;
+            padding: 5px 10px;
+            border-radius: 999px;
+            border: 1px solid rgba(223, 232, 240, 0.94);
+            background: rgba(255, 255, 255, 0.72);
+            font-size: 11px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-viewer-meta-item strong {
+            color: var(--dl-text-main);
+            font-weight: 700;
+        }
+
+        .dl-viewer-grid {
+            flex: 1;
+            min-height: 0;
+            display: grid;
+            grid-template-columns: minmax(620px, 1.28fr) minmax(360px, 0.92fr);
+            gap: 12px;
+        }
+
+        .dl-viewer-main,
+        .dl-viewer-side {
+            min-width: 0;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            gap: 10px;
+        }
+
+        .dl-viewer-side .dl-timeline-panel {
+            flex: 0 0 auto;
+        }
+
+        .dl-raw-toolbar {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 12px;
+            margin-bottom: 12px;
+        }
+
+        .dl-raw-meta {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px 12px;
+            font-size: 12px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-raw-tabs {
+            display: inline-flex;
+            gap: 8px;
+        }
+
+        .dl-tab-btn {
+            height: 30px;
+            padding: 0 12px;
+            border-radius: 999px;
+            border: 1px solid rgba(219, 228, 236, 0.96);
+            background: rgba(255, 255, 255, 0.84);
+            color: #4b667f;
+            font-size: 12px;
+            font-weight: 700;
+            cursor: pointer;
+            transition: all .16s ease;
+        }
+
+        .dl-tab-btn.is-active {
+            border-color: rgba(111, 149, 189, 0.44);
+            background: rgba(111, 149, 189, 0.14);
+            color: var(--dl-accent-strong);
+        }
+
+        .dl-json-box {
+            flex: 1;
+            min-height: 0;
+            overflow: auto;
+            padding: 14px;
+            border-radius: 16px;
+            border: 1px solid rgba(223, 232, 240, 0.94);
+            background: rgba(244, 248, 252, 0.88);
+            scrollbar-gutter: stable;
+        }
+
+        .dl-json-pre {
+            margin: 0;
+            font-size: 12px;
+            line-height: 1.55;
+            color: #2b425a;
+            font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+            white-space: pre-wrap;
+            word-break: break-word;
+        }
+
+        .dl-json-note {
+            margin-top: 8px;
+            font-size: 11px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-timeline-toolbar {
+            display: grid;
+            grid-template-columns: auto minmax(0, 1fr) auto;
+            align-items: center;
+            gap: 8px 10px;
+            margin-bottom: 6px;
+        }
+
+        .dl-timeline-range {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            min-width: 0;
+        }
+
+        .dl-timeline-range .el-slider {
+            flex: 1 1 220px;
+            width: 100%;
+            min-width: 180px;
+        }
+
+        .dl-time-readout {
+            min-width: 196px;
+            font-size: 13px;
+            font-weight: 700;
+            color: var(--dl-text-main);
+            font-family: monospace;
+            text-align: right;
+            flex-shrink: 0;
+        }
+
+        .dl-timeline-side {
+            display: inline-flex;
+            align-items: center;
+            justify-content: flex-end;
+            gap: 8px;
+            min-width: 0;
+        }
+
+        .dl-range-meta {
+            margin-top: 4px;
+            display: flex;
+            flex-wrap: wrap;
+            gap: 4px 10px;
+            font-size: 11px;
+            color: var(--dl-text-sub);
+        }
+
+        .dl-visual-card {
+            flex: 1;
+            min-height: 0;
+        }
+
+        .dl-visual-card .dl-panel-head {
+            padding: 12px 16px 8px;
+        }
+
+        .dl-visual-card .dl-panel-body {
+            height: 100%;
+            min-height: 0;
+            display: flex;
+            flex-direction: column;
+            overflow: hidden;
+            padding: 10px 12px 12px;
+        }
+
+        .dl-visual-card .mc-toolbar {
+            margin-bottom: 6px;
+        }
+
+        .dl-visual-card .mc-title {
+            font-size: 13px;
+        }
+
+        .dl-visual-card .mc-head {
+            padding: 8px 10px;
+        }
+
+        .dl-visual-card .mc-head-title {
+            font-size: 12px;
+        }
+
+        .dl-visual-card .mc-head-subtitle {
+            display: none;
+        }
+
+        .dl-visual-card .mc-body {
+            padding: 0 10px 8px;
+        }
+
+        .dl-visual-card .mc-detail-grid {
+            grid-template-columns: repeat(4, minmax(0, 1fr));
+            gap: 5px;
+            margin-top: 6px;
+        }
+
+        .dl-visual-card .mc-detail-cell {
+            padding: 7px 8px;
+            border-radius: 9px;
+        }
+
+        .dl-visual-card .mc-detail-label {
+            font-size: 10px;
+        }
+
+        .dl-visual-card .mc-detail-value {
+            margin-top: 2px;
+            font-size: 10px;
+            line-height: 1.25;
+        }
+
+        .dl-visual-card .mc-footer {
+            margin-top: 6px;
+        }
+
+        .dl-tree-wrap,
+        .dl-device-list,
+        .dl-log-list,
+        .dl-json-box,
+        .dl-visual-card .mc-collapse {
+            scrollbar-width: auto;
+            scrollbar-color: rgba(111, 149, 189, 0.9) rgba(225, 233, 241, 0.72);
+        }
+
+        .dl-visual-card .mc-collapse {
+            padding-right: 6px;
+            scrollbar-gutter: stable;
+        }
+
+        .dl-tree-wrap::-webkit-scrollbar,
+        .dl-device-list::-webkit-scrollbar,
+        .dl-log-list::-webkit-scrollbar,
+        .dl-json-box::-webkit-scrollbar,
+        .dl-visual-card .mc-collapse::-webkit-scrollbar {
+            width: 12px;
+            height: 12px;
+        }
+
+        .dl-tree-wrap::-webkit-scrollbar-track,
+        .dl-device-list::-webkit-scrollbar-track,
+        .dl-log-list::-webkit-scrollbar-track,
+        .dl-json-box::-webkit-scrollbar-track,
+        .dl-visual-card .mc-collapse::-webkit-scrollbar-track {
+            background: rgba(225, 233, 241, 0.78);
+            border-radius: 999px;
+            border: 2px solid rgba(248, 251, 253, 0.96);
+        }
+
+        .dl-tree-wrap::-webkit-scrollbar-thumb,
+        .dl-device-list::-webkit-scrollbar-thumb,
+        .dl-log-list::-webkit-scrollbar-thumb,
+        .dl-json-box::-webkit-scrollbar-thumb,
+        .dl-visual-card .mc-collapse::-webkit-scrollbar-thumb {
+            background: linear-gradient(180deg, rgba(111, 149, 189, 0.96) 0%, rgba(85, 124, 167, 0.96) 100%);
+            border-radius: 999px;
+            border: 2px solid rgba(248, 251, 253, 0.96);
+        }
+
+        .dl-tree-wrap::-webkit-scrollbar-thumb:hover,
+        .dl-device-list::-webkit-scrollbar-thumb:hover,
+        .dl-log-list::-webkit-scrollbar-thumb:hover,
+        .dl-json-box::-webkit-scrollbar-thumb:hover,
+        .dl-visual-card .mc-collapse::-webkit-scrollbar-thumb:hover {
+            background: linear-gradient(180deg, rgba(98, 136, 177, 0.98) 0%, rgba(74, 112, 154, 0.98) 100%);
+        }
+
+        .dl-empty,
+        .dl-loading {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            height: 100%;
+            min-height: 160px;
+            color: var(--dl-text-sub);
+            font-size: 13px;
+            text-align: center;
+            padding: 0 20px;
+            box-sizing: border-box;
+        }
+
+        .dl-loading i {
+            font-size: 22px;
+            margin-right: 8px;
+        }
+
+        @media (max-width: 1440px) {
+            .dl-workbench {
+                grid-template-columns: minmax(460px, 1.18fr) minmax(320px, 0.92fr);
+            }
+
+            .dl-picker-toolbar {
+                grid-template-columns: 1fr;
+            }
+
+            .dl-viewer-grid {
+                grid-template-columns: minmax(520px, 1.15fr) minmax(320px, 0.92fr);
+            }
+        }
+
+        @media (max-width: 1180px) {
+            .dl-workbench {
+                grid-template-columns: 1fr;
+            }
+
+            .dl-center,
+            .dl-visual {
+                order: initial;
+            }
+
+            .dl-summary-grid,
+            .dl-visual-card .mc-detail-grid {
+                grid-template-columns: repeat(2, minmax(0, 1fr));
+            }
+
+            .dl-viewer-grid {
+                grid-template-columns: 1fr;
+            }
+        }
+
+        @media (max-width: 1560px) {
+            .dl-timeline-toolbar {
+                grid-template-columns: auto auto;
+                align-items: center;
+                justify-content: space-between;
+            }
+
+            .dl-timeline-range {
+                grid-column: 1 / -1;
+                width: 100%;
+            }
+
+            .dl-timeline-side {
+                justify-content: flex-end;
+            }
+        }
+
+        @media (max-width: 1320px) {
+            .dl-timeline-range {
+                flex-wrap: wrap;
+            }
+
+            .dl-time-readout {
+                min-width: 0;
+                width: 100%;
+                text-align: left;
+            }
+        }
     </style>
 </head>
 <body>
-
 <div id="app" v-cloak>
-    <div class="main-container">
-        <!-- Sidebar: Date Tree -->
-        <div class="sidebar">
-            <el-card class="box-card" :body-style="{padding: '10px'}">
-                <div slot="header">鏃ユ湡閫夋嫨</div>
-                <el-tree 
-                    ref="dateTree"
-                    :data="dateTreeData" 
-                    :props="defaultProps" 
-                    node-key="id"
-                    :default-expanded-keys="defaultExpandedKeys"
-                    @node-click="handleNodeClick"
-                    highlight-current
-                    accordion>
-                    <span class="custom-tree-node" slot-scope="{ node, data }">
-                        <i v-if="data.children" class="el-icon-folder"></i>
-                        <i v-else class="el-icon-document"></i>
-                        <span style="margin-left: 5px;">{{ node.label }}</span>
-                    </span>
-                </el-tree>
-            </el-card>
-        </div>
+    <div class="dl-shell">
+        <template v-if="viewMode === 'picker'">
+            <aside class="dl-sidebar">
+                <section class="dl-panel dl-hero">
+                    <div class="dl-hero-title">璁惧鏃ュ織宸ヤ綔鍙�</div>
+                    <div class="dl-hero-desc">鍏堥�夋嫨鏃ュ織鏃ユ湡鍜岃澶囷紝鍐嶈繘鍏ョ嫭绔嬬殑鏁版嵁鏌ョ湅椤点�傜瓫閫夊叆鍙e拰鏌ョ湅鎬佹媶寮�锛岄伩鍏嶈澶囧垪琛ㄨ鏁版嵁鐪嬫澘鎸ゅ帇銆�</div>
+                    <div class="dl-hero-stats">
+                        <div class="dl-stat">
+                            <div class="dl-stat-value">{{ summaryStats.totalDevices || 0 }}</div>
+                            <div class="dl-stat-label">璁惧鏁�</div>
+                        </div>
+                        <div class="dl-stat">
+                            <div class="dl-stat-value">{{ summaryStats.totalFiles || 0 }}</div>
+                            <div class="dl-stat-label">鏃ュ織鏂囦欢</div>
+                        </div>
+                        <div class="dl-stat">
+                            <div class="dl-stat-value">{{ recentDays.length }}</div>
+                            <div class="dl-stat-label">鏈�杩戞棩鏈�</div>
+                        </div>
+                    </div>
+                </section>
 
-        <!-- Main Content -->
-        <div class="content">
-            <!-- Search Bar -->
-            <div class="control-bar">
-                <el-form :inline="true" :model="searchForm" size="small" style="margin-bottom: -18px;">
-                    <el-form-item label="閫変腑鏃ユ湡">
-                        <el-input v-model="searchForm.day" placeholder="yyyyMMdd" readonly style="width: 120px;" disabled></el-input>
-                    </el-form-item>
-                    <el-form-item label="璁惧绫诲瀷">
-                        <el-select v-model="searchForm.type" placeholder="鍏ㄩ儴" clearable style="width: 100px;">
-                            <el-option label="Crn" value="Crn"></el-option>
-                            <el-option label="Devp" value="Devp"></el-option>
-                            <el-option label="Rgv" value="Rgv"></el-option>
-                        </el-select>
-                    </el-form-item>
-                    <el-form-item label="璁惧缂栧彿">
-                        <el-input v-model="searchForm.deviceNo" placeholder="璇疯緭鍏ョ紪鍙�" style="width: 120px;" clearable></el-input>
-                    </el-form-item>
-                    <el-form-item label="璧峰搴忓彿">
-                        <el-input-number v-model="searchForm.offset" :min="0" controls-position="right" style="width: 100px;"></el-input-number>
-                    </el-form-item>
-                    <el-form-item label="鏈�澶ф枃浠�">
-                        <el-input-number v-model="searchForm.limit" :min="1" :max="1000" controls-position="right" style="width: 100px;"></el-input-number>
-                    </el-form-item>
-                    <el-form-item>
-                        <el-button type="primary" icon="el-icon-download" @click="handleBatchDownload" :disabled="!canDownload">涓嬭浇</el-button>
-                    </el-form-item>
-                </el-form>
-            </div>
+                <section class="dl-panel dl-date-panel">
+                    <div class="dl-panel-head">
+                        <div class="dl-panel-title">鏃ユ湡瀵艰埅</div>
+                        <div class="dl-panel-desc">鍏堥�夋棩蹇楁棩鏈燂紝鍐嶈繘鍏ヨ澶囬�夋嫨椤点��</div>
+                    </div>
+                    <div class="dl-panel-body">
+                        <div class="dl-quick-days">
+                            <button
+                                v-for="day in recentDays"
+                                :key="day.day"
+                                type="button"
+                                class="dl-day-chip"
+                                :class="{ 'is-active': selectedDay === day.day }"
+                                @click="handleRecentDayClick(day.day)"
+                            >{{ day.label }}</button>
+                        </div>
+                        <div class="dl-tree-wrap">
+                            <el-tree
+                                ref="dateTree"
+                                :data="dateTreeData"
+                                :props="defaultProps"
+                                node-key="id"
+                                :default-expanded-keys="defaultExpandedKeys"
+                                highlight-current
+                                accordion
+                                @node-click="handleNodeClick">
+                                <span slot-scope="{ node, data }">
+                                    <i v-if="data.children" class="el-icon-folder"></i>
+                                    <i v-else class="el-icon-date"></i>
+                                    <span style="margin-left: 6px;">{{ node.label }}</span>
+                                </span>
+                            </el-tree>
+                        </div>
+                    </div>
+                </section>
+            </aside>
 
-            <!-- Device List -->
-            <el-card class="box-card">
-                <div slot="header" class="clearfix">
-                    <span>璁惧鍒楄〃</span>
-                    <span style="float: right; color: #909399; font-size: 12px;">鍏� {{ filteredDeviceList.length }} 涓澶�</span>
-                </div>
-                
-                <div v-if="loading" style="text-align: center; padding: 20px;">
-                    <i class="el-icon-loading" style="font-size: 24px;"></i>
-                </div>
-                <div v-else-if="filteredDeviceList.length === 0" style="text-align: center; color: #909399; padding: 50px;">
-                    <i class="el-icon-info" style="margin-right: 5px;"></i>鏆傛棤鏁版嵁锛岃鍏堥�夋嫨鏃ユ湡
-                </div>
-                <div v-else>
-                    <div v-for="(item, index) in filteredDeviceList" :key="index" class="device-item">
-                        <div class="device-card">
-                            <div class="device-info">
-                                <div>
-                                    <span class="info-text"><b>璁惧缂栧彿:</b> {{ item.deviceNo }}</span>
-                                    <span class="info-text tag-group"><b>绫诲瀷:</b> {{ item.types.join(', ') }}</span>
-                                    <span class="info-text tag-group"><b>鏂囦欢鏁�:</b> {{ item.fileCount }}</span>
+            <section class="dl-picker-main">
+                <section class="dl-panel dl-picker-panel">
+                    <div class="dl-panel-head">
+                        <div class="dl-panel-title">璁惧閫夋嫨</div>
+                        <div class="dl-panel-desc">{{ selectedDay ? ('鏃ュ織鏃ユ湡 ' + formatDayText(selectedDay) + '锛屽厛绛涢�夎澶囩被鍨嬶紝鍐嶇偣鍑讳竴鍙拌澶囪繘鍏ョ姸鎬佹煡鐪嬮〉銆�') : '鍏堜粠宸︿晶閫夋嫨涓�涓棩蹇楁棩鏈熴��' }}</div>
+                    </div>
+                    <div class="dl-panel-body">
+                        <div class="dl-picker-toolbar">
+                            <div class="dl-type-tabs">
+                                <button
+                                    v-for="group in deviceGroups"
+                                    :key="group.type"
+                                    type="button"
+                                    class="dl-type-tab"
+                                    :class="{ 'is-active': activeType === group.type }"
+                                    @click="selectTypeGroup(group.type)">
+                                    <div class="dl-type-tab-label">{{ group.typeLabel }}</div>
+                                    <div class="dl-type-tab-meta">{{ group.deviceCount }} 鍙� / {{ group.totalFiles }} 鏂囦欢</div>
+                                </button>
+                            </div>
+                            <div class="dl-picker-side-tools">
+                                <div class="dl-picker-current-day">
+                                    褰撳墠鏃ユ湡
+                                    <strong>{{ selectedDay ? formatDayText(selectedDay) : '鏈�夋嫨' }}</strong>
                                 </div>
-                                <div>
-                                    <template v-for="t in item.types">
-                                        <el-button size="mini" icon="el-icon-download" @click="downloadLog(item.deviceNo, t)">涓嬭浇({{t}})</el-button>
-                                        <el-button size="mini" type="success" icon="el-icon-view" @click="visualizeLog(item.deviceNo, t)">鍙鍖�({{t}})</el-button>
-                                    </template>
+                                <div class="dl-device-search" style="margin-bottom: 0;">
+                                    <el-input
+                                        v-model.trim="searchDeviceNo"
+                                        size="small"
+                                        clearable
+                                        placeholder="鎸夎澶囩紪鍙风瓫閫�">
+                                        <i slot="prefix" class="el-input__icon el-icon-search"></i>
+                                    </el-input>
                                 </div>
                             </div>
                         </div>
+                        <div class="dl-picker-device-grid">
+                            <div v-if="summaryLoading" class="dl-loading dl-picker-span-all">
+                                <i class="el-icon-loading"></i><span>姝e湪鍔犺浇璁惧鎽樿...</span>
+                            </div>
+                            <div v-else-if="!selectedDay" class="dl-empty dl-picker-span-all">鍏堜粠宸︿晶閫夋嫨涓�涓棩鏈熴��</div>
+                            <div v-else-if="filteredDevices.length === 0" class="dl-empty dl-picker-span-all">褰撳墠鍒嗙粍涓嬫病鏈夊尮閰嶇殑璁惧銆�</div>
+                            <button
+                                v-else
+                                v-for="device in filteredDevices"
+                                :key="buildDeviceKey(device.type, device.deviceNo)"
+                                type="button"
+                                class="dl-device-item"
+                                @click="selectDevice(device)">
+                                <div class="dl-device-name">
+                                    <span>{{ device.deviceNo }} 鍙穥{ device.typeLabel }}</span>
+                                    <span class="dl-device-badge">{{ device.fileCount }} 鏂囦欢</span>
+                                </div>
+                                <div class="dl-device-meta">
+                                    <span>棣栨潯: {{ formatTimestamp(device.firstTime, false) }}</span>
+                                    <span>鏈潯: {{ formatTimestamp(device.lastTime, false) }}</span>
+                                </div>
+                            </button>
+                        </div>
+                    </div>
+                </section>
+            </section>
+        </template>
+
+        <section v-else class="dl-viewer-shell">
+            <section class="dl-panel dl-viewer-header">
+                <div class="dl-panel-body">
+                    <div class="dl-viewer-header-main">
+                        <button type="button" class="dl-btn is-ghost" @click="returnToSelector">杩斿洖绛涢��</button>
+                        <div class="dl-viewer-copy">
+                            <div class="dl-panel-title">{{ selectedDeviceSummary ? (selectedDeviceSummary.typeLabel + ' ' + selectedDeviceSummary.deviceNo + '鍙�') : '璁惧鐘舵�佹煡鐪�' }}</div>
+                            <div class="dl-panel-desc">{{ selectedDay ? ('鏃ュ織鏃ユ湡 ' + formatDayText(selectedDay)) : '璇烽�夋嫨鏃ユ湡鍜岃澶�' }}</div>
+                            <div v-if="selectedDeviceSummary" class="dl-viewer-meta">
+                                <span class="dl-viewer-meta-item">绫诲瀷 <strong>{{ selectedDeviceSummary.typeLabel }}</strong></span>
+                                <span class="dl-viewer-meta-item">鏂囦欢 <strong>{{ selectedDeviceSummary.fileCount }}</strong></span>
+                                <span class="dl-viewer-meta-item">宸茶浇 <strong>{{ loadedSegmentCount }}</strong></span>
+                                <span class="dl-viewer-meta-item">鑼冨洿 <strong>{{ timelineRangeText }}</strong></span>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="dl-inline-actions">
+                        <span class="dl-log-status" :class="'dl-tone-' + currentStatusTone">{{ currentStatusLabel }}</span>
+                        <button type="button" class="dl-btn is-ghost" @click="loadPreviousSegment" :disabled="!canLoadPreviousSegment">鍔犺浇鏇存棭鐗囨</button>
+                        <button type="button" class="dl-btn is-ghost" @click="loadNextSegment" :disabled="!canLoadNextSegment">鍔犺浇鏇存柊鐗囨</button>
+                        <button type="button" class="dl-btn" @click="handleCurrentDeviceDownload" :disabled="!canDownload">涓嬭浇褰撴棩鏃ュ織</button>
                     </div>
                 </div>
-            </el-card>
-        </div>
+            </section>
+
+            <div class="dl-viewer-grid">
+                <section class="dl-viewer-main">
+                    <section class="dl-panel dl-visual-card">
+                        <div class="dl-panel-head">
+                            <div class="dl-panel-title">鐘舵�佸彲瑙嗗寲</div>
+                            <div class="dl-panel-desc">{{ selectedLogRow ? selectedLogRow._summary.detail : '鎷栧姩鏃堕棿杞存垨鐐瑰嚮鏃ュ織鍚庯紝鍦ㄨ繖閲屾煡鐪嬭鏃跺埢鐨勮澶囩姸鎬併��' }}</div>
+                        </div>
+                        <div class="dl-panel-body">
+                            <component
+                                v-if="visualComponentName"
+                                :is="visualComponentName"
+                                :key="activeDeviceKey"
+                                :items="visualItems"
+                                :param="visualParam"
+                                :auto-refresh="false"
+                                :read-only="true"></component>
+                            <div v-else class="dl-empty">鍏堥�夋嫨涓�涓澶囷紝骞跺姞杞借嚦灏戜竴鏉℃棩蹇楄褰曘��</div>
+                        </div>
+                    </section>
+                </section>
+
+                <section class="dl-viewer-side">
+                    <section class="dl-panel dl-timeline-panel">
+                        <div class="dl-panel-head">
+                            <div class="dl-panel-title">鏃堕棿杞翠笌鍥炴斁</div>
+                            <div class="dl-panel-desc">鎷栧姩鏃堕棿杞存垨璺宠浆鏃堕棿鐐规煡鐪嬭璁惧鐨勭姸鎬佸彉鍖栥��</div>
+                        </div>
+                        <div class="dl-panel-body">
+                            <div class="dl-timeline-toolbar">
+                                <div class="dl-inline-actions">
+                                    <button type="button" class="dl-btn" v-if="!isPlaying" @click="play" :disabled="!canPlay">鎾斁</button>
+                                    <button type="button" class="dl-btn" v-else @click="pause">鏆傚仠</button>
+                                    <button type="button" class="dl-btn is-ghost" @click="resetPlayback" :disabled="!selectedDeviceSummary">閲嶇疆</button>
+                                </div>
+                                <div class="dl-timeline-range">
+                                    <el-slider
+                                        :value="sliderValue"
+                                        :max="sliderMax"
+                                        :disabled="!selectedDeviceSummary || sliderMax <= 0"
+                                        @input="handleSliderInput"
+                                        @change="handleSliderChange"
+                                        :format-tooltip="formatTooltip"></el-slider>
+                                    <div class="dl-time-readout">{{ currentTimeStr }}</div>
+                                </div>
+                                <div class="dl-timeline-side">
+                                    <el-popover
+                                        placement="bottom"
+                                        width="220"
+                                        trigger="click"
+                                        v-model="jumpVisible"
+                                        @show="initJumpTime">
+                                        <div style="display: flex; flex-direction: column; gap: 10px;">
+                                            <el-time-picker
+                                                v-model="jumpTime"
+                                                size="small"
+                                                placeholder="閫夋嫨鏃堕棿"
+                                                style="width: 100%;"
+                                                :picker-options="{ selectableRange: '00:00:00 - 23:59:59' }">
+                                            </el-time-picker>
+                                            <button type="button" class="dl-btn" style="width: 100%;" @click="confirmJump">璺宠浆</button>
+                                        </div>
+                                        <button slot="reference" type="button" class="dl-btn is-ghost" :disabled="!selectedDeviceSummary">璺宠浆</button>
+                                    </el-popover>
+                                    <el-select v-model="playbackSpeed" size="small" style="width: 92px;" :disabled="!selectedDeviceSummary">
+                                        <el-option :value="1" label="1x"></el-option>
+                                        <el-option :value="5" label="5x"></el-option>
+                                        <el-option :value="10" label="10x"></el-option>
+                                        <el-option :value="50" label="50x"></el-option>
+                                        <el-option :value="100" label="100x"></el-option>
+                                        <el-option :value="200" label="200x"></el-option>
+                                    </el-select>
+                                </div>
+                            </div>
+                            <div class="dl-range-meta">
+                                <span>瀹屾暣鑼冨洿: {{ timelineRangeText }}</span>
+                                <span>宸插姞杞界墖娈�: {{ loadedSegmentCount }} / {{ timelineMeta.totalFiles || 0 }}</span>
+                                <span v-if="selectedLogRow">褰撳墠璁板綍: {{ selectedLogRow._summary.title }}</span>
+                            </div>
+                        </div>
+                    </section>
+
+                    <section class="dl-panel dl-detail-panel">
+                        <div class="dl-detail-toolbar">
+                            <div>
+                                <div class="dl-panel-title">{{ detailTab === 'raw' ? '鍘熷鏁版嵁' : '鏃ュ織鍒楄〃' }}</div>
+                                <div v-if="detailTab === 'raw'" class="dl-panel-desc">{{ selectedLogRow ? '褰撳墠璁板綍鐨� wcsData JSON 宸叉牸寮忓寲灞曠ず锛屽繀瑕佹椂鍙垏鎹㈡煡鐪嬪師濮嬫枃鏈��' : '鍏堥�夋嫨涓�鏉℃棩蹇楋紝鍐嶆煡鐪嬪師濮嬫暟鎹��' }}</div>
+                            </div>
+                            <div class="dl-detail-tabs">
+                                <button type="button" class="dl-tab-btn" :class="{ 'is-active': detailTab === 'logs' }" @click="detailTab = 'logs'">鏃ュ織鍒楄〃</button>
+                                <button type="button" class="dl-tab-btn" :class="{ 'is-active': detailTab === 'raw' }" @click="detailTab = 'raw'" :disabled="!selectedLogRow">鍘熷鏁版嵁</button>
+                            </div>
+                        </div>
+                        <div class="dl-detail-body">
+                            <div v-show="detailTab === 'logs'" class="dl-log-list">
+                                <div v-if="timelineLoading || logLoading" class="dl-loading">
+                                    <i class="el-icon-loading"></i><span>{{ timelineLoading ? '姝e湪鏋勫缓璁惧鏃堕棿杞�...' : '姝e湪鍔犺浇鏃ュ織鐗囨...' }}</span>
+                                </div>
+                                <div v-else-if="!selectedDeviceSummary" class="dl-empty">鍏堣繑鍥炵瓫閫夐〉閫夋嫨璁惧銆�</div>
+                                <div v-else-if="logRows.length === 0" class="dl-empty">{{ logLoadError || '褰撳墠璁惧鏆傛棤鍙睍绀烘棩蹇椼��' }}</div>
+                                <div
+                                    v-else
+                                    v-for="row in logRows"
+                                    :key="row._key"
+                                    :id="'log-row-' + row._key"
+                                    class="dl-log-row"
+                                    :class="{ 'is-active': row._key === selectedLogKey }"
+                                    @click="handleLogRowClick(row)">
+                                    <div class="dl-log-row-head">
+                                        <span class="dl-log-time">{{ formatTimestamp(row._ts, true) }}</span>
+                                        <span class="dl-log-status" :class="'dl-tone-' + row._summary.tone">{{ row._summary.statusLabel }}</span>
+                                    </div>
+                                    <div class="dl-log-title">{{ row._summary.title }}</div>
+                                    <div class="dl-log-meta-line">{{ buildLogMetaLine(row._summary) }}</div>
+                                </div>
+                            </div>
+                            <div v-show="detailTab === 'raw'" class="dl-raw-content">
+                                <template v-if="selectedLogRow">
+                                    <div class="dl-raw-toolbar">
+                                        <div class="dl-raw-meta">
+                                            <span>{{ formatTimestamp(selectedLogRow._ts, true) }}</span>
+                                            <span>{{ currentLogTitle }}</span>
+                                        </div>
+                                        <div class="dl-raw-tabs">
+                                            <button type="button" class="dl-tab-btn" :class="{ 'is-active': rawTab === 'wcs' }" @click="rawTab = 'wcs'">WCS JSON</button>
+                                            <button type="button" class="dl-tab-btn" :class="{ 'is-active': rawTab === 'origin' }" @click="rawTab = 'origin'">originData</button>
+                                        </div>
+                                    </div>
+                                    <div class="dl-json-box">
+                                        <pre class="dl-json-pre">{{ activeRawText }}</pre>
+                                    </div>
+                                    <div class="dl-json-note">{{ activeRawHint }}</div>
+                                </template>
+                                <div v-else class="dl-empty">鍏堝湪鏃ュ織鍒楄〃鐐瑰嚮涓�鏉¤褰曪紝鍐嶆煡鐪嬪師濮嬫暟鎹��</div>
+                            </div>
+                        </div>
+                    </section>
+                </section>
+            </div>
+        </section>
     </div>
 
-    <!-- Visualization Dialog -->
-    <el-dialog 
-        :title="visualizationTitle" 
-        :visible.sync="visualizationVisible" 
-        width="90%" 
-        top="5vh"
-        :close-on-click-modal="false"
-        @close="handleVisualizationClose">
-        
-        <div class="vis-control-panel">
-            <el-button-group>
-                <el-button type="primary" icon="el-icon-video-play" @click="play" v-if="!isPlaying" size="small">鎾斁</el-button>
-                <el-button type="primary" icon="el-icon-video-pause" @click="pause" v-else size="small">鏆傚仠</el-button>
-                <el-button type="warning" icon="el-icon-refresh-left" @click="reset" size="small">閲嶇疆</el-button>
-            </el-button-group>
-            <div style="margin-left: 20px; flex: 1; padding-right: 20px;">
-                <el-slider v-model="sliderValue" :max="maxSliderValue" @change="sliderChange" @input="sliderInput" :format-tooltip="formatTooltip"></el-slider>
-            </div>
-            <div style="width: 210px; font-size: 14px; font-weight: bold; font-family: monospace; display: flex; align-items: center;">
-                {{ currentTimeStr }}
-                <el-popover
-                    placement="bottom"
-                    width="200"
-                    trigger="click"
-                    v-model="jumpVisible"
-                    @show="initJumpTime">
-                    <div style="text-align: center;">
-                        <el-time-picker 
-                            v-model="jumpTime" 
-                            size="small" 
-                            placeholder="閫夋嫨鏃堕棿" 
-                            style="width: 100%; margin-bottom: 10px;"
-                            :picker-options="{ selectableRange: '00:00:00 - 23:59:59' }">
-                        </el-time-picker>
-                        <el-button type="primary" size="mini" @click="confirmJump" style="width: 100%;">璺宠浆</el-button>
-                    </div>
-                    <el-button type="text" slot="reference" icon="el-icon-edit" style="margin-left: 5px; padding: 0;" title="璺宠浆鏃堕棿"></el-button>
-                </el-popover>
-            </div>
-             <div style="margin-left: 10px;">
-                <el-select v-model="playbackSpeed" style="width: 100px;" size="small" placeholder="鍊嶉��">
-                    <el-option :value="1" label="1x"></el-option>
-                    <el-option :value="5" label="5x"></el-option>
-                    <el-option :value="10" label="10x"></el-option>
-                    <el-option :value="50" label="50x"></el-option>
-                    <el-option :value="100" label="100x"></el-option>
-                    <el-option :value="200" label="200x"></el-option>
-                    <el-option :value="500" label="500x"></el-option>
-                    <el-option :value="1000" label="1000x"></el-option>
-                </el-select>
-            </div>
-        </div>
-
-        <div class="vis-container">
-            <watch-crn-card v-if="visDeviceType === 'Crn'" ref="card" :auto-refresh="false" :read-only="true"></watch-crn-card>
-            <watch-rgv-card v-else-if="visDeviceType === 'Rgv'" ref="card" :auto-refresh="false" :read-only="true"></watch-rgv-card>
-            <watch-dual-crn-card v-else-if="visDeviceType === 'DualCrn'" ref="card" :auto-refresh="false" :read-only="true"></watch-dual-crn-card>
-            <devp-card v-else-if="visDeviceType === 'Devp'" ref="card" :auto-refresh="false" :read-only="true"></devp-card>
-            <div v-else style="text-align: center; padding: 50px; color: #909399;">
-                鏈煡璁惧绫诲瀷: {{ visDeviceType }}
-            </div>
-        </div>
-    </el-dialog>
-
-    <!-- Download Progress Dialog -->
     <el-dialog :title="downloadDialogTitle" :visible.sync="downloadDialogVisible" width="400px" :close-on-click-modal="false" :show-close="false">
         <div style="padding: 10px;">
             <div style="margin-bottom: 5px; font-size: 14px;">鍘嬬缉鐢熸垚杩涘害</div>
@@ -206,11 +1360,11 @@
 <script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
 <script src="../../static/vue/js/vue.min.js"></script>
 <script src="../../static/vue/element/element.js"></script>
-<script src="../../components/MonitorCardKit.js"></script>
-<script src="../../components/WatchCrnCard.js"></script>
-<script src="../../components/WatchRgvCard.js"></script>
-<script src="../../components/WatchDualCrnCard.js"></script>
-<script src="../../components/DevpCard.js"></script>
-<script type="text/javascript" src="../../static/js/deviceLogs/deviceLogs.js?v=20260309_i18n_pagefix1" charset="utf-8"></script>
+<script src="../../components/MonitorCardKit.js?v=20260318_monitor_v2"></script>
+<script src="../../components/WatchCrnCard.js?v=20260318_monitor_v2"></script>
+<script src="../../components/WatchRgvCard.js?v=20260318_monitor_v2"></script>
+<script src="../../components/WatchDualCrnCard.js?v=20260318_monitor_v2"></script>
+<script src="../../components/DevpCard.js?v=20260318_monitor_v2"></script>
+<script type="text/javascript" src="../../static/js/deviceLogs/deviceLogs.js?v=20260318_workbench_v9" charset="utf-8"></script>
 </body>
 </html>

--
Gitblit v1.9.1