From 3511833fc1700890ff0da4cf38eb31ffe2fec168 Mon Sep 17 00:00:00 2001 From: Jack <2091813161@qq.com> Date: Tue, 24 Mar 2026 10:05:00 +0800 Subject: [PATCH] Refine the Sub2API remote proxy picker in settings. Replace the native select with a custom remote proxy picker that matches the account upload UI and refresh proxy status when opening the editor or picker. --- static/css/style.css | 146 ++++++++++++++++++++++++- static/js/settings.js | 233 ++++++++++++++++++++++++++++++++++------ templates/settings.html | 15 ++- 3 files changed, 356 insertions(+), 38 deletions(-) diff --git a/static/css/style.css b/static/css/style.css index 6c2b748..76305a2 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -363,10 +363,154 @@ body { gap: var(--spacing-sm); } -.sub2api-remote-proxy-select { +.sub2api-remote-proxy-picker { + position: relative; flex: 1; } +.sub2api-remote-proxy-trigger { + width: 100%; + min-height: 44px; + padding: 10px 14px; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--surface); + color: var(--text-primary); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-sm); + cursor: pointer; + transition: all var(--transition); + text-align: left; +} + +.sub2api-remote-proxy-trigger:hover, +.sub2api-remote-proxy-trigger[aria-expanded="true"] { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px var(--primary-light); +} + +.sub2api-remote-proxy-trigger-content { + flex: 1; + min-width: 0; +} + +.sub2api-remote-proxy-trigger-arrow { + color: var(--text-muted); + font-size: 0.875rem; + flex-shrink: 0; +} + +.sub2api-remote-proxy-placeholder { + color: var(--text-muted); + font-size: 0.875rem; +} + +.sub2api-remote-proxy-summary-title-row, +.sub2api-remote-proxy-option-title-row { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px; +} + +.sub2api-remote-proxy-summary-name, +.sub2api-remote-proxy-option-name { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-primary); +} + +.sub2api-remote-proxy-summary-meta, +.sub2api-remote-proxy-option-meta { + margin-top: 4px; + font-size: 0.75rem; + color: var(--text-muted); + line-height: 1.4; +} + +.sub2api-remote-proxy-pill { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: var(--radius-full); + background: var(--info-light); + color: var(--info-color); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.02em; +} + +.sub2api-remote-proxy-dropdown { + position: absolute; + top: calc(100% + 6px); + left: 0; + right: 0; + z-index: 20; + display: none; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + overflow: hidden; +} + +.sub2api-remote-proxy-dropdown.active { + display: block; +} + +.sub2api-remote-proxy-options { + max-height: 320px; + overflow-y: auto; + padding: var(--spacing-sm); + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.sub2api-remote-proxy-option { + width: 100%; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--surface); + color: inherit; + padding: 10px 12px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + cursor: pointer; + transition: all var(--transition-fast); + text-align: left; +} + +.sub2api-remote-proxy-option:hover { + background: var(--surface-hover); + border-color: var(--text-muted); +} + +.sub2api-remote-proxy-option.selected { + border-color: var(--primary-color); + background: var(--primary-light); +} + +.sub2api-remote-proxy-option-main { + min-width: 0; + flex: 1; +} + +.sub2api-remote-proxy-option-check { + flex-shrink: 0; + font-size: 0.75rem; + font-weight: 600; + color: var(--text-muted); +} + +.sub2api-remote-proxy-option.selected .sub2api-remote-proxy-option-check { + color: var(--primary-color); +} + .sub2api-remote-proxy-btn { flex-shrink: 0; } diff --git a/static/js/settings.js b/static/js/settings.js index 1a1b62b..a036b6b 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -58,6 +58,9 @@ const elements = { sub2ApiServiceModalTitle: document.getElementById('sub2api-service-modal-title'), testSub2ApiServiceBtn: document.getElementById('test-sub2api-service-btn'), loadSub2ApiRemoteProxiesBtn: document.getElementById('load-sub2api-remote-proxies-btn'), + sub2ApiRemoteProxyTrigger: document.getElementById('sub2api-remote-proxy-trigger'), + sub2ApiRemoteProxyDropdown: document.getElementById('sub2api-remote-proxy-dropdown'), + sub2ApiRemoteProxyOptions: document.getElementById('sub2api-remote-proxy-options'), // Team Manager 服务管理 addTmServiceBtn: document.getElementById('add-tm-service-btn'), tmServicesTable: document.getElementById('tm-services-table'), @@ -91,8 +94,11 @@ document.addEventListener('DOMContentLoaded', () => { initEventListeners(); }); -document.addEventListener('click', () => { +document.addEventListener('click', (e) => { document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active')); + if (!e.target.closest('.sub2api-remote-proxy-picker')) { + closeSub2ApiRemoteProxyDropdown(); + } }); // 初始化标签页 @@ -316,6 +322,23 @@ function initEventListeners() { if (elements.loadSub2ApiRemoteProxiesBtn) { elements.loadSub2ApiRemoteProxiesBtn.addEventListener('click', () => loadSub2ApiRemoteProxiesForForm()); } + if (elements.sub2ApiRemoteProxyTrigger) { + elements.sub2ApiRemoteProxyTrigger.addEventListener('click', (e) => { + e.stopPropagation(); + const opened = toggleSub2ApiRemoteProxyDropdown(); + if (opened) { + loadSub2ApiRemoteProxiesForForm({ silent: true }); + } + }); + } + if (elements.sub2ApiRemoteProxyOptions) { + elements.sub2ApiRemoteProxyOptions.addEventListener('click', (e) => { + const optionBtn = e.target.closest('.sub2api-remote-proxy-option'); + if (!optionBtn) return; + setSub2ApiDefaultRemoteProxyValue(optionBtn.dataset.proxyValue || ''); + closeSub2ApiRemoteProxyDropdown(); + }); + } } // 加载设置 @@ -1382,51 +1405,193 @@ async function handleTestCpaService() { // ============================================================================ let _sub2apiEditingId = null; +let _sub2apiRemoteProxyOptions = []; -function getSub2ApiDefaultRemoteProxySelect() { +function normalizeSub2ApiRemoteProxyValue(value) { + return value == null || value === '' ? '' : String(value); +} + +function getSub2ApiDefaultRemoteProxyInput() { return document.getElementById('sub2api-default-remote-proxy-id'); } -function resetSub2ApiRemoteProxyOptions(selectedValue = '', firstOptionText = '不设置默认代理') { - const select = getSub2ApiDefaultRemoteProxySelect(); - if (!select) return; +function getSub2ApiRemoteProxyTriggerContent() { + return document.getElementById('sub2api-remote-proxy-trigger-content'); +} - const normalizedValue = selectedValue == null ? '' : String(selectedValue); - const options = [``]; - if (normalizedValue) { - options.push(``); +function getSub2ApiRemoteProxyOptionByValue(value) { + const normalizedValue = normalizeSub2ApiRemoteProxyValue(value); + return _sub2apiRemoteProxyOptions.find(option => option.value === normalizedValue) || null; +} + +function getSub2ApiRemoteProxyStatusClass(status) { + const normalizedStatus = String(status || '').trim().toLowerCase(); + if (normalizedStatus === 'active') return 'active'; + if ([ 'inactive', 'disabled', 'offline' ].includes(normalizedStatus)) return 'disabled'; + if ([ 'error', 'failed' ].includes(normalizedStatus)) return 'error'; + if ([ 'warning', 'missing' ].includes(normalizedStatus)) return 'warning'; + return 'pending'; +} + +function buildSub2ApiRemoteProxySummary(option) { + if (!option || option.value === '') { + return '不设置默认代理'; } - select.innerHTML = options.join(''); - select.value = normalizedValue; + const protocol = option.protocol + ? `${escapeHtml(option.protocol)}` + : ''; + const status = option.status + ? `${escapeHtml(option.status)}` + : ''; + const meta = option.meta + ? `
` + : ''; + + return ` +与账号页上传时选择的远端代理一致;仅在未显式选择代理时作为默认值。