From 669123f348b51f6ecf6f4569eb9df4e5e1acf2d7 Mon Sep 17 00:00:00 2001 From: snaily Date: Mon, 18 Aug 2025 16:31:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=94=AF=E6=8C=81=E5=80=BC?= =?UTF-8?q?=E5=BE=97=E6=B3=A8=E6=84=8F=E7=9A=84Key=E5=A4=9A=E9=80=89?= =?UTF-8?q?=E3=80=81=E5=85=A8=E9=80=89=E4=B8=8E=E6=89=B9=E9=87=8F=E6=93=8D?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为“值得注意的Key”列表新增可见复选框和全选开关,提供 批量验证/复制/删除操作,并优化选择逻辑与样式。 - attentionKeysList 每行新增可见复选框并设置 data-key - 新增“全选”和批量操作栏,实时显示已选数量 - getSelectedKeys/updateBatchActions/toggleSelectAll 适配 attention 根节点,且仅作用于可见项 - initializeKeySelectionListeners 增加 attention 事件绑定 - fetchAndRenderAttentionKeys 阻止按钮冒泡、绑定复选框变更, 在加载成功/失败后刷新批量栏状态 - attention 列表不与 valid/invalid 主列表同步勾选,避免交叉影响 - CSS 仅隐藏有效/无效列表复选框,新增 attention 列表 hover/选中态样式 - 增强空值判断,避免批量栏或全选元素缺失时报错 --- app/static/js/keys_status.js | 119 +++++++++++++++++++-------------- app/templates/keys_status.html | 83 ++++++++++++++++++++--- 2 files changed, 143 insertions(+), 59 deletions(-) diff --git a/app/static/js/keys_status.js b/app/static/js/keys_status.js index 209bcc2..d46ef86 100644 --- a/app/static/js/keys_status.js +++ b/app/static/js/keys_status.js @@ -108,8 +108,14 @@ function initStatItemAnimations() { // 获取指定类型区域内选中的密钥 function getSelectedKeys(type) { + let selectorRoot; + if (type === 'attention') { + selectorRoot = '#attentionKeysList'; + } else { + selectorRoot = `#${type}Keys`; + } const checkboxes = document.querySelectorAll( - `#${type}Keys .key-checkbox:checked` + `${selectorRoot} .key-checkbox:checked` ); return Array.from(checkboxes).map((cb) => cb.value); } @@ -119,27 +125,27 @@ function updateBatchActions(type) { const selectedKeys = getSelectedKeys(type); const count = selectedKeys.length; const batchActionsDiv = document.getElementById(`${type}BatchActions`); + if (!batchActionsDiv) return; const selectedCountSpan = document.getElementById(`${type}SelectedCount`); const buttons = batchActionsDiv.querySelectorAll("button"); if (count > 0) { batchActionsDiv.classList.remove("hidden"); - selectedCountSpan.textContent = count; + if (selectedCountSpan) selectedCountSpan.textContent = count; buttons.forEach((button) => (button.disabled = false)); } else { batchActionsDiv.classList.add("hidden"); - selectedCountSpan.textContent = "0"; + if (selectedCountSpan) selectedCountSpan.textContent = "0"; buttons.forEach((button) => (button.disabled = true)); } // 更新全选复选框状态 - const selectAllCheckbox = document.getElementById( - `selectAll${type.charAt(0).toUpperCase() + type.slice(1)}` - ); - const allCheckboxes = document.querySelectorAll(`#${type}Keys .key-checkbox`); + const selectAllId = `selectAll${type.charAt(0).toUpperCase() + type.slice(1)}`; + const selectAllCheckbox = document.getElementById(selectAllId); + const rootId = type === 'attention' ? 'attentionKeysList' : `${type}Keys`; // 只有在有可见的 key 时才考虑全选状态 const visibleCheckboxes = document.querySelectorAll( - `#${type}Keys li:not([style*="display: none"]) .key-checkbox` + `#${rootId} li:not([style*="display: none"]) .key-checkbox` ); if (selectAllCheckbox && visibleCheckboxes.length > 0) { selectAllCheckbox.checked = count === visibleCheckboxes.length; @@ -153,29 +159,28 @@ function updateBatchActions(type) { // 全选/取消全选指定类型的密钥 function toggleSelectAll(type, isChecked) { - const listElement = document.getElementById(`${type}Keys`); - // Select checkboxes within LI elements that are NOT styled with display:none - // This targets currently visible items based on filtering. + const rootId = type === 'attention' ? 'attentionKeysList' : `${type}Keys`; + const listElement = document.getElementById(rootId); + if (!listElement) return; const visibleCheckboxes = listElement.querySelectorAll( `li:not([style*="display: none"]) .key-checkbox` ); visibleCheckboxes.forEach((checkbox) => { checkbox.checked = isChecked; - const listItem = checkbox.closest("li[data-key]"); // Get the LI from the current DOM + const listItem = checkbox.closest("li[data-key]"); if (listItem) { listItem.classList.toggle("selected", isChecked); - - // Sync with master array - const key = listItem.dataset.key; - const masterList = type === "valid" ? allValidKeys : allInvalidKeys; - if (masterList) { - // Ensure masterList is defined - const masterListItem = masterList.find((li) => li.dataset.key === key); - if (masterListItem) { - const masterCheckbox = masterListItem.querySelector(".key-checkbox"); - if (masterCheckbox) { - masterCheckbox.checked = isChecked; + if (type !== 'attention') { + const key = listItem.dataset.key; + const masterList = type === "valid" ? allValidKeys : allInvalidKeys; + if (masterList) { + const masterListItem = masterList.find((li) => li.dataset.key === key); + if (masterListItem) { + const masterCheckbox = masterListItem.querySelector(".key-checkbox"); + if (masterCheckbox) { + masterCheckbox.checked = isChecked; + } } } } @@ -1162,20 +1167,21 @@ function initializeKeySelectionListeners() { if (listItem) { listItem.classList.toggle("selected", checkbox.checked); - // Sync with master array - const key = listItem.dataset.key; - const masterList = - keyType === "valid" ? allValidKeys : allInvalidKeys; - if (masterList) { - // Ensure masterList is defined - const masterListItem = masterList.find( - (li) => li.dataset.key === key - ); - if (masterListItem) { - const masterCheckbox = - masterListItem.querySelector(".key-checkbox"); - if (masterCheckbox) { - masterCheckbox.checked = checkbox.checked; + // Sync with master array (only for valid/invalid lists) + if (keyType !== 'attention') { + const key = listItem.dataset.key; + const masterList = + keyType === "valid" ? allValidKeys : allInvalidKeys; + if (masterList) { + const masterListItem = masterList.find( + (li) => li.dataset.key === key + ); + if (masterListItem) { + const masterCheckbox = + masterListItem.querySelector(".key-checkbox"); + if (masterCheckbox) { + masterCheckbox.checked = checkbox.checked; + } } } } @@ -1187,6 +1193,7 @@ function initializeKeySelectionListeners() { setupEventListenersForList("validKeys", "valid"); setupEventListenersForList("invalidKeys", "invalid"); + setupEventListenersForList("attentionKeysList", "attention"); } @@ -1579,33 +1586,43 @@ async function fetchAndRenderAttentionKeys(statusCode = 429, limit = 10) { listEl.innerHTML = ''; if (!data || (Array.isArray(data) && data.length === 0) || data.error) { listEl.innerHTML = '
  • 暂无需要注意的Key
  • '; + updateBatchActions('attention'); return; } data.forEach(item => { const li = document.createElement('li'); li.className = 'flex items-center justify-between bg-white rounded border px-3 py-2'; + li.dataset.key = item.key || ''; const masked = item.key ? `${item.key.substring(0,4)}...${item.key.substring(item.key.length-4)}` : 'N/A'; const code = item.status_code ?? statusCode; li.innerHTML = ` - \u003cdiv class=\"flex items-center gap-3\"\u003e - \u003cspan class=\"font-mono text-sm\"\u003e${masked}\u003c/span\u003e - \u003cspan class=\"text-xs bg-red-100 text-red-700 px-2 py-0.5 rounded\"\u003e${code}: ${item.count}\u003c/span\u003e - \u003c/div\u003e - \u003cdiv class=\"flex items-center gap-2\"\u003e - \u003cbutton class=\"px-2 py-1 text-xs rounded bg-success-600 hover:bg-success-700 text-white\" title=\"验证此Key\"\u003e验证\u003c/button\u003e - \u003cbutton class=\"px-2 py-1 text-xs rounded bg-blue-600 hover:bg-blue-700 text-white\" title=\"查看24小时详情\"\u003e详情\u003c/button\u003e - \u003cbutton class=\"px-2 py-1 text-xs rounded bg-blue-500 hover:bg-blue-600 text-white\" title=\"复制Key\"\u003e复制\u003c/button\u003e - \u003cbutton class=\"px-2 py-1 text-xs rounded bg-red-800 hover:bg-red-900 text-white\" title=\"删除此Key\"\u003e删除\u003c/button\u003e - \u003c/div\u003e`; +
    + + ${masked} + ${code}: ${item.count} +
    +
    + + + + +
    `; const [verifyBtn, detailBtn, copyBtn, deleteBtn] = li.querySelectorAll('button'); - verifyBtn.addEventListener('click', (e) => verifyKey(item.key, e.currentTarget)); - detailBtn.addEventListener('click', () => window.showKeyUsageDetails(item.key)); - copyBtn.addEventListener('click', () => copyKey(item.key)); - deleteBtn.addEventListener('click', (e) => showSingleKeyDeleteConfirmModal(item.key, e.currentTarget)); + verifyBtn.addEventListener('click', (e) => { e.stopPropagation(); verifyKey(item.key, e.currentTarget); }); + detailBtn.addEventListener('click', (e) => { e.stopPropagation(); window.showKeyUsageDetails(item.key); }); + copyBtn.addEventListener('click', (e) => { e.stopPropagation(); copyKey(item.key); }); + deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); showSingleKeyDeleteConfirmModal(item.key, e.currentTarget); }); + // Checkbox change updates batch actions + const checkbox = li.querySelector('.key-checkbox'); + if (checkbox) { + checkbox.addEventListener('change', () => updateBatchActions('attention')); + } listEl.appendChild(li); }); + updateBatchActions('attention'); } catch (e) { listEl.innerHTML = `
  • 加载失败: ${e.message}
  • `; + updateBatchActions('attention'); } } diff --git a/app/templates/keys_status.html b/app/templates/keys_status.html index 343f62c..0a5de82 100644 --- a/app/templates/keys_status.html +++ b/app/templates/keys_status.html @@ -322,12 +322,13 @@ endblock %} {% block head_extra_styles %} border-color: rgba(59, 130, 246, 0.3); } - /* 隐藏原生复选框 */ - .key-checkbox { + /* 隐藏原生复选框(仅隐藏有效/无效列表中的复选框,保留值得注意的Key列表中的复选框可见) */ + #validKeys .key-checkbox, + #invalidKeys .key-checkbox { display: none; } - /* 自定义复选框样式 */ + /* 自定义复选框样式(仅针对有效/无效列表) */ #validKeys li::before, #invalidKeys li::before { content: ""; @@ -363,6 +364,31 @@ endblock %} {% block head_extra_styles %} font-size: 0.8rem; } + /* 值得注意的Key列表样式与选中态(保留原生复选框可见) */ + #attentionKeysList li { + display: flex; + align-items: center; + justify-content: space-between; + background-color: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 0.5rem; + padding: 0.5rem 0.75rem; + transition: all 0.2s ease; + cursor: pointer; + } + #attentionKeysList li:hover { + border-color: rgba(0, 0, 0, 0.12); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + background-color: rgba(249, 250, 251, 0.95); + } + #attentionKeysList li.selected { + background-color: rgba(239, 246, 255, 0.95); /* light blue */ + border-color: rgba(59, 130, 246, 0.35); + } + #attentionKeysList .key-checkbox { + margin-right: 0.25rem; + } + .key-text { color: #374151 !important; /* gray-700 for light theme */ text-shadow: none; @@ -1293,18 +1319,59 @@ endblock %} {% block head_extra_styles %} -
    +
    + +
    + + +
    -
    - +
    + + +
      +
    • 加载中...
    • +
    +