// ==================== 分组相关 ==================== // 加载分组列表 async function loadGroups() { const container = document.getElementById('groupList'); container.innerHTML = '
加载中…
'; try { const response = await fetch('/api/groups'); const data = await response.json(); if (data.success) { groups = data.groups; // 找到临时邮箱分组 const tempGroup = groups.find(g => g.name === '临时邮箱'); if (tempGroup) { tempEmailGroupId = tempGroup.id; } renderGroupList(data.groups); updateGroupSelects(); // 如果之前选中了分组,保持选中状态并刷新邮箱列表 if (currentGroupId) { const group = groups.find(g => g.id === currentGroupId); if (group) { // 刷新当前分组的邮箱列表 if (currentGroupId === tempEmailGroupId) { loadTempEmails(true); } else { loadAccountsByGroup(currentGroupId, true); } } } else { // 首次进入:自动选中第一个非临时邮箱分组 const firstNormalGroup = groups.find(g => g.name !== '临时邮箱'); if (firstNormalGroup) { selectGroup(firstNormalGroup.id); } } } } catch (error) { container.innerHTML = '

加载失败

'; showToast('加载分组失败', 'error'); } } // 渲染分组列表 function renderGroupList(groups) { const container = document.getElementById('groupList'); // 过滤掉临时邮箱分组(已有独立页面管理) const filteredGroups = groups.filter(g => g.name !== '临时邮箱'); if (filteredGroups.length === 0) { container.innerHTML = `
📁

暂无分组

`; return; } container.innerHTML = filteredGroups.map(group => { const isSystem = group.is_system === 1; const isDefault = group.id === 1; return `
${escapeHtml(group.name)} ${group.account_count || 0}
${!isSystem ? `` : ''} ${!isDefault && !isSystem ? `` : ''}
`; }).join(''); } // 选择分组 async function selectGroup(groupId) { currentGroupId = groupId; // 清空搜索框 const searchInput = document.getElementById('globalSearch'); if (searchInput) { searchInput.value = ''; } // 重置右侧邮件列 UI(清除上一个分组的残留状态) currentAccount = null; const accountBar = document.getElementById('currentAccountBar'); if (accountBar) accountBar.style.display = 'none'; const emailListEl = document.getElementById('emailList'); if (emailListEl) emailListEl.innerHTML = '
📬

请从左侧选择一个邮箱账号

'; const detailSection = document.getElementById('emailDetailSection'); if (detailSection) detailSection.style.display = 'none'; const folderTabs = document.getElementById('folderTabs'); if (folderTabs) folderTabs.style.display = 'none'; const emailCount = document.getElementById('emailCount'); if (emailCount) emailCount.textContent = ''; const methodTag = document.getElementById('methodTag'); if (methodTag) methodTag.style.display = 'none'; // 检查是否是临时邮箱分组 const group = groups.find(g => g.id === groupId); isTempEmailGroup = group && group.name === '临时邮箱'; // 更新分组列表 UI document.querySelectorAll('.group-item').forEach(item => { item.classList.toggle('active', parseInt(item.dataset.groupId) === groupId); }); // 更新邮箱面板标题 if (group) { document.getElementById('currentGroupName').textContent = group.name; document.getElementById('currentGroupColor').style.backgroundColor = group.color || '#666'; // 更新导入邮箱时的默认分组 const importSelect = document.getElementById('importGroupSelect'); if (importSelect) { importSelect.value = groupId; } } // 显示「注册Outlook账号」按钮(仅在非临时邮箱分组时) const registerBtn = document.getElementById('registerOutlookBtn'); if (registerBtn) { registerBtn.style.display = isTempEmailGroup ? 'none' : ''; } // 更新底部按钮 updateAccountPanelFooter(); // 加载该分组的邮箱 if (isTempEmailGroup) { // 临时邮箱已有独立页面,跳转到专属页面管理 navigate('temp-emails'); return; } else { await loadAccountsByGroup(groupId); } } // 更新账号面板底部按钮(新布局无独立footer,通过topbar按钮实现) function updateAccountPanelFooter() { // No-op: new layout uses topbar action buttons instead } // 加载分组下的账号 async function loadAccountsByGroup(groupId, forceRefresh = false) { const container = document.getElementById('accountList'); // 保存当前滚动位置(forceRefresh 时恢复) const savedScrollTop = forceRefresh ? container.scrollTop : 0; // 如果有缓存且不强制刷新,直接使用缓存 if (!forceRefresh && accountsCache[groupId]) { renderAccountList(accountsCache[groupId]); return; } // forceRefresh 时不显示 loading(保持旧内容,静默刷新) if (!forceRefresh) { container.innerHTML = '
加载中…
'; } try { const response = await fetch(`/api/accounts?group_id=${groupId}`); const data = await response.json(); if (data.success) { // 缓存数据 accountsCache[groupId] = data.accounts; renderAccountList(data.accounts); // 恢复滚动位置 if (forceRefresh) { requestAnimationFrame(() => { container.scrollTop = savedScrollTop; }); } } } catch (error) { container.innerHTML = '

加载失败

'; } } // 获取 provider 的中文展示名(账号卡片 tag) function getProviderLabel(provider) { const key = (provider || 'outlook').toString().toLowerCase(); const labels = { outlook: 'Outlook', gmail: 'Gmail', qq: 'QQ邮箱', '163': '163邮箱', '126': '126邮箱', yahoo: 'Yahoo邮箱', aliyun: '阿里邮箱', custom: '自定义IMAP' }; return labels[key] || provider || '未知'; } // 渲染邮箱列表 function renderAccountList(accounts) { const container = document.getElementById('accountList'); if (accounts.length === 0) { container.innerHTML = `
📭

该分组暂无邮箱

`; const selectAllCheckbox = document.getElementById('selectAllCheckbox'); if (selectAllCheckbox) { selectAllCheckbox.checked = false; selectAllCheckbox.indeterminate = selectedAccountIds.size > 0; } updateBatchActionBar(); return; } // 头像颜色轮转数组 — 8 组渐变色 const avatarGradients = [ ['#B85C38', '#E8734A'], // 砖红→珊瑚 ['#3A7D44', '#5BAF6A'], // 翠绿→嫩绿 ['#2E6B8A', '#4BA3CC'], // 海蓝→天蓝 ['#8B5E3C', '#C8963E'], // 棕→琥珀金 ['#7B4F9B', '#B77FD8'], // 紫罗兰→薰衣草 ['#C75050', '#E88080'], // 朱红→浅红 ['#2C7A7B', '#4DC9C9'], // 青绿→薄荷 ['#9B6B3E', '#D4A65A'], // 赭石→沙金 ]; container.innerHTML = accounts.map((acc, index) => { const isChecked = selectedAccountIds.has(acc.id); const initial = (acc.email || '?')[0].toUpperCase(); const isFailed = acc.last_refresh_status === 'failed'; const gradient = avatarGradients[index % avatarGradients.length]; const providerLabel = getProviderLabel(acc.provider || acc.account_type || 'outlook'); const providerTagHtml = `${escapeHtml(providerLabel)}`; let tokenBadge = '– 未知'; if (acc.token_status === 'valid') { tokenBadge = '✓ 有效'; } else if (acc.token_status === 'invalid' || acc.token_status === 'expired') { tokenBadge = '✗ 过期'; } else if (acc.token_status === 'expiring') { tokenBadge = '⚠ 即将过期'; } return `
${tokenBadge}
`}).join(''); updateSelectAllCheckbox(); updateBatchActionBar(); } // 排序相关变量 let currentSortBy = 'refresh_time'; let currentSortOrder = 'asc'; // 排序账号列表 function sortAccounts(sortBy) { // 如果点击同一个排序按钮,切换排序顺序 if (currentSortBy === sortBy) { currentSortOrder = currentSortOrder === 'asc' ? 'desc' : 'asc'; } else { currentSortBy = sortBy; currentSortOrder = sortBy === 'refresh_time' ? 'asc' : 'asc'; } // 更新按钮状态 document.querySelectorAll('.sort-btn').forEach(btn => { btn.classList.remove('active'); }); const activeBtn = document.querySelector(`[data-sort="${sortBy}"]`); if (activeBtn) { activeBtn.classList.add('active'); } // 重新加载并排序账号列表 if (accountsCache[currentGroupId]) { const sortedAccounts = applyFiltersAndSort(accountsCache[currentGroupId]); renderAccountList(sortedAccounts); } } // 应用筛选和排序 function applyFiltersAndSort(accounts) { let result = [...accounts]; // 1. Tag 筛选 // Get checked tags const checkedBoxes = document.querySelectorAll('.tag-filter-checkbox:checked'); const selectedTagIds = Array.from(checkedBoxes).map(cb => parseInt(cb.value)); if (selectedTagIds.length > 0) { result = result.filter(acc => { if (!acc.tags) return false; // Check if account has ANY of the selected tags (OR logic) // If you want AND logic, use every() instead of some() return acc.tags.some(t => selectedTagIds.includes(t.id)); }); } // 2. 排序 return result.sort((a, b) => { if (currentSortBy === 'refresh_time') { const timeA = a.last_refresh_at ? new Date(a.last_refresh_at) : new Date(0); const timeB = b.last_refresh_at ? new Date(b.last_refresh_at) : new Date(0); return currentSortOrder === 'asc' ? timeA - timeB : timeB - timeA; } else { const emailA = a.email.toLowerCase(); const emailB = b.email.toLowerCase(); return currentSortOrder === 'asc' ? emailA.localeCompare(emailB) : emailB.localeCompare(emailA); } }); } // Tag Filter Change Handler function handleTagFilterChange() { if (accountsCache[currentGroupId]) { const filteredAccounts = applyFiltersAndSort(accountsCache[currentGroupId]); renderAccountList(filteredAccounts); } } // 防抖函数 function debounce(func, wait) { let timeout; return function (...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } // 全局搜索函数 async function searchAccounts(query) { const container = document.getElementById('accountList'); const titleElement = document.getElementById('currentGroupName'); if (!query.trim()) { loadAccountsByGroup(currentGroupId); return; } container.innerHTML = '
搜索中…
'; try { const response = await fetch(`/api/accounts/search?q=${encodeURIComponent(query)}`); const data = await response.json(); if (data.success) { titleElement.textContent = `搜索结果 (${data.accounts.length})`; renderAccountList(data.accounts); } else { container.innerHTML = '

搜索失败

'; } } catch (error) { console.error('搜索失败:', error); container.innerHTML = '

搜索失败,请重试

'; } } // 更新分组下拉选择框 function updateGroupSelects() { const selects = ['importGroupSelect', 'editGroupSelect']; selects.forEach(selectId => { const select = document.getElementById(selectId); if (select) { const currentValue = select.value; // 过滤掉临时邮箱分组(导入邮箱时不能选择临时邮箱分组) const filteredGroups = selectId === 'importGroupSelect' ? groups.filter(g => g.name !== '临时邮箱') : groups; select.innerHTML = filteredGroups.map(g => `` ).join(''); // 恢复之前的选择 if (currentValue && filteredGroups.find(g => g.id === parseInt(currentValue))) { select.value = currentValue; } else if (currentGroupId && filteredGroups.find(g => g.id === currentGroupId)) { select.value = currentGroupId; } } }); } // 显示添加分组模态框 function showAddGroupModal() { editingGroupId = null; document.getElementById('groupModalTitle').textContent = '添加分组'; document.getElementById('groupName').value = ''; document.getElementById('groupDescription').value = ''; selectedColor = '#B85C38'; document.querySelectorAll('.color-option').forEach(o => { o.classList.toggle('selected', o.dataset.color === selectedColor); }); document.getElementById('customColorInput').value = selectedColor; document.getElementById('customColorHex').value = selectedColor; document.getElementById('groupProxyUrl').value = ''; document.getElementById('addGroupModal').classList.add('show'); } // 隐藏添加分组模态框 function hideAddGroupModal() { document.getElementById('addGroupModal').classList.remove('show'); } // 编辑分组 async function editGroup(groupId) { try { const response = await fetch(`/api/groups/${groupId}`); const data = await response.json(); if (data.success) { editingGroupId = groupId; document.getElementById('groupModalTitle').textContent = '编辑分组'; document.getElementById('groupName').value = data.group.name; document.getElementById('groupDescription').value = data.group.description || ''; selectedColor = data.group.color || '#B85C38'; // 检查是否是预设颜色 let isPresetColor = false; document.querySelectorAll('.color-option').forEach(o => { if (o.dataset.color === selectedColor) { o.classList.add('selected'); isPresetColor = true; } else { o.classList.remove('selected'); } }); // 更新自定义颜色输入框 document.getElementById('customColorInput').value = selectedColor; document.getElementById('customColorHex').value = selectedColor; // 填充代理设置 document.getElementById('groupProxyUrl').value = data.group.proxy_url || ''; document.getElementById('addGroupModal').classList.add('show'); } } catch (error) { showToast('加载分组信息失败', 'error'); } } // 保存分组 async function saveGroup() { const name = document.getElementById('groupName').value.trim(); const description = document.getElementById('groupDescription').value.trim(); if (!name) { showToast('请输入分组名称', 'error'); return; } try { const url = editingGroupId ? `/api/groups/${editingGroupId}` : '/api/groups'; const method = editingGroupId ? 'PUT' : 'POST'; const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description, color: selectedColor, proxy_url: document.getElementById('groupProxyUrl').value.trim() }) }); const data = await response.json(); if (data.success) { showToast(data.message, 'success'); hideAddGroupModal(); loadGroups(); } else { handleApiError(data, '保存分组失败'); } } catch (error) { showToast('保存失败', 'error'); } } // 删除分组 async function deleteGroup(groupId) { if (!confirm('确定要删除该分组吗?分组下的邮箱将移至默认分组。')) { return; } try { const response = await fetch(`/api/groups/${groupId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showToast(data.message, 'success'); // 清除缓存 delete accountsCache[groupId]; // 如果删除的是当前选中的分组,切换到默认分组 if (currentGroupId === groupId) { currentGroupId = 1; } loadGroups(); } else { handleApiError(data, '删除分组失败'); } } catch (error) { showToast('删除失败', 'error'); } } // ==================== 全选功能 ==================== // 全选/取消全选账号(当前分组) function toggleSelectAll() { const selectAllCheckbox = document.getElementById('selectAllCheckbox'); if (selectAllCheckbox.checked) { selectAllAccounts(); } else { unselectAllAccounts(); } } // 全选当前分组所有账号 function selectAllAccounts() { const checkboxes = document.querySelectorAll('.account-select-checkbox'); checkboxes.forEach(cb => { cb.checked = true; selectedAccountIds.add(parseInt(cb.value)); }); updateBatchActionBar(); updateSelectAllCheckbox(); } // 取消全选当前分组 function unselectAllAccounts() { const checkboxes = document.querySelectorAll('.account-select-checkbox'); checkboxes.forEach(cb => { cb.checked = false; selectedAccountIds.delete(parseInt(cb.value)); }); updateBatchActionBar(); updateSelectAllCheckbox(); } // 更新全选复选框状态(基于当前分组) function updateSelectAllCheckbox() { const selectAllCheckbox = document.getElementById('selectAllCheckbox'); const checkboxes = document.querySelectorAll('.account-select-checkbox'); const checkedCount = document.querySelectorAll('.account-select-checkbox:checked').length; if (checkboxes.length === 0) { // 当前分组没有账号,但如果其他分组有选中则显示半选 selectAllCheckbox.checked = false; selectAllCheckbox.indeterminate = selectedAccountIds.size > 0; } else if (checkedCount === 0) { selectAllCheckbox.checked = false; selectAllCheckbox.indeterminate = selectedAccountIds.size > 0; } else if (checkedCount === checkboxes.length) { selectAllCheckbox.checked = true; selectAllCheckbox.indeterminate = false; } else { selectAllCheckbox.checked = false; selectAllCheckbox.indeterminate = true; } } // ==================== 验证码复制功能 ==================== // 复制验证信息到剪贴板 let copyVerificationInProgress = false; // 防重复点击 async function copyVerificationInfo(email, buttonElement) { // 防止重复点击 if (copyVerificationInProgress) { return; } copyVerificationInProgress = true; // 禁用按钮并显示加载状态 const originalContent = buttonElement.innerHTML; buttonElement.disabled = true; buttonElement.innerHTML = '⏳'; buttonElement.style.opacity = '0.6'; buttonElement.style.cursor = 'wait'; try { const response = await fetch(`/api/emails/${encodeURIComponent(email)}/extract-verification`); const data = await response.json(); if (data.success && data.data && data.data.formatted) { await copyToClipboard(data.data.formatted); showToast(`已复制: ${data.data.formatted}`, 'success'); // 成功状态 buttonElement.innerHTML = '✅'; buttonElement.style.opacity = '1'; } else { const errorMsg = data.error?.message || data.error || '未找到验证码或链接'; showToast(errorMsg, 'error'); // 失败状态 buttonElement.innerHTML = '❌'; buttonElement.style.opacity = '1'; } } catch (error) { console.error('提取验证码失败:', error); showToast('网络错误,请重试', 'error'); // 失败状态 buttonElement.innerHTML = '❌'; buttonElement.style.opacity = '1'; } finally { copyVerificationInProgress = false; // 延迟恢复按钮状态 setTimeout(() => { buttonElement.disabled = false; buttonElement.innerHTML = originalContent; buttonElement.style.cursor = 'pointer'; }, 1500); } } // 复制文本到剪贴板 async function copyToClipboard(text) { try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text); } else { // 降级方案:使用 textarea const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); } } catch (error) { console.error('复制失败:', error); throw error; } }