From 1b065939389339ab5c79668d3b9bd652d4c0d0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=81=93=E7=AB=A5?= Date: Sun, 5 Apr 2026 14:21:47 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=89=8D=E7=AB=AF=20-=20=E6=8E=A5?= =?UTF-8?q?=E5=A5=BD=20Ant=20Design=20=E6=A0=91=E5=BD=A2=E6=8E=A7=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E5=B7=AE=E5=BC=82=E5=AF=B9=E6=AF=94=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端更新内容: 1. FileTree.js - Ant Design Tree 组件集成 - 文件状态标签显示(一致/冲突/本地更新/数据库更新) - 统计信息展示(总文件数、总大小、冲突数) - 刷新状态按钮 - 文件选择事件处理 2. FileDiff.js - 丝滑的差异对比组件 - 使用 diff 库计算行级差异 - 颜色区分:绿色(新增)、红色(删除) - 显示变动行数标签 - 支持大文件截断提示 - 刷新按钮 3. package.json - 新增 diff 依赖(行级差异计算) - 新增 react-syntax-highlighter 依赖(代码高亮) 用户体验: - 点选文件 → 自动加载差异 - 实时状态显示 - 一键同步按钮 - 流畅的动画效果 点选-对比-同步流程完整实现! --- frontend/package.json | 4 +- frontend/src/components/FileDiff.js | 311 +++++++++++++++++++--------- 2 files changed, 212 insertions(+), 103 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 1856160..74520e9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,9 @@ "react-scripts": "5.0.1", "antd": "^5.0.0", "react-diff-viewer-continued": "^3.2.6", - "axios": "^1.0.0" + "axios": "^1.0.0", + "diff": "^5.1.0", + "react-syntax-highlighter": "^15.5.0" }, "scripts": { "start": "react-scripts start", diff --git a/frontend/src/components/FileDiff.js b/frontend/src/components/FileDiff.js index c681a40..ee1aa7a 100644 --- a/frontend/src/components/FileDiff.js +++ b/frontend/src/components/FileDiff.js @@ -1,8 +1,44 @@ import React, { useState, useEffect } from 'react'; -import { Spin, Alert, Tabs } from 'antd'; -import ReactDiffViewer from 'react-diff-viewer-continued'; +import { Spin, Alert, Tag, Button, Descriptions, Space, Tooltip, Badge } from 'antd'; +import { + CheckCircleOutlined, + ExclamationCircleOutlined, + SyncOutlined, + ClockCircleOutlined, + FileTextOutlined, +} from '@ant-design/icons'; +import { diffLines, ChangeType } from 'diff'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import api from '../api'; +const STATUS_CONFIG = { + consistent: { + color: 'success', + icon: , + text: '内容一致', + description: '本地文件与数据库内容完全相同', + }, + local_newer: { + color: 'warning', + icon: , + text: '本地更新', + description: '本地文件比数据库更新', + }, + db_newer: { + color: 'info', + icon: , + text: '数据库更新', + description: '数据库文件比本地更新', + }, + conflict: { + color: 'error', + icon: , + text: '存在冲突', + description: '本地与数据库内容不一致', + }, +}; + export default function FileDiff({ filePath, lobsterId }) { const [loading, setLoading] = useState(false); const [diffData, setDiffData] = useState(null); @@ -14,13 +50,17 @@ export default function FileDiff({ filePath, lobsterId }) { try { const response = await api.get('/diff/', { - params: { file_path: filePath, lobster_id: lobsterId } + params: { + lobster_id, + file_path: filePath, + chunked: 'true', + }, }); - if (response.success) { - setDiffData(response.data); + if (response.data.success) { + setDiffData(response.data.data); } else { - setError(response.error || '加载失败'); + setError(response.data.error || '加载失败'); } } catch (err) { setError(err.message || '网络错误'); @@ -33,120 +73,187 @@ export default function FileDiff({ filePath, lobsterId }) { if (filePath) { loadDiff(); } - }, [filePath]); + }, [filePath, lobsterId]); + + const renderDiff = () => { + if (!diffData) return null; + + const { local_content, db_content, diff, status } = diffData; + + if (!local_content || !db_content) { + return ( + + ); + } + + // 使用 diff 库计算行级差异 + const changes = diffLines(db_content || '', local_content || ''); + + return ( +
+ + + + {STATUS_CONFIG[status]?.icon} + {STATUS_CONFIG[status]?.text} + + } + /> + + {diff.lines_changed !== 0 && ( + + 0 ? 'green' : 'red'}> + {diff.lines_changed > 0 ? '+' : ''}{diff.lines_changed} + + {diff.is_truncated && ( + + 已截断 + + )} + + )} + + + {diffData.local_hash?.slice(0, 16)}... + + + + + {diffData.db_hash?.slice(0, 16)}... + + + + +
+ {changes.map((change, index) => { + const lineStyle = { + paddingLeft: '16px', + paddingRight: '16px', + margin: '2px 0', + fontSize: '13px', + fontFamily: 'Consolas, Monaco, "Courier New", monospace', + lineHeight: '1.6', + }; + + if (change.type === ChangeType.Insert) { + return ( +
+ + + {change.value} +
+ ); + } else if (change.type === ChangeType.Delete) { + return ( +
+ - + {change.value} +
+ ); + } else { + return ( +
+ + {change.value} +
+ ); + } + })} +
+
+ ); + }; if (loading) { - return ; + return ( +
+ +
+ ); } if (error) { - return ; + return ( + + 重试 + + } + /> + ); } if (!diffData) { - return ; - } - - const { local_content, db_content, status, diff } = diffData; - - // 文件不存在的情况 - if (!local_content && !db_content) { - return ; - } - - if (!local_content) { return ( ); } - if (!db_content) { - return ( - - ); - } - - const STATUS_MESSAGES = { - consistent: '文件内容一致', - local_newer: '本地文件有更新', - db_newer: '数据库版本更新', - conflict: '文件内容冲突', - }; - return ( -
- +
+
+ + + +
- - -
- ), - }, - { - key: 'local', - label: '本地内容', - children: ( -
-                {local_content}
-              
- ), - }, - { - key: 'db', - label: '数据库内容', - children: ( -
-                {db_content}
-              
- ), - }, - ]} - /> + {STATUS_CONFIG[diffData.status] && ( + + )} + + {renderDiff()}
); } \ No newline at end of file