/**
* 注册页面 JavaScript
* 使用 utils.js 中的工具库
*/
// 状态
let currentTask = null;
let currentBatch = null;
let logPollingInterval = null;
let batchPollingInterval = null;
let accountsPollingInterval = null;
let isBatchMode = false;
let isOutlookBatchMode = false;
let outlookAccounts = [];
let availableServices = {
tempmail: { available: true, services: [] },
outlook: { available: false, services: [] },
custom_domain: { available: false, services: [] }
};
// WebSocket 相关变量
let webSocket = null;
let batchWebSocket = null; // 批量任务 WebSocket
let useWebSocket = true; // 是否使用 WebSocket
let wsHeartbeatInterval = null; // 心跳定时器
let batchWsHeartbeatInterval = null; // 批量任务心跳定时器
// DOM 元素
const elements = {
form: document.getElementById('registration-form'),
emailService: document.getElementById('email-service'),
regMode: document.getElementById('reg-mode'),
regModeGroup: document.getElementById('reg-mode-group'),
batchCountGroup: document.getElementById('batch-count-group'),
batchCount: document.getElementById('batch-count'),
batchOptions: document.getElementById('batch-options'),
intervalMin: document.getElementById('interval-min'),
intervalMax: document.getElementById('interval-max'),
startBtn: document.getElementById('start-btn'),
cancelBtn: document.getElementById('cancel-btn'),
taskStatusRow: document.getElementById('task-status-row'),
batchProgressSection: document.getElementById('batch-progress-section'),
consoleLog: document.getElementById('console-log'),
clearLogBtn: document.getElementById('clear-log-btn'),
// 任务状态
taskId: document.getElementById('task-id'),
taskEmail: document.getElementById('task-email'),
taskStatus: document.getElementById('task-status'),
taskService: document.getElementById('task-service'),
taskStatusBadge: document.getElementById('task-status-badge'),
// 批量状态
batchProgressText: document.getElementById('batch-progress-text'),
batchProgressPercent: document.getElementById('batch-progress-percent'),
progressBar: document.getElementById('progress-bar'),
batchSuccess: document.getElementById('batch-success'),
batchFailed: document.getElementById('batch-failed'),
batchRemaining: document.getElementById('batch-remaining'),
// 已注册账号
recentAccountsTable: document.getElementById('recent-accounts-table'),
refreshAccountsBtn: document.getElementById('refresh-accounts-btn'),
// Outlook 批量注册
outlookBatchSection: document.getElementById('outlook-batch-section'),
outlookAccountsContainer: document.getElementById('outlook-accounts-container'),
outlookIntervalMin: document.getElementById('outlook-interval-min'),
outlookIntervalMax: document.getElementById('outlook-interval-max'),
outlookSkipRegistered: document.getElementById('outlook-skip-registered')
};
// 初始化
document.addEventListener('DOMContentLoaded', () => {
initEventListeners();
loadAvailableServices();
loadRecentAccounts();
startAccountsPolling();
});
// 事件监听
function initEventListeners() {
// 注册表单提交
elements.form.addEventListener('submit', handleStartRegistration);
// 注册模式切换
elements.regMode.addEventListener('change', handleModeChange);
// 邮箱服务切换
elements.emailService.addEventListener('change', handleServiceChange);
// 取消按钮
elements.cancelBtn.addEventListener('click', handleCancelTask);
// 清空日志
elements.clearLogBtn.addEventListener('click', () => {
elements.consoleLog.innerHTML = '
[系统] 日志已清空
';
});
// 刷新账号列表
elements.refreshAccountsBtn.addEventListener('click', () => {
loadRecentAccounts();
toast.info('已刷新');
});
}
// 加载可用的邮箱服务
async function loadAvailableServices() {
try {
const data = await api.get('/registration/available-services');
availableServices = data;
// 更新邮箱服务选择框
updateEmailServiceOptions();
addLog('info', '[系统] 邮箱服务列表已加载');
} catch (error) {
console.error('加载邮箱服务列表失败:', error);
addLog('warning', '[警告] 加载邮箱服务列表失败');
}
}
// 更新邮箱服务选择框
function updateEmailServiceOptions() {
const select = elements.emailService;
select.innerHTML = '';
// Tempmail
if (availableServices.tempmail.available) {
const optgroup = document.createElement('optgroup');
optgroup.label = '🌐 临时邮箱';
availableServices.tempmail.services.forEach(service => {
const option = document.createElement('option');
option.value = `tempmail:${service.id || 'default'}`;
option.textContent = service.name;
option.dataset.type = 'tempmail';
optgroup.appendChild(option);
});
select.appendChild(optgroup);
}
// Outlook
if (availableServices.outlook.available) {
const optgroup = document.createElement('optgroup');
optgroup.label = `📧 Outlook (${availableServices.outlook.count} 个账户)`;
availableServices.outlook.services.forEach(service => {
const option = document.createElement('option');
option.value = `outlook:${service.id}`;
option.textContent = service.name + (service.has_oauth ? ' (OAuth)' : '');
option.dataset.type = 'outlook';
option.dataset.serviceId = service.id;
optgroup.appendChild(option);
});
select.appendChild(optgroup);
// Outlook 批量注册选项
const batchOption = document.createElement('option');
batchOption.value = 'outlook_batch:all';
batchOption.textContent = `📋 Outlook 批量注册 (${availableServices.outlook.count} 个账户)`;
batchOption.dataset.type = 'outlook_batch';
optgroup.appendChild(batchOption);
} else {
const optgroup = document.createElement('optgroup');
optgroup.label = '📧 Outlook (未配置)';
const option = document.createElement('option');
option.value = '';
option.textContent = '请先在邮箱服务页面导入账户';
option.disabled = true;
optgroup.appendChild(option);
select.appendChild(optgroup);
}
// 自定义域名
if (availableServices.custom_domain.available) {
const optgroup = document.createElement('optgroup');
optgroup.label = `🔗 自定义域名 (${availableServices.custom_domain.count} 个服务)`;
availableServices.custom_domain.services.forEach(service => {
const option = document.createElement('option');
option.value = `custom_domain:${service.id || 'default'}`;
option.textContent = service.name;
option.dataset.type = 'custom_domain';
if (service.id) {
option.dataset.serviceId = service.id;
}
optgroup.appendChild(option);
});
select.appendChild(optgroup);
} else {
const optgroup = document.createElement('optgroup');
optgroup.label = '🔗 自定义域名 (未配置)';
const option = document.createElement('option');
option.value = '';
option.textContent = '请先在邮箱服务页面添加服务';
option.disabled = true;
optgroup.appendChild(option);
select.appendChild(optgroup);
}
}
// 处理邮箱服务切换
function handleServiceChange(e) {
const value = e.target.value;
if (!value) return;
const [type, id] = value.split(':');
const selectedOption = e.target.options[e.target.selectedIndex];
// 处理 Outlook 批量注册模式
if (type === 'outlook_batch') {
isOutlookBatchMode = true;
elements.outlookBatchSection.style.display = 'block';
elements.regModeGroup.style.display = 'none';
elements.batchCountGroup.style.display = 'none';
elements.batchOptions.style.display = 'none';
loadOutlookAccounts();
addLog('info', '[系统] 已切换到 Outlook 批量注册模式');
return;
} else {
isOutlookBatchMode = false;
elements.outlookBatchSection.style.display = 'none';
elements.regModeGroup.style.display = 'block';
}
// 显示服务信息
if (type === 'outlook') {
const service = availableServices.outlook.services.find(s => s.id == id);
if (service) {
addLog('info', `[系统] 已选择 Outlook 账户: ${service.name}`);
}
} else if (type === 'custom_domain') {
const service = availableServices.custom_domain.services.find(s => s.id == id);
if (service) {
addLog('info', `[系统] 已选择自定义域名服务: ${service.name}`);
}
}
}
// 模式切换
function handleModeChange(e) {
const mode = e.target.value;
isBatchMode = mode === 'batch';
elements.batchCountGroup.style.display = isBatchMode ? 'block' : 'none';
elements.batchOptions.style.display = isBatchMode ? 'block' : 'none';
}
// 开始注册
async function handleStartRegistration(e) {
e.preventDefault();
const selectedValue = elements.emailService.value;
if (!selectedValue) {
toast.error('请选择一个邮箱服务');
return;
}
// 处理 Outlook 批量注册模式
if (isOutlookBatchMode) {
await handleOutlookBatchRegistration();
return;
}
const [emailServiceType, serviceId] = selectedValue.split(':');
// 禁用开始按钮
elements.startBtn.disabled = true;
elements.cancelBtn.disabled = false;
// 清空日志
elements.consoleLog.innerHTML = '';
// 构建请求数据(代理从设置中自动获取)
const requestData = {
email_service_type: emailServiceType
};
// 如果选择了数据库中的服务,传递 service_id
if (serviceId && serviceId !== 'default') {
requestData.email_service_id = parseInt(serviceId);
}
if (isBatchMode) {
await handleBatchRegistration(requestData);
} else {
await handleSingleRegistration(requestData);
}
}
// 单次注册
async function handleSingleRegistration(requestData) {
addLog('info', '[系统] 正在启动注册任务...');
try {
const data = await api.post('/registration/start', requestData);
currentTask = data;
addLog('info', `[系统] 任务已创建: ${data.task_uuid}`);
showTaskStatus(data);
updateTaskStatus('running');
// 优先使用 WebSocket
connectWebSocket(data.task_uuid);
} catch (error) {
addLog('error', `[错误] 启动失败: ${error.message}`);
toast.error(error.message);
resetButtons();
}
}
// ============== WebSocket 功能 ==============
// 连接 WebSocket
function connectWebSocket(taskUuid) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/ws/task/${taskUuid}`;
try {
webSocket = new WebSocket(wsUrl);
webSocket.onopen = () => {
console.log('WebSocket 连接成功');
useWebSocket = true;
// 停止轮询(如果有)
stopLogPolling();
// 开始心跳
startWebSocketHeartbeat();
};
webSocket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'log') {
const logType = getLogType(data.message);
addLog(logType, data.message);
} else if (data.type === 'status') {
updateTaskStatus(data.status);
// 检查是否完成
if (['completed', 'failed', 'cancelled', 'cancelling'].includes(data.status)) {
disconnectWebSocket();
resetButtons();
if (data.status === 'completed') {
addLog('success', '[成功] 注册成功!');
toast.success('注册成功!');
// 刷新账号列表
loadRecentAccounts();
} else if (data.status === 'failed') {
addLog('error', '[错误] 注册失败');
toast.error('注册失败');
} else if (data.status === 'cancelled' || data.status === 'cancelling') {
addLog('warning', '[警告] 任务已取消');
}
}
} else if (data.type === 'pong') {
// 心跳响应,忽略
}
};
webSocket.onclose = (event) => {
console.log('WebSocket 连接关闭:', event.code);
stopWebSocketHeartbeat();
// 如果任务仍在运行,切换到轮询
if (currentTask && ['pending', 'running'].includes(currentTask.status)) {
console.log('切换到轮询模式');
useWebSocket = false;
startLogPolling(currentTask.task_uuid);
}
};
webSocket.onerror = (error) => {
console.error('WebSocket 错误:', error);
// 切换到轮询
useWebSocket = false;
stopWebSocketHeartbeat();
startLogPolling(taskUuid);
};
} catch (error) {
console.error('WebSocket 连接失败:', error);
useWebSocket = false;
startLogPolling(taskUuid);
}
}
// 断开 WebSocket
function disconnectWebSocket() {
stopWebSocketHeartbeat();
if (webSocket) {
webSocket.close();
webSocket = null;
}
}
// 开始心跳
function startWebSocketHeartbeat() {
stopWebSocketHeartbeat();
wsHeartbeatInterval = setInterval(() => {
if (webSocket && webSocket.readyState === WebSocket.OPEN) {
webSocket.send(JSON.stringify({ type: 'ping' }));
}
}, 25000); // 每 25 秒发送一次心跳
}
// 停止心跳
function stopWebSocketHeartbeat() {
if (wsHeartbeatInterval) {
clearInterval(wsHeartbeatInterval);
wsHeartbeatInterval = null;
}
}
// 发送取消请求
function cancelViaWebSocket() {
if (webSocket && webSocket.readyState === WebSocket.OPEN) {
webSocket.send(JSON.stringify({ type: 'cancel' }));
}
}
// 批量注册
async function handleBatchRegistration(requestData) {
const count = parseInt(elements.batchCount.value) || 5;
const intervalMin = parseInt(elements.intervalMin.value) || 5;
const intervalMax = parseInt(elements.intervalMax.value) || 30;
requestData.count = count;
requestData.interval_min = intervalMin;
requestData.interval_max = intervalMax;
addLog('info', `[系统] 正在启动批量注册任务 (数量: ${count})...`);
try {
const data = await api.post('/registration/batch', requestData);
currentBatch = data;
addLog('info', `[系统] 批量任务已创建: ${data.batch_id}`);
addLog('info', `[系统] 共 ${data.count} 个任务已加入队列`);
showBatchStatus(data);
// 开始轮询批量状态
startBatchPolling(data.batch_id);
} catch (error) {
addLog('error', `[错误] 启动失败: ${error.message}`);
toast.error(error.message);
resetButtons();
}
}
// 取消任务
async function handleCancelTask() {
if (isBatchMode && currentBatch) {
// 优先通过 WebSocket 取消批量任务
if (batchWebSocket && batchWebSocket.readyState === WebSocket.OPEN) {
cancelBatchViaWebSocket();
addLog('warning', '[警告] 批量任务取消请求已提交');
toast.info('任务取消请求已提交');
} else {
// 降级到 REST API
try {
await api.post(`/registration/batch/${currentBatch.batch_id}/cancel`);
addLog('warning', '[警告] 批量任务取消请求已提交');
toast.info('任务取消请求已提交');
stopBatchPolling();
resetButtons();
} catch (error) {
addLog('error', `[错误] 取消失败: ${error.message}`);
toast.error(error.message);
}
}
} else if (isOutlookBatchMode && currentBatch) {
// Outlook 批量任务取消
if (batchWebSocket && batchWebSocket.readyState === WebSocket.OPEN) {
cancelBatchViaWebSocket();
addLog('warning', '[警告] Outlook 批量任务取消请求已提交');
toast.info('任务取消请求已提交');
} else {
// 降级到 REST API
try {
await api.post(`/registration/outlook-batch/${currentBatch.batch_id}/cancel`);
addLog('warning', '[警告] Outlook 批量任务取消请求已提交');
toast.info('任务取消请求已提交');
stopBatchPolling();
resetButtons();
} catch (error) {
addLog('error', `[错误] 取消失败: ${error.message}`);
toast.error(error.message);
}
}
} else if (currentTask) {
// 优先通过 WebSocket 取消
if (useWebSocket && webSocket && webSocket.readyState === WebSocket.OPEN) {
cancelViaWebSocket();
addLog('warning', '[警告] 任务取消请求已提交');
toast.info('任务取消请求已提交');
} else {
// 降级到 REST API
try {
await api.post(`/registration/tasks/${currentTask.task_uuid}/cancel`);
addLog('warning', '[警告] 任务已取消');
toast.info('任务已取消');
stopLogPolling();
resetButtons();
} catch (error) {
addLog('error', `[错误] 取消失败: ${error.message}`);
toast.error(error.message);
}
}
}
}
// 开始轮询日志
function startLogPolling(taskUuid) {
let lastLogIndex = 0;
logPollingInterval = setInterval(async () => {
try {
const data = await api.get(`/registration/tasks/${taskUuid}/logs`);
// 更新任务状态
updateTaskStatus(data.status);
// 更新邮箱信息
if (data.email) {
elements.taskEmail.textContent = data.email;
}
if (data.email_service) {
elements.taskService.textContent = getServiceTypeText(data.email_service);
}
// 添加新日志
const logs = data.logs || [];
for (let i = lastLogIndex; i < logs.length; i++) {
const log = logs[i];
const logType = getLogType(log);
addLog(logType, log);
}
lastLogIndex = logs.length;
// 检查任务是否完成
if (['completed', 'failed', 'cancelled'].includes(data.status)) {
stopLogPolling();
resetButtons();
if (data.status === 'completed') {
addLog('success', '[成功] 注册成功!');
toast.success('注册成功!');
// 刷新账号列表
loadRecentAccounts();
} else if (data.status === 'failed') {
addLog('error', '[错误] 注册失败');
toast.error('注册失败');
} else if (data.status === 'cancelled') {
addLog('warning', '[警告] 任务已取消');
}
}
} catch (error) {
console.error('轮询日志失败:', error);
}
}, 1000);
}
// 停止轮询日志
function stopLogPolling() {
if (logPollingInterval) {
clearInterval(logPollingInterval);
logPollingInterval = null;
}
}
// 开始轮询批量状态
function startBatchPolling(batchId) {
batchPollingInterval = setInterval(async () => {
try {
const data = await api.get(`/registration/batch/${batchId}`);
updateBatchProgress(data);
// 检查是否完成
if (data.finished) {
stopBatchPolling();
resetButtons();
addLog('info', `[完成] 批量任务完成!成功: ${data.success}, 失败: ${data.failed}`);
if (data.success > 0) {
toast.success(`批量注册完成,成功 ${data.success} 个`);
// 刷新账号列表
loadRecentAccounts();
} else {
toast.warning('批量注册完成,但没有成功注册任何账号');
}
}
} catch (error) {
console.error('轮询批量状态失败:', error);
}
}, 2000);
}
// 停止轮询批量状态
function stopBatchPolling() {
if (batchPollingInterval) {
clearInterval(batchPollingInterval);
batchPollingInterval = null;
}
}
// 显示任务状态
function showTaskStatus(task) {
elements.taskStatusRow.style.display = 'grid';
elements.batchProgressSection.style.display = 'none';
elements.taskStatusBadge.style.display = 'inline-flex';
elements.taskId.textContent = task.task_uuid.substring(0, 8) + '...';
elements.taskEmail.textContent = '-';
elements.taskService.textContent = '-';
}
// 更新任务状态
function updateTaskStatus(status) {
const statusInfo = {
pending: { text: '等待中', class: 'pending' },
running: { text: '运行中', class: 'running' },
completed: { text: '已完成', class: 'completed' },
failed: { text: '失败', class: 'failed' },
cancelled: { text: '已取消', class: 'disabled' }
};
const info = statusInfo[status] || { text: status, class: '' };
elements.taskStatusBadge.textContent = info.text;
elements.taskStatusBadge.className = `status-badge ${info.class}`;
elements.taskStatus.textContent = info.text;
}
// 显示批量状态
function showBatchStatus(batch) {
elements.batchProgressSection.style.display = 'block';
elements.taskStatusRow.style.display = 'none';
elements.taskStatusBadge.style.display = 'none';
elements.batchProgressText.textContent = `0/${batch.count}`;
elements.batchProgressPercent.textContent = '0%';
elements.progressBar.style.width = '0%';
elements.batchSuccess.textContent = '0';
elements.batchFailed.textContent = '0';
elements.batchRemaining.textContent = batch.count;
// 重置计数器
elements.batchSuccess.dataset.last = '0';
elements.batchFailed.dataset.last = '0';
}
// 更新批量进度
function updateBatchProgress(data) {
const progress = ((data.completed / data.total) * 100).toFixed(0);
elements.batchProgressText.textContent = `${data.completed}/${data.total}`;
elements.batchProgressPercent.textContent = `${progress}%`;
elements.progressBar.style.width = `${progress}%`;
elements.batchSuccess.textContent = data.success;
elements.batchFailed.textContent = data.failed;
elements.batchRemaining.textContent = data.total - data.completed;
// 记录日志(避免重复)
if (data.completed > 0) {
const lastSuccess = parseInt(elements.batchSuccess.dataset.last || '0');
const lastFailed = parseInt(elements.batchFailed.dataset.last || '0');
if (data.success > lastSuccess) {
addLog('success', `[成功] 第 ${data.success} 个账号注册成功`);
}
if (data.failed > lastFailed) {
addLog('error', `[失败] 第 ${data.failed} 个账号注册失败`);
}
elements.batchSuccess.dataset.last = data.success;
elements.batchFailed.dataset.last = data.failed;
}
}
// 加载最近注册的账号
async function loadRecentAccounts() {
try {
const data = await api.get('/accounts?page=1&page_size=10');
if (data.accounts.length === 0) {
elements.recentAccountsTable.innerHTML = `
|
|
`;
return;
}
elements.recentAccountsTable.innerHTML = data.accounts.map(account => `
| ${account.id} |
${escapeHtml(account.email)}
|
${account.password ? `${escapeHtml(account.password.substring(0, 8))}...` : '-'}
|
${getStatusText('account', account.status)}
|
${account.password ? `` : ''}
|
`).join('');
} catch (error) {
console.error('加载账号列表失败:', error);
}
}
// 开始账号列表轮询
function startAccountsPolling() {
// 每30秒刷新一次账号列表
accountsPollingInterval = setInterval(() => {
loadRecentAccounts();
}, 30000);
}
// 添加日志
function addLog(type, message) {
const line = document.createElement('div');
line.className = `log-line ${type}`;
// 添加时间戳
const timestamp = new Date().toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
line.innerHTML = `[${timestamp}]${escapeHtml(message)}`;
elements.consoleLog.appendChild(line);
// 自动滚动到底部
elements.consoleLog.scrollTop = elements.consoleLog.scrollHeight;
// 限制日志行数
const lines = elements.consoleLog.querySelectorAll('.log-line');
if (lines.length > 500) {
lines[0].remove();
}
}
// 获取日志类型
function getLogType(log) {
if (typeof log !== 'string') return 'info';
const lowerLog = log.toLowerCase();
if (lowerLog.includes('error') || lowerLog.includes('失败') || lowerLog.includes('错误')) {
return 'error';
}
if (lowerLog.includes('warning') || lowerLog.includes('警告')) {
return 'warning';
}
if (lowerLog.includes('success') || lowerLog.includes('成功') || lowerLog.includes('完成')) {
return 'success';
}
return 'info';
}
// 重置按钮状态
function resetButtons() {
elements.startBtn.disabled = false;
elements.cancelBtn.disabled = true;
currentTask = null;
currentBatch = null;
isBatchMode = false;
// 断开 WebSocket
disconnectWebSocket();
disconnectBatchWebSocket();
// 注意:不重置 isOutlookBatchMode,因为用户可能想继续使用 Outlook 批量模式
}
// HTML 转义
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ============== Outlook 批量注册功能 ==============
// 加载 Outlook 账户列表
async function loadOutlookAccounts() {
try {
elements.outlookAccountsContainer.innerHTML = '加载中...
';
const data = await api.get('/registration/outlook-accounts');
outlookAccounts = data.accounts || [];
renderOutlookAccountsList();
addLog('info', `[系统] 已加载 ${data.total} 个 Outlook 账户 (已注册: ${data.registered_count}, 未注册: ${data.unregistered_count})`);
} catch (error) {
console.error('加载 Outlook 账户列表失败:', error);
elements.outlookAccountsContainer.innerHTML = `加载失败: ${error.message}
`;
addLog('error', `[错误] 加载 Outlook 账户列表失败: ${error.message}`);
}
}
// 渲染 Outlook 账户列表
function renderOutlookAccountsList() {
if (outlookAccounts.length === 0) {
elements.outlookAccountsContainer.innerHTML = '没有可用的 Outlook 账户
';
return;
}
const html = outlookAccounts.map(account => `
`).join('');
elements.outlookAccountsContainer.innerHTML = html;
}
// 全选
function selectAllOutlookAccounts() {
const checkboxes = document.querySelectorAll('.outlook-account-checkbox');
checkboxes.forEach(cb => cb.checked = true);
}
// 只选未注册
function selectUnregisteredOutlook() {
const items = document.querySelectorAll('.outlook-account-item');
items.forEach(item => {
const checkbox = item.querySelector('.outlook-account-checkbox');
const isRegistered = item.dataset.registered === 'true';
checkbox.checked = !isRegistered;
});
}
// 取消全选
function deselectAllOutlookAccounts() {
const checkboxes = document.querySelectorAll('.outlook-account-checkbox');
checkboxes.forEach(cb => cb.checked = false);
}
// 处理 Outlook 批量注册
async function handleOutlookBatchRegistration() {
// 获取选中的账户
const selectedIds = [];
document.querySelectorAll('.outlook-account-checkbox:checked').forEach(cb => {
selectedIds.push(parseInt(cb.value));
});
if (selectedIds.length === 0) {
toast.error('请选择至少一个 Outlook 账户');
return;
}
const intervalMin = parseInt(elements.outlookIntervalMin.value) || 5;
const intervalMax = parseInt(elements.outlookIntervalMax.value) || 30;
const skipRegistered = elements.outlookSkipRegistered.checked;
// 禁用开始按钮
elements.startBtn.disabled = true;
elements.cancelBtn.disabled = false;
// 清空日志
elements.consoleLog.innerHTML = '';
const requestData = {
service_ids: selectedIds,
skip_registered: skipRegistered,
interval_min: intervalMin,
interval_max: intervalMax
};
addLog('info', `[系统] 正在启动 Outlook 批量注册 (${selectedIds.length} 个账户)...`);
try {
const data = await api.post('/registration/outlook-batch', requestData);
if (data.to_register === 0) {
addLog('warning', '[警告] 所有选中的邮箱都已注册,无需重复注册');
toast.warning('所有选中的邮箱都已注册');
resetButtons();
return;
}
currentBatch = { batch_id: data.batch_id, ...data };
addLog('info', `[系统] 批量任务已创建: ${data.batch_id}`);
addLog('info', `[系统] 总数: ${data.total}, 跳过已注册: ${data.skipped}, 待注册: ${data.to_register}`);
// 初始化批量状态显示
showBatchStatus({ count: data.to_register });
// 优先使用 WebSocket
connectBatchWebSocket(data.batch_id);
} catch (error) {
addLog('error', `[错误] 启动失败: ${error.message}`);
toast.error(error.message);
resetButtons();
}
}
// ============== 批量任务 WebSocket 功能 ==============
// 连接批量任务 WebSocket
function connectBatchWebSocket(batchId) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/ws/batch/${batchId}`;
try {
batchWebSocket = new WebSocket(wsUrl);
batchWebSocket.onopen = () => {
console.log('批量任务 WebSocket 连接成功');
// 停止轮询(如果有)
stopBatchPolling();
// 开始心跳
startBatchWebSocketHeartbeat();
};
batchWebSocket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'log') {
const logType = getLogType(data.message);
addLog(logType, data.message);
} else if (data.type === 'status') {
// 更新进度
if (data.total !== undefined) {
updateBatchProgress({
total: data.total,
completed: data.completed || 0,
success: data.success || 0,
failed: data.failed || 0
});
}
// 检查是否完成
if (['completed', 'failed', 'cancelled', 'cancelling'].includes(data.status)) {
disconnectBatchWebSocket();
resetButtons();
if (data.status === 'completed') {
addLog('success', `[完成] Outlook 批量任务完成!成功: ${data.success}, 失败: ${data.failed}, 跳过: ${data.skipped || 0}`);
if (data.success > 0) {
toast.success(`Outlook 批量注册完成,成功 ${data.success} 个`);
loadRecentAccounts();
} else {
toast.warning('Outlook 批量注册完成,但没有成功注册任何账号');
}
} else if (data.status === 'failed') {
addLog('error', '[错误] 批量任务执行失败');
toast.error('批量任务执行失败');
} else if (data.status === 'cancelled' || data.status === 'cancelling') {
addLog('warning', '[警告] 批量任务已取消');
}
}
} else if (data.type === 'pong') {
// 心跳响应,忽略
}
};
batchWebSocket.onclose = (event) => {
console.log('批量任务 WebSocket 连接关闭:', event.code);
stopBatchWebSocketHeartbeat();
// 如果任务仍在运行,切换到轮询
if (currentBatch && !['completed', 'failed', 'cancelled'].includes(currentBatch.status)) {
console.log('切换到轮询模式');
startOutlookBatchPolling(currentBatch.batch_id);
}
};
batchWebSocket.onerror = (error) => {
console.error('批量任务 WebSocket 错误:', error);
stopBatchWebSocketHeartbeat();
// 切换到轮询
startOutlookBatchPolling(batchId);
};
} catch (error) {
console.error('批量任务 WebSocket 连接失败:', error);
startOutlookBatchPolling(batchId);
}
}
// 断开批量任务 WebSocket
function disconnectBatchWebSocket() {
stopBatchWebSocketHeartbeat();
if (batchWebSocket) {
batchWebSocket.close();
batchWebSocket = null;
}
}
// 开始批量任务心跳
function startBatchWebSocketHeartbeat() {
stopBatchWebSocketHeartbeat();
batchWsHeartbeatInterval = setInterval(() => {
if (batchWebSocket && batchWebSocket.readyState === WebSocket.OPEN) {
batchWebSocket.send(JSON.stringify({ type: 'ping' }));
}
}, 25000); // 每 25 秒发送一次心跳
}
// 停止批量任务心跳
function stopBatchWebSocketHeartbeat() {
if (batchWsHeartbeatInterval) {
clearInterval(batchWsHeartbeatInterval);
batchWsHeartbeatInterval = null;
}
}
// 发送批量任务取消请求
function cancelBatchViaWebSocket() {
if (batchWebSocket && batchWebSocket.readyState === WebSocket.OPEN) {
batchWebSocket.send(JSON.stringify({ type: 'cancel' }));
}
}
// 开始轮询 Outlook 批量状态(降级方案)
function startOutlookBatchPolling(batchId) {
batchPollingInterval = setInterval(async () => {
try {
const data = await api.get(`/registration/outlook-batch/${batchId}`);
// 更新进度
updateBatchProgress({
total: data.total,
completed: data.completed,
success: data.success,
failed: data.failed
});
// 输出日志
if (data.logs && data.logs.length > 0) {
const lastLogIndex = batchPollingInterval.lastLogIndex || 0;
for (let i = lastLogIndex; i < data.logs.length; i++) {
const log = data.logs[i];
const logType = getLogType(log);
addLog(logType, log);
}
batchPollingInterval.lastLogIndex = data.logs.length;
}
// 检查是否完成
if (data.finished) {
stopBatchPolling();
resetButtons();
addLog('info', `[完成] Outlook 批量任务完成!成功: ${data.success}, 失败: ${data.failed}, 跳过: ${data.skipped || 0}`);
if (data.success > 0) {
toast.success(`Outlook 批量注册完成,成功 ${data.success} 个`);
loadRecentAccounts();
} else {
toast.warning('Outlook 批量注册完成,但没有成功注册任何账号');
}
}
} catch (error) {
console.error('轮询 Outlook 批量状态失败:', error);
}
}, 2000);
batchPollingInterval.lastLogIndex = 0;
}