mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-06-08 17:19:48 +08:00
feat(ui): 支持值得注意的Key多选、全选与批量操作
为“值得注意的Key”列表新增可见复选框和全选开关,提供 批量验证/复制/删除操作,并优化选择逻辑与样式。 - attentionKeysList 每行新增可见复选框并设置 data-key - 新增“全选”和批量操作栏,实时显示已选数量 - getSelectedKeys/updateBatchActions/toggleSelectAll 适配 attention 根节点,且仅作用于可见项 - initializeKeySelectionListeners 增加 attention 事件绑定 - fetchAndRenderAttentionKeys 阻止按钮冒泡、绑定复选框变更, 在加载成功/失败后刷新批量栏状态 - attention 列表不与 valid/invalid 主列表同步勾选,避免交叉影响 - CSS 仅隐藏有效/无效列表复选框,新增 attention 列表 hover/选中态样式 - 增强空值判断,避免批量栏或全选元素缺失时报错
This commit is contained in:
@@ -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 = '<li class="text-center text-gray-500 py-2">暂无需要注意的Key</li>';
|
||||
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`;
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" class="form-checkbox h-4 w-4 text-primary-600 border-gray-300 rounded key-checkbox" value="${item.key || ''}">
|
||||
<span class="font-mono text-sm">${masked}</span>
|
||||
<span class="text-xs bg-red-100 text-red-700 px-2 py-0.5 rounded">${code}: ${item.count}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="px-2 py-1 text-xs rounded bg-success-600 hover:bg-success-700 text-white" title="验证此Key">验证</button>
|
||||
<button class="px-2 py-1 text-xs rounded bg-blue-600 hover:bg-blue-700 text-white" title="查看24小时详情">详情</button>
|
||||
<button class="px-2 py-1 text-xs rounded bg-blue-500 hover:bg-blue-600 text-white" title="复制Key">复制</button>
|
||||
<button class="px-2 py-1 text-xs rounded bg-red-800 hover:bg-red-900 text-white" title="删除此Key">删除</button>
|
||||
</div>`;
|
||||
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 = `<li class="text-center text-red-500 py-2">加载失败: ${e.message}</li>`;
|
||||
updateBatchActions('attention');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user