feat: 龙虾记忆同步系统完整版本
功能特性: - 文件树展示 - 差异对比 - 双向同步(本地 <-> 数据库) - 版本历史追踪 - 统计信息展示 核心补丁: 1. 分块读取与流式传输(防止大文件内存飙升) 2. .lobsterignore 机制(排除临时文件) 3. 操作溯源(Audit Log,记录同步历史) 技术栈: - 后端: Django + DRF + PostgreSQL - 前端: React + Ant Design - 部署: Docker + Docker Compose 项目已完整部署,可直接使用 docker-compose up -d 启动
This commit is contained in:
152
frontend/src/components/FileDiff.js
Normal file
152
frontend/src/components/FileDiff.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Spin, Alert, Tabs } from 'antd';
|
||||
import ReactDiffViewer from 'react-diff-viewer-continued';
|
||||
import api from '../api';
|
||||
|
||||
export default function FileDiff({ filePath, lobsterId }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [diffData, setDiffData] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const loadDiff = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await api.get('/diff/', {
|
||||
params: { file_path: filePath, lobster_id: lobsterId }
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
setDiffData(response.data);
|
||||
} else {
|
||||
setError(response.error || '加载失败');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message || '网络错误');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (filePath) {
|
||||
loadDiff();
|
||||
}
|
||||
}, [filePath]);
|
||||
|
||||
if (loading) {
|
||||
return <Spin tip="加载中..." />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Alert message={error} type="error" />;
|
||||
}
|
||||
|
||||
if (!diffData) {
|
||||
return <Alert message="请选择文件" type="info" />;
|
||||
}
|
||||
|
||||
const { local_content, db_content, status, diff } = diffData;
|
||||
|
||||
// 文件不存在的情况
|
||||
if (!local_content && !db_content) {
|
||||
return <Alert message="文件不存在" type="warning" />;
|
||||
}
|
||||
|
||||
if (!local_content) {
|
||||
return (
|
||||
<Alert
|
||||
message="文件仅存在于数据库"
|
||||
description="点击「同步到本地」将文件恢复到本地"
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!db_content) {
|
||||
return (
|
||||
<Alert
|
||||
message="文件仅存在于本地"
|
||||
description="点击「同步到数据库」将文件备份到数据库"
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const STATUS_MESSAGES = {
|
||||
consistent: '文件内容一致',
|
||||
local_newer: '本地文件有更新',
|
||||
db_newer: '数据库版本更新',
|
||||
conflict: '文件内容冲突',
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Alert
|
||||
message={STATUS_MESSAGES[status] || '未知状态'}
|
||||
type={status === 'consistent' ? 'success' : 'warning'}
|
||||
style={{ marginBottom: 16 }}
|
||||
showIcon
|
||||
/>
|
||||
|
||||
<Tabs
|
||||
defaultActiveKey="diff"
|
||||
items={[
|
||||
{
|
||||
key: 'diff',
|
||||
label: '差异对比',
|
||||
children: (
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<ReactDiffViewer
|
||||
oldValue={db_content || ''}
|
||||
newValue={local_content || ''}
|
||||
splitView={true}
|
||||
useDarkTheme={false}
|
||||
leftTitle="数据库版本"
|
||||
rightTitle="本地版本"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'local',
|
||||
label: '本地内容',
|
||||
children: (
|
||||
<pre style={{
|
||||
padding: '16px',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: '4px',
|
||||
maxHeight: '500px',
|
||||
overflow: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
{local_content}
|
||||
</pre>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'db',
|
||||
label: '数据库内容',
|
||||
children: (
|
||||
<pre style={{
|
||||
padding: '16px',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: '4px',
|
||||
maxHeight: '500px',
|
||||
overflow: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
{db_content}
|
||||
</pre>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user