csv export with UTF-8 BOM for Excel compatibility

This commit is contained in:
Dauren777 2026-06-11 04:25:11 +00:00
parent f8883205aa
commit ac969e09fc

View File

@ -31,6 +31,13 @@ body{
font-weight:700;font-size:14px;cursor:pointer;
}
.btn:hover{opacity:.85}
.btn-sm{
background:var(--gray-100);color:var(--ink);
border:none;padding:5px 12px;border-radius:6px;
font-weight:600;font-size:12px;cursor:pointer;
white-space:nowrap;
}
.btn-sm:hover{background:var(--cyan)}
.timer{font-size:13px;color:var(--gray-500)}
.badge{padding:4px 14px;border-radius:20px;font-size:13px;font-weight:600}
.badge.ok{background:var(--green);color:var(--ink)}
@ -117,6 +124,7 @@ tr:hover td{background:var(--cyan-50)}
<div class="controls">
<span id="statusBadge" class="badge ok">Загрузка...</span>
<button class="btn" onclick="loadAll()">↻ Обновить</button>
<button class="btn" onclick="downloadAll()" style="background:var(--gray-700);color:var(--white)">📥 Скачать всё</button>
<span class="timer" id="timerLabel">60 сек</span>
</div>
</header>
@ -256,9 +264,14 @@ function renderMain(results) {
const s = r.sheet;
const openLink = `https://docs.google.com/spreadsheets/d/${s.id}/edit?gid=${s.gid}`;
html += `<div class="sheet-section">`;
html += `<div class="sheet-header" onclick="toggleSheet(this)">
<h3>📋 ${s.label} <span class="arrow"></span></h3>
<span class="sheet-meta">${r.error ? "❌ " + r.error : r.rows.length + " строк"}</span>
html += `<div class="sheet-header">
<h3 style="display:flex;align-items:center;gap:12px" onclick="toggleSheet(this.parentElement)">
<span class="arrow"></span> 📋 ${s.label}
</h3>
<span class="sheet-meta" style="display:flex;align-items:center;gap:10px">
${r.error ? "❌ " + r.error : r.rows.length + " строк"}
${!r.error && r.rows.length > 0 ? `<button class="btn-sm" onclick="event.stopPropagation();downloadCSV(${i})" title="Скачать CSV (Excel-совместимый)">📥 CSV</button>` : ""}
</span>
</div>`;
html += `<div class="sheet-body ${r.rows.length === 0 ? 'collapsed' : ''}">`;
@ -303,6 +316,66 @@ function toggleSheet(header) {
arrow.classList.toggle("open");
}
function downloadCSV(sheetIndex) {
const r = allData[sheetIndex];
if (!r || r.error || r.rows.length === 0) return;
const rows = r.rows;
let csv = "\uFEFF"; // UTF-8 BOM — чтобы старый Excel сразу читал кириллицу
for (let i = 0; i < rows.length; i++) {
const cells = rows[i].map(c => {
const s = String(c);
if (s.includes(",") || s.includes("\"") || s.includes("\n")) {
return "\"" + s.replace(/\"/g, "\"\"") + "\"";
}
return s;
});
csv += cells.join(",") + "\n";
}
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
const name = r.sheet.label.replace(/[^a-zA-Zа-яА-ЯёЁ0-9_\- ]/g, "").trim() || "export";
a.download = name + ".csv";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function downloadAll() {
const ok = allData.filter(r => !r.error && r.rows.length > 0);
if (ok.length === 0) { alert("Нет данных для скачивания"); return; }
let csv = "\uFEFF";
ok.forEach((r, idx) => {
csv += "\n" + r.sheet.label + "\n";
r.rows.forEach(row => {
const cells = row.map(c => {
const s = String(c);
if (s.includes(",") || s.includes("\"") || s.includes("\n")) {
return "\"" + s.replace(/\"/g, "\"\"") + "\"";
}
return s;
});
csv += cells.join(",") + "\n";
});
if (idx < ok.length - 1) csv += "\n";
});
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "svodnaya_tablitsa.csv";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function updateTimer() {
document.getElementById("timerLabel").textContent = countdown + " сек";
}