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