字符级内容差异对比工具

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>字符级差异对比工具</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }
        
        :root {
            --primary-color: #4361ee;
            --secondary-color: #3f37c9;
            --light-color: #f8f9fa;
            --dark-color: #212529;
            --delete-color: #f94144;
            --add-color: #4cc9f0;
            --modify-color: #f8961e;
            --border-color: #dee2e6;
            --shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
            --success-color: #2ecc71;
            --warning-color: #f39c12;
            --char-delete: #ff4757;
            --char-add: #2ed573;
            --char-modify: #ffa502;
        }
        
        body {
            background-color: #f5f7fb;
            color: var(--dark-color);
            line-height: 1.6;
        }
        
        .container {
            max-width: 1800px;
            margin: 0 auto;
            padding: 20px;
        }
        
        header {
            text-align: center;
            margin-bottom: 30px;
            padding-bottom: 20px;
            border-bottom: 1px solid var(--border-color);
        }
        
        h1 {
            color: var(--primary-color);
            margin-bottom: 10px;
            font-size: 2.5rem;
            font-weight: 700;
        }
        
        .subtitle {
            color: #6c757d;
            font-size: 1.1rem;
            max-width: 800px;
            margin: 0 auto;
        }
        
        .controls-panel {
            display: flex;
            flex-direction: column;
            gap: 20px;
            margin-bottom: 30px;
        }
        
        .mode-selector {
            display: flex;
            justify-content: center;
            gap: 10px;
            flex-wrap: wrap;
        }
        
        .mode-btn {
            padding: 12px 24px;
            background-color: white;
            border: 2px solid var(--border-color);
            border-radius: 8px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .mode-btn.active {
            background-color: var(--primary-color);
            color: white;
            border-color: var(--primary-color);
        }
        
        .mode-btn:hover:not(.active) {
            background-color: #f8f9fa;
            border-color: var(--primary-color);
        }
        
        .diff-level-selector {
            display: flex;
            justify-content: center;
            gap: 10px;
            margin: 15px 0;
        }
        
        .level-btn {
            padding: 10px 20px;
            background-color: white;
            border: 2px solid var(--border-color);
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
        }
        
        .level-btn.active {
            background-color: var(--primary-color);
            color: white;
            border-color: var(--primary-color);
        }
        
        .level-btn:hover:not(.active) {
            background-color: #f8f9fa;
            border-color: var(--primary-color);
        }
        
        .input-panel {
            display: flex;
            flex-direction: column;
            gap: 30px;
        }
        
        @media (min-width: 992px) {
            .input-panel {
                flex-direction: row;
            }
        }
        
        .input-section {
            flex: 1;
            display: flex;
            flex-direction: column;
        }
        
        .section-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 15px 20px;
            background-color: white;
            border-radius: 12px 12px 0 0;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
            border-bottom: 1px solid var(--border-color);
        }
        
        .section-title {
            font-size: 1.3rem;
            font-weight: 600;
            color: var(--primary-color);
        }
        
        .section-actions {
            display: flex;
            gap: 15px;
        }
        
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-weight: 600;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
            transition: all 0.2s;
        }
        
        .btn-success {
            background-color: var(--success-color);
            color: white;
        }
        
        .btn-success:hover {
            background-color: #27ae60;
            transform: translateY(-2px);
        }
        
        .btn-outline {
            background-color: transparent;
            color: var(--primary-color);
            border: 2px solid var(--primary-color);
        }
        
        .btn-outline:hover {
            background-color: rgba(67, 97, 238, 0.1);
            transform: translateY(-2px);
        }
        
        .input-actions {
            display: flex;
            gap: 10px;
            margin-top: 10px;
        }
        
        .input-action-btn {
            background: none;
            border: none;
            color: #6c757d;
            cursor: pointer;
            font-size: 1.1rem;
            transition: color 0.2s;
            padding: 5px;
            border-radius: 4px;
        }
        
        .input-action-btn:hover {
            color: var(--primary-color);
            background-color: #f8f9fa;
        }
        
        .textarea-container {
            flex: 1;
            position: relative;
            background-color: white;
            border-radius: 0 0 12px 12px;
            overflow: hidden;
            box-shadow: var(--shadow);
        }
        
        .input-textarea {
            width: 100%;
            height: 300px;
            padding: 20px;
            border: none;
            resize: vertical;
            font-size: 1.05rem;
            line-height: 1.8;
            font-family: 'Courier New', monospace;
            outline: none;
        }
        
        .textarea-info {
            position: absolute;
            bottom: 10px;
            right: 15px;
            color: #adb5bd;
            font-size: 0.9rem;
            background-color: rgba(255, 255, 255, 0.8);
            padding: 2px 8px;
            border-radius: 4px;
        }
        
        .stats-legend-panel {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            align-items: center;
            background: white;
            padding: 20px;
            border-radius: 12px;
            box-shadow: var(--shadow);
            margin-bottom: 30px;
            gap: 15px;
        }
        
        .stats {
            display: flex;
            gap: 20px;
            flex-wrap: wrap;
        }
        
        .stat-box {
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 12px 20px;
            border-radius: 8px;
            background-color: var(--light-color);
            min-width: 120px;
        }
        
        .stat-label {
            font-size: 0.9rem;
            color: #6c757d;
            margin-bottom: 5px;
        }
        
        .stat-value {
            font-size: 1.8rem;
            font-weight: 700;
        }
        
        .deletions .stat-value {
            color: var(--delete-color);
        }
        
        .additions .stat-value {
            color: var(--add-color);
        }
        
        .modifications .stat-value {
            color: var(--modify-color);
        }
        
        .legend {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
        }
        
        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .color-box {
            width: 20px;
            height: 20px;
            border-radius: 4px;
        }
        
        .color-delete {
            background-color: var(--delete-color);
        }
        
        .color-add {
            background-color: var(--add-color);
        }
        
        .color-modify {
            background-color: var(--modify-color);
        }
        
        .char-legend {
            display: flex;
            gap: 15px;
            margin-top: 10px;
            flex-wrap: wrap;
        }
        
        .char-legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 0.9rem;
        }
        
        .char-color-box {
            width: 16px;
            height: 16px;
            border-radius: 3px;
        }
        
        .char-delete {
            background-color: var(--char-delete);
        }
        
        .char-add {
            background-color: var(--char-add);
        }
        
        .char-modify {
            background-color: var(--char-modify);
        }
        
        .comparison-area {
            display: flex;
            flex-direction: column;
            gap: 30px;
        }
        
        @media (min-width: 992px) {
            .comparison-area {
                flex-direction: row;
            }
        }
        
        .comparison-section {
            flex: 1;
            display: flex;
            flex-direction: column;
        }
        
        .content-box {
            flex: 1;
            background-color: white;
            border-radius: 0 0 12px 12px;
            overflow: hidden;
            box-shadow: var(--shadow);
            display: flex;
            flex-direction: column;
        }
        
        .content-area {
            flex: 1;
            padding: 25px;
            overflow-y: auto;
            max-height: 500px;
            line-height: 1.8;
            font-size: 1.05rem;
        }
        
        .content-area.left {
            border-right: 1px solid #f0f0f0;
        }
        
        .line-number {
            display: inline-block;
            width: 40px;
            text-align: right;
            margin-right: 20px;
            color: #adb5bd;
            user-select: none;
            flex-shrink: 0;
        }
        
        .line-content {
            flex: 1;
        }
        
        .line {
            padding: 4px 8px;
            border-radius: 4px;
            margin-bottom: 2px;
            transition: background-color 0.2s;
            white-space: pre-wrap;
            word-break: break-word;
            display: flex;
        }
        
        .line:hover {
            background-color: #f8f9fa;
        }
        
        .deleted {
            background-color: rgba(249, 65, 68, 0.1);
        }
        
        .added {
            background-color: rgba(76, 201, 240, 0.1);
        }
        
        .modified {
            background-color: rgba(248, 150, 30, 0.1);
        }
        
        /* 优化的字符级别差异样式 - 更显眼 */
        .char-deleted {
            background-color: var(--char-delete);
            text-decoration: line-through;
            padding: 0 2px;
            border-radius: 3px;
            margin: 0 1px;
            font-weight: bold;
            color: white;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
        }
        
        .char-added {
            background-color: var(--char-add);
            padding: 0 2px;
            border-radius: 3px;
            margin: 0 1px;
            font-weight: bold;
            color: white;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
        }
        
        .char-modified {
            background-color: var(--char-modify);
            padding: 0 2px;
            border-radius: 3px;
            margin: 0 1px;
            font-weight: bold;
            color: white;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
        }
        
        .sync-toggle {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 10px 20px;
            background-color: white;
            border-radius: 12px;
            box-shadow: var(--shadow);
            margin: 20px auto;
            width: fit-content;
            cursor: pointer;
            transition: all 0.2s;
        }
        
        .sync-toggle:hover {
            background-color: #f8f9fa;
        }
        
        .sync-toggle.active {
            background-color: rgba(67, 97, 238, 0.1);
            color: var(--primary-color);
        }
        
        .toggle-switch {
            width: 50px;
            height: 24px;
            background-color: #ccc;
            border-radius: 12px;
            position: relative;
            transition: background-color 0.3s;
        }
        
        .toggle-switch::after {
            content: '';
            position: absolute;
            width: 20px;
            height: 20px;
            background-color: white;
            border-radius: 50%;
            top: 2px;
            left: 2px;
            transition: transform 0.3s;
        }
        
        .sync-toggle.active .toggle-switch {
            background-color: var(--primary-color);
        }
        
        .sync-toggle.active .toggle-switch::after {
            transform: translateX(26px);
        }
        
        .actions {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin-top: 30px;
            flex-wrap: wrap;
        }
        
        .btn-primary {
            background-color: var(--primary-color);
            color: white;
        }
        
        .btn-primary:hover {
            background-color: var(--secondary-color);
            transform: translateY(-2px);
        }
        
        .btn-warning {
            background-color: var(--warning-color);
            color: white;
        }
        
        .btn-warning:hover {
            background-color: #e67e22;
            transform: translateY(-2px);
        }
        
        footer {
            text-align: center;
            margin-top: 40px;
            padding-top: 20px;
            color: #6c757d;
            border-top: 1px solid var(--border-color);
            font-size: 0.9rem;
        }
        
        .notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 20px;
            border-radius: 8px;
            background-color: white;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            display: flex;
            align-items: center;
            gap: 10px;
            z-index: 1000;
            transform: translateX(150%);
            transition: transform 0.3s ease-in-out;
        }
        
        .notification.show {
            transform: translateX(0);
        }
        
        .notification.success {
            border-left: 4px solid var(--success-color);
        }
        
        .notification.warning {
            border-left: 4px solid var(--warning-color);
        }
        
        .notification.info {
            border-left: 4px solid var(--primary-color);
        }
        
        .notification i {
            font-size: 1.2rem;
        }
        
        .notification.success i {
            color: var(--success-color);
        }
        
        .notification.warning i {
            color: var(--warning-color);
        }
        
        .notification.info i {
            color: var(--primary-color);
        }
        
        .active-diff {
            box-shadow: 0 0 0 2px var(--primary-color);
            position: relative;
            z-index: 10;
        }
        
        .active-diff::before {
            content: '▶';
            color: var(--primary-color);
            position: absolute;
            left: -25px;
            top: 50%;
            transform: translateY(-50%);
            font-size: 1.2rem;
        }
        
        /* 新增差异行数导航表格样式 */
        .diff-nav-container {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-bottom: 20px;
        }
        
        .diff-nav-header {
            font-size: 1.1rem;
            font-weight: 600;
            color: var(--primary-color);
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .diff-nav-table {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            max-height: 120px;
            overflow-y: auto;
            padding: 10px;
            background-color: white;
            border-radius: 8px;
            box-shadow: var(--shadow);
        }
        
        .diff-nav-item {
            padding: 6px 12px;
            background-color: #f8f9fa;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            gap: 5px;
            font-size: 0.9rem;
        }
        
        .diff-nav-item:hover {
            background-color: #e9ecef;
            transform: translateY(-2px);
        }
        
        .diff-nav-item.active {
            background-color: var(--primary-color);
            color: white;
            font-weight: 600;
        }
        
        .diff-nav-item.deleted {
            background-color: rgba(249, 65, 68, 0.15);
            color: var(--delete-color);
        }
        
        .diff-nav-item.deleted.active {
            background-color: var(--delete-color);
            color: white;
        }
        
        .diff-nav-item.added {
            background-color: rgba(76, 201, 240, 0.15);
            color: var(--add-color);
        }
        
        .diff-nav-item.added.active {
            background-color: var(--add-color);
            color: white;
        }
        
        .diff-nav-item.modified {
            background-color: rgba(248, 150, 30, 0.15);
            color: var(--modify-color);
        }
        
        .diff-nav-item.modified.active {
            background-color: var(--modify-color);
            color: white;
        }
        
        .diff-type-icon {
            font-size: 0.8rem;
        }
        
        @media (max-width: 768px) {
            .stats-legend-panel {
                flex-direction: column;
                align-items: flex-start;
            }
            
            .legend, .char-legend {
                margin-top: 10px;
            }
            
            .content-area {
                max-height: 400px;
                padding: 15px;
            }
            
            h1 {
                font-size: 2rem;
            }
            
            .stat-box {
                min-width: 100px;
                padding: 10px 15px;
            }
            
            .mode-selector, .diff-level-selector {
                flex-direction: column;
                align-items: center;
            }
            
            .mode-btn, .level-btn {
                width: 100%;
                max-width: 300px;
                justify-content: center;
            }
            
            .section-actions {
                flex-direction: column;
                gap: 8px;
            }
            
            .section-actions .btn {
                padding: 8px 12px;
                font-size: 0.9rem;
            }
            
            .diff-nav-table {
                max-height: 100px;
            }
        }
        
        .empty-state {
            text-align: center;
            padding: 40px 20px;
            color: #6c757d;
        }
        
        .empty-state i {
            font-size: 3rem;
            margin-bottom: 15px;
            color: #adb5bd;
        }
        
        .loading {
            display: inline-block;
            width: 20px;
            height: 20px;
            border: 2px solid #f3f3f3;
            border-top: 2px solid var(--primary-color);
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1><i class="fas fa-code-compare"></i> 字符级差异对比工具</h1>
            <p class="subtitle">输入或粘贴需要对比的内容,系统将自动检测并高亮显示行级和字符级的差异。</p>
        </header>
        
        <div class="controls-panel">
            <div class="mode-selector">
                <button class="mode-btn active" id="edit-mode-btn">
                    <i class="fas fa-edit"></i> 编辑模式
                </button>
                <button class="mode-btn" id="view-mode-btn">
                    <i class="fas fa-eye"></i> 查看模式
                </button>
            </div>
            
            <div class="diff-level-selector">
                <button class="level-btn active" data-level="line">
                    行级对比
                </button>
                <button class="level-btn" data-level="char">
                    字符级对比
                </button>
                <button class="level-btn" data-level="both">
                    双重对比
                </button>
            </div>
        </div>
        
        <div class="input-panel" id="input-panel">
            <div class="input-section">
                <div class="section-header">
                    <h2 class="section-title">原文内容</h2>
                    <div class="section-actions">
                        <button class="btn btn-success" id="compare-btn">
                            <i class="fas fa-code-compare"></i> 对比内容
                        </button>
                        <button class="btn btn-outline" id="reset-view">
                            <i class="fas fa-redo"></i> 重置所有
                        </button>
                    </div>
                </div>
                <div class="textarea-container">
                    <textarea class="input-textarea" id="left-input" placeholder="在此输入或粘贴原文内容..."></textarea>
                    <div class="textarea-info">
                        <span id="left-count">0</span> 字
                    </div>
                </div>
                <div class="input-actions">
                    <button class="input-action-btn" id="clear-left" title="清空内容">
                        <i class="fas fa-trash-alt"></i> 清空
                    </button>
                    <button class="input-action-btn" id="paste-left" title="粘贴内容">
                        <i class="fas fa-paste"></i> 粘贴
                    </button>
                </div>
            </div>
            
            <div class="input-section">
                <div class="section-header">
                    <h2 class="section-title">修改后内容</h2>
                    <div style="min-width: 120px;"></div> <!-- 占位,保持对齐 -->
                </div>
                <div class="textarea-container">
                    <textarea class="input-textarea" id="right-input" placeholder="在此输入或粘贴修改后的内容..."></textarea>
                    <div class="textarea-info">
                        <span id="right-count">0</span> 字
                    </div>
                </div>
                <div class="input-actions">
                    <button class="input-action-btn" id="clear-right" title="清空内容">
                        <i class="fas fa-trash-alt"></i> 清空
                    </button>
                    <button class="input-action-btn" id="paste-right" title="粘贴内容">
                        <i class="fas fa-paste"></i> 粘贴
                    </button>
                </div>
            </div>
        </div>
        
        <div class="stats-legend-panel">
            <div class="stats">
                <div class="stat-box deletions">
                    <div class="stat-label">删除内容</div>
                    <div class="stat-value" id="delete-count">0</div>
                </div>
                <div class="stat-box additions">
                    <div class="stat-label">新增内容</div>
                    <div class="stat-value" id="add-count">0</div>
                </div>
                <div class="stat-box modifications">
                    <div class="stat-label">修改内容</div>
                    <div class="stat-value" id="modify-count">0</div>
                </div>
            </div>
            
            <div class="legend-section">
                <div class="legend">
                    <div class="legend-item">
                        <div class="color-box color-delete"></div>
                        <span>删除的行</span>
                    </div>
                    <div class="legend-item">
                        <div class="color-box color-add"></div>
                        <span>新增的行</span>
                    </div>
                    <div class="legend-item">
                        <div class="color-box color-modify"></div>
                        <span>修改的行</span>
                    </div>
                </div>
                <div class="char-legend" id="char-legend" style="display: none;">
                    <div class="char-legend-item">
                        <div class="char-color-box char-delete"></div>
                        <span>删除的字符</span>
                    </div>
                    <div class="char-legend-item">
                        <div class="char-color-box char-add"></div>
                        <span>新增的字符</span>
                    </div>
                    <div class="char-legend-item">
                        <div class="char-color-box char-modify"></div>
                        <span>修改的字符</span>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="comparison-area" id="comparison-area" style="display: none;">
            <div class="comparison-section">
                <div class="section-header">
                    <h2 class="section-title">原文内容</h2>
                    <span id="left-line-count">0 行</span>
                </div>
                
                <!-- 左侧差异行数导航表格 -->
                <div class="diff-nav-container" id="left-diff-nav" style="display: none;">
                    <div class="diff-nav-header">
                        <i class="fas fa-list-ol"></i>
                        <span>差异行号导航</span>
                    </div>
                    <div class="diff-nav-table" id="left-diff-table">
                        <!-- 差异行号将通过JavaScript动态生成 -->
                    </div>
                </div>
                
                <div class="content-box">
                    <div class="content-area left" id="left-content">
                        <div class="empty-state">
                            <i class="fas fa-code-compare"></i>
                            <p>点击"对比内容"按钮开始对比</p>
                        </div>
                    </div>
                </div>
            </div>
            
            <div class="comparison-section">
                <div class="section-header">
                    <h2 class="section-title">修改后内容</h2>
                    <span id="right-line-count">0 行</span>
                </div>
                
                <!-- 右侧差异行数导航表格 -->
                <div class="diff-nav-container" id="right-diff-nav" style="display: none;">
                    <div class="diff-nav-header">
                        <i class="fas fa-list-ol"></i>
                        <span>差异行号导航</span>
                    </div>
                    <div class="diff-nav-table" id="right-diff-table">
                        <!-- 差异行号将通过JavaScript动态生成 -->
                    </div>
                </div>
                
                <div class="content-box">
                    <div class="content-area" id="right-content">
                        <div class="empty-state">
                            <i class="fas fa-code-compare"></i>
                            <p>点击"对比内容"按钮开始对比</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="sync-toggle active" id="sync-toggle" style="display: none;">
            <div class="toggle-switch"></div>
            <span>同步滚动</span>
        </div>
        
        <div class="actions">
            <button class="btn btn-primary" id="copy-diff">
                <i class="fas fa-copy"></i> 复制差异报告
            </button>
            <button class="btn btn-warning" id="export-diff">
                <i class="fas fa-file-export"></i> 导出差异
            </button>
        </div>
        
        <footer>
            <p>字符级差异对比工具 &copy; 2023 | 支持行级和字符级双重差异对比,实时输入和智能检测</p>
        </footer>
    </div>
    
    <div class="notification" id="notification">
        <i class="fas fa-check-circle"></i>
        <span id="notification-text">操作成功</span>
    </div>

    <script>
        // 应用状态
        const appState = {
            diffMode: 'both', // 'line', 'char', 'both'
            isComparing: false,
            diffIndices: [],
            currentDiffIndex: 0,
            isSyncing: true,
            diffData: null, // 保存差异数据
            activeDiffItem: null // 当前激活的差异项
        };

        // 字符级差异算法
        function charLevelDiff(str1, str2) {
            // 如果字符串完全相同,直接返回
            if (str1 === str2) {
                return {
                    left: [{ type: 'same', text: str1 }],
                    right: [{ type: 'same', text: str2 }]
                };
            }

            // 使用动态规划算法计算最小编辑距离
            const m = str1.length;
            const n = str2.length;
            
            // 创建DP表
            const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
            
            // 初始化第一行和第一列
            for (let i = 0; i <= m; i++) {
                dp[i][0] = i;
            }
            for (let j = 0; j <= n; j++) {
                dp[0][j] = j;
            }
            
            // 填充DP表
            for (let i = 1; i <= m; i++) {
                for (let j = 1; j <= n; j++) {
                    if (str1[i - 1] === str2[j - 1]) {
                        dp[i][j] = dp[i - 1][j - 1];
                    } else {
                        dp[i][j] = Math.min(
                            dp[i - 1][j] + 1, // 删除
                            dp[i][j - 1] + 1, // 插入
                            dp[i - 1][j - 1] + 1 // 替换
                        );
                    }
                }
            }
            
            // 回溯构建差异结果
            let i = m, j = n;
            const leftResult = [];
            const rightResult = [];
            
            while (i > 0 || j > 0) {
                if (i > 0 && j > 0 && str1[i - 1] === str2[j - 1]) {
                    // 字符相同
                    leftResult.unshift({ type: 'same', char: str1[i - 1] });
                    rightResult.unshift({ type: 'same', char: str2[j - 1] });
                    i--;
                    j--;
                } else if (i > 0 && (j === 0 || dp[i][j] === dp[i - 1][j] + 1)) {
                    // 删除字符
                    leftResult.unshift({ type: 'delete', char: str1[i - 1] });
                    i--;
                } else if (j > 0 && (i === 0 || dp[i][j] === dp[i][j - 1] + 1)) {
                    // 插入字符
                    rightResult.unshift({ type: 'add', char: str2[j - 1] });
                    j--;
                } else {
                    // 替换字符
                    leftResult.unshift({ type: 'modify', char: str1[i - 1] });
                    rightResult.unshift({ type: 'modify', char: str2[j - 1] });
                    i--;
                    j--;
                }
            }
            
            // 将连续的相同类型字符合并为片段,提高渲染性能
            return {
                left: mergeCharSegments(leftResult),
                right: mergeCharSegments(rightResult)
            };
        }

        // 合并连续的相同类型字符
        function mergeCharSegments(chars) {
            if (chars.length === 0) return [];
            
            const segments = [];
            let currentSegment = {
                type: chars[0].type,
                text: chars[0].char
            };
            
            for (let i = 1; i < chars.length; i++) {
                if (chars[i].type === currentSegment.type) {
                    currentSegment.text += chars[i].char;
                } else {
                    segments.push(currentSegment);
                    currentSegment = {
                        type: chars[i].type,
                        text: chars[i].char
                    };
                }
            }
            
            segments.push(currentSegment);
            return segments;
        }

        // 行级差异检测算法
        function findLineDifferences(text1, text2) {
            const lines1 = text1.split('\n');
            const lines2 = text2.split('\n');
            
            // 创建差异数组
            const diff = [];
            let i = 0, j = 0;
            
            while (i < lines1.length || j < lines2.length) {
                if (i < lines1.length && j < lines2.length && lines1[i] === lines2[j]) {
                    // 行相同
                    diff.push({ 
                        type: 'same', 
                        left: lines1[i], 
                        right: lines2[j],
                        charDiff: null,
                        lineNumber: i + 1 // 记录行号(原文行号)
                    });
                    i++;
                    j++;
                } else if (j < lines2.length && (i >= lines1.length || lines1[i] !== lines2[j])) {
                    // 检查是否是修改(下一行是否匹配)
                    if (i + 1 < lines1.length && j + 1 < lines2.length && 
                        lines1[i + 1] === lines2[j + 1]) {
                        // 可能是修改
                        diff.push({ 
                            type: 'modified', 
                            left: lines1[i], 
                            right: lines2[j],
                            charDiff: null,
                            lineNumber: i + 1 // 记录行号(原文行号)
                        });
                        i++;
                        j++;
                    } else {
                        // 新增行
                        diff.push({ 
                            type: 'added', 
                            left: null, 
                            right: lines2[j],
                            charDiff: null,
                            lineNumber: j + 1 // 记录行号(修改后行号)
                        });
                        j++;
                    }
                } else if (i < lines1.length && (j >= lines2.length || lines1[i] !== lines2[j])) {
                    // 检查是否是修改
                    if (i + 1 < lines1.length && j + 1 < lines2.length && 
                        lines1[i + 1] === lines2[j + 1]) {
                        // 可能是修改
                        diff.push({ 
                            type: 'modified', 
                            left: lines1[i], 
                            right: lines2[j],
                            charDiff: null,
                            lineNumber: i + 1 // 记录行号(原文行号)
                        });
                        i++;
                        j++;
                    } else {
                        // 删除行
                        diff.push({ 
                            type: 'deleted', 
                            left: lines1[i], 
                            right: null,
                            charDiff: null,
                            lineNumber: i + 1 // 记录行号(原文行号)
                        });
                        i++;
                    }
                }
            }
            
            return diff;
        }

        // 增强的差异检测,包含字符级差异
        function findEnhancedDifferences(text1, text2, diffLevel = 'both') {
            const lineDiff = findLineDifferences(text1, text2);
            
            // 如果不需要字符级差异,直接返回
            if (diffLevel === 'line') {
                return lineDiff;
            }
            
            // 为修改的行计算字符级差异
            return lineDiff.map(item => {
                if (item.type === 'modified' && item.left && item.right) {
                    item.charDiff = charLevelDiff(item.left, item.right);
                }
                return item;
            });
        }

        // 渲染字符级差异内容
        function renderCharDiff(segments) {
            const fragment = document.createDocumentFragment();
            
            segments.forEach(segment => {
                const span = document.createElement('span');
                span.textContent = segment.text;
                
                switch(segment.type) {
                    case 'same':
                        // 不添加特殊类名
                        break;
                    case 'delete':
                        span.className = 'char-deleted';
                        break;
                    case 'add':
                        span.className = 'char-added';
                        break;
                    case 'modify':
                        span.className = 'char-modified';
                        break;
                }
                
                fragment.appendChild(span);
            });
            
            return fragment;
        }

        // 渲染内容
        function renderContent(diff) {
            const leftContent = document.getElementById('left-content');
            const rightContent = document.getElementById('right-content');
            
            leftContent.innerHTML = '';
            rightContent.innerHTML = '';
            
            if (diff.length === 0) {
                const emptyState = document.createElement('div');
                emptyState.className = 'empty-state';
                emptyState.innerHTML = '<i class="fas fa-exclamation-circle"></i><p>没有内容可以对比</p>';
                leftContent.appendChild(emptyState);
                rightContent.appendChild(emptyState.cloneNode(true));
                return { diffIndices: [], leftDiffRows: [], rightDiffRows: [] };
            }
            
            let deleteCount = 0;
            let addCount = 0;
            let modifyCount = 0;
            
            // 用于差异导航
            const diffIndices = [];
            // 左侧差异行号(删除和修改)
            const leftDiffRows = [];
            // 右侧差异行号(新增和修改)
            const rightDiffRows = [];
            
            diff.forEach((item, index) => {
                const leftLine = document.createElement('div');
                const rightLine = document.createElement('div');
                
                leftLine.className = 'line';
                rightLine.className = 'line';
                
                // 添加行号
                const leftLineNum = document.createElement('span');
                leftLineNum.className = 'line-number';
                leftLineNum.textContent = index + 1;
                
                const rightLineNum = document.createElement('span');
                rightLineNum.className = 'line-number';
                rightLineNum.textContent = index + 1;
                
                // 创建内容容器
                const leftContentDiv = document.createElement('div');
                leftContentDiv.className = 'line-content';
                
                const rightContentDiv = document.createElement('div');
                rightContentDiv.className = 'line-content';
                
                // 根据类型设置样式和内容
                switch(item.type) {
                    case 'same':
                        leftLine.appendChild(leftLineNum);
                        leftContentDiv.textContent = item.left || '';
                        leftLine.appendChild(leftContentDiv);
                        
                        rightLine.appendChild(rightLineNum);
                        rightContentDiv.textContent = item.right || '';
                        rightLine.appendChild(rightContentDiv);
                        break;
                        
                    case 'deleted':
                        leftLine.className += ' deleted';
                        leftLine.appendChild(leftLineNum);
                        leftContentDiv.textContent = item.left || '';
                        leftLine.appendChild(leftContentDiv);
                        
                        rightLine.appendChild(rightLineNum);
                        rightContentDiv.textContent = '';
                        rightLine.appendChild(rightContentDiv);
                        deleteCount++;
                        diffIndices.push({index, side: 'left', type: 'deleted'});
                        leftDiffRows.push({index, type: 'deleted', lineNumber: item.lineNumber || index + 1});
                        break;
                        
                    case 'added':
                        rightLine.className += ' added';
                        leftLine.appendChild(leftLineNum);
                        leftContentDiv.textContent = '';
                        leftLine.appendChild(leftContentDiv);
                        
                        rightLine.appendChild(rightLineNum);
                        rightContentDiv.textContent = item.right || '';
                        rightLine.appendChild(rightContentDiv);
                        addCount++;
                        diffIndices.push({index, side: 'right', type: 'added'});
                        rightDiffRows.push({index, type: 'added', lineNumber: item.lineNumber || index + 1});
                        break;
                        
                    case 'modified':
                        leftLine.className += ' modified';
                        rightLine.className += ' modified';
                        
                        leftLine.appendChild(leftLineNum);
                        rightLine.appendChild(rightLineNum);
                        
                        // 根据diffLevel决定渲染方式
                        if (appState.diffMode !== 'line' && item.charDiff) {
                            // 渲染字符级差异
                            leftContentDiv.appendChild(renderCharDiff(item.charDiff.left));
                            rightContentDiv.appendChild(renderCharDiff(item.charDiff.right));
                        } else {
                            // 渲染整行差异
                            leftContentDiv.textContent = item.left || '';
                            rightContentDiv.textContent = item.right || '';
                        }
                        
                        leftLine.appendChild(leftContentDiv);
                        rightLine.appendChild(rightContentDiv);
                        modifyCount++;
                        diffIndices.push({index, side: 'both', type: 'modified'});
                        leftDiffRows.push({index, type: 'modified', lineNumber: item.lineNumber || index + 1});
                        rightDiffRows.push({index, type: 'modified', lineNumber: item.lineNumber || index + 1});
                        break;
                }
                
                leftContent.appendChild(leftLine);
                rightContent.appendChild(rightLine);
            });
            
            // 更新统计
            document.getElementById('delete-count').textContent = deleteCount;
            document.getElementById('add-count').textContent = addCount;
            document.getElementById('modify-count').textContent = modifyCount;
            document.getElementById('left-line-count').textContent = diff.length + ' 行';
            document.getElementById('right-line-count').textContent = diff.length + ' 行';
            
            return { diffIndices, leftDiffRows, rightDiffRows };
        }

        // 创建差异行号导航表格
        function createDiffNavigation(diffRows, side) {
            const container = document.getElementById(`${side}-diff-nav`);
            const table = document.getElementById(`${side}-diff-table`);
            
            if (!diffRows || diffRows.length === 0) {
                container.style.display = 'none';
                return;
            }
            
            container.style.display = 'flex';
            table.innerHTML = '';
            
            diffRows.forEach(diffRow => {
                const item = document.createElement('div');
                item.className = `diff-nav-item ${diffRow.type}`;
                item.dataset.index = diffRow.index;
                item.dataset.side = side;
                
                // 添加类型图标
                let icon = '';
                if (diffRow.type === 'deleted') {
                    icon = '<i class="fas fa-minus diff-type-icon"></i>';
                } else if (diffRow.type === 'added') {
                    icon = '<i class="fas fa-plus diff-type-icon"></i>';
                } else if (diffRow.type === 'modified') {
                    icon = '<i class="fas fa-pen diff-type-icon"></i>';
                }
                
                item.innerHTML = `${icon}第 ${diffRow.lineNumber} 行`;
                
                // 点击事件
                item.addEventListener('click', () => {
                    navigateToDiff(diffRow.index, side);
                });
                
                table.appendChild(item);
            });
        }

        // 导航到指定差异行
        function navigateToDiff(index, side) {
            const leftLines = document.querySelectorAll('#left-content .line');
            const rightLines = document.querySelectorAll('#right-content .line');
            
            // 移除之前的高亮
            leftLines.forEach(line => line.classList.remove('active-diff'));
            rightLines.forEach(line => line.classList.remove('active-diff'));
            
            // 移除之前激活的导航项
            document.querySelectorAll('.diff-nav-item.active').forEach(item => {
                item.classList.remove('active');
            });
            
            // 添加新的高亮
            if (side === 'left' && leftLines[index]) {
                leftLines[index].classList.add('active-diff');
                leftLines[index].scrollIntoView({ behavior: 'smooth', block: 'center' });
                
                // 高亮对应的导航项
                const navItem = document.querySelector(`.diff-nav-item[data-index="${index}"][data-side="left"]`);
                if (navItem) {
                    navItem.classList.add('active');
                }
            }
            
            if (side === 'right' && rightLines[index]) {
                rightLines[index].classList.add('active-diff');
                rightLines[index].scrollIntoView({ behavior: 'smooth', block: 'center' });
                
                // 高亮对应的导航项
                const navItem = document.querySelector(`.diff-nav-item[data-index="${index}"][data-side="right"]`);
                if (navItem) {
                    navItem.classList.add('active');
                }
            }
            
            // 如果是修改的行,同时高亮两侧
            const leftNavItem = document.querySelector(`.diff-nav-item[data-index="${index}"][data-side="left"]`);
            const rightNavItem = document.querySelector(`.diff-nav-item[data-index="${index}"][data-side="right"]`);
            
            if (leftNavItem && rightNavItem) {
                // 说明是修改的行,两个导航项同时高亮
                leftNavItem.classList.add('active');
                rightNavItem.classList.add('active');
                
                // 同时滚动两侧到该行
                if (leftLines[index] && rightLines[index]) {
                    leftLines[index].scrollIntoView({ behavior: 'smooth', block: 'center' });
                }
            }
        }

        // 同步滚动功能
        function setupSyncScroll() {
            const leftContent = document.getElementById('left-content');
            const rightContent = document.getElementById('right-content');
            const syncToggle = document.getElementById('sync-toggle');
            let isSyncing = false;
            
            function syncScroll(source, target) {
                if (!appState.isSyncing || isSyncing) return;
                isSyncing = true;
                
                const percent = source.scrollTop / (source.scrollHeight - source.clientHeight);
                target.scrollTop = percent * (target.scrollHeight - target.clientHeight);
                
                setTimeout(() => {
                    isSyncing = false;
                }, 50);
            }
            
            leftContent.addEventListener('scroll', () => {
                syncScroll(leftContent, rightContent);
            });
            
            rightContent.addEventListener('scroll', () => {
                syncScroll(rightContent, leftContent);
            });
            
            // 切换同步滚动
            syncToggle.addEventListener('click', () => {
                syncToggle.classList.toggle('active');
                appState.isSyncing = syncToggle.classList.contains('active');
            });
        }

        // 显示通知
        function showNotification(message, type = 'success') {
            const notification = document.getElementById('notification');
            const textElement = document.getElementById('notification-text');
            
            textElement.textContent = message;
            notification.className = `notification ${type}`;
            notification.classList.add('show');
            
            setTimeout(() => {
                notification.classList.remove('show');
            }, 3000);
        }

        // 更新字数统计
        function updateWordCount() {
            const leftInput = document.getElementById('left-input');
            const rightInput = document.getElementById('right-input');
            const leftCount = document.getElementById('left-count');
            const rightCount = document.getElementById('right-count');
            
            leftCount.textContent = leftInput.value.length;
            rightCount.textContent = rightInput.value.length;
        }

        // 对比内容
        function compareContent() {
            if (appState.isComparing) return;
            
            const leftInput = document.getElementById('left-input');
            const rightInput = document.getElementById('right-input');
            
            if (!leftInput.value.trim() && !rightInput.value.trim()) {
                showNotification('请输入要对比的内容', 'warning');
                return;
            }
            
            appState.isComparing = true;
            const compareBtn = document.getElementById('compare-btn');
            const originalText = compareBtn.innerHTML;
            compareBtn.innerHTML = '<div class="loading"></div> 对比中...';
            compareBtn.disabled = true;
            
            // 模拟处理时间,让用户看到加载状态
            setTimeout(() => {
                // 计算差异
                appState.diffData = findEnhancedDifferences(leftInput.value, rightInput.value, appState.diffMode);
                const { diffIndices, leftDiffRows, rightDiffRows } = renderContent(appState.diffData);
                
                // 显示对比区域
                document.getElementById('comparison-area').style.display = 'flex';
                document.getElementById('sync-toggle').style.display = 'flex';
                
                // 创建差异导航表格
                createDiffNavigation(leftDiffRows, 'left');
                createDiffNavigation(rightDiffRows, 'right');
                
                // 更新字符级差异图例显示
                const charLegend = document.getElementById('char-legend');
                if (appState.diffMode !== 'line' && diffIndices.length > 0) {
                    charLegend.style.display = 'flex';
                } else {
                    charLegend.style.display = 'none';
                }
                
                // 恢复按钮状态
                compareBtn.innerHTML = originalText;
                compareBtn.disabled = false;
                appState.isComparing = false;
                
                // 显示结果通知
                const totalChanges = diffIndices.length;
                if (totalChanges > 0) {
                    showNotification(`对比完成,发现 ${totalChanges} 处差异`, 'success');
                    
                    // 如果有差异,自动定位到第一个差异
                    if (leftDiffRows.length > 0) {
                        navigateToDiff(leftDiffRows[0].index, 'left');
                    } else if (rightDiffRows.length > 0) {
                        navigateToDiff(rightDiffRows[0].index, 'right');
                    }
                } else {
                    showNotification('内容完全相同,没有发现差异', 'info');
                }
            }, 300);
        }

        // 复制差异报告
        function setupCopyDiff() {
            const copyBtn = document.getElementById('copy-diff');
            
            copyBtn.addEventListener('click', () => {
                const deleteCount = document.getElementById('delete-count').textContent;
                const addCount = document.getElementById('add-count').textContent;
                const modifyCount = document.getElementById('modify-count').textContent;
                
                const diffReport = `差异对比报告:
删除内容:${deleteCount} 处
新增内容:${addCount} 处
修改内容:${modifyCount} 处
对比级别:${appState.diffMode === 'line' ? '行级' : appState.diffMode === 'char' ? '字符级' : '双重'}

生成时间:${new Date().toLocaleString()}`;
                
                navigator.clipboard.writeText(diffReport).then(() => {
                    showNotification('差异报告已复制到剪贴板', 'success');
                }).catch(err => {
                    showNotification('复制失败,请手动复制', 'warning');
                });
            });
        }

        // 重置视图
        function resetAll() {
            // 重置输入框
            document.getElementById('left-input').value = '';
            document.getElementById('right-input').value = '';
            
            // 重置对比区域
            document.getElementById('comparison-area').style.display = 'none';
            document.getElementById('sync-toggle').style.display = 'none';
            
            // 重置差异导航表格
            document.getElementById('left-diff-nav').style.display = 'none';
            document.getElementById('right-diff-nav').style.display = 'none';
            
            // 重置统计
            document.getElementById('delete-count').textContent = '0';
            document.getElementById('add-count').textContent = '0';
            document.getElementById('modify-count').textContent = '0';
            
            // 重置字数
            updateWordCount();
            
            // 重置字符图例
            document.getElementById('char-legend').style.display = 'none';
            
            // 重置模式选择
            document.querySelectorAll('.level-btn').forEach(btn => {
                if (btn.dataset.level === 'both') {
                    btn.classList.add('active');
                } else {
                    btn.classList.remove('active');
                }
            });
            
            // 重置应用状态
            appState.diffMode = 'both';
            appState.diffIndices = [];
            appState.currentDiffIndex = 0;
            appState.diffData = null;
            appState.activeDiffItem = null;
            
            showNotification('所有内容已重置', 'info');
        }

        // 导出差异
        function setupExportDiff() {
            const exportBtn = document.getElementById('export-diff');
            
            exportBtn.addEventListener('click', () => {
                const leftInput = document.getElementById('left-input').value;
                const rightInput = document.getElementById('right-input').value;
                
                if (!leftInput && !rightInput) {
                    showNotification('没有内容可以导出', 'warning');
                    return;
                }
                
                // 使用已计算的差异数据或重新计算
                const diff = appState.diffData || findEnhancedDifferences(leftInput, rightInput, appState.diffMode);
                let exportText = "=== 字符级差异对比报告 ===\n\n";
                
                diff.forEach((item, index) => {
                    exportText += `行 ${index + 1}: `;
                    
                    switch(item.type) {
                        case 'same':
                            exportText += `[相同] ${item.left}\n`;
                            break;
                        case 'deleted':
                            exportText += `[删除] ${item.left}\n`;
                            break;
                        case 'added':
                            exportText += `[新增] ${item.right}\n`;
                            break;
                        case 'modified':
                            exportText += `[修改] 原文: ${item.left}\n`;
                            exportText += `       修改后: ${item.right}\n`;
                            break;
                    }
                });
                
                exportText += `\n=== 统计 ===\n`;
                exportText += `删除内容: ${document.getElementById('delete-count').textContent} 处\n`;
                exportText += `新增内容: ${document.getElementById('add-count').textContent} 处\n`;
                exportText += `修改内容: ${document.getElementById('modify-count').textContent} 处\n`;
                exportText += `对比级别: ${appState.diffMode === 'line' ? '行级' : appState.diffMode === 'char' ? '字符级' : '双重'}\n`;
                exportText += `生成时间: ${new Date().toLocaleString()}\n`;
                
                // 创建下载链接
                const blob = new Blob([exportText], { type: 'text/plain' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `差异报告_${new Date().toISOString().slice(0, 10)}.txt`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
                
                showNotification('差异报告已导出', 'success');
            });
        }

        // 初始化输入框事件
        function setupInputEvents() {
            const leftInput = document.getElementById('left-input');
            const rightInput = document.getElementById('right-input');
            
            // 字数统计
            leftInput.addEventListener('input', updateWordCount);
            rightInput.addEventListener('input', updateWordCount);
            
            // 粘贴功能
            document.getElementById('paste-left').addEventListener('click', async () => {
                try {
                    const text = await navigator.clipboard.readText();
                    leftInput.value = text;
                    updateWordCount();
                    showNotification('内容已粘贴到原文', 'success');
                } catch (err) {
                    showNotification('粘贴失败,请使用Ctrl+V', 'warning');
                }
            });
            
            document.getElementById('paste-right').addEventListener('click', async () => {
                try {
                    const text = await navigator.clipboard.readText();
                    rightInput.value = text;
                    updateWordCount();
                    showNotification('内容已粘贴到修改后', 'success');
                } catch (err) {
                    showNotification('粘贴失败,请使用Ctrl+V', 'warning');
                }
            });
            
            // 清空功能
            document.getElementById('clear-left').addEventListener('click', () => {
                leftInput.value = '';
                updateWordCount();
                showNotification('原文内容已清空', 'info');
            });
            
            document.getElementById('clear-right').addEventListener('click', () => {
                rightInput.value = '';
                updateWordCount();
                showNotification('修改后内容已清空', 'info');
            });
        }

        // 初始化模式切换
        function setupModeToggle() {
            const editModeBtn = document.getElementById('edit-mode-btn');
            const viewModeBtn = document.getElementById('view-mode-btn');
            
            editModeBtn.addEventListener('click', () => {
                editModeBtn.classList.add('active');
                viewModeBtn.classList.remove('active');
                document.getElementById('input-panel').style.display = 'flex';
                document.getElementById('comparison-area').style.display = 'none';
                document.getElementById('sync-toggle').style.display = 'none';
            });
            
            viewModeBtn.addEventListener('click', () => {
                if (!appState.diffData || appState.diffData.length === 0) {
                    showNotification('请先对比内容再切换到查看模式', 'warning');
                    return;
                }
                
                viewModeBtn.classList.add('active');
                editModeBtn.classList.remove('active');
                document.getElementById('input-panel').style.display = 'none';
                document.getElementById('comparison-area').style.display = 'flex';
                document.getElementById('sync-toggle').style.display = 'flex';
            });
        }

        // 初始化对比级别选择
        function setupDiffLevelSelector() {
            document.querySelectorAll('.level-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    // 更新按钮状态
                    document.querySelectorAll('.level-btn').forEach(b => b.classList.remove('active'));
                    btn.classList.add('active');
                    
                    // 更新对比级别
                    appState.diffMode = btn.dataset.level;
                    
                    // 更新字符图例显示
                    const charLegend = document.getElementById('char-legend');
                    if (appState.diffMode !== 'line') {
                        charLegend.style.display = 'flex';
                    } else {
                        charLegend.style.display = 'none';
                    }
                    
                    // 如果已有对比内容,重新对比
                    const leftInput = document.getElementById('left-input');
                    const rightInput = document.getElementById('right-input');
                    
                    if ((leftInput.value.trim() || rightInput.value.trim()) && appState.diffData) {
                        // 重新渲染内容
                        const { diffIndices, leftDiffRows, rightDiffRows } = renderContent(appState.diffData);
                        
                        // 重新创建差异导航表格
                        createDiffNavigation(leftDiffRows, 'left');
                        createDiffNavigation(rightDiffRows, 'right');
                        
                        showNotification(`已切换到${btn.textContent}模式`, 'info');
                    }
                });
            });
        }

        // 初始化应用
        function initApp() {
            // 初始字数统计
            updateWordCount();
            
            // 设置输入框事件
            setupInputEvents();
            
            // 设置模式切换
            setupModeToggle();
            
            // 设置对比级别选择
            setupDiffLevelSelector();
            
            // 设置同步滚动
            setupSyncScroll();
            
            // 设置功能按钮
            setupCopyDiff();
            setupExportDiff();
            
            // 对比按钮
            document.getElementById('compare-btn').addEventListener('click', compareContent);
            
            // 重置按钮
            document.getElementById('reset-view').addEventListener('click', resetAll);
            
            // 初始提示
            showNotification('欢迎使用字符级差异对比工具!输入内容后点击"对比内容"按钮开始对比。', 'info');
        }

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', initApp);
    </script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值