本文于 2025年7月25日 6:36 更新,注意查看最新内容
======2025.07.25日更新======
1、上传功能修复:原代码中const editor = doc.editor(side); 改成 const editor = doc.cm(side); (感谢友链@倦意博客指正)
2、部分样式调整:由于原代码内字体大小及颜色不够明显,线上部署版本进行了如下调整:
/* 新增右侧编辑器样式 */
.mergely-editor .mergely.ch.ina.rhs {
color: #FFA500 !important;
text-decoration: underline;
}
.CodeMirror {
font-size: 1.125rem; /* 默认字体大小 */
}======2025.07.14日原文======
因为经常使用在线文本对比,但不太放心使用公共的在线文本比对工具,于是使用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 | 3 条评论
上传文件里面有问题哈 要把const editor = doc.editor(side); 改成 const editor = doc.cm(side); 否则会报错
@倦意
测试确实有问题,感谢指正。原来有个清空功能,一直有问题,然后图省事,于是发的时候用了原来的版本,没想到上传功能有问题
不错 偷了