import { parseCSV } from './core.js'; const $ = id => document.getElementById(id); let data, csvText = null, accounts = [], page = 0, pending = false, reading = 0; async function request(route, body = {}) { const result = await chrome.runtime.sendMessage({ app: 'dww11-v3', route, body }); if (!result?.ok) throw new Error(result?.error || '无法连接扩展后台。'); return result.data; } function status(text, error = false) { $('status').textContent = text; $('status').classList.toggle('error', error); } function buttons() { $('import').disabled = pending || !csvText || !!data?.flow; $('end').disabled = pending || !data?.flow; $('file').disabled = pending; $('encoding').disabled = pending; } async function act(fn) { if (pending) return; pending = true; buttons(); try { await fn(); } catch (e) { status(e.message || '操作失败', true); } finally { pending = false; buttons(); } } const dateText = value => value ? new Date(value).toLocaleString() : '—'; function renderTable() { const query = $('search').value.trim().toLowerCase(); const filtered = accounts.filter(a => a.username.toLowerCase().includes(query)); const pages = Math.max(1, Math.ceil(filtered.length / 50)); page = Math.min(page, pages - 1); const body = $('accounts'); body.replaceChildren(); for (const a of filtered.slice(page * 50, page * 50 + 50)) { const tr = document.createElement('tr'); const cell = text => { const td = document.createElement('td'); td.textContent = text; tr.append(td); return td; }; cell(a.username); const badge = document.createElement('span'); badge.className = `badge ${a.status}`; badge.textContent = ({ unknown: '未检测', available: '可用', invalid: '失效' })[a.status]; cell('').append(badge); cell(dateText(a.lastchecked)); cell(dateText(a.lastlogin)); const error = cell(a.lastError?.message || '—'); if (a.lastError) error.title = `${a.lastError.code} · ${dateText(a.lastError.at)}`; const action = cell(''); if (a.status === 'invalid') { const button = document.createElement('button'); button.className = 'secondary'; button.textContent = '恢复待检测'; button.disabled = !!data.flow; button.addEventListener('click', () => act(async () => { await request('/restore', { username: a.username }); await refresh(); status('账号已恢复待检测;本轮已抽记录保留。'); })); action.append(button); } else action.textContent = '—'; body.append(tr); } if (!filtered.length) { const tr = document.createElement('tr'), td = document.createElement('td'); td.colSpan = 6; td.textContent = '暂无匹配的账号'; tr.append(td); body.append(tr); } $('count').textContent = `共 ${filtered.length} 个账号,每页 50 个`; $('page').textContent = `${page + 1} / ${pages}`; $('prev').disabled = page === 0; $('next').disabled = page >= pages - 1; } async function refresh() { data = await request('/list'); accounts = data.accounts; for (const key of ['total', 'active', 'round', 'remaining']) $(key).textContent = data[key]; $('progress').textContent = `今天 ${data.day}(电脑本地日期) · 第 ${data.round} 轮已抽 ${data.drawn} 个 · 累计抽取 ${data.totalDraws} 次。跨天保留已抽记录,只重排剩余账号。`; const f = data.flow; $('flow').textContent = f ? `${f.paused ? '已暂停' : '未完成'} · ${f.current?.username || '尚未抽取'} · 阶段:${({ select: '抽取账号', logout: '退出原账号', login: '准备登录', submitted: '核对登录结果', invalid: '保存失效结果', success: '保存登录结果' })[f.phase] || f.phase}${f.reason ? ' · ' + f.reason : ''}。打开网站换号卡片,点击继续可恢复。` : '当前没有未完成流程。在网站顶栏点击“登陆下一帐号”开始。'; renderTable(); buttons(); } async function readFile() { const generation = ++reading; csvText = null; buttons(); const file = $('file').files[0]; if (!file) { $('preview').textContent = '尚未选择 CSV。'; return; } try { if (file.size > 20 * 1024 * 1024) throw new Error('文件超过 20 MiB,请拆分后分批导入。'); const text = new TextDecoder($('encoding').value, { fatal: true }).decode(await file.arrayBuffer()); const parsed = parseCSV(text); if (generation !== reading) return; csvText = text; $('preview').textContent = `${file.name}:已校验 ${parsed.length} 个账号。密码中的空格会原样保留。确认后整批写入 IndexedDB。`; status('CSV 校验通过,等待确认导入。'); } catch (e) { if (generation !== reading) return; $('preview').textContent = '未导入任何数据。'; status(e instanceof TypeError ? '无法按所选编码解码,请切换 UTF-8 或 GB18030 后重试。' : e.message, true); } buttons(); } $('file').addEventListener('change', readFile); $('encoding').addEventListener('change', readFile); $('import').addEventListener('click', () => act(async () => { const result = await request('/import', { text: csvText }); csvText = null; $('file').value = ''; $('preview').textContent = '已保存到扩展 IndexedDB,无需保持 CSV 文件打开。'; await refresh(); status(`导入完成:新增 ${result.added} 个,更新 ${result.updated} 个。历史状态及本轮已抽记录保留。`); })); $('refresh').addEventListener('click', () => act(async () => { await refresh(); status('状态已刷新。'); })); $('end').addEventListener('click', () => act(async () => { if (!confirm('结束当前流程?已抽账号仍计入本轮已用,下一次会抽取新账号。')) return; await request('/pause'); await refresh(); status('流程已结束,抽取进度保留。请刷新网站页面。'); })); $('search').addEventListener('input', () => { page = 0; renderTable(); }); $('prev').addEventListener('click', () => { page--; renderTable(); }); $('next').addEventListener('click', () => { page++; renderTable(); }); window.addEventListener('focus', () => act(refresh)); act(async () => { await refresh(); status('本地 IndexedDB 已连接。请在网站页面操作换号。'); });