From 93ab9842004adc263f2089a07f281ec34a7560d7 Mon Sep 17 00:00:00 2001 From: cnlimiter Date: Thu, 19 Mar 2026 01:58:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor(html):=20=E4=BC=98=E5=8C=96=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/css/style.css | 24 ++++++++++++ static/js/accounts.js | 78 ++++++++++++++++++++++++------------- static/js/app.js | 34 +++++++++------- static/js/email_services.js | 55 ++++++++++++++++++-------- static/js/settings.js | 70 +++++++++++++++------------------ static/js/utils.js | 39 +++++++++++++++++-- templates/accounts.html | 2 +- templates/index.html | 1 - 8 files changed, 202 insertions(+), 101 deletions(-) diff --git a/static/css/style.css b/static/css/style.css index b6e7818..4c40acc 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -439,6 +439,30 @@ body { color: var(--text-secondary); } +.btn-copy-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + font-size: 11px; + line-height: 1; + background: var(--surface-hover); + border: 1px solid var(--border); + border-radius: 50%; + cursor: pointer; + color: var(--text-muted); + flex-shrink: 0; + transition: background 0.15s, color 0.15s; +} + +.btn-copy-icon:hover { + background: var(--primary-light); + color: var(--primary-color); + border-color: var(--primary-color); +} + .btn-ghost:hover:not(:disabled) { background: var(--surface-hover); color: var(--text-primary); diff --git a/static/js/accounts.js b/static/js/accounts.js index db18034..923d3b9 100644 --- a/static/js/accounts.js +++ b/static/js/accounts.js @@ -172,6 +172,7 @@ function initEventListeners() { document.addEventListener('click', () => { elements.exportMenu.classList.remove('active'); uploadMenu.classList.remove('active'); + document.querySelectorAll('#accounts-table .dropdown-menu.active').forEach(m => m.classList.remove('active')); }); } @@ -289,21 +290,21 @@ function renderAccounts(accounts) { ${account.id} - - ${escapeHtml(account.email)} + + + ${account.password - ? `${escapeHtml(account.password.substring(0, 4) + '****')}` + ? ` + ${escapeHtml(account.password.substring(0, 4) + '****')} + + ` : '-'} ${getServiceTypeText(account.email_service)} - - - ${getStatusText('account', account.status)} - - + ${getStatusIcon(account.status)}
${account.cpa_uploaded @@ -320,26 +321,17 @@ function renderAccounts(accounts) { ${format.date(account.last_refresh) || '-'} -
- - - - - - ${account.password ? `` : ''} - +
+ + +
@@ -365,6 +357,22 @@ function renderAccounts(accounts) { }); }); + // 绑定复制邮箱按钮 + elements.table.querySelectorAll('.copy-email-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + copyToClipboard(btn.dataset.email); + }); + }); + + // 绑定复制密码按钮 + elements.table.querySelectorAll('.copy-pwd-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + copyToClipboard(btn.dataset.pwd); + }); + }); + // 渲染后同步全选框状态 const allCbs = elements.table.querySelectorAll('input[type="checkbox"][data-id]'); const checkedCbs = elements.table.querySelectorAll('input[type="checkbox"][data-id]:checked'); @@ -1106,6 +1114,20 @@ async function handleBatchUploadTm() { } } +// 更多菜单切换 +function toggleMoreMenu(btn) { + const menu = btn.nextElementSibling; + const isActive = menu.classList.contains('active'); + // 关闭所有其他更多菜单 + document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active')); + if (!isActive) menu.classList.add('active'); +} + +function closeMoreMenu(el) { + const menu = el.closest('.dropdown-menu'); + if (menu) menu.classList.remove('active'); +} + // 保存账号 Cookies async function saveCookies(id) { const textarea = document.getElementById(`cookies-input-${id}`); diff --git a/static/js/app.js b/static/js/app.js index 73e8878..c240222 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -898,27 +898,33 @@ async function loadRecentAccounts() { ${account.id} - ${escapeHtml(account.email)} - - - ${account.password ? `${escapeHtml(account.password.substring(0, 8))}...` : '-'} - - - - ${getStatusText('account', account.status)} + + ${escapeHtml(account.email)} + + + ${account.password + ? ` + ${escapeHtml(account.password.substring(0, 8))}... + + ` + : '-'} + -
- - ${account.password ? `` : ''} -
+ ${getStatusIcon(account.status)} `).join(''); + // 绑定复制按钮事件 + elements.recentAccountsTable.querySelectorAll('.copy-email-btn').forEach(btn => { + btn.addEventListener('click', (e) => { e.stopPropagation(); copyToClipboard(btn.dataset.email); }); + }); + elements.recentAccountsTable.querySelectorAll('.copy-pwd-btn').forEach(btn => { + btn.addEventListener('click', (e) => { e.stopPropagation(); copyToClipboard(btn.dataset.pwd); }); + }); + } catch (error) { console.error('加载账号列表失败:', error); } diff --git a/static/js/email_services.js b/static/js/email_services.js index e142480..aca83cc 100644 --- a/static/js/email_services.js +++ b/static/js/email_services.js @@ -145,6 +145,23 @@ function initEventListeners() { // 临时邮箱配置 elements.tempmailForm.addEventListener('submit', handleSaveTempmail); elements.testTempmailBtn.addEventListener('click', handleTestTempmail); + + // 点击其他地方关闭更多菜单 + document.addEventListener('click', () => { + document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active')); + }); +} + +function toggleEmailMoreMenu(btn) { + const menu = btn.nextElementSibling; + const isActive = menu.classList.contains('active'); + document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active')); + if (!isActive) menu.classList.add('active'); +} + +function closeEmailMoreMenu(el) { + const menu = el.closest('.dropdown-menu'); + if (menu) menu.classList.remove('active'); } // 切换添加表单子类型 @@ -211,19 +228,20 @@ async function loadOutlookServices() { ${service.config?.has_oauth ? 'OAuth' : '密码'}
- - - ${service.enabled ? '启用' : '禁用'} - - + ${service.enabled ? '✅' : '⭕'} ${service.priority} ${format.date(service.last_used)} -
- - - - +
+ + +
@@ -281,15 +299,20 @@ async function loadCustomServices() { ${escapeHtml(service.name)} ${typeLabel} ${escapeHtml(addr)} - ${service.enabled ? '启用' : '禁用'} + ${service.enabled ? '✅' : '⭕'} ${service.priority} ${format.date(service.last_used)} -
- - - - +
+ + +
`; diff --git a/static/js/settings.js b/static/js/settings.js index 4dc3a9e..595f038 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -90,6 +90,10 @@ document.addEventListener('DOMContentLoaded', () => { initEventListeners(); }); +document.addEventListener('click', () => { + document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active')); +}); + // 初始化标签页 function initTabs() { elements.tabs.forEach(btn => { @@ -424,11 +428,7 @@ function renderEmailServices(services) { ${escapeHtml(service.name)} ${getServiceTypeText(service.service_type)} - - - ${service.enabled ? '已启用' : '已禁用'} - - + ${service.enabled ? '✅' : '⭕'} ${service.priority} ${format.date(service.last_used)} @@ -811,32 +811,38 @@ function renderProxies(proxies) { : `` } - - - ${proxy.enabled ? '已启用' : '已禁用'} - - + ${proxy.enabled ? '✅' : '⭕'} ${format.date(proxy.last_used)} -
- - - - +
+ + +
`).join(''); } +function toggleSettingsMoreMenu(btn) { + const menu = btn.nextElementSibling; + const isActive = menu.classList.contains('active'); + document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active')); + if (!isActive) menu.classList.add('active'); +} + +function closeSettingsMoreMenu(el) { + const menu = el.closest('.dropdown-menu'); + if (menu) menu.classList.remove('active'); +} + // 设为默认代理 async function handleSetProxyDefault(id) { try { @@ -1070,11 +1076,7 @@ function renderTmServicesTable(services) { ${escapeHtml(s.name)} ${escapeHtml(s.api_url)} - - - ${s.enabled ? '启用' : '禁用'} - - + ${s.enabled ? '✅' : '⭕'} ${s.priority} @@ -1235,11 +1237,7 @@ function renderCpaServicesTable(services) { ${escapeHtml(s.name)} ${escapeHtml(s.api_url)} - - - ${s.enabled ? '启用' : '禁用'} - - + ${s.enabled ? '✅' : '⭕'} ${s.priority} @@ -1402,11 +1400,7 @@ function renderSub2ApiServices(services) { ${escapeHtml(s.name)} ${escapeHtml(s.api_url)} - - - ${s.enabled ? '启用' : '禁用'} - - + ${s.enabled ? '✅' : '⭕'} ${s.priority} diff --git a/static/js/utils.js b/static/js/utils.js index 3c17184..e5adf85 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -368,6 +368,19 @@ function getServiceTypeText(type) { return statusMap.service[type] || type; } +const accountStatusIconMap = { + active: { icon: '🟢', title: '活跃' }, + expired: { icon: '🟡', title: '过期' }, + banned: { icon: '🔴', title: '封禁' }, + failed: { icon: '❌', title: '失败' }, +}; + +function getStatusIcon(status) { + const s = accountStatusIconMap[status]; + if (!s) return ``; + return `${s.icon}`; +} + // ============================================ // 确认对话框 // ============================================ @@ -420,10 +433,29 @@ function confirm(message, title = '确认操作') { // ============================================ async function copyToClipboard(text) { + if (navigator.clipboard && navigator.clipboard.writeText) { + try { + await navigator.clipboard.writeText(text); + toast.success('已复制到剪贴板'); + return true; + } catch (err) { + // 降级到 execCommand + } + } try { - await navigator.clipboard.writeText(text); - toast.success('已复制到剪贴板'); - return true; + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0;pointer-events:none;'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(ta); + if (ok) { + toast.success('已复制到剪贴板'); + return true; + } + throw new Error('execCommand failed'); } catch (err) { toast.error('复制失败'); return false; @@ -498,3 +530,4 @@ window.throttle = throttle; window.getStatusText = getStatusText; window.getStatusClass = getStatusClass; window.getServiceTypeText = getServiceTypeText; +window.getStatusIcon = getStatusIcon; diff --git a/templates/accounts.html b/templates/accounts.html index fec857d..efd1204 100644 --- a/templates/accounts.html +++ b/templates/accounts.html @@ -172,7 +172,7 @@ CPA 订阅 最后刷新 - 操作 + 操作 diff --git a/templates/index.html b/templates/index.html index 12b9602..6a52391 100644 --- a/templates/index.html +++ b/templates/index.html @@ -386,7 +386,6 @@ 邮箱 密码 状态 - 操作