站点图标 龙鲲博客

在线文本比对工具(附源码)

因为经常使用在线文本对比,但不太放心使用公共的在线文本比对工具,于是使用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">&copy; 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/

参考网站:https://www.jyshare.com/front-end/8006/

退出移动版