|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>ExcelRowComparator - Find Matching Rows</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://cdn.sheetjs.com/xlsx-0.19.3/package/dist/xlsx.full.min.js"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
:root { |
|
|
--primary: #6366f1; |
|
|
--primary-dark: #4f46e5; |
|
|
--secondary: #8b5cf6; |
|
|
--accent: #ec4899; |
|
|
--dark: #1e293b; |
|
|
--light: #f8fafc; |
|
|
} |
|
|
|
|
|
body { |
|
|
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); |
|
|
font-family: 'Inter', sans-serif; |
|
|
color: #f8fafc; |
|
|
} |
|
|
|
|
|
.file-drop-area { |
|
|
border: 2px dashed #334155; |
|
|
transition: all 0.3s ease; |
|
|
background: rgba(30, 41, 59, 0.7); |
|
|
backdrop-filter: blur(10px); |
|
|
} |
|
|
|
|
|
.file-drop-area.active { |
|
|
border-color: var(--primary); |
|
|
background: rgba(99, 102, 241, 0.2); |
|
|
} |
|
|
|
|
|
.progress-bar { |
|
|
transition: width 0.3s ease; |
|
|
background: linear-gradient(90deg, var(--primary) 0%, var(--accent) 100%); |
|
|
} |
|
|
|
|
|
.result-table { |
|
|
max-height: 500px; |
|
|
overflow-y: auto; |
|
|
scrollbar-width: thin; |
|
|
scrollbar-color: var(--primary) #1e293b; |
|
|
} |
|
|
|
|
|
.result-table::-webkit-scrollbar { |
|
|
width: 8px; |
|
|
} |
|
|
|
|
|
.result-table::-webkit-scrollbar-track { |
|
|
background: #1e293b; |
|
|
border-radius: 10px; |
|
|
} |
|
|
|
|
|
.result-table::-webkit-scrollbar-thumb { |
|
|
background: var(--primary); |
|
|
border-radius: 10px; |
|
|
} |
|
|
|
|
|
.highlight-row { |
|
|
animation: highlight 1.5s ease-out; |
|
|
} |
|
|
|
|
|
@keyframes highlight { |
|
|
0% { background-color: rgba(167, 139, 250, 0.5); } |
|
|
100% { background-color: transparent; } |
|
|
} |
|
|
|
|
|
.glow { |
|
|
box-shadow: 0 0 15px rgba(99, 102, 241, 0.3); |
|
|
} |
|
|
|
|
|
.card { |
|
|
background: rgba(30, 41, 59, 0.8); |
|
|
backdrop-filter: blur(10px); |
|
|
border-radius: 16px; |
|
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.btn-primary { |
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
|
|
color: white; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.btn-primary:hover { |
|
|
background: linear-gradient(135deg, var(--primary-dark) 0%, #7c3aed 100%); |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 10px 20px rgba(99, 102, 241, 0.3); |
|
|
} |
|
|
|
|
|
.btn-secondary { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
color: var(--light); |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.btn-secondary:hover { |
|
|
background: rgba(255, 255, 255, 0.2); |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
[lang="zh"] { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
body:lang(zh) [lang="en"] { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
body:lang(zh) [lang="zh"] { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.pulse { |
|
|
animation: pulse 2s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.7); } |
|
|
70% { box-shadow: 0 0 0 10px rgba(99, 102, 241, 0); } |
|
|
100% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0); } |
|
|
} |
|
|
|
|
|
.tech-border { |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.tech-border::after { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
height: 3px; |
|
|
background: linear-gradient(90deg, var(--primary) 0%, var(--accent) 100%); |
|
|
} |
|
|
|
|
|
.data-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
|
|
gap: 1px; |
|
|
background-color: #334155; |
|
|
} |
|
|
|
|
|
.data-cell { |
|
|
background-color: #1e293b; |
|
|
padding: 12px; |
|
|
border-bottom: 1px solid #334155; |
|
|
} |
|
|
|
|
|
.data-header { |
|
|
background-color: #0f172a; |
|
|
font-weight: 600; |
|
|
color: #94a3b8; |
|
|
text-transform: uppercase; |
|
|
font-size: 0.75rem; |
|
|
letter-spacing: 0.05em; |
|
|
} |
|
|
|
|
|
.match-indicator { |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
border-radius: 50%; |
|
|
background-color: #10b981; |
|
|
display: inline-block; |
|
|
margin-right: 8px; |
|
|
} |
|
|
|
|
|
.hud-display { |
|
|
background: rgba(15, 23, 42, 0.7); |
|
|
border: 1px solid rgba(74, 222, 128, 0.2); |
|
|
border-radius: 8px; |
|
|
padding: 12px; |
|
|
font-family: 'Courier New', monospace; |
|
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; transform: translateY(10px); } |
|
|
to { opacity: 1; transform: translateY(0); } |
|
|
} |
|
|
|
|
|
.animate-fade-in { |
|
|
animation: fadeIn 0.3s ease-out forwards; |
|
|
} |
|
|
|
|
|
.key-column { |
|
|
background-color: rgba(99, 102, 241, 0.15); |
|
|
border-left: 3px solid var(--primary); |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="min-h-screen" lang="en"> |
|
|
<div class="absolute top-5 right-5 z-10"> |
|
|
<div class="flex bg-gray-800 rounded-full shadow-lg overflow-hidden border border-gray-700"> |
|
|
<button onclick="switchLanguage('en')" class="px-4 py-2 text-sm font-medium transition-colors duration-200" :class="{'bg-indigo-600 text-white': document.documentElement.lang === 'en', 'text-gray-400 hover:bg-gray-700': document.documentElement.lang !== 'en'}"> |
|
|
EN |
|
|
</button> |
|
|
<button onclick="switchLanguage('zh')" class="px-4 py-2 text-sm font-medium transition-colors duration-200" :class="{'bg-indigo-600 text-white': document.documentElement.lang === 'zh', 'text-gray-400 hover:bg-gray-700': document.documentElement.lang !== 'zh'}"> |
|
|
中文 |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="container mx-auto px-4 py-8 max-w-7xl"> |
|
|
|
|
|
<header class="mb-12 text-center"> |
|
|
<div class="inline-block relative"> |
|
|
<div class="absolute -inset-1 bg-gradient-to-r from-indigo-500 to-pink-500 rounded-lg blur opacity-25"></div> |
|
|
<h1 class="relative text-4xl font-bold text-white bg-gray-800 px-6 py-3 rounded-lg glow border border-gray-700"> |
|
|
<i class="fas fa-file-excel mr-2 text-indigo-400"></i><span lang="en">ExcelRowComparator</span><span lang="zh">Excel行比较工具</span> |
|
|
</h1> |
|
|
</div> |
|
|
<p class="text-gray-400 mt-4 max-w-2xl mx-auto"> |
|
|
<span lang="en">Find matching rows between two Excel files with identical columns</span> |
|
|
<span lang="zh">在具有相同列的两个Excel文件中查找匹配行</span> |
|
|
</p> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> |
|
|
|
|
|
<div class="card tech-border p-6 relative"> |
|
|
<div class="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"></div> |
|
|
<h2 class="text-xl font-semibold text-white mb-6 flex items-center"> |
|
|
<div class="w-8 h-8 rounded-full bg-indigo-900 flex items-center justify-center mr-3"> |
|
|
<i class="fas fa-file-import text-indigo-400"></i> |
|
|
</div> |
|
|
<span lang="en">Select Files to Compare</span> |
|
|
<span lang="zh">选择要比较的文件</span> |
|
|
</h2> |
|
|
|
|
|
|
|
|
<div class="mb-8"> |
|
|
<label class="block text-sm font-medium text-gray-300 mb-3 flex items-center"> |
|
|
<span class="w-6 h-6 rounded-full bg-indigo-600 text-white text-xs flex items-center justify-center mr-2">1</span> |
|
|
<span lang="en">First Excel File</span> |
|
|
<span lang="zh">第一个Excel文件</span> |
|
|
</label> |
|
|
<div class="file-drop-area rounded-xl p-6 text-center cursor-pointer transition-all hover:shadow-md" id="file1-drop-area"> |
|
|
<input type="file" id="file1-input" class="hidden" accept=".xlsx,.xls,.csv"> |
|
|
<div class="flex flex-col items-center justify-center"> |
|
|
<div class="w-16 h-16 rounded-full bg-indigo-900 flex items-center justify-center mb-3 pulse"> |
|
|
<i class="fas fa-file-excel text-3xl text-indigo-400"></i> |
|
|
</div> |
|
|
<p class="text-gray-400 mb-1 font-medium"> |
|
|
<span lang="en">Drag & drop your Excel file here</span> |
|
|
<span lang="zh">拖放Excel文件到此处</span> |
|
|
</p> |
|
|
<p class="text-sm text-gray-500"> |
|
|
<span lang="en">or click to browse</span> |
|
|
<span lang="zh">或点击浏览</span> |
|
|
</p> |
|
|
<p class="text-sm font-medium text-indigo-400 mt-3" id="file1-name"> |
|
|
<span lang="en">No file selected</span> |
|
|
<span lang="zh">未选择文件</span> |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-3 flex items-center"> |
|
|
<label class="text-sm text-gray-400 mr-2"> |
|
|
<span lang="en">Sheet:</span> |
|
|
<span lang="zh">工作表:</span> |
|
|
</label> |
|
|
<select class="border border-gray-700 rounded-lg px-3 py-2 text-sm w-full max-w-xs bg-gray-800 text-white shadow-sm" id="file1-sheet" disabled> |
|
|
<option value=""> |
|
|
<span lang="en">Select a sheet</span> |
|
|
<span lang="zh">选择工作表</span> |
|
|
</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-8"> |
|
|
<label class="block text-sm font-medium text-gray-300 mb-3 flex items-center"> |
|
|
<span class="w-6 h-6 rounded-full bg-purple-600 text-white text-xs flex items-center justify-center mr-2">2</span> |
|
|
<span lang="en">Second Excel File</span> |
|
|
<span lang="zh">第二个Excel文件</span> |
|
|
</label> |
|
|
<div class="file-drop-area rounded-xl p-6 text-center cursor-pointer transition-all hover:shadow-md" id="file2-drop-area"> |
|
|
<input type="file" id="file2-input" class="hidden" accept=".xlsx,.xls,.csv"> |
|
|
<div class="flex flex-col items-center justify-center"> |
|
|
<div class="w-16 h-16 rounded-full bg-purple-900 flex items-center justify-center mb-3 pulse"> |
|
|
<i class="fas fa-file-excel text-3xl text-purple-400"></i> |
|
|
</div> |
|
|
<p class="text-gray-400 mb-1 font-medium"> |
|
|
<span lang="en">Drag & drop your Excel file here</span> |
|
|
<span lang="zh">拖放Excel文件到此处</span> |
|
|
</p> |
|
|
<p class="text-sm text-gray-500"> |
|
|
<span lang="en">or click to browse</span> |
|
|
<span lang="zh">或点击浏览</span> |
|
|
</p> |
|
|
<p class="text-sm font-medium text-purple-400 mt-3" id="file2-name"> |
|
|
<span lang="en">No file selected</span> |
|
|
<span lang="zh">未选择文件</span> |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-3 flex items-center"> |
|
|
<label class="text-sm text-gray-400 mr-2"> |
|
|
<span lang="en">Sheet:</span> |
|
|
<span lang="zh">工作表:</span> |
|
|
</label> |
|
|
<select class="border border-gray-700 rounded-lg px-3 py-2 text-sm w-full max-w-xs bg-gray-800 text-white shadow-sm" id="file2-sheet" disabled> |
|
|
<option value=""> |
|
|
<span lang="en">Select a sheet</span> |
|
|
<span lang="zh">选择工作表</span> |
|
|
</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-8"> |
|
|
<h3 class="text-sm font-medium text-gray-300 mb-3 flex items-center"> |
|
|
<i class="fas fa-key text-gray-500 mr-2"></i> |
|
|
<span lang="en">Key Column (Optional)</span> |
|
|
<span lang="zh">关键列(可选)</span> |
|
|
</h3> |
|
|
<select id="key-column-select" class="border border-gray-700 rounded-lg px-3 py-2 text-sm w-full bg-gray-800 text-white shadow-sm" disabled> |
|
|
<option value="">All columns (default)</option> |
|
|
</select> |
|
|
<p class="text-xs text-gray-500 mt-1"> |
|
|
<span lang="en">Select a column to use as matching key (leave blank to match all columns)</span> |
|
|
<span lang="zh">选择作为匹配关键字的列(留空则匹配所有列)</span> |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-8"> |
|
|
<h3 class="text-sm font-medium text-gray-300 mb-3 flex items-center"> |
|
|
<i class="fas fa-cog text-gray-500 mr-2"></i> |
|
|
<span lang="en">Comparison Options</span> |
|
|
<span lang="zh">比较选项</span> |
|
|
</h3> |
|
|
<div class="space-y-3"> |
|
|
<label class="flex items-center"> |
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> |
|
|
<input type="checkbox" name="ignoreCase" id="ignoreCase" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/> |
|
|
<label for="ignoreCase" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-700 cursor-pointer"></label> |
|
|
</div> |
|
|
<span class="text-sm text-gray-400"> |
|
|
<span lang="en">Ignore case sensitivity</span> |
|
|
<span lang="zh">忽略大小写</span> |
|
|
</span> |
|
|
</label> |
|
|
<label class="flex items-center"> |
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> |
|
|
<input type="checkbox" name="trimWhitespace" id="trimWhitespace" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked/> |
|
|
<label for="trimWhitespace" class="toggle-label block overflow-hidden h-6 rounded-full bg-indigo-600 cursor-pointer"></label> |
|
|
</div> |
|
|
<span class="text-sm text-gray-400"> |
|
|
<span lang="en">Trim whitespace</span> |
|
|
<span lang="zh">去除空格</span> |
|
|
</span> |
|
|
</label> |
|
|
<label class="flex items-center"> |
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> |
|
|
<input type="checkbox" name="showOnlyMatches" id="showOnlyMatches" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked/> |
|
|
<label for="showOnlyMatches" class="toggle-label block overflow-hidden h-6 rounded-full bg-indigo-600 cursor-pointer"></label> |
|
|
</div> |
|
|
<span class="text-sm text-gray-400"> |
|
|
<span lang="en">Show only matching rows</span> |
|
|
<span lang="zh">仅显示匹配行</span> |
|
|
</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<button id="compare-btn" class="w-full py-3 rounded-xl btn-primary font-medium flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed transition-all" disabled> |
|
|
<i class="fas fa-exchange-alt mr-2"></i> |
|
|
<span lang="en">Find Matching Rows</span> |
|
|
<span lang="zh">查找匹配行</span> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="card tech-border p-6 relative"> |
|
|
<div class="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"></div> |
|
|
<div class="flex justify-between items-center mb-6"> |
|
|
<h2 class="text-xl font-semibold text-white flex items-center"> |
|
|
<div class="w-8 h-8 rounded-full bg-pink-900 flex items-center justify-center mr-3"> |
|
|
<i class="fas fa-poll text-pink-400"></i> |
|
|
</div> |
|
|
<span lang="en">Matching Rows</span> |
|
|
<span lang="zh">匹配行结果</span> |
|
|
</h2> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="export-btn" class="px-4 py-2 rounded-lg btn-secondary text-sm font-medium flex items-center disabled:opacity-50 disabled:cursor-not-allowed" disabled> |
|
|
<i class="fas fa-file-export mr-2"></i> |
|
|
<span lang="en">Export</span> |
|
|
<span lang="zh">导出</span> |
|
|
</button> |
|
|
<button id="copy-btn" class="px-4 py-2 rounded-lg btn-secondary text-sm font-medium flex items-center disabled:opacity-50 disabled:cursor-not-allowed" disabled> |
|
|
<i class="fas fa-copy mr-2"></i> |
|
|
<span lang="en">Copy</span> |
|
|
<span lang="zh">复制</span> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="progress-container" class="mb-6 hidden"> |
|
|
<div class="flex justify-between text-sm text-gray-400 mb-2"> |
|
|
<span lang="en">Processing files...</span> |
|
|
<span lang="zh">正在处理文件...</span> |
|
|
<span id="progress-percent">0%</span> |
|
|
</div> |
|
|
<div class="w-full bg-gray-800 rounded-full h-2.5 overflow-hidden"> |
|
|
<div id="progress-bar" class="progress-bar h-2.5 rounded-full" style="width: 0%"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="results-placeholder" class="border-2 border-dashed border-gray-700 rounded-xl p-8 text-center bg-gray-800/50"> |
|
|
<div class="w-20 h-20 rounded-full bg-indigo-900 flex items-center justify-center mx-auto mb-4"> |
|
|
<i class="fas fa-file-alt text-3xl text-indigo-500"></i> |
|
|
</div> |
|
|
<h3 class="text-lg font-medium text-gray-400"> |
|
|
<span lang="en">No results yet</span> |
|
|
<span lang="zh">暂无结果</span> |
|
|
</h3> |
|
|
<p class="text-sm text-gray-500 mt-2 max-w-xs mx-auto"> |
|
|
<span lang="en">Select two Excel files and click "Find Matching Rows"</span> |
|
|
<span lang="zh">选择两个Excel文件并点击"查找匹配行"</span> |
|
|
</p> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="results-container" class="hidden"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<div class="hud-display flex items-center"> |
|
|
<span class="match-indicator"></span> |
|
|
<span class="text-green-400 font-mono"> |
|
|
<span id="match-count" class="font-bold">0</span> |
|
|
<span lang="en"> matching rows found</span> |
|
|
<span lang="zh"> 行匹配结果</span> |
|
|
</span> |
|
|
</div> |
|
|
<div class="hud-display"> |
|
|
<span class="text-blue-400 font-mono"> |
|
|
<span id="row-count" class="font-bold">0</span> |
|
|
<span lang="en"> rows processed</span> |
|
|
<span lang="zh"> 行已处理</span> |
|
|
</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="result-table border border-gray-700 rounded-xl overflow-hidden"> |
|
|
<div class="data-grid" id="data-grid-headers"> |
|
|
|
|
|
</div> |
|
|
<div class="data-grid" id="data-grid-body" style="max-height: 400px; overflow-y: auto;"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-4 flex justify-between items-center text-sm text-gray-500"> |
|
|
<div> |
|
|
<span lang="en">Scroll to view more results</span> |
|
|
<span lang="zh">滚动查看更多结果</span> |
|
|
</div> |
|
|
<div class="flex items-center"> |
|
|
<i class="fas fa-info-circle mr-1"></i> |
|
|
<span id="last-updated" lang="en">Last updated: Just now</span> |
|
|
<span id="last-updated" lang="zh">最后更新: 刚刚</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mt-8 card p-4"> |
|
|
<div class="flex items-center justify-between"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<div id="status-icon" class="w-3 h-3 rounded-full bg-gray-500"></div> |
|
|
<span id="status-text" class="text-sm text-gray-400"> |
|
|
<span lang="en">Ready</span> |
|
|
<span lang="zh">准备就绪</span> |
|
|
</span> |
|
|
</div> |
|
|
<div class="text-sm text-gray-500 flex space-x-4"> |
|
|
<span id="file1-status" class="flex items-center"> |
|
|
<span class="w-2 h-2 rounded-full bg-indigo-500 mr-2"></span> |
|
|
<span lang="en">File 1: Not loaded</span> |
|
|
<span lang="zh">文件1: 未加载</span> |
|
|
</span> |
|
|
<span id="file2-status" class="flex items-center"> |
|
|
<span class="w-2 h-2 rounded-full bg-purple-500 mr-2"></span> |
|
|
<span lang="en">File 2: Not loaded</span> |
|
|
<span lang="zh">文件2: 未加载</span> |
|
|
</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const file1DropArea = document.getElementById('file1-drop-area'); |
|
|
const file1Input = document.getElementById('file1-input'); |
|
|
const file1Name = document.getElementById('file1-name'); |
|
|
const file1Sheet = document.getElementById('file1-sheet'); |
|
|
|
|
|
const file2DropArea = document.getElementById('file2-drop-area'); |
|
|
const file2Input = document.getElementById('file2-input'); |
|
|
const file2Name = document.getElementById('file2-name'); |
|
|
const file2Sheet = document.getElementById('file2-sheet'); |
|
|
|
|
|
const keyColumnSelect = document.getElementById('key-column-select'); |
|
|
const ignoreCaseCheckbox = document.getElementById('ignoreCase'); |
|
|
const trimWhitespaceCheckbox = document.getElementById('trimWhitespace'); |
|
|
const showOnlyMatchesCheckbox = document.getElementById('showOnlyMatches'); |
|
|
|
|
|
const compareBtn = document.getElementById('compare-btn'); |
|
|
const exportBtn = document.getElementById('export-btn'); |
|
|
const copyBtn = document.getElementById('copy-btn'); |
|
|
|
|
|
const progressContainer = document.getElementById('progress-container'); |
|
|
const progressBar = document.getElementById('progress-bar'); |
|
|
const progressPercent = document.getElementById('progress-percent'); |
|
|
|
|
|
const resultsPlaceholder = document.getElementById('results-placeholder'); |
|
|
const resultsContainer = document.getElementById('results-container'); |
|
|
const dataGridHeaders = document.getElementById('data-grid-headers'); |
|
|
const dataGridBody = document.getElementById('data-grid-body'); |
|
|
const matchCount = document.getElementById('match-count'); |
|
|
const rowCount = document.getElementById('row-count'); |
|
|
const lastUpdated = document.getElementById('last-updated'); |
|
|
|
|
|
const statusIcon = document.getElementById('status-icon'); |
|
|
const statusText = document.getElementById('status-text'); |
|
|
const file1Status = document.getElementById('file1-status'); |
|
|
const file2Status = document.getElementById('file2-status'); |
|
|
|
|
|
|
|
|
let comparisonResults = []; |
|
|
let file1Data = null; |
|
|
let file2Data = null; |
|
|
let headers = []; |
|
|
|
|
|
|
|
|
file1DropArea.addEventListener('click', () => file1Input.click()); |
|
|
file1Input.addEventListener('change', handleFileSelect.bind(null, file1Input, file1Name, file1Sheet, file1Status, 'File 1')); |
|
|
|
|
|
file1DropArea.addEventListener('dragover', (e) => { |
|
|
e.preventDefault(); |
|
|
file1DropArea.classList.add('active'); |
|
|
}); |
|
|
|
|
|
file1DropArea.addEventListener('dragleave', () => { |
|
|
file1DropArea.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
file1DropArea.addEventListener('drop', (e) => { |
|
|
e.preventDefault(); |
|
|
file1DropArea.classList.remove('active'); |
|
|
if (e.dataTransfer.files.length) { |
|
|
file1Input.files = e.dataTransfer.files; |
|
|
handleFileSelect(file1Input, file1Name, file1Sheet, file1Status, 'File 1'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
file2DropArea.addEventListener('click', () => file2Input.click()); |
|
|
file2Input.addEventListener('change', handleFileSelect.bind(null, file2Input, file2Name, file2Sheet, file2Status, 'File 2')); |
|
|
|
|
|
file2DropArea.addEventListener('dragover', (e) => { |
|
|
e.preventDefault(); |
|
|
file2DropArea.classList.add('active'); |
|
|
}); |
|
|
|
|
|
file2DropArea.addEventListener('dragleave', () => { |
|
|
file2DropArea.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
file2DropArea.addEventListener('drop', (e) => { |
|
|
e.preventDefault(); |
|
|
file2DropArea.classList.remove('active'); |
|
|
if (e.dataTransfer.files.length) { |
|
|
file2Input.files = e.dataTransfer.files; |
|
|
handleFileSelect(file2Input, file2Name, file2Sheet, file2Status, 'File 2'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
compareBtn.addEventListener('click', compareFiles); |
|
|
|
|
|
|
|
|
exportBtn.addEventListener('click', exportResults); |
|
|
|
|
|
|
|
|
copyBtn.addEventListener('click', copyResults); |
|
|
|
|
|
|
|
|
file1Sheet.addEventListener('change', updateKeyColumnSelect); |
|
|
file2Sheet.addEventListener('change', updateKeyColumnSelect); |
|
|
|
|
|
|
|
|
function handleFileSelect(input, nameElement, sheetElement, statusElement, fileLabel) { |
|
|
if (input.files.length) { |
|
|
const file = input.files[0]; |
|
|
nameElement.textContent = file.name; |
|
|
|
|
|
if (document.documentElement.lang === 'en') { |
|
|
statusElement.innerHTML = `<span class="w-2 h-2 rounded-full ${fileLabel === 'File 1' ? 'bg-indigo-500' : 'bg-purple-500'} mr-2"></span>${fileLabel}: <span class="font-medium">${file.name}</span>`; |
|
|
} else { |
|
|
statusElement.innerHTML = `<span class="w-2 h-2 rounded-full ${fileLabel === 'File 1' ? 'bg-indigo-500' : 'bg-purple-500'} mr-2"></span>${fileLabel === 'File 1' ? '文件1' : '文件2'}: <span class="font-medium">${file.name}</span>`; |
|
|
} |
|
|
|
|
|
|
|
|
const reader = new FileReader(); |
|
|
reader.onload = function(e) { |
|
|
const data = new Uint8Array(e.target.result); |
|
|
const workbook = XLSX.read(data, {type: 'array'}); |
|
|
|
|
|
|
|
|
if (fileLabel === 'File 1') { |
|
|
file1Data = workbook; |
|
|
} else { |
|
|
file2Data = workbook; |
|
|
} |
|
|
|
|
|
|
|
|
sheetElement.disabled = false; |
|
|
sheetElement.innerHTML = ''; |
|
|
|
|
|
|
|
|
const defaultOption = document.createElement('option'); |
|
|
defaultOption.value = ''; |
|
|
if (document.documentElement.lang === 'en') { |
|
|
defaultOption.textContent = 'Select a sheet'; |
|
|
} else { |
|
|
defaultOption.textContent = '选择工作表'; |
|
|
} |
|
|
sheetElement.appendChild(defaultOption); |
|
|
|
|
|
|
|
|
workbook.SheetNames.forEach(sheet => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = sheet; |
|
|
option.textContent = sheet; |
|
|
sheetElement.appendChild(option); |
|
|
}); |
|
|
|
|
|
|
|
|
if (workbook.SheetNames.length > 0) { |
|
|
sheetElement.value = workbook.SheetNames[0]; |
|
|
} |
|
|
|
|
|
updateCompareButtonState(); |
|
|
}; |
|
|
reader.readAsArrayBuffer(file); |
|
|
} |
|
|
} |
|
|
|
|
|
function updateKeyColumnSelect() { |
|
|
|
|
|
if (!file1Sheet.value || !file2Sheet.value || !file1Data || !file2Data) { |
|
|
keyColumnSelect.disabled = true; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const worksheet1 = file1Data.Sheets[file1Sheet.value]; |
|
|
const json1 = XLSX.utils.sheet_to_json(worksheet1, {header: 1}); |
|
|
|
|
|
|
|
|
headers = json1.length > 0 ? json1[0] : []; |
|
|
|
|
|
|
|
|
keyColumnSelect.innerHTML = ''; |
|
|
keyColumnSelect.disabled = false; |
|
|
|
|
|
|
|
|
const defaultOption = document.createElement('option'); |
|
|
defaultOption.value = ''; |
|
|
defaultOption.textContent = document.documentElement.lang === 'en' ? 'All columns (default)' : '所有列(默认)'; |
|
|
keyColumnSelect.appendChild(defaultOption); |
|
|
|
|
|
|
|
|
headers.forEach(header => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = header; |
|
|
option.textContent = header; |
|
|
keyColumnSelect.appendChild(option); |
|
|
}); |
|
|
} |
|
|
|
|
|
function updateCompareButtonState() { |
|
|
const file1Ready = file1Input.files.length && file1Sheet.value; |
|
|
const file2Ready = file2Input.files.length && file2Sheet.value; |
|
|
|
|
|
compareBtn.disabled = !(file1Ready && file2Ready); |
|
|
|
|
|
if (compareBtn.disabled) { |
|
|
if (document.documentElement.lang === 'en') { |
|
|
compareBtn.title = file1Ready ? 'Select a sheet for both files' : 'Select both files'; |
|
|
} else { |
|
|
compareBtn.title = file1Ready ? '请为两个文件选择工作表' : '请选择两个文件'; |
|
|
} |
|
|
} else { |
|
|
compareBtn.title = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
function compareFiles() { |
|
|
|
|
|
compareBtn.disabled = true; |
|
|
if (document.documentElement.lang === 'en') { |
|
|
compareBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Comparing...'; |
|
|
statusText.textContent = 'Comparing files...'; |
|
|
} else { |
|
|
compareBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> 正在比较...'; |
|
|
statusText.textContent = '正在比较文件...'; |
|
|
} |
|
|
statusIcon.className = 'w-3 h-3 rounded-full bg-yellow-400'; |
|
|
progressContainer.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
resultsPlaceholder.classList.add('hidden'); |
|
|
resultsContainer.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
const sheet1Name = file1Sheet.value; |
|
|
const sheet2Name = file2Sheet.value; |
|
|
|
|
|
|
|
|
const worksheet1 = file1Data.Sheets[sheet1Name]; |
|
|
const worksheet2 = file2Data.Sheets[sheet2Name]; |
|
|
const json1 = XLSX.utils.sheet_to_json(worksheet1); |
|
|
const json2 = XLSX.utils.sheet_to_json(worksheet2); |
|
|
|
|
|
|
|
|
if (headers.length === 0) { |
|
|
headers = Object.keys(json1[0] || json2[0] || {}); |
|
|
} |
|
|
|
|
|
|
|
|
const keyColumn = keyColumnSelect.value; |
|
|
const ignoreCase = ignoreCaseCheckbox.checked; |
|
|
const trimWhitespace = trimWhitespaceCheckbox.checked; |
|
|
const showOnlyMatches = showOnlyMatchesCheckbox.checked; |
|
|
|
|
|
|
|
|
let progress = 0; |
|
|
const progressInterval = setInterval(() => { |
|
|
progress += Math.random() * 10; |
|
|
if (progress > 100) progress = 100; |
|
|
|
|
|
progressBar.style.width = `${progress}%`; |
|
|
progressPercent.textContent = `${Math.floor(progress)}%`; |
|
|
|
|
|
if (progress === 100) { |
|
|
clearInterval(progressInterval); |
|
|
} |
|
|
}, 200); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
comparisonResults = []; |
|
|
|
|
|
|
|
|
const file2Map = new Map(); |
|
|
|
|
|
|
|
|
json2.forEach(row => { |
|
|
const key = keyColumn ? |
|
|
(ignoreCase ? String(row[keyColumn]).toLowerCase() : String(row[keyColumn])) : |
|
|
JSON.stringify(normalizeRow(row, headers, ignoreCase, trimWhitespace)); |
|
|
|
|
|
if (!file2Map.has(key)) { |
|
|
file2Map.set(key, []); |
|
|
} |
|
|
file2Map.get(key).push(row); |
|
|
}); |
|
|
|
|
|
|
|
|
json1.forEach(row1 => { |
|
|
const key = keyColumn ? |
|
|
(ignoreCase ? String(row1[keyColumn]).toLowerCase() : String(row1[keyColumn])) : |
|
|
JSON.stringify(normalizeRow(row1, headers, ignoreCase, trimWhitespace)); |
|
|
|
|
|
if (file2Map.has(key)) { |
|
|
|
|
|
const matchingRows = file2Map.get(key); |
|
|
matchingRows.forEach(row2 => { |
|
|
|
|
|
const resultRow = {}; |
|
|
headers.forEach(header => { |
|
|
resultRow[`${header} (File 1)`] = row1[header]; |
|
|
resultRow[`${header} (File 2)`] = row2[header]; |
|
|
}); |
|
|
comparisonResults.push(resultRow); |
|
|
}); |
|
|
} else if (!showOnlyMatches) { |
|
|
|
|
|
const resultRow = {}; |
|
|
headers.forEach(header => { |
|
|
resultRow[`${header} (File 1)`] = row1[header]; |
|
|
resultRow[`${header} (File 2)`] = ''; |
|
|
}); |
|
|
comparisonResults.push(resultRow); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
if (!showOnlyMatches) { |
|
|
const matchedKeys = new Set(); |
|
|
json1.forEach(row1 => { |
|
|
const key = keyColumn ? |
|
|
(ignoreCase ? String(row1[keyColumn]).toLowerCase() : String(row1[keyColumn])) : |
|
|
JSON.stringify(normalizeRow(row1, headers, ignoreCase, trimWhitespace)); |
|
|
matchedKeys.add(key); |
|
|
}); |
|
|
|
|
|
json2.forEach(row2 => { |
|
|
const key = keyColumn ? |
|
|
(ignoreCase ? String(row2[keyColumn]).toLowerCase() : String(row2[keyColumn])) : |
|
|
JSON.stringify(normalizeRow(row2, headers, ignoreCase, trimWhitespace)); |
|
|
|
|
|
if (!matchedKeys.has(key)) { |
|
|
const resultRow = {}; |
|
|
headers.forEach(header => { |
|
|
resultRow[`${header} (File 1)`] = ''; |
|
|
resultRow[`${header} (File 2)`] = row2[header]; |
|
|
}); |
|
|
comparisonResults.push(resultRow); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const displayHeaders = []; |
|
|
headers.forEach(header => { |
|
|
displayHeaders.push(`${header} (File 1)`); |
|
|
displayHeaders.push(`${header} (File 2)`); |
|
|
}); |
|
|
|
|
|
|
|
|
dataGridHeaders.innerHTML = ''; |
|
|
displayHeaders.forEach(header => { |
|
|
const headerCell = document.createElement('div'); |
|
|
headerCell.className = 'data-cell data-header'; |
|
|
headerCell.textContent = header; |
|
|
|
|
|
|
|
|
if (keyColumn && header.includes(keyColumn)) { |
|
|
headerCell.classList.add('key-column'); |
|
|
} |
|
|
|
|
|
dataGridHeaders.appendChild(headerCell); |
|
|
}); |
|
|
|
|
|
|
|
|
dataGridHeaders.style.gridTemplateColumns = `repeat(${displayHeaders.length}, minmax(150px, 1fr))`; |
|
|
dataGridBody.style.gridTemplateColumns = `repeat(${displayHeaders.length}, minmax(150px, 1fr))`; |
|
|
|
|
|
|
|
|
dataGridBody.innerHTML = ''; |
|
|
comparisonResults.forEach((row, index) => { |
|
|
displayHeaders.forEach(header => { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'data-cell animate-fade-in'; |
|
|
cell.style.animationDelay = `${index * 0.05}s`; |
|
|
|
|
|
|
|
|
const isMatch = row[header] !== '' && |
|
|
row[header.replace(' (File 1)', ' (File 2)')] !== undefined; |
|
|
|
|
|
|
|
|
cell.textContent = row[header] !== undefined ? row[header] : ''; |
|
|
|
|
|
|
|
|
if (keyColumn && header.includes(keyColumn)) { |
|
|
cell.classList.add('key-column'); |
|
|
} |
|
|
|
|
|
|
|
|
if (isMatch) { |
|
|
cell.style.backgroundColor = 'rgba(16, 185, 129, 0.1)'; |
|
|
} |
|
|
|
|
|
dataGridBody.appendChild(cell); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const actualMatches = comparisonResults.filter(row => { |
|
|
return headers.some(header => row[`${header} (File 1)`] && row[`${header} (File 2)`]); |
|
|
}).length; |
|
|
|
|
|
matchCount.textContent = actualMatches; |
|
|
rowCount.textContent = json1.length + json2.length; |
|
|
|
|
|
|
|
|
const now = new Date(); |
|
|
if (document.documentElement.lang === 'en') { |
|
|
lastUpdated.textContent = `Last updated: ${now.toLocaleTimeString()}`; |
|
|
} else { |
|
|
lastUpdated.textContent = `最后更新: ${now.toLocaleTimeString()}`; |
|
|
} |
|
|
|
|
|
|
|
|
exportBtn.disabled = false; |
|
|
copyBtn.disabled = false; |
|
|
|
|
|
|
|
|
onComparisonComplete(); |
|
|
}, 1000); |
|
|
} |
|
|
|
|
|
function normalizeRow(row, headers, ignoreCase, trimWhitespace) { |
|
|
const normalized = {}; |
|
|
headers.forEach(header => { |
|
|
let value = row[header] !== undefined ? String(row[header]) : ''; |
|
|
if (trimWhitespace) value = value.trim(); |
|
|
if (ignoreCase) value = value.toLowerCase(); |
|
|
normalized[header] = value; |
|
|
}); |
|
|
return normalized; |
|
|
} |
|
|
|
|
|
function onComparisonComplete() { |
|
|
compareBtn.disabled = false; |
|
|
if (document.documentElement.lang === 'en') { |
|
|
compareBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> Find Matching Rows'; |
|
|
statusText.textContent = 'Comparison complete'; |
|
|
} else { |
|
|
compareBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> 查找匹配行'; |
|
|
statusText.textContent = '比较完成'; |
|
|
} |
|
|
statusIcon.className = 'w-3 h-3 rounded-full bg-green-500'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
progressContainer.classList.add('hidden'); |
|
|
}, 1000); |
|
|
} |
|
|
|
|
|
function exportResults() { |
|
|
if (comparisonResults.length === 0) { |
|
|
showToast( |
|
|
document.documentElement.lang === 'en' ? 'No results to export' : '没有结果可导出', |
|
|
'error' |
|
|
); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const wb = XLSX.utils.book_new(); |
|
|
|
|
|
|
|
|
const ws = XLSX.utils.json_to_sheet(comparisonResults); |
|
|
|
|
|
|
|
|
XLSX.utils.book_append_sheet(wb, ws, "Matching Rows"); |
|
|
|
|
|
|
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); |
|
|
const fileName = `excel_matching_rows_${timestamp}.xlsx`; |
|
|
|
|
|
|
|
|
XLSX.writeFile(wb, fileName); |
|
|
|
|
|
|
|
|
statusIcon.className = 'w-3 h-3 rounded-full bg-green-500'; |
|
|
if (document.documentElement.lang === 'en') { |
|
|
statusText.textContent = 'Export complete!'; |
|
|
showToast('Results exported successfully!', 'success'); |
|
|
} else { |
|
|
statusText.textContent = '导出完成!'; |
|
|
showToast('结果导出成功!', 'success'); |
|
|
} |
|
|
} |
|
|
|
|
|
function copyResults() { |
|
|
if (comparisonResults.length === 0) { |
|
|
showToast( |
|
|
document.documentElement.lang === 'en' ? 'No results to copy' : '没有结果可复制', |
|
|
'error' |
|
|
); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const displayHeaders = []; |
|
|
headers.forEach(header => { |
|
|
displayHeaders.push(`${header} (File 1)`); |
|
|
displayHeaders.push(`${header} (File 2)`); |
|
|
}); |
|
|
|
|
|
let csv = displayHeaders.join(',') + '\n'; |
|
|
|
|
|
comparisonResults.forEach(row => { |
|
|
const values = displayHeaders.map(header => { |
|
|
const value = row[header] || ''; |
|
|
return `"${value.toString().replace(/"/g, '""')}"`; |
|
|
}); |
|
|
csv += values.join(',') + '\n'; |
|
|
}); |
|
|
|
|
|
// Copy to clipboard |
|
|
navigator.clipboard.writeText(csv).then(() => { |
|
|
statusIcon.className = 'w-3 h-3 rounded-full bg-green-500'; |
|
|
if (document.documentElement.lang === 'en') { |
|
|
statusText.textContent = 'Copied to clipboard!'; |
|
|
showToast('Results copied to clipboard!', 'success'); |
|
|
} else { |
|
|
statusText.textContent = '已复制到剪贴板!'; |
|
|
showToast('结果已复制到剪贴板!', 'success'); |
|
|
} |
|
|
}).catch(err => { |
|
|
statusIcon.className = 'w-3 h-3 rounded-full bg-red-500'; |
|
|
if (document.documentElement.lang === 'en') { |
|
|
statusText.textContent = 'Copy failed'; |
|
|
showToast('Failed to copy results', 'error'); |
|
|
} else { |
|
|
statusText.textContent = '复制失败'; |
|
|
showToast('复制结果失败', 'error'); |
|
|
} |
|
|
console.error('Failed to copy: ', err); |
|
|
}); |
|
|
} |
|
|
|
|
|
function showToast(message, type) { |
|
|
const toast = document.createElement('div'); |
|
|
toast.className = `fixed bottom-4 right-4 px-4 py-3 rounded-lg shadow-lg text-white flex items-center ${ |
|
|
type === 'success' ? 'bg-green-600' : 'bg-red-600' |
|
|
} animate-fade-in`; |
|
|
toast.innerHTML = ` |
|
|
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} mr-2"></i> |
|
|
${message} |
|
|
`; |
|
|
document.body.appendChild(toast); |
|
|
|
|
|
setTimeout(() => { |
|
|
toast.classList.add('opacity-0', 'transition-opacity', 'duration-300'); |
|
|
setTimeout(() => { |
|
|
toast.remove(); |
|
|
}, 300); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
function switchLanguage(lang) { |
|
|
document.documentElement.lang = lang; |
|
|
// Update dynamic text that might have changed |
|
|
if (lastUpdated) { |
|
|
const now = new Date(); |
|
|
if (lang === 'en') { |
|
|
lastUpdated.textContent = `Last updated: ${now.toLocaleTimeString()}`; |
|
|
} else { |
|
|
lastUpdated.textContent = `最后更新: ${now.toLocaleTimeString()}`; |
|
|
} |
|
|
} |
|
|
|
|
|
// Update file status text |
|
|
if (file1Input.files.length) { |
|
|
const fileName = file1Input.files[0].name; |
|
|
if (lang === 'en') { |
|
|
file1Status.innerHTML = `<span class="w-2 h-2 rounded-full bg-indigo-500 mr-2"></span>File 1: <span class="font-medium">${fileName}</span>`; |
|
|
} else { |
|
|
file1Status.innerHTML = `<span class="w-2 h-2 rounded-full bg-indigo-500 mr-2"></span>文件1: <span class="font-medium">${fileName}</span>`; |
|
|
} |
|
|
} |
|
|
|
|
|
if (file2Input.files.length) { |
|
|
const fileName = file2Input.files[0].name; |
|
|
if (lang === 'en') { |
|
|
file2Status.innerHTML = `<span class="w-2 h-2 rounded-full bg-purple-500 mr-2"></span>File 2: <span class="font-medium">${fileName}</span>`; |
|
|
} else { |
|
|
file2Status.innerHTML = `<span class="w-2 h-2 rounded-full bg-purple-500 mr-2"></span>文件2: <span class="font-medium">${fileName}</span>`; |
|
|
} |
|
|
} |
|
|
|
|
|
// Update compare button text |
|
|
if (compareBtn.disabled) { |
|
|
if (lang === 'en') { |
|
|
compareBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> Find Matching Rows'; |
|
|
statusText.textContent = 'Ready'; |
|
|
} else { |
|
|
compareBtn.innerHTML = '<i class="fas fa-exchange-alt mr-2"></i> 查找匹配行'; |
|
|
statusText.textContent = '准备就绪'; |
|
|
} |
|
|
} |
|
|
|
|
|
// Update key column select default option |
|
|
if (keyColumnSelect.options.length > 0) { |
|
|
keyColumnSelect.options[0].textContent = lang === 'en' ? 'All columns (default)' : '所有列(默认)'; |
|
|
} |
|
|
} |
|
|
|
|
|
// Initialize toggle switches |
|
|
document.querySelectorAll('.toggle-checkbox').forEach(checkbox => { |
|
|
checkbox.addEventListener('change', function() { |
|
|
const label = this.nextElementSibling; |
|
|
if (this.checked) { |
|
|
label.classList.remove('bg-gray-700'); |
|
|
label.classList.add('bg-indigo-600'); |
|
|
} else { |
|
|
label.classList.remove('bg-indigo-600'); |
|
|
label.classList.add('bg-gray-700'); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=chagtptmm/excelrow" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |