本文于 2025年7月14日 4:11 更新,注意查看最新内容
因为经常使用在线文本对比,但不太放心使用公共的在线文本比对工具,于是使用AI编写了相关的功能,代码如下(若要实现离线使用,将静态资源文件全部替换为本地文件即可):
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>在线文本比对工具</title> <meta name="description" content="纯前端在线文本比对工具,无需网络即可使用。支持文本差异高亮显示、文件上传等功能。"> <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdn.jsdelivr.net/npm/codemirror@5.65.2/lib/codemirror.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/codemirror@5.65.2/theme/material.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/mergely@5.3.6/lib/mergely.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/codemirror@5.65.2/lib/codemirror.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/codemirror@5.65.2/addon/search/searchcursor.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/mergely@5.3.6/lib/mergely.min.js"></script> <script> tailwind.config = { theme: { extend: { colors: { primary: '#4B5168', secondary: '#8D426C', tertiary: '#5199D3', accent: '#22C994', warning: '#F0C612', danger: '#D2465D', info: '#FFAB4D', }, fontFamily: { inter: ['Inter', 'system-ui', 'sans-serif'], }, } } } </script> <style type="text/tailwindcss"> @layer utilities { .content-auto { content-visibility: auto; } .text-shadow { text-shadow: 0 2px 4px rgba(0,0,0,0.1); } .bg-gradient-primary { background: linear-gradient(90deg, #8D426C 16%, #5199D3 16%, #5199D3 32%, #22C994 32%, #22C994 48%, #F0C612 48%, #F0C612 64%, #FFAB4D 64%, #FFAB4D 82%, #D2465D 82%); } .border-gradient { border-image: linear-gradient(90deg, #8D426C 16%, #5199D3 16%, #5199D3 32%, #22C994 32%, #22C994 48%, #F0C612 48%, #F0C612 64%, #FFAB4D 64%, #FFAB4D 82%, #D2465D 82%) 1; } } </style> </head> <body class="font-inter bg-gray-50 min-h-screen flex flex-col"> <!-- 顶部导航栏 --> <header class="bg-primary text-white shadow-md sticky top-0 z-50"> <div class="container mx-auto px-4 py-3 flex items-center justify-between"> <div class="flex items-center"> <i class="fa fa-file-code-o text-2xl mr-2 text-accent"></i> <h1 class="text-xl font-bold">文本比对工具</h1> </div> <button class="md:hidden text-white focus:outline-none" id="mobile-menu-button"> <i class="fa fa-bars text-xl"></i> </button> </div> <!-- 底部渐变线 --> <div class="h-1 bg-gradient-primary"></div> </header> <!-- 移动端菜单 (默认隐藏) --> <div class="md:hidden hidden bg-primary text-white shadow-lg absolute w-full z-40" id="mobile-menu"> <!-- 空内容,移除帮助和关于链接 --> </div> <!-- 主内容区 --> <main class="flex-grow container mx-auto px-4 py-8"> <div class="max-w-7xl mx-auto"> <!-- 工具标题和操作区 --> <div class="mb-6 flex flex-col md:flex-row md:items-center md:justify-between"> <div class="mb-4 md:mb-0"> <h2 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-gray-800 flex items-center"> <i class="fa fa-file-signature text-accent mr-3"></i> 在线文本比对工具 </h2> <p class="text-gray-600 mt-1">比较两个文本的差异,高亮显示修改、添加和删除的部分</p> </div> <div class="flex flex-wrap gap-2"> <button id="uploadFile" class="px-4 py-2 bg-warning hover:bg-warning/90 text-gray-800 rounded-lg shadow transition-all duration-200 flex items-center"> <i class="fa fa-cloud-upload-alt mr-2"></i>上传文件 </button> </div> </div> <!-- 文件上传区域 (默认隐藏) --> <div id="fileArea" class="mb-6 bg-gray-100 p-4 rounded-lg shadow-sm hidden transition-all duration-300"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label for="lhsFile" class="block text-sm font-medium text-gray-700 mb-1">左侧文件</label> <input type="file" id="lhsFile" class="w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-accent file:text-white hover:file:bg-accent/90"> </div> <div> <label for="rhsFile" class="block text-sm font-medium text-gray-700 mb-1">右侧文件</label> <input type="file" id="rhsFile" class="w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-tertiary file:text-white hover:file:bg-tertiary/90"> </div> </div> </div> <!-- 文本比对区域 --> <div class="bg-white rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl"> <div id="compare1" class="w-full h-[60vh] min-h-[400px]"></div> </div> <!-- 功能说明卡片 --> <div class="mt-8 bg-white rounded-xl shadow-lg p-6 transition-all duration-300 hover:shadow-xl"> <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center"> <i class="fa fa-info-circle text-tertiary mr-2"></i>使用说明 </h3> <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="flex items-start"> <div class="flex-shrink-0 bg-tertiary/10 p-3 rounded-full"> <i class="fa fa-edit text-tertiary text-xl"></i> </div> <div class="ml-4"> <h4 class="text-lg font-medium text-gray-800">直接输入文本</h4> <p class="mt-1 text-gray-600">在左右两个编辑器中直接输入或粘贴要比较的文本,系统会自动高亮显示差异。</p> </div> </div> <div class="flex items-start"> <div class="flex-shrink-0 bg-accent/10 p-3 rounded-full"> <i class="fa fa-upload text-accent text-xl"></i> </div> <div class="ml-4"> <h4 class="text-lg font-medium text-gray-800">上传文件</h4> <p class="mt-1 text-gray-600">点击"上传文件"按钮,选择要比较的两个文本文件(支持TXT、HTML、JSON等纯文本格式)。</p> </div> </div> <div class="flex items-start"> <div class="flex-shrink-0 bg-warning/10 p-3 rounded-full"> <i class="fa fa-search text-warning text-xl"></i> </div> <div class="ml-4"> <h4 class="text-lg font-medium text-gray-800">查看差异</h4> <p class="mt-1 text-gray-600">绿色标记表示新增内容,红色标记表示删除内容,黄色标记表示修改内容。</p> </div> </div> <div class="flex items-start"> <div class="flex-shrink-0 bg-info/10 p-3 rounded-full"> <i class="fa fa-mobile text-info text-xl"></i> </div> <div class="ml-4"> <h4 class="text-lg font-medium text-gray-800">响应式设计</h4> <p class="mt-1 text-gray-600">在任何设备上都能获得良好的使用体验,支持从手机到桌面的各种屏幕尺寸。</p> </div> </div> </div> </div> </div> </main> <!-- 页脚 --> <footer class="bg-primary text-white mt-12"> <!-- 顶部渐变线 --> <div class="h-1 bg-gradient-primary"></div> <div class="container mx-auto px-4 py-6"> <div class="flex justify-center items-center"> <p class="text-sm text-gray-300">© 2025 文本比对工具</p> </div> </div> </footer> <!-- 模态框 (用于提示信息) --> <div id="alertModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> <div class="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 transform transition-all"> <div class="text-center"> <div id="modalIcon" class="mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4"> <i class="fa fa-check text-green-500 text-2xl"></i> </div> <h3 id="modalTitle" class="text-lg font-medium text-gray-900 mb-2">操作成功</h3> <p id="modalMessage" class="text-gray-500 mb-6">您的操作已成功完成。</p> <button id="modalClose" class="px-4 py-2 bg-primary hover:bg-primary/90 text-white rounded-lg shadow transition-all duration-200"> 确定 </button> </div> </div> </div> <script> // 全局变量存储Mergely实例 let doc; document.addEventListener('DOMContentLoaded', function() { // 初始化Mergely实例 initMergely(); // 移动端菜单切换 document.getElementById('mobile-menu-button').addEventListener('click', function() { const mobileMenu = document.getElementById('mobile-menu'); mobileMenu.classList.toggle('hidden'); }); // 上传文件区域显示/隐藏 document.getElementById('uploadFile').addEventListener('click', function() { const fileArea = document.getElementById('fileArea'); fileArea.classList.toggle('hidden'); // 添加动画效果 if (!fileArea.classList.contains('hidden')) { fileArea.style.maxHeight = '0'; setTimeout(() => { fileArea.style.maxHeight = '200px'; }, 10); } else { fileArea.style.maxHeight = '200px'; setTimeout(() => { fileArea.style.maxHeight = '0'; }, 10); } }); // 处理文件上传 document.getElementById('lhsFile').addEventListener('change', function() { handleFileInput(this, 'lhs'); }); document.getElementById('rhsFile').addEventListener('change', function() { handleFileInput(this, 'rhs'); }); // 窗口大小改变时调整Mergely大小 window.addEventListener('resize', function() { if (doc) { doc.resize(); } }); }); // 初始化Mergely实例 function initMergely() { doc = new Mergely('#compare1', { width: 'auto', license: 'lgpl-separate-notice', cmsettings: { readOnly: false, lineWrapping: true, theme: 'material', lineNumbers: true }, lhs: setValue => setValue('欢迎使用文本比对工具\n请在此处输入或粘贴要比较的第一个文本'), rhs: setValue => setValue('Welcome to the text comparison tool\nPlease enter or paste the second text to compare here') }); // 同时保存到window对象以保持向后兼容性 window.doc = doc; } // 处理文件输入 function handleFileInput(input, side) { const file = input.files[0]; if (!file) return; // 检查文件大小 (限制为30MB) if (file.size > 30 * 1024 * 1024) { showAlert('上传内容不能大于 30 MB!', 'error'); return; } const reader = new FileReader(); reader.onload = e => { if (doc) { // 使用editor方法获取CodeMirror实例并设置值 const editor = doc.editor(side); if (editor) { editor.setValue(e.target.result); showAlert(`已成功加载文件: ${file.name}`, 'success'); } } }; reader.onerror = () => showAlert('读取文件时发生错误', 'error'); reader.readAsText(file, "UTF-8"); } // 显示提示框 function showAlert(message, type = 'success') { const modal = document.getElementById('alertModal'); const title = document.getElementById('modalTitle'); const msg = document.getElementById('modalMessage'); const icon = document.getElementById('modalIcon'); // 设置提示类型 if (type === 'success') { title.textContent = '操作成功'; icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4'; icon.innerHTML = '<i class="fa fa-check text-green-500 text-2xl"></i>'; } else if (type === 'error') { title.textContent = '操作失败'; icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-red-100 mb-4'; icon.innerHTML = '<i class="fa fa-times text-red-500 text-2xl"></i>'; } else if (type === 'warning') { title.textContent = '警告'; icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-yellow-100 mb-4'; icon.innerHTML = '<i class="fa fa-exclamation-triangle text-yellow-500 text-2xl"></i>'; } else if (type === 'info') { title.textContent = '信息'; icon.className = 'mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-blue-100 mb-4'; icon.innerHTML = '<i class="fa fa-info-circle text-blue-500 text-2xl"></i>'; } // 设置提示消息 msg.textContent = message; // 显示模态框 modal.classList.remove('hidden'); modal.classList.add('flex'); // 添加动画效果 const modalContent = modal.querySelector('div'); modalContent.classList.add('scale-95', 'opacity-0'); setTimeout(() => { modalContent.classList.remove('scale-95', 'opacity-0'); modalContent.classList.add('scale-100', 'opacity-100'); }, 10); } // 关闭提示框 document.getElementById('modalClose').addEventListener('click', function() { const modal = document.getElementById('alertModal'); const modalContent = modal.querySelector('div'); // 添加关闭动画 modalContent.classList.remove('scale-100', 'opacity-100'); modalContent.classList.add('scale-95', 'opacity-0'); setTimeout(() => { modal.classList.add('hidden'); modal.classList.remove('flex'); }, 300); }); // 点击模态框背景关闭 document.getElementById('alertModal').addEventListener('click', function(e) { if (e.target === this) { document.getElementById('modalClose').click(); } }); </script> </body> </html>
目前代码运行良好,待实现一键清空文本框内容。
在线预览地址:https://lab.lklog.cn/duibis/
Comments | NOTHING