upgrade size only compare

This commit is contained in:
Julian Freeman
2026-02-26 18:20:46 -04:00
parent 02b3697978
commit 5ae8d48261

View File

@@ -4,63 +4,70 @@
<meta charset="UTF-8">
<title>Rclone 尺寸对比器</title>
<style>
:root { --success: #28a745; --danger: #dc3545; --warning: #ffc107; --bg: #f8f9fa; --primary: #0984e3; }
body { font-family: 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: #333; padding: 20px; line-height: 1.5; }
.container { max-width: 1000px; margin: auto; }
:root {
--success: #28a745; --danger: #dc3545; --warning: #ffc107; --info: #17a2b8;
--bg: #f8f9fa; --card-bg: #ffffff; --text: #2d3436; --primary: #0984e3;
}
body { font-family: 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); padding: 20px; margin: 0; }
.container { max-width: 1100px; margin: auto; padding-top: 5px; }
/* 拖拽区 */
.drop-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 25px; }
.drop-box { border: 2px dashed #ccc; border-radius: 10px; padding: 30px 15px; text-align: center; background: white; cursor: pointer; transition: 0.2s; }
.drop-box { border: 2px dashed #bdc3c7; border-radius: 12px; padding: 35px 15px; text-align: center; background: white; cursor: pointer; transition: 0.2s; }
.drop-box.loaded { border-color: var(--success); background: #f6ffed; }
.drop-box.hover { border-color: var(--primary); background: #e6f7ff; }
/* 统计卡片 */
.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-bottom: 25px; display: none; }
.stat-card { background: white; padding: 15px; border-radius: 8px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
.stat-val { font-size: 22px; font-weight: bold; display: block; }
/* 统计卡片 - 增加到4项 */
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 25px; display: none; }
.stat-card { background: white; padding: 18px; border-radius: 10px; text-align: center; box-shadow: 0 4px 6px rgba(0,0,0,0.05); border-bottom: 4px solid #ddd; }
.stat-val { font-size: 24px; font-weight: 800; display: block; margin-bottom: 4px; }
.stat-label { font-size: 12px; color: #636e72; font-weight: 600; text-transform: uppercase; }
/* 树状视图核心修复 */
.report-card { background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); display: none; }
ul { list-style: none; padding-left: 20px; border-left: 1px solid #eee; margin: 5px 0; }
li { margin: 4px 0; }
/* 报告区域 */
.report-card { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.08); display: none; }
.btn-group { margin-bottom: 20px; display: flex; gap: 10px; }
.btn { padding: 9px 18px; border: 1px solid #dfe6e9; border-radius: 8px; background: white; cursor: pointer; font-weight: 600; transition: 0.2s; }
.btn:hover { background: #f1f2f6; }
.btn-active { background: var(--danger) !important; color: white; border-color: var(--danger); }
/* 修正对齐:使用 Flexbox */
.item-row { display: flex; align-items: center; padding: 3px 6px; border-radius: 4px; }
.item-row:hover { background: #f0f7ff; }
/* 树状 UI */
ul { list-style: none; padding-left: 22px; border-left: 1px dashed #dfe6e9; margin: 6px 0; }
li { margin: 5px 0; }
.item-row { display: flex; align-items: center; padding: 4px 8px; border-radius: 5px; transition: 0.1s; }
.item-row:hover { background: #f8f9fa; }
summary { cursor: pointer; font-weight: 600; outline: none; list-style: none; }
summary { cursor: pointer; outline: none; list-style: none; }
summary::-webkit-details-marker { display: none; }
.badge {
display: inline-block; min-width: 22px; height: 18px; line-height: 18px;
text-align: center; border-radius: 3px; color: white; font-size: 11px;
font-weight: bold; margin-right: 10px; flex-shrink: 0;
text-align: center; border-radius: 4px; color: white; font-size: 11px;
font-weight: bold; margin-right: 12px; flex-shrink: 0;
}
.folder-icon { margin-right: 8px; font-size: 1.1em; }
.error-dot { color: var(--danger); margin-left: 6px; font-size: 14px; }
.folder-icon { margin-right: 8px; font-size: 1.1em; opacity: 0.7; }
.error-dot { color: var(--danger); margin-left: 8px; font-size: 14px; animation: blink 2s infinite; }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
/* 过滤逻辑 */
/* 过滤模式:隐藏一致的项目 */
.filter-mode .is-match, .filter-mode .is-pure-match { display: none; }
.btn-group { margin-bottom: 15px; display: flex; gap: 10px; }
.btn { padding: 8px 16px; border: 1px solid #ddd; border-radius: 6px; background: white; cursor: pointer; font-weight: 600; }
.btn-active { background: var(--danger); color: white; border-color: var(--danger); }
</style>
</head>
<body>
<div class="container">
<h2 style="text-align:center;">🎬 Rclone 极速尺寸对比</h2>
<h2 style="text-align:center; margin-bottom:30px;">🎬 Rclone 双向对比工具</h2>
<div class="drop-grid">
<div id="drop-remote" class="drop-box"><p id="t-remote">1. 拖入云端清单</p><input type="file" id="file-remote" style="display:none"></div>
<div id="drop-local" class="drop-box"><p id="t-local">2. 拖入本地清单</p><input type="file" id="file-local" style="display:none"></div>
</div>
<div id="report-section" style="display: none;">
<div id="report-section">
<div class="stat-grid" id="stat-bar">
<div class="stat-card" style="color:var(--success)">一致 (Match)<span id="cnt-match" class="stat-val">0</span></div>
<div class="stat-card" style="color:var(--danger)">不同 (Diff)<span id="cnt-diff" class="stat-val">0</span></div>
<div class="stat-card" style="color:var(--warning)">缺失 (Missing)<span id="cnt-miss" class="stat-val">0</span></div>
<div class="stat-card" style="border-color: var(--success)"><span class="stat-label">一致 (Match)</span><span id="cnt-match" class="stat-val" style="color: var(--success)">0</span></div>
<div class="stat-card" style="border-color: var(--danger)"><span class="stat-label">不同 (Diff)</span><span id="cnt-diff" class="stat-val" style="color: var(--danger)">0</span></div>
<div class="stat-card" style="border-color: var(--warning)"><span class="stat-label">缺失 (Missing)</span><span id="cnt-miss" class="stat-val" style="color: var(--warning)">0</span></div>
<div class="stat-card" style="border-color: var(--info)"><span class="stat-label">多余 (Extra)</span><span id="cnt-extra" class="stat-val" style="color: var(--info)">0</span></div>
</div>
<div class="report-card" id="report-card">
@@ -75,7 +82,7 @@
</div>
<script>
let remoteData = null, localData = null, errorMode = false;
let remoteMap = null, localMap = null, errorMode = false;
function setupBox(id, fileId, type) {
const box = document.getElementById(id);
@@ -83,14 +90,14 @@
box.onclick = () => input.click();
box.ondragover = (e) => { e.preventDefault(); box.classList.add('hover'); };
box.ondragleave = () => box.classList.remove('hover');
box.ondrop = (e) => { e.preventDefault(); box.classList.remove('hover'); loadFile(e.dataTransfer.files[0], type); };
input.onchange = (e) => loadFile(e.target.files[0], type);
box.ondrop = (e) => { e.preventDefault(); box.classList.remove('hover'); handleFile(e.dataTransfer.files[0], type); };
input.onchange = (e) => handleFile(e.target.files[0], type);
}
setupBox('drop-remote', 'file-remote', 'remote');
setupBox('drop-local', 'file-local', 'local');
function loadFile(file, type) {
function handleFile(file, type) {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
@@ -100,30 +107,38 @@
const parts = line.split('|');
if (parts.length >= 2) {
const size = parts[0].trim();
// 修复:标准化路径,去掉前后斜杠
const path = parts.slice(1).join('|').replace(/^\/+|\/+$/g, '');
map.set(path, size);
}
});
if (type === 'remote') { remoteData = map; document.getElementById('t-remote').innerText = "✅ 云端加载成功"; document.getElementById('drop-remote').classList.add('loaded'); }
else { localData = map; document.getElementById('t-local').innerText = "✅ 本地加载成功"; document.getElementById('drop-local').classList.add('loaded'); }
if (remoteData && localData) compare();
if (type === 'remote') { remoteMap = map; document.getElementById('t-remote').innerText = "✅ 云端清单已加载"; document.getElementById('drop-remote').classList.add('loaded'); }
else { localMap = map; document.getElementById('t-local').innerText = "✅ 本地清单已加载"; document.getElementById('drop-local').classList.add('loaded'); }
if (remoteMap && localMap) startComparison();
};
reader.readAsText(file);
}
function compare() {
const tree = {}, stats = {match:0, diff:0, miss:0}, folderErrors = new Set();
function startComparison() {
const tree = {}, stats = {match:0, diff:0, miss:0, extra:0}, folderErrors = new Set();
// 以云端清单为基准
remoteData.forEach((rSize, path) => {
const lSize = localData.get(path);
// 获取所有路径的并集
const allPaths = new Set([...remoteMap.keys(), ...localMap.keys()]);
allPaths.forEach(path => {
const rSize = remoteMap.get(path);
const lSize = localMap.get(path);
let symbol = '';
if (lSize === undefined) { symbol = '-'; stats.miss++; }
else if (lSize !== rSize) { symbol = '*'; stats.diff++; }
else { symbol = '='; stats.match++; }
// 填充树形结构
if (rSize !== undefined && lSize !== undefined) {
if (rSize === lSize) { symbol = '='; stats.match++; }
else { symbol = '*'; stats.diff++; }
} else if (rSize !== undefined && lSize === undefined) {
symbol = '-'; stats.miss++; // 缺失
} else {
symbol = '+'; stats.extra++; // 多余
}
// 构建树状结构
let curr = tree;
const parts = path.split('/');
parts.forEach((part, i) => {
@@ -135,7 +150,7 @@
}
});
// 冒泡标记文件夹错误
// 标记含异常的文件夹
if (symbol !== '=') {
let acc = "";
parts.slice(0, -1).forEach(p => {
@@ -145,10 +160,12 @@
}
});
// 更新统计 UI
document.getElementById('cnt-match').innerText = stats.match;
document.getElementById('cnt-diff').innerText = stats.diff;
document.getElementById('cnt-miss').innerText = stats.miss;
document.getElementById('report-section').style.display = 'block';
document.getElementById('cnt-extra').innerText = stats.extra;
document.getElementById('stat-bar').style.display = 'grid';
document.getElementById('report-card').style.display = 'block';
document.getElementById('treeContainer').innerHTML = renderTree(tree, "", folderErrors);
@@ -175,10 +192,16 @@
</details>
</li>`;
} else {
const colors = {'=': 'var(--success)', '*': 'var(--danger)', '-': 'var(--warning)'};
const cfg = {
'=': {c: 'var(--success)', s: '='},
'*': {c: 'var(--danger)', s: '*'},
'-': {c: 'var(--warning)', s: '-'},
'+': {c: 'var(--info)', s: '+'}
}[val];
html += `<li class="${val==='='?'is-match':'is-error'}">
<div class="item-row">
<span class="badge" style="background:${colors[val]}">${val}</span>
<span class="badge" style="background:${cfg.c}">${cfg.s}</span>
<span class="file-name">${name}</span>
</div>
</li>`;