Просмотр исходного кода

feat: separate system log page and rework settings layout

- system log moves to a dedicated 系统日志 view; log polling only runs
  while that page is active; panel fills the content area
- settings page back to natural document flow (old flex+height:100%
  layout was squeezing the form and clipping the save button)
- settings reworked: save button lives in the page header toolbar
  (always visible), API 设置 and 采集配置 panels side by side in a
  two-column grid (max-width 1080px, single column under 960px)
KenZ1117 2 недель назад
Родитель
Сommit
371d310ca8
3 измененных файлов с 37 добавлено и 19 удалено
  1. 8 1
      public/app.js
  2. 19 6
      public/index.html
  3. 10 12
      public/style.css

+ 8 - 1
public/app.js

@@ -30,6 +30,7 @@
     reports: document.getElementById('viewReports'),
     analysis: document.getElementById('viewAnalysis'),
     settings: document.getElementById('viewSettings'),
+    logs: document.getElementById('viewLogs'),
   };
   const navItems = document.querySelectorAll('.nav-item[data-view]');
   let settingsLoaded = false;
@@ -259,6 +260,10 @@
     logTimer = setInterval(refreshLogs, 1500);
   }
 
+  function stopLogPolling() {
+    if (logTimer) { clearInterval(logTimer); logTimer = null; }
+  }
+
   async function clearLogs() {
     await fetch('/api/logs/clear', { method: 'POST' });
     lastLogSignature = '';
@@ -277,6 +282,9 @@
     if (name === 'reports' && window.shanReportsInit) window.shanReportsInit();
     if (name === 'analysis' && window.shanAnalysisActivate) window.shanAnalysisActivate();
     if (name === 'settings') initSettings();
+    // 系统日志:仅日志页激活时轮询
+    if (name === 'logs') startLogPolling();
+    else stopLogPolling();
     // 智能问数机器人:仅经营分析相关页面显示
     if (window.shanSetAnalysisVisible) window.shanSetAnalysisVisible(name === 'reports' || name === 'analysis');
   }
@@ -285,7 +293,6 @@
 
   const requestedView = new URLSearchParams(location.search).get('view');
   switchView(views[requestedView] ? requestedView : 'crawl');
-  startLogPolling();
 
   const splash = document.getElementById('splash');
   if (splash) {

+ 19 - 6
public/index.html

@@ -44,6 +44,7 @@
 
     <div class="nav-section-label">设置</div>
     <button class="nav-item" data-view="settings"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.05A1.7 1.7 0 0 0 9 19.4a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.05A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6h.09A1.7 1.7 0 0 0 10 3.05V3a2 2 0 1 1 4 0v.05A1.7 1.7 0 0 0 15 4.6a1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9v.09a1.7 1.7 0 0 0 1.55.91H21a2 2 0 1 1 0 4h-.05a1.7 1.7 0 0 0-1.55 1z"/></svg></span><span>系统设置</span></button>
+    <button class="nav-item" data-view="logs"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16v11H8l-4 4Z"/><path d="M8 9h8M8 12h5"/></svg></span><span>系统日志</span></button>
 
     <div class="sidebar-bottom">
       <span>v1.0.0</span>
@@ -442,6 +443,10 @@
             <h1>系统设置</h1>
             <div class="sub">API 密钥与采集配置,保存后立即生效</div>
           </div>
+          <div class="toolbar">
+            <span id="settingsStatus" class="badge"></span>
+            <button class="btn btn-primary btn-small" id="btnSaveSettings">保存设置</button>
+          </div>
         </div>
         <div class="settings-workspace">
           <div class="settings-forms">
@@ -543,14 +548,23 @@
                 <input type="text" id="settingsAccessToken" placeholder="未设置" autocomplete="off">
                 <div class="help">设置后,同一局域网的其他电脑访问本服务需要输入此口令(本机 127.0.0.1 不受影响)。留空保持不变;输入 - 清除口令。临时开关也可用环境变量 ACCESS_TOKEN。</div>
               </div>
-	              <div style="display:flex;align-items:center;gap:8px;">
-	                <button class="btn btn-primary btn-small" id="btnSaveSettings">保存设置</button>
-	                <span id="settingsStatus" class="badge"></span>
-	              </div>
 	            </div>
 	          </div>
 	          </div>
-	          <div class="panel panel-fixed panel-log settings-log">
+	                  </div>
+      </main>
+    </div>
+
+    <!-- ==================== 系统日志 ==================== -->
+    <div id="viewLogs" class="view">
+      <main class="content">
+        <div class="page-header">
+          <div>
+            <h1>系统日志</h1>
+            <div class="sub">任务与系统的实时运行日志(最近 500 条,自动刷新)</div>
+          </div>
+        </div>
+<div class="panel panel-fixed panel-log settings-log">
             <div class="panel-title">
               <span>实时日志</span>
               <span class="actions">
@@ -560,7 +574,6 @@
             <span class="panel-watermark log-watermark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16v11H8l-4 4Z"/><path d="M8 9h8M8 12h5"/></svg></span>
             <div class="log-box" id="logPanel"></div>
           </div>
-        </div>
       </main>
     </div>
 

+ 10 - 12
public/style.css

@@ -163,8 +163,8 @@ main.content { padding: var(--page-padding-y) var(--page-padding-x) var(--page-p
 .field-row .field input { width: 100%; min-width: 0; }
 .field-label-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
 .field-label-row label { flex: 1 1 auto; margin: 0; }
-.settings-workspace { display: grid; grid-template-columns: minmax(340px, 420px) minmax(0, 1fr); gap: 16px; align-items: stretch; }
-.settings-forms { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
+.settings-workspace { max-width: 1080px; }
+.settings-forms { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; min-width: 0; }
 .settings-forms .field textarea { min-height: 78px; resize: vertical; line-height: 1.5; }
 .settings-forms .panel,
 .panel-designed,
@@ -200,18 +200,16 @@ main.content { padding: var(--page-padding-y) var(--page-padding-x) var(--page-p
 .settings-log { height: auto; min-height: 360px; }
 .settings-log .log-box { min-height: 0; }
 
-#viewSettings.active { display: flex; height: 100%; min-height: 0; }
-#viewSettings .content { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; padding-bottom: 20px; }
-#viewSettings .settings-workspace { flex: 1; min-height: 0; }
-#viewSettings .settings-forms { min-height: 0; }
-#viewSettings .settings-log { flex: 1; min-height: 0; }
+/* 系统日志独立页:面板占满内容区高度 */
+#viewLogs.active { display: flex; height: 100%; min-height: 0; }
+#viewLogs .content { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; padding-bottom: 20px; }
+#viewLogs .settings-log { flex: 1; min-height: 0; height: auto; }
 
 @media (max-width: 960px) {
-  .settings-workspace { grid-template-columns: 1fr; }
-  #viewSettings.active { display: block; height: auto; }
-  #viewSettings .content { min-height: 0; }
-  #viewSettings .settings-workspace { flex: none; }
-  #viewSettings .settings-log { flex: none; min-height: 320px; }
+  .settings-forms { grid-template-columns: 1fr; }
+  #viewLogs.active { display: block; height: auto; }
+  #viewLogs .content { min-height: 0; }
+  #viewLogs .settings-log { flex: none; height: auto; min-height: 420px; }
 }
 
 /* ── Buttons ───────────────────────────────────────────── */