694 lines
31 KiB
JavaScript
694 lines
31 KiB
JavaScript
// ==================== 分组相关 ====================
|
||
|
||
// 加载分组列表
|
||
async function loadGroups() {
|
||
const container = document.getElementById('groupList');
|
||
container.innerHTML = '<div class="loading-overlay"><span class="spinner"></span> 加载中…</div>';
|
||
|
||
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 = '<div class="empty-state"><p>加载失败</p></div>';
|
||
showToast('加载分组失败', 'error');
|
||
}
|
||
}
|
||
|
||
// 渲染分组列表
|
||
function renderGroupList(groups) {
|
||
const container = document.getElementById('groupList');
|
||
|
||
// 过滤掉临时邮箱分组(已有独立页面管理)
|
||
const filteredGroups = groups.filter(g => g.name !== '临时邮箱');
|
||
|
||
if (filteredGroups.length === 0) {
|
||
container.innerHTML = `
|
||
<div class="empty-state">
|
||
<span class="empty-icon">📁</span>
|
||
<p>暂无分组</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = filteredGroups.map(group => {
|
||
const isSystem = group.is_system === 1;
|
||
const isDefault = group.id === 1;
|
||
|
||
return `
|
||
<div class="group-item ${currentGroupId === group.id ? 'active' : ''}"
|
||
data-group-id="${group.id}"
|
||
onclick="selectGroup(${group.id})">
|
||
<span class="group-color-dot" style="background-color: ${group.color || '#666'}"></span>
|
||
<span class="group-name">${escapeHtml(group.name)}</span>
|
||
<span class="badge-count">${group.account_count || 0}</span>
|
||
<div class="group-actions">
|
||
${!isSystem ? `<button class="btn-icon" onclick="event.stopPropagation(); editGroup(${group.id})" title="编辑">✏️</button>` : ''}
|
||
${!isDefault && !isSystem ? `<button class="btn-icon" onclick="event.stopPropagation(); deleteGroup(${group.id})" title="删除">🗑️</button>` : ''}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).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 = '<div class="empty-state"><span class="empty-icon">📬</span><p>请从左侧选择一个邮箱账号</p></div>';
|
||
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 = '<div class="loading-overlay"><span class="spinner"></span> 加载中…</div>';
|
||
}
|
||
|
||
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 = '<div class="empty-state"><p>加载失败</p></div>';
|
||
}
|
||
}
|
||
|
||
// 获取 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 = `
|
||
<div class="empty-state">
|
||
<span class="empty-icon">📭</span>
|
||
<p>该分组暂无邮箱</p>
|
||
</div>
|
||
`;
|
||
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 = `<span class="account-provider-tag">${escapeHtml(providerLabel)}</span>`;
|
||
|
||
let tokenBadge = '<span class="badge badge-gray">– 未知</span>';
|
||
if (acc.token_status === 'valid') {
|
||
tokenBadge = '<span class="badge badge-green">✓ 有效</span>';
|
||
} else if (acc.token_status === 'invalid' || acc.token_status === 'expired') {
|
||
tokenBadge = '<span class="badge badge-red">✗ 过期</span>';
|
||
} else if (acc.token_status === 'expiring') {
|
||
tokenBadge = '<span class="badge badge-gold">⚠ 即将过期</span>';
|
||
}
|
||
|
||
return `
|
||
<div class="account-card ${currentAccount === acc.email ? 'active' : ''}"
|
||
onclick="selectAccount('${escapeJs(acc.email)}')">
|
||
<div class="account-token-badge">${tokenBadge}</div>
|
||
<div class="account-card-top">
|
||
<input type="checkbox" class="account-select-checkbox" value="${acc.id}"
|
||
${isChecked ? 'checked' : ''}
|
||
onclick="event.stopPropagation(); updateBatchActionBar(); updateSelectAllCheckbox()">
|
||
<div class="account-avatar" style="background: linear-gradient(135deg, ${gradient[0]}, ${gradient[1]})">${initial}</div>
|
||
<div class="account-info">
|
||
<div class="account-email"
|
||
onclick="event.stopPropagation(); copyEmail('${escapeJs(acc.email)}')"
|
||
title="点击复制 ${escapeHtml(acc.email)}"
|
||
style="${isFailed ? 'color:var(--clr-danger);' : ''}cursor:pointer;">
|
||
${escapeHtml(acc.email)}
|
||
</div>
|
||
${acc.remark && acc.remark.trim() ? `<div style="font-size:0.72rem;color:var(--text-muted);margin-top:2px;">📝 ${escapeHtml(acc.remark)}</div>` : ''}
|
||
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-top:3px;">
|
||
${providerTagHtml}
|
||
${(acc.tags || []).map(tag => `<span class="tag" style="background-color:${tag.color};color:white;">${escapeHtml(tag.name)}</span>`).join('')}
|
||
${acc.telegram_push_enabled ? `<span class="tag tg-push-tag" onclick="event.stopPropagation(); toggleTelegramPush(${acc.id}, false)" title="点击关闭推送">🔔 推送</span>` : ''}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="account-card-bottom">
|
||
<div class="account-meta">
|
||
<span class="account-api-tag">${acc.method || 'Graph'}</span>
|
||
<span>🕐 ${formatRelativeTime(acc.last_refresh_at)}</span>
|
||
${isFailed ? `<button class="btn btn-sm btn-danger" onclick="event.stopPropagation(); showRefreshError(${acc.id}, '${escapeJs(acc.last_refresh_error || '未知错误')}', '${escapeJs(acc.email)}')" style="padding:1px 6px;font-size:0.65rem;">查看错误</button>` : ''}
|
||
</div>
|
||
<div class="account-actions">
|
||
<button class="btn-icon ${acc.telegram_push_enabled ? 'tg-push-active' : ''}" onclick="event.stopPropagation(); toggleTelegramPush(${acc.id}, ${!acc.telegram_push_enabled})" title="Telegram推送${acc.telegram_push_enabled ? '(已开启)' : ''}">🔔</button>
|
||
<button class="btn btn-sm btn-accent" onclick="event.stopPropagation(); copyVerificationInfo('${escapeJs(acc.email)}', this)" title="提取验证码" style="font-size:0.72rem;padding:2px 8px;">🔑 验证码</button>
|
||
<button class="btn-icon" onclick="event.stopPropagation(); copyEmail('${escapeJs(acc.email)}')" title="复制">📋</button>
|
||
<button class="btn-icon" onclick="event.stopPropagation(); showEditAccountModal(${acc.id})" title="编辑">✏️</button>
|
||
<button class="btn-icon" onclick="event.stopPropagation(); deleteAccount(${acc.id}, '${escapeJs(acc.email)}')" title="删除" style="color:var(--clr-danger);">🗑️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`}).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 = '<div class="loading-overlay"><span class="spinner"></span> 搜索中…</div>';
|
||
|
||
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 = '<div class="empty-state"><p>搜索失败</p></div>';
|
||
}
|
||
} catch (error) {
|
||
console.error('搜索失败:', error);
|
||
container.innerHTML = '<div class="empty-state"><p>搜索失败,请重试</p></div>';
|
||
}
|
||
}
|
||
|
||
// 更新分组下拉选择框
|
||
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 =>
|
||
`<option value="${g.id}">${escapeHtml(g.name)}</option>`
|
||
).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;
|
||
}
|
||
}
|
||
|