feat(proxy): add batch delete functionality for proxies and enhance verification code handling

This commit is contained in:
cnlimiter
2026-03-28 04:13:15 +08:00
parent 51922ef2a6
commit 8b8ef7c6c0
14 changed files with 996 additions and 97 deletions

View File

@@ -29,7 +29,9 @@ const elements = {
selectAllServices: document.getElementById('select-all-services'),
// 代理列表
proxiesTable: document.getElementById('proxies-table'),
selectAllProxies: document.getElementById('select-all-proxies'),
addProxyBtn: document.getElementById('add-proxy-btn'),
batchDeleteProxiesBtn: document.getElementById('batch-delete-proxies-btn'),
testAllProxiesBtn: document.getElementById('test-all-proxies-btn'),
deleteDisabledProxiesBtn: document.getElementById('delete-disabled-proxies-btn'),
batchImportProxyBtn: document.getElementById('batch-import-proxy-btn'),
@@ -89,6 +91,8 @@ const elements = {
// 选中的服务 ID
let selectedServiceIds = new Set();
let selectedProxyIds = new Set();
let disabledProxyCount = 0;
// 初始化
document.addEventListener('DOMContentLoaded', () => {
@@ -252,6 +256,20 @@ function initEventListeners() {
elements.testAllProxiesBtn.addEventListener('click', handleTestAllProxies);
}
if (elements.selectAllProxies) {
elements.selectAllProxies.addEventListener('change', (e) => {
toggleSelectAllProxies(e.target.checked);
});
}
if (elements.batchDeleteProxiesBtn) {
elements.batchDeleteProxiesBtn.addEventListener('click', handleBatchDeleteProxies);
}
if (elements.deleteDisabledProxiesBtn) {
elements.deleteDisabledProxiesBtn.addEventListener('click', handleDeleteDisabledProxies);
}
if (elements.closeProxyModal) {
elements.closeProxyModal.addEventListener('click', closeProxyModal);
}
@@ -845,12 +863,16 @@ function escapeHtml(text) {
async function loadProxies() {
try {
const data = await api.get('/settings/proxies');
syncSelectedProxyIds(data.proxies || []);
renderProxies(data.proxies);
} catch (error) {
console.error('加载代理列表失败:', error);
selectedProxyIds = new Set();
disabledProxyCount = 0;
updateProxyBatchActions();
elements.proxiesTable.innerHTML = `
<tr>
<td colspan="7">
<td colspan="9">
<div class="empty-state">
<div class="empty-state-icon">❌</div>
<div class="empty-state-title">加载失败</div>
@@ -864,9 +886,12 @@ async function loadProxies() {
// 渲染代理列表
function renderProxies(proxies) {
if (!proxies || proxies.length === 0) {
selectedProxyIds = new Set();
disabledProxyCount = 0;
updateProxyBatchActions();
elements.proxiesTable.innerHTML = `
<tr>
<td colspan="7">
<td colspan="9">
<div class="empty-state">
<div class="empty-state-icon">🌐</div>
<div class="empty-state-title">暂无代理</div>
@@ -878,8 +903,20 @@ function renderProxies(proxies) {
return;
}
disabledProxyCount = proxies.filter((proxy) => proxy && proxy.enabled === false).length;
elements.proxiesTable.innerHTML = proxies.map(proxy => `
<tr data-proxy-id="${proxy.id}">
<td style="text-align:center;">
<input
type="checkbox"
class="proxy-checkbox"
data-id="${proxy.id}"
${selectedProxyIds.has(proxy.id) ? 'checked' : ''}
onchange="updateSelectedProxies()"
aria-label="选择代理 ${proxy.id}"
>
</td>
<td>${proxy.id}</td>
<td>${escapeHtml(proxy.name)}</td>
<td><span class="badge">${proxy.type.toUpperCase()}</span></td>
@@ -911,6 +948,56 @@ function renderProxies(proxies) {
</td>
</tr>
`).join('');
updateProxyBatchActions();
}
function syncSelectedProxyIds(proxies) {
const validIds = new Set((proxies || []).map(proxy => proxy.id));
selectedProxyIds = new Set([...selectedProxyIds].filter(id => validIds.has(id)));
}
function toggleSelectAllProxies(checked) {
document.querySelectorAll('.proxy-checkbox').forEach((checkbox) => {
checkbox.checked = checked;
});
updateSelectedProxies();
}
function updateSelectedProxies() {
selectedProxyIds = new Set(
[...document.querySelectorAll('.proxy-checkbox:checked')]
.map((checkbox) => parseInt(checkbox.dataset.id, 10))
.filter((id) => Number.isInteger(id) && id > 0)
);
updateProxyBatchActions();
}
function updateProxyBatchActions() {
const proxyCheckboxes = [...document.querySelectorAll('.proxy-checkbox')];
const checkedCount = selectedProxyIds.size;
if (elements.selectAllProxies) {
const totalCount = proxyCheckboxes.length;
const allChecked = totalCount > 0 && checkedCount === totalCount;
elements.selectAllProxies.checked = allChecked;
elements.selectAllProxies.indeterminate = checkedCount > 0 && checkedCount < totalCount;
elements.selectAllProxies.disabled = totalCount === 0;
}
if (elements.batchDeleteProxiesBtn) {
elements.batchDeleteProxiesBtn.disabled = checkedCount === 0;
elements.batchDeleteProxiesBtn.textContent = checkedCount > 0
? `🗑️ 批量删除 (${checkedCount})`
: '🗑️ 批量删除';
}
if (elements.deleteDisabledProxiesBtn) {
elements.deleteDisabledProxiesBtn.disabled = disabledProxyCount === 0;
elements.deleteDisabledProxiesBtn.textContent = disabledProxyCount > 0
? `🧹 删除禁用项 (${disabledProxyCount})`
: '🧹 删除禁用项';
}
}
function toggleSettingsMoreMenu(btn) {
@@ -1071,6 +1158,49 @@ async function deleteProxyItem(id) {
}
}
async function handleBatchDeleteProxies() {
const ids = [...selectedProxyIds];
if (ids.length === 0) {
toast.error('请先选择要删除的代理');
return;
}
const confirmed = await confirm(`确定要批量删除选中的 ${ids.length} 个代理吗?`);
if (!confirmed) return;
try {
const result = await api.post('/settings/proxies/batch-delete', { ids });
const missingCount = Array.isArray(result.not_found_ids) ? result.not_found_ids.length : 0;
const summary = missingCount > 0
? `${result.message},其中 ${missingCount} 个代理不存在或已被删除`
: result.message;
toast.success(summary || `已删除 ${ids.length} 个代理`);
selectedProxyIds = new Set();
await loadProxies();
} catch (error) {
toast.error('批量删除失败: ' + error.message);
}
}
async function handleDeleteDisabledProxies() {
if (disabledProxyCount === 0) {
toast.error('当前没有可删除的禁用代理');
return;
}
const confirmed = await confirm(`确定要删除全部 ${disabledProxyCount} 个禁用代理吗?`);
if (!confirmed) return;
try {
const result = await api.post('/settings/proxies/delete-disabled');
selectedProxyIds = new Set();
toast.success(result.message || `已删除 ${result.deleted_count || 0} 个禁用代理`);
await loadProxies();
} catch (error) {
toast.error('删除禁用代理失败: ' + error.message);
}
}
// 测试所有代理
async function handleTestAllProxies() {
elements.testAllProxiesBtn.disabled = true;