Files
2026-10-31 14:43:15 +08:00

97 lines
6.0 KiB
JavaScript

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 已连接。请在网站页面操作换号。'); });