Преглед на файлове

fix: 系统设置 help 小字治理(删除/并入占位符/收进 ? 弹窗)+ 同步数据按钮换同步图标

KenZ1117 преди 2 седмици
родител
ревизия
30ffbae4a2
променени са 3 файла, в които са добавени 80 реда и са изтрити 12 реда
  1. 19 0
      public/app.js
  2. 29 10
      public/index.html
  3. 32 2
      public/style.css

+ 19 - 0
public/app.js

@@ -283,6 +283,25 @@
     document.getElementById('btnTestImage').addEventListener('click', testImageApi);
     document.getElementById('btnTestChat').addEventListener('click', testChatModel);
     document.getElementById('btnTestAitable').addEventListener('click', () => aitableAction('test'));
+    const bindHelpPopover = (btnId, popId) => {
+      const btn = document.getElementById(btnId);
+      const pop = document.getElementById(popId);
+      btn.addEventListener('click', (event) => {
+        event.stopPropagation();
+        pop.hidden = !pop.hidden;
+        btn.classList.toggle('active', !pop.hidden);
+        btn.setAttribute('aria-expanded', String(!pop.hidden));
+      });
+      document.addEventListener('click', (event) => {
+        if (!pop.hidden && !event.target.closest('.conn-help-wrap')) {
+          pop.hidden = true;
+          btn.classList.remove('active');
+          btn.setAttribute('aria-expanded', 'false');
+        }
+      });
+    };
+    bindHelpPopover('btnSecurityHelp', 'securityHelpPopover');
+    bindHelpPopover('btnAitableHelp', 'aitableHelpPopover');
     document.getElementById('btnClearLog').addEventListener('click', () => {
       clearLogs().catch(() => {});
     });

+ 29 - 10
public/index.html

@@ -291,7 +291,7 @@
               刷新
             </button>
             <button class="btn btn-primary btn-small" id="btnOpenSync">
-              <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="M12 3v12"/><path d="M7 10l5 5 5-5"/><path d="M4 21h16"/></svg>
+              <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="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg>
               同步数据
             </button>
           </div>
@@ -644,13 +644,11 @@
                   <div class="field">
                     <label>间隔 (ms)</label>
                     <input type="number" id="delayInput" min="0" value="300">
-                    <div class="help">建议 ≥ 200</div>
                   </div>
                 </div>
                 <div class="field">
                   <label>Cookie(淘宝闪购)</label>
-                  <textarea id="settingsCookie" rows="3" placeholder="登录 melody.shop.ele.me 后从 DevTools 复制"></textarea>
-                  <div class="help">Cookie 保存后不再回显;会话过期时粘贴新值即可更新</div>
+                  <textarea id="settingsCookie" rows="3" placeholder="登录 melody.shop.ele.me 后从 DevTools 复制粘贴;过期后重新粘贴即可更新"></textarea>
                 </div>
                 <div class="panel-save-row">
                   <button class="btn btn-primary btn-small" id="btnSaveCrawl">保存设置</button>
@@ -662,13 +660,24 @@
 
           <div class="settings-col">
             <div class="panel panel-narrow">
-              <div class="stage-marker"><span class="num">3</span><span>安全设置</span></div>
+              <div class="stage-marker"><span class="num">3</span><span>安全设置</span>
+                <span class="conn-help-wrap">
+                  <button type="button" class="help-btn" id="btnSecurityHelp" title="局域网访问口令说明" aria-expanded="false">?</button>
+                  <div class="help-popover pop-left" id="securityHelpPopover" hidden>
+                    <div class="help-popover-title">局域网访问口令</div>
+                    <ol class="help-popover-steps">
+                      <li><b>作用:</b>设置后,同一局域网的其他电脑访问本服务需要输入此口令;本机 127.0.0.1 不受影响。</li>
+                      <li><b>清除:</b>输入 <code>-</code> 保存即清除;留空保存保持不变。</li>
+                      <li><b>临时开关:</b>也可用环境变量 <code>ACCESS_TOKEN</code>。</li>
+                    </ol>
+                  </div>
+                </span>
+              </div>
               <span class="panel-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="M12 3l7 3v5c0 4.4-3 8.4-7 10-4-1.6-7-5.6-7-10V6Z"/><path d="M9.5 12l1.8 1.8L15 10"/></svg></span>
               <div class="panel-body">
                 <div class="field">
                   <label>局域网访问口令(accessToken)</label>
                   <input type="text" id="settingsAccessToken" placeholder="未设置" autocomplete="off">
-                  <div class="help">设置后,同一局域网的其他电脑访问本服务需要输入此口令(本机 127.0.0.1 不受影响)。留空保持不变;输入 - 清除口令。临时开关也可用环境变量 ACCESS_TOKEN。</div>
                 </div>
                 <div class="panel-save-row">
                   <button class="btn btn-primary btn-small" id="btnSaveSecurity">保存设置</button>
@@ -678,7 +687,20 @@
             </div>
 
             <div class="panel panel-narrow">
-              <div class="stage-marker"><span class="num">4</span><span>钉钉 AI 表格</span></div>
+              <div class="stage-marker"><span class="num">4</span><span>钉钉 AI 表格</span>
+                <span class="conn-help-wrap">
+                  <button type="button" class="help-btn" id="btnAitableHelp" title="钉钉 AI 表格配置说明" aria-expanded="false">?</button>
+                  <div class="help-popover pop-left" id="aitableHelpPopover" hidden>
+                    <div class="help-popover-title">钉钉 AI 表格 · 配置说明</div>
+                    <ol class="help-popover-steps">
+                      <li><b>前置条件:</b>钉钉企业内部应用需开通「AI 表格」API 权限。</li>
+                      <li><b>Base ID:</b>可直接粘贴分享链接(形如 alidocs.dingtalk.com/i/nodes/xxx),保存时自动提取 ID;三张表名留空用默认值。</li>
+                      <li><b>为什么拆三张表:</b>云端按维度拆表(门店明细/连锁汇总/公司汇总),每张表内粒度唯一,钉钉 AI 问数/人工聚合不会跨维度算重。</li>
+                      <li><b>推送与拉取:</b>取数机「同步 → 自动推送」(数据采集页有手动推送按钮),其他电脑在「数据中台」页执行「云端拉取」。本面板只管配置。</li>
+                    </ol>
+                  </div>
+                </span>
+              </div>
               <span class="panel-watermark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M3 10h18M9 10v10"/></svg></span>
               <div class="panel-body">
                 <div class="field">
@@ -691,7 +713,6 @@
                   <label>AppKey / AppSecret(钉钉企业内部应用)</label>
                   <input type="text" id="settingsAitableAppKey" placeholder="钉钉开放平台应用的 AppKey" autocomplete="off">
                   <input type="text" id="settingsAitableAppSecret" placeholder="AppSecret 未设置" autocomplete="off" style="margin-top:6px;">
-                  <div class="help">Secret 保存后不回显;输入 - 清除。应用需开通「AI 表格」API 权限。</div>
                 </div>
                 <div class="field">
                   <label>Base ID / 三张数据表名称</label>
@@ -701,13 +722,11 @@
                     <input type="text" id="settingsAitableSheetChain" placeholder="连锁汇总" autocomplete="off">
                     <input type="text" id="settingsAitableSheetCompany" placeholder="公司汇总" autocomplete="off">
                   </div>
-                  <div class="help">Base ID 可直接粘贴分享链接(形如 alidocs.dingtalk.com/i/nodes/xxx),保存时自动提取 ID。云端按维度拆三张表(门店明细/连锁汇总/公司汇总),每张表内粒度唯一,钉钉 AI 问数/人工聚合不会跨维度算重;表名留空用默认值。</div>
                 </div>
                 <div class="settings-links">
                   <button type="button" class="settings-link settings-link-btn" id="btnTestAitable"><span>测试连通</span></button>
                   <span id="aitableStatus" class="badge"></span>
                 </div>
-                <div class="help">架构:取数机「同步 → 自动推送」(数据采集页有手动推送按钮),其他电脑在「数据中台」页执行「云端拉取」。推送/拉取入口分别在数据采集与数据中台页,本面板只管配置。</div>
                 <div class="panel-save-row">
                   <button class="btn btn-primary btn-small" id="btnSaveAitable">保存设置</button>
                   <span id="aitableSaveStatus" class="badge"></span>

+ 32 - 2
public/style.css

@@ -158,7 +158,7 @@ main.content { padding: var(--page-padding-y) var(--page-padding-x) var(--page-p
   cursor: not-allowed;
   opacity: 1;
 }
-.field .help { font-size: 11px; color: var(--text-3); }
+.help { font-size: 11px; color: var(--text-3); }
 .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
 .field-row .field input { width: 100%; min-width: 0; }
 .field-label-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
@@ -732,6 +732,10 @@ main.content { padding: var(--page-padding-y) var(--page-padding-x) var(--page-p
 .entity-select-menu .scan-dropdown-item > span:last-child { flex: 0 0 auto; overflow: visible; text-overflow: clip; }
 .entity-select-no-match { padding: 10px; font-size: var(--font-size-small); color: var(--text-3); text-align: center; }
 .entity-select-empty { font-size: var(--font-size-body); color: var(--text-3); padding: 10px 0; }
+.entity-select .scan-dropdown-trigger.is-empty span { color: var(--text-3); }
+.entity-select-guide { display: flex; flex-direction: column; gap: 8px; padding: 8px 10px; }
+.entity-select-guide-steps { font-size: var(--font-size-small); color: var(--text-2); line-height: 1.6; white-space: normal; }
+.entity-select-guide-btn { align-self: flex-start; }
 
 .report-toolbar { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; padding: 14px 16px; margin-bottom: 14px; }
 .report-field-sm { width: 130px; flex: 0 0 auto; }
@@ -847,6 +851,8 @@ main.content { padding: var(--page-padding-y) var(--page-padding-x) var(--page-p
   background: var(--card-bg); border-left: 1px solid var(--border); border-top: 1px solid var(--border);
   transform: rotate(45deg);
 }
+.help-popover.pop-left { left: 0; right: auto; }
+.help-popover.pop-left::before { left: 14px; right: auto; }
 .help-popover-title { font-weight: 700; font-size: var(--font-size-h2); margin-bottom: 8px; }
 .help-popover-steps { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 8px; }
 .help-popover-steps li { line-height: 1.55; }
@@ -915,10 +921,34 @@ main.content { padding: var(--page-padding-y) var(--page-padding-x) var(--page-p
 .ana-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; padding-bottom: 40px; }
 .ana-cell { min-width: 0; }
 .ana-span2 { grid-column: span 2; }
+.ana-span3 { grid-column: 1 / -1; }
 .chart-lg { width: 100%; height: 300px; }
 .ana-compare-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: var(--font-size-body); color: var(--text-2); cursor: pointer; padding-bottom: 2px; white-space: nowrap; }
 .ana-compare-toggle input { margin: 0; }
-@media (max-width: 1080px) { .ana-grid { grid-template-columns: 1fr; } .ana-span2 { grid-column: span 1; } }
+@media (max-width: 1080px) { .ana-grid { grid-template-columns: 1fr; } .ana-span2, .ana-span3 { grid-column: span 1; } }
+
+/* ── 趋势图表 · 分析模板 ────────────────────────────────── */
+.ana-tpl-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
+.ana-tpl-group { font-size: var(--font-size-small); color: var(--text-3); font-weight: var(--font-weight-h3); flex-shrink: 0; }
+.ana-tpl-bar .seg-control { background: var(--border-light); }
+#anaTplDesc { margin: 0 2px 10px; }
+.ana-note { font-size: var(--font-size-small); color: var(--text-3); margin: 0 2px; }
+.panel .ana-note { margin-top: -4px; }
+.ana-empty { min-height: 220px; display: flex; align-items: center; justify-content: center; color: var(--text-3); font-size: var(--font-size-body); text-align: center; padding: 0 20px; }
+.ana-table-scroll { max-height: 316px; overflow: auto; }
+.ana-table { width: 100%; border-collapse: collapse; font-size: var(--font-size-body); }
+.ana-table th { position: sticky; top: 0; z-index: 1; background: var(--card-bg); text-align: right; padding: 7px 8px; font-size: var(--font-size-small); font-weight: var(--font-weight-h3); color: var(--text-2); border-bottom: 1px solid var(--border); white-space: nowrap; }
+.ana-table td { text-align: right; padding: 5px 8px; border-bottom: 1px solid var(--border-light); font-variant-numeric: tabular-nums; white-space: nowrap; }
+.ana-table th:first-child, .ana-table td:first-child { text-align: left; }
+.ana-help { font-size: var(--font-size-small); color: var(--text-2); line-height: 1.8; margin: 0; padding-left: 16px; }
+.ana-help b { color: var(--text); font-weight: var(--font-weight-h3); }
+.diag-steps { display: flex; flex-direction: column; gap: 8px; }
+.diag-step { border: 1px solid var(--border); border-radius: var(--radius-md); padding: 8px 10px; }
+.diag-step-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
+.diag-step-name { font-size: var(--font-size-small); font-weight: var(--font-weight-h3); color: var(--text-2); }
+.diag-step-val { font-size: 16px; font-weight: 700; margin: 2px 0; font-variant-numeric: tabular-nums; }
+.diag-step-meta { font-size: var(--font-size-small); color: var(--text-3); }
+.diag-conclusion { background: var(--warn-bg); color: var(--warn); border-radius: var(--radius-md); padding: 8px 10px; font-size: var(--font-size-body); line-height: 1.5; margin-top: 8px; }
 
 /* ── 智能问数 · 机器人 FAB + 对话弹窗 ───────────────────── */
 .chatbot-fab {