mne-nuzhno-prilozhenie-kotor/index.html

211 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="ru" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Сравнение Excel</title>
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
<link rel="stylesheet" href="design-system/kt-ai-components.css">
<link rel="stylesheet" href="design-system/kt-ai-page.css">
<style>
.drop-zone{border:2px dashed var(--kt-ai-border);border-radius:var(--kt-ai-radius-xl);padding:var(--kt-ai-space-8) var(--kt-ai-space-6);text-align:center;cursor:pointer;transition:all .2s;background:var(--kt-ai-card-bg)}
.drop-zone:hover,.drop-zone.drag{border-color:var(--kt-ai-primary);background:var(--kt-ai-bg-soft)}
.drop-zone input{display:none}
.drop-zone .icon{font-size:48px;margin-bottom:var(--kt-ai-space-3);opacity:.6}
.drop-zone .label{font-size:var(--kt-ai-text-lg);color:var(--kt-ai-fg);font-weight:var(--kt-ai-weight-medium)}
.drop-zone .hint{font-size:var(--kt-ai-text-sm);color:var(--kt-ai-fg-muted);margin-top:var(--kt-ai-space-2)}
.file-name{margin-top:var(--kt-ai-space-3);font-size:var(--kt-ai-text-sm);color:var(--kt-ai-primary);font-weight:var(--kt-ai-weight-medium)}
.compare-btn{width:100%;margin-top:var(--kt-ai-space-4)}
.result-box{background:var(--kt-ai-card-bg);border:1px solid var(--kt-ai-card-border);border-radius:var(--kt-ai-radius-xl);padding:var(--kt-ai-space-6);margin-top:var(--kt-ai-space-6);white-space:pre-wrap;font-family:var(--kt-ai-font-mono);font-size:var(--kt-ai-text-sm);line-height:1.7;color:var(--kt-ai-fg);max-height:60vh;overflow-y:auto}
.result-box:empty{display:none}
.stats{display:flex;gap:var(--kt-ai-space-3);flex-wrap:wrap;margin-bottom:var(--kt-ai-space-4)}
.stat{background:var(--kt-ai-bg-soft);border:1px solid var(--kt-ai-card-border);border-radius:var(--kt-ai-radius-lg);padding:var(--kt-ai-space-3) var(--kt-ai-space-5);text-align:center;flex:1;min-width:120px}
.stat .num{font-size:var(--kt-ai-text-2xl);font-weight:var(--kt-ai-weight-bold);color:var(--kt-ai-primary)}
.stat .lbl{font-size:var(--kt-ai-text-xs);color:var(--kt-ai-fg-muted);margin-top:var(--kt-ai-space-1)}
.tabs{display:flex;gap:var(--kt-ai-space-2);margin-bottom:var(--kt-ai-space-5)}
.tabs button{flex:1;padding:var(--kt-ai-space-3);border:1px solid var(--kt-ai-card-border);background:var(--kt-ai-card-bg);color:var(--kt-ai-fg);border-radius:var(--kt-ai-radius-lg);cursor:pointer;font-size:var(--kt-ai-text-sm);font-weight:var(--kt-ai-weight-medium);transition:all .2s}
.tabs button.active{background:var(--kt-ai-primary);color:#fff;border-color:var(--kt-ai-primary)}
.upload-row{display:grid;grid-template-columns:1fr 1fr;gap:var(--kt-ai-space-4)}
@media(max-width:640px){.upload-row{grid-template-columns:1fr}}
.spinner{display:inline-block;width:20px;height:20px;border:2px solid var(--kt-ai-border);border-top-color:var(--kt-ai-primary);border-radius:50%;animation:spin .6s linear infinite;margin-right:var(--kt-ai-space-2);vertical-align:middle}
@keyframes spin{to{transform:rotate(360deg)}}
</style>
</head>
<body>
<header class="kt-ai-pagehead">
<div class="kt-ai-wrap kt-ai-pagehead-in">
<strong>Excel Compare</strong>
<span class="kt-ai-spacer"></span>
</div>
</header>
<section class="kt-ai-hero" data-align="center">
<div class="kt-ai-wrap">
<h1>Сравнение и анализ Excel</h1>
<p class="kt-ai-hero-sub">Загрузите два файла — получите подробный отчёт по всем отличиям в каждом листе и ячейке.</p>
</div>
</section>
<section class="kt-ai-section" data-divider="true">
<div class="kt-ai-wrap">
<div class="tabs">
<button class="active" onclick="switchTab('compare')">Сравнение двух файлов</button>
<button onclick="switchTab('analyze')">Анализ одного файла</button>
</div>
<div id="tab-compare">
<div class="upload-row">
<div class="drop-zone" id="drop1" onclick="document.getElementById('file1').click()">
<div class="icon">📄</div>
<div class="label">Файл 1 (оригинал)</div>
<div class="hint">Нажмите или перетащите .xlsx / .xls</div>
<div class="file-name" id="name1"></div>
<input type="file" id="file1" accept=".xlsx,.xls,.csv" onchange="pickFile(1,this)">
</div>
<div class="drop-zone" id="drop2" onclick="document.getElementById('file2').click()">
<div class="icon">📄</div>
<div class="label">Файл 2 (новая версия)</div>
<div class="hint">Нажмите или перетащите .xlsx / .xls</div>
<div class="file-name" id="name2"></div>
<input type="file" id="file2" accept=".xlsx,.xls,.csv" onchange="pickFile(2,this)">
</div>
</div>
<button class="kt-ai-btn compare-btn" data-variant="primary" data-size="lg" id="compareBtn" onclick="doCompare()" disabled>Сравнить файлы</button>
</div>
<div id="tab-analyze" style="display:none">
<div class="drop-zone" id="dropA" onclick="document.getElementById('fileA').click()" style="max-width:480px;margin:0 auto">
<div class="icon">📊</div>
<div class="label">Загрузите Excel-файл</div>
<div class="hint">Нажмите или перетащите .xlsx / .xls</div>
<div class="file-name" id="nameA"></div>
<input type="file" id="fileA" accept=".xlsx,.xls,.csv" onchange="pickFile('A',this)">
</div>
<button class="kt-ai-btn compare-btn" data-variant="primary" data-size="lg" id="analyzeBtn" onclick="doAnalyze()" disabled style="max-width:480px;margin:var(--kt-ai-space-4) auto 0;display:block">Анализировать</button>
</div>
<div class="stats" id="stats" style="display:none">
<div class="stat"><div class="num" id="statSheets">0</div><div class="lbl">Листов</div></div>
<div class="stat"><div class="num" id="statChanges">0</div><div class="lbl">Изменений</div></div>
<div class="stat"><div class="num" id="statAdded">0</div><div class="lbl">Добавлено</div></div>
<div class="stat"><div class="num" id="statRemoved">0</div><div class="lbl">Удалено</div></div>
</div>
<div class="result-box" id="result"></div>
</div>
</section>
<footer class="kt-ai-wrap kt-ai-pagefoot">
<span>Excel Compare</span>
<span class="kt-ai-spacer"></span>
<span>Бесплатно, без ограничений</span>
</footer>
<script>
var files = {};
var currentTab = 'compare';
function switchTab(tab) {
currentTab = tab;
document.querySelectorAll('.tabs button').forEach(function(b,i){
b.classList.toggle('active', (i===0 && tab==='compare') || (i===1 && tab==='analyze'));
});
document.getElementById('tab-compare').style.display = tab==='compare' ? '' : 'none';
document.getElementById('tab-analyze').style.display = tab==='analyze' ? '' : 'none';
document.getElementById('result').textContent = '';
document.getElementById('stats').style.display = 'none';
}
function pickFile(n, input) {
var f = input.files[0];
if (!f) return;
files[n] = f;
var el = document.getElementById('name' + n);
el.textContent = f.name + ' (' + (f.size/1024).toFixed(1) + ' КБ)';
var dz = document.getElementById('drop' + n);
dz.style.borderColor = 'var(--kt-ai-primary)';
if (n === 1 || n === 2) {
document.getElementById('compareBtn').disabled = !(files[1] && files[2]);
}
if (n === 'A') {
document.getElementById('analyzeBtn').disabled = !files['A'];
}
}
function setupDrop(id, n) {
var dz = document.getElementById(id);
dz.addEventListener('dragover', function(e){ e.preventDefault(); dz.classList.add('drag'); });
dz.addEventListener('dragleave', function(){ dz.classList.remove('drag'); });
dz.addEventListener('drop', function(e){
e.preventDefault(); dz.classList.remove('drag');
var f = e.dataTransfer.files[0];
if (f && (f.name.endsWith('.xlsx') || f.name.endsWith('.xls') || f.name.endsWith('.csv'))) {
files[n] = f;
var input = document.getElementById(n === 'A' ? 'fileA' : 'file' + n);
var dt = new DataTransfer(); dt.items.add(f); input.files = dt.files;
pickFile(n, input);
}
});
}
setupDrop('drop1', 1);
setupDrop('drop2', 2);
setupDrop('dropA', 'A');
function doCompare() {
if (!files[1] || !files[2]) return;
var btn = document.getElementById('compareBtn');
btn.innerHTML = '<span class="spinner"></span> Сравниваю...';
btn.disabled = true;
var fd = new FormData();
fd.append('file1', files[1]);
fd.append('file2', files[2]);
fetch('/api/compare', { method: 'POST', body: fd })
.then(function(r){ return r.json(); })
.then(function(j){
btn.innerHTML = 'Сравнить файлы'; btn.disabled = false;
if (!j.ok) { document.getElementById('result').textContent = 'Ошибка: ' + j.error; return; }
var d = j.data;
document.getElementById('result').textContent = d.report;
var changes = (d.report.match(/ [~+-]/g) || []).length;
var added = (d.report.match(/ \+/g) || []).length;
var removed = (d.report.match(/ -/g) || []).length;
var sheets = Math.max(d.sheetsA, d.sheetsB);
document.getElementById('statSheets').textContent = sheets;
document.getElementById('statChanges').textContent = changes;
document.getElementById('statAdded').textContent = added;
document.getElementById('statRemoved').textContent = removed;
document.getElementById('stats').style.display = '';
})
.catch(function(e){
btn.innerHTML = 'Сравнить файлы'; btn.disabled = false;
document.getElementById('result').textContent = 'Ошибка сети: ' + e.message;
});
}
function doAnalyze() {
if (!files['A']) return;
var btn = document.getElementById('analyzeBtn');
btn.innerHTML = '<span class="spinner"></span> Анализирую...';
btn.disabled = true;
var fd = new FormData();
fd.append('file', files['A']);
fetch('/api/analyze', { method: 'POST', body: fd })
.then(function(r){ return r.json(); })
.then(function(j){
btn.innerHTML = 'Анализировать'; btn.disabled = false;
if (!j.ok) { document.getElementById('result').textContent = 'Ошибка: ' + j.error; return; }
document.getElementById('result').textContent = j.data.report;
document.getElementById('stats').style.display = 'none';
})
.catch(function(e){
btn.innerHTML = 'Анализировать'; btn.disabled = false;
document.getElementById('result').textContent = 'Ошибка сети: ' + e.message;
});
}
</script>
</body>
</html>