From 74d3b4c0ded1ac53112c69215b99062584e8357c Mon Sep 17 00:00:00 2001 From: cnlimiter Date: Wed, 18 Mar 2026 19:43:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(front):=20=E4=BC=98=E5=8C=96=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/js/app.js | 87 ++++++++++++++++++++++++++++------------- static/js/settings.js | 36 ++++++++++++----- templates/index.html | 56 +++++++++++++++++++++++--- templates/settings.html | 18 ++++----- 4 files changed, 144 insertions(+), 53 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index 7b49337..73e8878 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -85,13 +85,13 @@ const elements = { // 注册后自动操作 autoUploadCpa: document.getElementById('auto-upload-cpa'), cpaServiceSelectGroup: document.getElementById('cpa-service-select-group'), - cpaServiceCheckboxes: document.getElementById('cpa-service-checkboxes'), + cpaServiceSelect: document.getElementById('cpa-service-select'), autoUploadSub2api: document.getElementById('auto-upload-sub2api'), sub2apiServiceSelectGroup: document.getElementById('sub2api-service-select-group'), - sub2apiServiceCheckboxes: document.getElementById('sub2api-service-checkboxes'), + sub2apiServiceSelect: document.getElementById('sub2api-service-select'), autoUploadTm: document.getElementById('auto-upload-tm'), tmServiceSelectGroup: document.getElementById('tm-service-select-group'), - tmServiceCheckboxes: document.getElementById('tm-service-checkboxes'), + tmServiceSelect: document.getElementById('tm-service-select'), }; // 初始化 @@ -108,14 +108,14 @@ document.addEventListener('DOMContentLoaded', () => { // 初始化注册后自动操作选项(CPA / Sub2API / TM) async function initAutoUploadOptions() { await Promise.all([ - loadServiceCheckboxes('cpa', '/cpa-services?enabled=true', elements.cpaServiceCheckboxes, elements.autoUploadCpa, elements.cpaServiceSelectGroup), - loadServiceCheckboxes('sub2api', '/sub2api-services?enabled=true', elements.sub2apiServiceCheckboxes, elements.autoUploadSub2api, elements.sub2apiServiceSelectGroup), - loadServiceCheckboxes('tm', '/tm-services?enabled=true', elements.tmServiceCheckboxes, elements.autoUploadTm, elements.tmServiceSelectGroup), + loadServiceSelect('/cpa-services?enabled=true', elements.cpaServiceSelect, elements.autoUploadCpa, elements.cpaServiceSelectGroup), + loadServiceSelect('/sub2api-services?enabled=true', elements.sub2apiServiceSelect, elements.autoUploadSub2api, elements.sub2apiServiceSelectGroup), + loadServiceSelect('/tm-services?enabled=true', elements.tmServiceSelect, elements.autoUploadTm, elements.tmServiceSelectGroup), ]); } -// 通用:加载服务 checkbox 列表,并处理联动 -async function loadServiceCheckboxes(type, apiPath, container, checkbox, selectGroup) { +// 通用:构建自定义多选下拉组件并处理联动 +async function loadServiceSelect(apiPath, container, checkbox, selectGroup) { if (!checkbox || !container) return; let services = []; try { @@ -127,14 +127,31 @@ async function loadServiceCheckboxes(type, apiPath, container, checkbox, selectG checkbox.title = '请先在设置中添加对应服务'; const label = checkbox.closest('label'); if (label) label.style.opacity = '0.5'; - if (container) container.innerHTML = '
暂无可用服务
'; + container.innerHTML = '
暂无可用服务
'; } else { - container.innerHTML = services.map(s => ` - - `).join(''); + const items = services.map(s => + `` + ).join(''); + container.innerHTML = ` +
+
+ 全部 (${services.length}) + +
+
${items}
+
`; + // 监听 checkbox 变化,更新触发器文字 + container.querySelectorAll('.msd-item input').forEach(cb => { + cb.addEventListener('change', () => updateMsdLabel(container.id + '-dd')); + }); + // 点击外部关闭 + document.addEventListener('click', (e) => { + const dd = document.getElementById(container.id + '-dd'); + if (dd && !dd.contains(e.target)) dd.classList.remove('open'); + }, true); } // 联动显示/隐藏服务选择区 @@ -143,13 +160,27 @@ async function loadServiceCheckboxes(type, apiPath, container, checkbox, selectG }); } -// 获取选中的服务 ID 列表 -function getCheckedServiceIds(type) { - const ids = []; - document.querySelectorAll(`.upload-svc-${type}:checked`).forEach(cb => { - ids.push(parseInt(cb.value)); - }); - return ids; +function toggleMsd(ddId) { + const dd = document.getElementById(ddId); + if (dd) dd.classList.toggle('open'); +} + +function updateMsdLabel(ddId) { + const dd = document.getElementById(ddId); + if (!dd) return; + const all = dd.querySelectorAll('.msd-item input'); + const checked = dd.querySelectorAll('.msd-item input:checked'); + const label = dd.querySelector('.msd-label'); + if (!label) return; + if (checked.length === 0) label.textContent = '未选择'; + else if (checked.length === all.length) label.textContent = `全部 (${all.length})`; + else label.textContent = Array.from(checked).map(c => c.nextElementSibling.textContent).join(', '); +} + +// 获取自定义多选下拉中选中的服务 ID 列表 +function getSelectedServiceIds(container) { + if (!container) return []; + return Array.from(container.querySelectorAll('.msd-item input:checked')).map(cb => parseInt(cb.value)); } // 事件监听 @@ -395,11 +426,11 @@ async function handleStartRegistration(e) { const requestData = { email_service_type: emailServiceType, auto_upload_cpa: elements.autoUploadCpa ? elements.autoUploadCpa.checked : false, - cpa_service_ids: elements.autoUploadCpa && elements.autoUploadCpa.checked ? getCheckedServiceIds('cpa') : [], + cpa_service_ids: elements.autoUploadCpa && elements.autoUploadCpa.checked ? getSelectedServiceIds(elements.cpaServiceSelect) : [], auto_upload_sub2api: elements.autoUploadSub2api ? elements.autoUploadSub2api.checked : false, - sub2api_service_ids: elements.autoUploadSub2api && elements.autoUploadSub2api.checked ? getCheckedServiceIds('sub2api') : [], + sub2api_service_ids: elements.autoUploadSub2api && elements.autoUploadSub2api.checked ? getSelectedServiceIds(elements.sub2apiServiceSelect) : [], auto_upload_tm: elements.autoUploadTm ? elements.autoUploadTm.checked : false, - tm_service_ids: elements.autoUploadTm && elements.autoUploadTm.checked ? getCheckedServiceIds('tm') : [], + tm_service_ids: elements.autoUploadTm && elements.autoUploadTm.checked ? getSelectedServiceIds(elements.tmServiceSelect) : [], }; // 如果选择了数据库中的服务,传递 service_id @@ -1099,11 +1130,11 @@ async function handleOutlookBatchRegistration() { concurrency: Math.min(50, Math.max(1, concurrency)), mode: mode, auto_upload_cpa: elements.autoUploadCpa ? elements.autoUploadCpa.checked : false, - cpa_service_ids: elements.autoUploadCpa && elements.autoUploadCpa.checked ? getCheckedServiceIds('cpa') : [], + cpa_service_ids: elements.autoUploadCpa && elements.autoUploadCpa.checked ? getSelectedServiceIds(elements.cpaServiceSelect) : [], auto_upload_sub2api: elements.autoUploadSub2api ? elements.autoUploadSub2api.checked : false, - sub2api_service_ids: elements.autoUploadSub2api && elements.autoUploadSub2api.checked ? getCheckedServiceIds('sub2api') : [], + sub2api_service_ids: elements.autoUploadSub2api && elements.autoUploadSub2api.checked ? getSelectedServiceIds(elements.sub2apiServiceSelect) : [], auto_upload_tm: elements.autoUploadTm ? elements.autoUploadTm.checked : false, - tm_service_ids: elements.autoUploadTm && elements.autoUploadTm.checked ? getCheckedServiceIds('tm') : [], + tm_service_ids: elements.autoUploadTm && elements.autoUploadTm.checked ? getSelectedServiceIds(elements.tmServiceSelect) : [], }; addLog('info', `[系统] 正在启动 Outlook 批量注册 (${selectedIds.length} 个账户)...`); diff --git a/static/js/settings.js b/static/js/settings.js index 47f4468..4dc3a9e 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -1076,7 +1076,7 @@ function renderTmServicesTable(services) { ${s.priority} - + @@ -1241,7 +1241,7 @@ function renderCpaServicesTable(services) { ${s.priority} - + @@ -1395,20 +1395,23 @@ async function loadSub2ApiServices() { function renderSub2ApiServices(services) { if (!elements.sub2ApiServicesTable) return; if (!services || services.length === 0) { - elements.sub2ApiServicesTable.innerHTML = '暂无服务,点击"添加服务"按钮添加'; + elements.sub2ApiServicesTable.innerHTML = '暂无 Sub2API 服务,点击「添加服务」新增'; return; } elements.sub2ApiServicesTable.innerHTML = services.map(s => ` ${escapeHtml(s.name)} - ${escapeHtml(s.api_url)} - ${s.enabled ? '已启用' : '已禁用'} - ${s.priority} + ${escapeHtml(s.api_url)} -
- - -
+ + ${s.enabled ? '启用' : '禁用'} + + + ${s.priority} + + + + `).join(''); @@ -1486,6 +1489,19 @@ async function handleSaveSub2ApiService(e) { } } +async function testSub2ApiServiceById(id) { + try { + const result = await api.post(`/sub2api-services/${id}/test`); + if (result.success) { + toast.success(result.message); + } else { + toast.error(result.message); + } + } catch (e) { + toast.error('测试失败: ' + e.message); + } +} + async function handleTestSub2ApiService() { const apiUrl = document.getElementById('sub2api-service-url').value.trim(); const apiKey = document.getElementById('sub2api-service-key').value.trim(); diff --git a/templates/index.html b/templates/index.html index 62d4a21..12b9602 100644 --- a/templates/index.html +++ b/templates/index.html @@ -83,6 +83,53 @@ filter: blur(0); } + /* 自定义多选下拉 */ + .multi-select-dropdown { + position: relative; + width: 100%; + font-size: 0.85rem; + } + .msd-trigger { + display: flex; + align-items: center; + justify-content: space-between; + padding: 5px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--surface); + color: var(--text-primary); + cursor: pointer; + user-select: none; + } + .msd-trigger:hover { border-color: var(--primary-color); } + .msd-arrow { font-size: 0.7rem; transition: transform 0.15s; } + .msd-dropdown.open .msd-arrow { transform: rotate(180deg); } + .msd-list { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; right: 0; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.12); + z-index: 100; + max-height: 150px; + overflow-y: auto; + padding: 4px 0; + } + .msd-dropdown.open .msd-list { display: block; } + .msd-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + cursor: pointer; + } + .msd-item:hover { background: var(--surface-hover); } + .msd-item input[type=checkbox] { margin: 0; cursor: pointer; } + .msd-empty { padding: 8px 12px; color: var(--text-muted); font-size: 0.8rem; } + /* 响应式 */ @media (max-width: 1024px) { .two-column-layout { @@ -231,8 +278,7 @@ 上传到 CPA @@ -241,8 +287,7 @@ 上传到 Sub2API @@ -251,8 +296,7 @@ 上传到 Team Manager diff --git a/templates/settings.html b/templates/settings.html index 82a89ed..91cda3e 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -214,11 +214,11 @@ - + - - + + @@ -240,11 +240,11 @@
名称名称 API URL 状态优先级操作优先级操作
- + - - + + @@ -266,11 +266,11 @@
名称名称 API URL 状态优先级操作优先级操作
- + - - + +
名称名称 API URL 状态优先级操作优先级操作