153 lines
4.1 KiB
JavaScript
153 lines
4.1 KiB
JavaScript
const REQUIRED_ID_FIELD = '账号ID';
|
|
const REQUIRED_ALIAS_FIELD = '最终输出';
|
|
|
|
function setStatus(message) {
|
|
document.getElementById('status').textContent = message;
|
|
}
|
|
|
|
function parseCsv(text) {
|
|
const rows = [];
|
|
let row = [];
|
|
let cell = '';
|
|
let inQuotes = false;
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
const char = text[i];
|
|
const nextChar = text[i + 1];
|
|
|
|
if (char === '"') {
|
|
if (inQuotes && nextChar === '"') {
|
|
cell += '"';
|
|
i++;
|
|
} else {
|
|
inQuotes = !inQuotes;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (char === ',' && !inQuotes) {
|
|
row.push(cell);
|
|
cell = '';
|
|
continue;
|
|
}
|
|
|
|
if ((char === '\n' || char === '\r') && !inQuotes) {
|
|
if (char === '\r' && nextChar === '\n') i++;
|
|
row.push(cell);
|
|
if (row.some(value => value.trim() !== '')) rows.push(row);
|
|
row = [];
|
|
cell = '';
|
|
continue;
|
|
}
|
|
|
|
cell += char;
|
|
}
|
|
|
|
row.push(cell);
|
|
if (row.some(value => value.trim() !== '')) rows.push(row);
|
|
|
|
return rows;
|
|
}
|
|
|
|
function normalizeHeader(value) {
|
|
return value.replace(/^\uFEFF/, '').trim();
|
|
}
|
|
|
|
function aliasesFromCsv(text) {
|
|
const rows = parseCsv(text);
|
|
if (rows.length === 0) {
|
|
throw new Error('CSV 文件为空。');
|
|
}
|
|
|
|
const headers = rows[0].map(normalizeHeader);
|
|
const idIndex = headers.indexOf(REQUIRED_ID_FIELD);
|
|
const aliasIndex = headers.indexOf(REQUIRED_ALIAS_FIELD);
|
|
|
|
if (idIndex === -1 || aliasIndex === -1) {
|
|
throw new Error(`CSV 必须包含“${REQUIRED_ID_FIELD}”和“${REQUIRED_ALIAS_FIELD}”两列。`);
|
|
}
|
|
|
|
const aliases = {};
|
|
|
|
rows.slice(1).forEach(row => {
|
|
const id = (row[idIndex] || '').trim();
|
|
const alias = (row[aliasIndex] || '').trim();
|
|
|
|
if (id && alias) {
|
|
aliases[id] = alias;
|
|
}
|
|
});
|
|
|
|
return aliases;
|
|
}
|
|
|
|
function decodeCsvBuffer(buffer, encoding) {
|
|
return new TextDecoder(encoding).decode(buffer);
|
|
}
|
|
|
|
function readAliasesFromCsvBuffer(buffer) {
|
|
const encodings = ['utf-8', 'gb18030', 'gbk'];
|
|
let lastError = null;
|
|
|
|
for (const encoding of encodings) {
|
|
try {
|
|
return aliasesFromCsv(decodeCsvBuffer(buffer, encoding));
|
|
} catch (error) {
|
|
lastError = error;
|
|
}
|
|
}
|
|
|
|
throw lastError;
|
|
}
|
|
|
|
// 导入 CSV
|
|
document.getElementById('import').addEventListener('click', () => {
|
|
document.getElementById('fileInput').click();
|
|
});
|
|
|
|
document.getElementById('fileInput').addEventListener('change', async (event) => {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
const btn = document.getElementById('import');
|
|
const originalText = btn.textContent;
|
|
btn.textContent = '导入中...';
|
|
btn.disabled = true;
|
|
setStatus('');
|
|
|
|
try {
|
|
const buffer = await file.arrayBuffer();
|
|
const aliases = readAliasesFromCsvBuffer(buffer);
|
|
await chrome.storage.local.set({ aliases, lastImport: Date.now(), sourceFileName: file.name });
|
|
setStatus(`已导入 ${Object.keys(aliases).length} 条`);
|
|
alert(`导入成功!共导入 ${Object.keys(aliases).length} 条数据。`);
|
|
} catch (error) {
|
|
setStatus('导入失败');
|
|
alert(error.message || '导入失败,请检查 CSV 文件。');
|
|
console.error(error);
|
|
} finally {
|
|
btn.textContent = originalText;
|
|
btn.disabled = false;
|
|
event.target.value = '';
|
|
}
|
|
});
|
|
|
|
// 导出数据
|
|
document.getElementById('export').addEventListener('click', async () => {
|
|
const { aliases } = await chrome.storage.local.get('aliases');
|
|
const blob = new Blob([JSON.stringify(aliases || {}, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const timestamp = new Date()
|
|
.toLocaleString('sv-SE') // ISO-like 格式
|
|
.replace(' ', '_')
|
|
.replace(/:/g, '-'); // 避免非法文件名
|
|
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `teams-alias-${timestamp}.json`;
|
|
a.click();
|
|
|
|
URL.revokeObjectURL(url);
|
|
});
|