zen-gitsync 2.0.1 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/ui/client/package.json +1 -0
- package/src/ui/client/src/App.vue +174 -171
- package/src/ui/client/src/components/CommitForm.vue +591 -290
- package/src/ui/client/src/components/GitStatus.vue +528 -110
- package/src/ui/client/src/components/LogList.vue +351 -85
- package/src/ui/client/src/main.ts +3 -0
- package/src/ui/client/src/stores/gitLogStore.ts +464 -0
- package/src/ui/client/src/stores/gitStore.ts +216 -0
- package/src/ui/client/stats.html +1 -1
- package/src/ui/public/assets/index-D5irnfho.css +1 -0
- package/src/ui/public/assets/index-DBck3u67.js +8 -0
- package/src/ui/public/assets/vendor-CdJ34PvS.js +45 -0
- package/src/ui/public/index.html +3 -3
- package/src/ui/server/index.js +260 -4
- package/src/ui/public/assets/index-BcTk2R6G.js +0 -9
- package/src/ui/public/assets/index-ChUZ1vPG.css +0 -1
- package/src/ui/public/assets/vendor-BAXrrwNU.js +0 -41
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, onMounted,
|
|
3
|
-
import { ElMessage } from 'element-plus'
|
|
2
|
+
import { ref, onMounted, computed } from 'vue'
|
|
3
|
+
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
4
4
|
// import { io } from 'socket.io-client'
|
|
5
|
-
import { Refresh, ArrowLeft, ArrowRight, Folder } from '@element-plus/icons-vue'
|
|
5
|
+
import { Refresh, ArrowLeft, ArrowRight, Folder, Document, ArrowUp, RefreshRight } from '@element-plus/icons-vue'
|
|
6
|
+
import { useGitLogStore } from '../stores/gitLogStore'
|
|
7
|
+
import { useGitStore } from '../stores/gitStore'
|
|
6
8
|
|
|
9
|
+
// 定义props
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
initialDirectory: {
|
|
12
|
+
type: String,
|
|
13
|
+
default: ''
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const gitLogStore = useGitLogStore()
|
|
18
|
+
const gitStore = useGitStore()
|
|
7
19
|
const status = ref('加载中...')
|
|
8
20
|
// const socket = io()
|
|
9
|
-
const isRefreshing =
|
|
21
|
+
const isRefreshing = computed(() => gitLogStore.isLoadingStatus)
|
|
10
22
|
const fileList = ref<{path: string, type: string}[]>([])
|
|
11
23
|
const selectedFile = ref('')
|
|
12
24
|
const diffContent = ref('')
|
|
@@ -14,6 +26,16 @@ const diffDialogVisible = ref(false)
|
|
|
14
26
|
const isLoadingDiff = ref(false)
|
|
15
27
|
// 添加当前文件索引
|
|
16
28
|
const currentFileIndex = ref(-1)
|
|
29
|
+
// 添加切换目录相关的状态
|
|
30
|
+
const isDirectoryDialogVisible = ref(false)
|
|
31
|
+
const newDirectoryPath = ref('')
|
|
32
|
+
const isChangingDirectory = ref(false)
|
|
33
|
+
// 添加目录浏览相关的状态
|
|
34
|
+
const isDirectoryBrowserVisible = ref(false)
|
|
35
|
+
const currentBrowsePath = ref('')
|
|
36
|
+
const directoryItems = ref<{name: string, path: string, type: string}[]>([])
|
|
37
|
+
const isBrowsing = ref(false)
|
|
38
|
+
const browseErrorMessage = ref('')
|
|
17
39
|
|
|
18
40
|
// 解析 git status 输出,提取文件及类型
|
|
19
41
|
function parseStatus(statusText: string) {
|
|
@@ -38,23 +60,24 @@ function parseStatus(statusText: string) {
|
|
|
38
60
|
fileList.value = files
|
|
39
61
|
}
|
|
40
62
|
|
|
41
|
-
const currentDirectory = ref('')
|
|
63
|
+
const currentDirectory = ref(props.initialDirectory || '');
|
|
42
64
|
async function loadStatus() {
|
|
43
65
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
// 如果没有初始目录,才需要请求当前目录
|
|
67
|
+
if (!currentDirectory.value) {
|
|
68
|
+
const responseDir = await fetch('/api/current_directory')
|
|
69
|
+
const dirData = await responseDir.json()
|
|
70
|
+
currentDirectory.value = dirData.directory || '未知目录'
|
|
71
|
+
}
|
|
49
72
|
|
|
50
|
-
// 如果不是Git
|
|
51
|
-
if (
|
|
73
|
+
// 如果不是Git仓库,直接显示提示并返回
|
|
74
|
+
if (!gitStore.isGitRepo) {
|
|
52
75
|
status.value = '当前目录不是一个Git仓库'
|
|
53
76
|
fileList.value = []
|
|
54
|
-
ElMessage.warning('当前目录不是一个Git仓库')
|
|
55
77
|
return
|
|
56
78
|
}
|
|
57
79
|
|
|
80
|
+
// 直接获取Git状态,不再通过store调用
|
|
58
81
|
const response = await fetch('/api/status')
|
|
59
82
|
const data = await response.json()
|
|
60
83
|
status.value = data.status
|
|
@@ -73,8 +96,6 @@ async function loadStatus() {
|
|
|
73
96
|
message: '刷新失败: ' + (error as Error).message,
|
|
74
97
|
type: 'error',
|
|
75
98
|
})
|
|
76
|
-
} finally {
|
|
77
|
-
isRefreshing.value = false
|
|
78
99
|
}
|
|
79
100
|
}
|
|
80
101
|
|
|
@@ -158,24 +179,224 @@ async function goToNextFile() {
|
|
|
158
179
|
await getFileDiff(nextFile.path)
|
|
159
180
|
}
|
|
160
181
|
|
|
182
|
+
// 打开切换目录对话框
|
|
183
|
+
function openDirectoryDialog() {
|
|
184
|
+
newDirectoryPath.value = currentDirectory.value
|
|
185
|
+
isDirectoryDialogVisible.value = true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 打开目录浏览器
|
|
189
|
+
function openDirectoryBrowser() {
|
|
190
|
+
browseErrorMessage.value = ''
|
|
191
|
+
currentBrowsePath.value = newDirectoryPath.value || currentDirectory.value
|
|
192
|
+
isDirectoryBrowserVisible.value = true
|
|
193
|
+
browseDirectory(currentBrowsePath.value)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 浏览目录
|
|
197
|
+
async function browseDirectory(directoryPath: string) {
|
|
198
|
+
try {
|
|
199
|
+
isBrowsing.value = true
|
|
200
|
+
browseErrorMessage.value = ''
|
|
201
|
+
|
|
202
|
+
// 确保Windows盘符路径格式正确
|
|
203
|
+
let normalizedPath = directoryPath
|
|
204
|
+
if (/^[A-Za-z]:$/.test(normalizedPath)) {
|
|
205
|
+
normalizedPath += '/'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const response = await fetch(`/api/browse_directory?path=${encodeURIComponent(normalizedPath)}`)
|
|
209
|
+
|
|
210
|
+
if (response.status === 403) {
|
|
211
|
+
const data = await response.json()
|
|
212
|
+
browseErrorMessage.value = data.error || '目录浏览功能未启用'
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
const data = await response.json()
|
|
218
|
+
browseErrorMessage.value = data.error || '获取目录内容失败'
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const data = await response.json()
|
|
223
|
+
|
|
224
|
+
if (data.success) {
|
|
225
|
+
directoryItems.value = data.items
|
|
226
|
+
currentBrowsePath.value = data.currentPath
|
|
227
|
+
} else {
|
|
228
|
+
browseErrorMessage.value = data.error || '获取目录内容失败'
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
browseErrorMessage.value = `获取目录内容失败: ${(error as Error).message}`
|
|
232
|
+
} finally {
|
|
233
|
+
isBrowsing.value = false
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 导航到父目录
|
|
238
|
+
function navigateToParent() {
|
|
239
|
+
// 检查是否已经是根目录
|
|
240
|
+
// Windows盘符根目录情况 (如 "E:")
|
|
241
|
+
if (/^[A-Za-z]:$/.test(currentBrowsePath.value) ||
|
|
242
|
+
/^[A-Za-z]:[\\/]$/.test(currentBrowsePath.value) ||
|
|
243
|
+
currentBrowsePath.value === '/') {
|
|
244
|
+
// 已经是根目录,不做任何操作
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 获取当前路径的父目录
|
|
249
|
+
let pathParts = currentBrowsePath.value.split(/[/\\]/)
|
|
250
|
+
|
|
251
|
+
// 移除最后一个目录部分
|
|
252
|
+
pathParts.pop()
|
|
253
|
+
|
|
254
|
+
// 处理Windows盘符特殊情况
|
|
255
|
+
let parentPath = pathParts.join('/')
|
|
256
|
+
if (pathParts.length === 1 && /^[A-Za-z]:$/.test(pathParts[0])) {
|
|
257
|
+
// 如果只剩下盘符,确保添加斜杠 (例如 "E:/")
|
|
258
|
+
parentPath = pathParts[0] + '/'
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (parentPath) {
|
|
262
|
+
browseDirectory(parentPath)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 选择目录项
|
|
267
|
+
function selectDirectoryItem(item: {name: string, path: string, type: string}) {
|
|
268
|
+
if (item.type === 'directory') {
|
|
269
|
+
browseDirectory(item.path)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 选择当前目录
|
|
274
|
+
function selectCurrentDirectory() {
|
|
275
|
+
newDirectoryPath.value = currentBrowsePath.value
|
|
276
|
+
isDirectoryBrowserVisible.value = false
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 切换工作目录
|
|
280
|
+
async function changeDirectory() {
|
|
281
|
+
if (!newDirectoryPath.value) {
|
|
282
|
+
ElMessage.warning('目录路径不能为空')
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
isChangingDirectory.value = true
|
|
288
|
+
const response = await fetch('/api/change_directory', {
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: {
|
|
291
|
+
'Content-Type': 'application/json'
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({ path: newDirectoryPath.value })
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
const result = await response.json()
|
|
297
|
+
|
|
298
|
+
if (result.success) {
|
|
299
|
+
ElMessage.success('已切换工作目录')
|
|
300
|
+
currentDirectory.value = result.directory
|
|
301
|
+
isDirectoryDialogVisible.value = false
|
|
302
|
+
|
|
303
|
+
// 直接使用API返回的Git仓库状态
|
|
304
|
+
gitStore.isGitRepo = result.isGitRepo
|
|
305
|
+
|
|
306
|
+
// 如果是Git仓库,加载Git相关数据
|
|
307
|
+
if (result.isGitRepo) {
|
|
308
|
+
// 加载Git分支和用户信息
|
|
309
|
+
await Promise.all([
|
|
310
|
+
gitStore.getCurrentBranch(),
|
|
311
|
+
gitStore.getAllBranches(),
|
|
312
|
+
gitStore.getUserInfo()
|
|
313
|
+
])
|
|
314
|
+
|
|
315
|
+
// 刷新Git状态
|
|
316
|
+
await loadStatus()
|
|
317
|
+
} else {
|
|
318
|
+
ElMessage.warning('当前目录不是一个Git仓库')
|
|
319
|
+
status.value = '当前目录不是一个Git仓库'
|
|
320
|
+
fileList.value = []
|
|
321
|
+
|
|
322
|
+
// 清空Git相关状态
|
|
323
|
+
gitStore.$reset() // 使用pinia的reset方法重置状态
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
ElMessage.error(result.error || '切换目录失败')
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
ElMessage.error(`切换目录失败: ${(error as Error).message}`)
|
|
330
|
+
} finally {
|
|
331
|
+
isChangingDirectory.value = false
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
161
335
|
// 处理文件点击
|
|
162
336
|
function handleFileClick(file: {path: string, type: string}) {
|
|
163
337
|
getFileDiff(file.path)
|
|
164
338
|
}
|
|
165
339
|
|
|
340
|
+
// 文件类型标签显示
|
|
341
|
+
function fileTypeLabel(type: string) {
|
|
342
|
+
switch (type) {
|
|
343
|
+
case 'added': return '新增';
|
|
344
|
+
case 'modified': return '修改';
|
|
345
|
+
case 'deleted': return '删除';
|
|
346
|
+
case 'untracked': return '未跟踪';
|
|
347
|
+
default: return '其他';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
166
351
|
// 刷新Git状态的方法
|
|
167
352
|
async function refreshStatus() {
|
|
168
353
|
await loadStatus()
|
|
169
354
|
}
|
|
170
355
|
|
|
356
|
+
// 添加撤回文件修改的方法
|
|
357
|
+
async function revertFileChanges(filePath: string) {
|
|
358
|
+
try {
|
|
359
|
+
// 请求用户确认
|
|
360
|
+
await ElMessageBox.confirm(
|
|
361
|
+
`确定要撤回文件 "${filePath}" 的所有修改吗?此操作无法撤销。`,
|
|
362
|
+
'撤回修改',
|
|
363
|
+
{
|
|
364
|
+
confirmButtonText: '确定',
|
|
365
|
+
cancelButtonText: '取消',
|
|
366
|
+
type: 'warning'
|
|
367
|
+
}
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
// 发送请求到后端API
|
|
371
|
+
const response = await fetch('/api/revert_file', {
|
|
372
|
+
method: 'POST',
|
|
373
|
+
headers: {
|
|
374
|
+
'Content-Type': 'application/json'
|
|
375
|
+
},
|
|
376
|
+
body: JSON.stringify({ filePath })
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
const result = await response.json()
|
|
380
|
+
|
|
381
|
+
if (result.success) {
|
|
382
|
+
ElMessage.success('已撤回文件修改')
|
|
383
|
+
// 刷新Git状态
|
|
384
|
+
await loadStatus()
|
|
385
|
+
} else {
|
|
386
|
+
ElMessage.error(result.error || '撤回文件修改失败')
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
// 用户取消或发生错误
|
|
390
|
+
if ((error as Error).message !== 'cancel') {
|
|
391
|
+
ElMessage.error(`撤回文件修改失败: ${(error as Error).message}`)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
171
396
|
onMounted(() => {
|
|
397
|
+
// App.vue已经加载了Git相关数据,此时只需加载状态
|
|
398
|
+
// 如果已有初始目录,则只需加载状态
|
|
172
399
|
loadStatus()
|
|
173
|
-
|
|
174
|
-
// Socket.io 事件
|
|
175
|
-
// socket.on('status_update', (data: { status: string }) => {
|
|
176
|
-
// status.value = data.status
|
|
177
|
-
// parseStatus(data.status)
|
|
178
|
-
// })
|
|
179
400
|
})
|
|
180
401
|
|
|
181
402
|
// onUnmounted(() => {
|
|
@@ -193,9 +414,12 @@ defineExpose({
|
|
|
193
414
|
<div class="current-directory">
|
|
194
415
|
<el-icon><Folder /></el-icon>
|
|
195
416
|
<span>{{ currentDirectory }}</span>
|
|
417
|
+
<el-button type="primary" size="small" @click="openDirectoryDialog" plain>
|
|
418
|
+
切换目录
|
|
419
|
+
</el-button>
|
|
196
420
|
</div>
|
|
197
421
|
<div class="status-header">
|
|
198
|
-
<h2>Git
|
|
422
|
+
<h2>Git 状态(git status)</h2>
|
|
199
423
|
<el-button
|
|
200
424
|
type="primary"
|
|
201
425
|
:icon="Refresh"
|
|
@@ -212,13 +436,99 @@ defineExpose({
|
|
|
212
436
|
v-for="file in fileList"
|
|
213
437
|
:key="file.path"
|
|
214
438
|
:class="['file-item', file.type]"
|
|
215
|
-
@click="handleFileClick(file)"
|
|
216
439
|
>
|
|
217
|
-
<
|
|
218
|
-
|
|
440
|
+
<div class="file-info" @click="handleFileClick(file)">
|
|
441
|
+
<span class="file-type">{{ fileTypeLabel(file.type) }}</span>
|
|
442
|
+
<span class="file-path">{{ file.path }}</span>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="file-actions">
|
|
445
|
+
<el-tooltip content="撤回修改" placement="top" :hide-after="1000">
|
|
446
|
+
<el-button
|
|
447
|
+
type="danger"
|
|
448
|
+
size="small"
|
|
449
|
+
:icon="RefreshRight"
|
|
450
|
+
circle
|
|
451
|
+
@click.stop="revertFileChanges(file.path)"
|
|
452
|
+
/>
|
|
453
|
+
</el-tooltip>
|
|
454
|
+
</div>
|
|
219
455
|
</div>
|
|
220
456
|
</div>
|
|
221
457
|
|
|
458
|
+
<!-- 切换目录对话框 -->
|
|
459
|
+
<el-dialog
|
|
460
|
+
v-model="isDirectoryDialogVisible"
|
|
461
|
+
title="切换工作目录"
|
|
462
|
+
width="500px"
|
|
463
|
+
>
|
|
464
|
+
<el-form>
|
|
465
|
+
<el-form-item label="目录路径">
|
|
466
|
+
<el-input v-model="newDirectoryPath" placeholder="请输入目录路径" clearable />
|
|
467
|
+
<div class="directory-buttons">
|
|
468
|
+
<el-button @click="openDirectoryBrowser" type="primary" plain class="no-padding-left">
|
|
469
|
+
<el-icon><Folder /></el-icon>
|
|
470
|
+
浏览
|
|
471
|
+
</el-button>
|
|
472
|
+
<el-button @click="changeDirectory" :loading="isChangingDirectory" type="primary">
|
|
473
|
+
切换
|
|
474
|
+
</el-button>
|
|
475
|
+
</div>
|
|
476
|
+
</el-form-item>
|
|
477
|
+
</el-form>
|
|
478
|
+
</el-dialog>
|
|
479
|
+
|
|
480
|
+
<!-- 目录浏览对话框 -->
|
|
481
|
+
<el-dialog
|
|
482
|
+
v-model="isDirectoryBrowserVisible"
|
|
483
|
+
title="浏览目录"
|
|
484
|
+
width="600px"
|
|
485
|
+
>
|
|
486
|
+
<div class="browser-current-path">
|
|
487
|
+
<span>当前路径: {{ currentBrowsePath }}</span>
|
|
488
|
+
</div>
|
|
489
|
+
|
|
490
|
+
<div v-if="browseErrorMessage" class="browser-error">
|
|
491
|
+
{{ browseErrorMessage }}
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<div v-loading="isBrowsing" class="directory-browser">
|
|
495
|
+
<!-- 导航栏 -->
|
|
496
|
+
<div class="browser-nav">
|
|
497
|
+
<el-button
|
|
498
|
+
@click="navigateToParent"
|
|
499
|
+
:disabled="!currentBrowsePath || isBrowsing"
|
|
500
|
+
size="small"
|
|
501
|
+
class="no-padding-left"
|
|
502
|
+
>
|
|
503
|
+
<el-icon><ArrowUp /></el-icon>
|
|
504
|
+
上级目录
|
|
505
|
+
</el-button>
|
|
506
|
+
<el-button
|
|
507
|
+
@click="selectCurrentDirectory"
|
|
508
|
+
type="primary"
|
|
509
|
+
size="small"
|
|
510
|
+
class="no-padding-left"
|
|
511
|
+
>
|
|
512
|
+
选择当前目录
|
|
513
|
+
</el-button>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<!-- 目录内容列表 -->
|
|
517
|
+
<ul class="directory-items">
|
|
518
|
+
<li
|
|
519
|
+
v-for="item in directoryItems"
|
|
520
|
+
:key="item.path"
|
|
521
|
+
:class="['directory-item', item.type]"
|
|
522
|
+
@click="selectDirectoryItem(item)"
|
|
523
|
+
>
|
|
524
|
+
<el-icon v-if="item.type === 'directory'"><Folder /></el-icon>
|
|
525
|
+
<el-icon v-else><Document /></el-icon>
|
|
526
|
+
<span>{{ item.name }}</span>
|
|
527
|
+
</li>
|
|
528
|
+
</ul>
|
|
529
|
+
</div>
|
|
530
|
+
</el-dialog>
|
|
531
|
+
|
|
222
532
|
<!-- 文件差异对话框 -->
|
|
223
533
|
<el-dialog
|
|
224
534
|
v-model="diffDialogVisible"
|
|
@@ -251,18 +561,15 @@ defineExpose({
|
|
|
251
561
|
</div>
|
|
252
562
|
</template>
|
|
253
563
|
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return '其它'
|
|
564
|
+
<style scoped>
|
|
565
|
+
.card {
|
|
566
|
+
background-color: #fff;
|
|
567
|
+
border-radius: 8px;
|
|
568
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
569
|
+
padding: 20px;
|
|
570
|
+
margin-bottom: 20px;
|
|
262
571
|
}
|
|
263
|
-
</script>
|
|
264
572
|
|
|
265
|
-
<style scoped>
|
|
266
573
|
.status-header {
|
|
267
574
|
display: flex;
|
|
268
575
|
justify-content: space-between;
|
|
@@ -274,130 +581,241 @@ function fileTypeLabel(type: string) {
|
|
|
274
581
|
margin: 0;
|
|
275
582
|
}
|
|
276
583
|
|
|
584
|
+
.status-box {
|
|
585
|
+
white-space: pre-wrap;
|
|
586
|
+
font-family: monospace;
|
|
587
|
+
background-color: #f5f7fa;
|
|
588
|
+
padding: 15px;
|
|
589
|
+
border-radius: 4px;
|
|
590
|
+
margin-bottom: 15px;
|
|
591
|
+
max-height: 300px;
|
|
592
|
+
overflow-y: auto;
|
|
593
|
+
}
|
|
594
|
+
|
|
277
595
|
.file-list {
|
|
278
|
-
|
|
596
|
+
max-height: 300px;
|
|
597
|
+
overflow-y: auto;
|
|
279
598
|
}
|
|
280
599
|
|
|
281
600
|
.file-item {
|
|
601
|
+
padding: 8px 12px;
|
|
602
|
+
margin-bottom: 5px;
|
|
603
|
+
border-radius: 4px;
|
|
604
|
+
cursor: pointer;
|
|
282
605
|
display: flex;
|
|
283
606
|
align-items: center;
|
|
284
|
-
|
|
285
|
-
padding: 2px 6px;
|
|
286
|
-
border-radius: 3px;
|
|
287
|
-
font-size: 14px;
|
|
288
|
-
cursor: pointer;
|
|
289
|
-
transition: opacity 0.2s;
|
|
607
|
+
justify-content: space-between;
|
|
290
608
|
}
|
|
609
|
+
|
|
291
610
|
.file-item:hover {
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
.file-item.added {
|
|
295
|
-
background: #e6ffed;
|
|
296
|
-
color: #22863a;
|
|
611
|
+
background-color: #f5f7fa;
|
|
297
612
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
613
|
+
|
|
614
|
+
.file-info {
|
|
615
|
+
display: flex;
|
|
616
|
+
align-items: center;
|
|
617
|
+
flex-grow: 1;
|
|
301
618
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
619
|
+
|
|
620
|
+
.file-actions {
|
|
621
|
+
margin-left: 10px;
|
|
622
|
+
opacity: 0.5;
|
|
623
|
+
transition: opacity 0.2s;
|
|
305
624
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
625
|
+
|
|
626
|
+
.file-item:hover .file-actions {
|
|
627
|
+
opacity: 1;
|
|
309
628
|
}
|
|
629
|
+
|
|
310
630
|
.file-type {
|
|
311
|
-
font-
|
|
312
|
-
|
|
631
|
+
font-size: 12px;
|
|
632
|
+
padding: 2px 6px;
|
|
633
|
+
border-radius: 10px;
|
|
634
|
+
margin-right: 10px;
|
|
635
|
+
flex-shrink: 0;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.added .file-type {
|
|
639
|
+
background-color: #e1f3d8;
|
|
640
|
+
color: #67c23a;
|
|
313
641
|
}
|
|
642
|
+
|
|
643
|
+
.modified .file-type {
|
|
644
|
+
background-color: #e6f1fc;
|
|
645
|
+
color: #409eff;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.deleted .file-type {
|
|
649
|
+
background-color: #fef0f0;
|
|
650
|
+
color: #f56c6c;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.untracked .file-type {
|
|
654
|
+
background-color: #fdf6ec;
|
|
655
|
+
color: #e6a23c;
|
|
656
|
+
}
|
|
657
|
+
|
|
314
658
|
.file-path {
|
|
315
659
|
font-family: monospace;
|
|
660
|
+
word-break: break-all;
|
|
316
661
|
}
|
|
317
662
|
|
|
318
663
|
.diff-content {
|
|
319
|
-
max-height: 70vh;
|
|
320
|
-
overflow-y: auto;
|
|
321
|
-
background-color: #f6f8fa;
|
|
322
|
-
border: 1px solid #e1e4e8;
|
|
323
|
-
border-radius: 3px;
|
|
324
|
-
padding: 15px;
|
|
325
664
|
font-family: monospace;
|
|
326
665
|
white-space: pre-wrap;
|
|
666
|
+
max-height: 60vh;
|
|
667
|
+
overflow-y: auto;
|
|
668
|
+
padding: 10px;
|
|
669
|
+
background-color: #f5f7fa;
|
|
670
|
+
border-radius: 4px;
|
|
327
671
|
}
|
|
328
672
|
|
|
329
673
|
.diff-formatted {
|
|
330
|
-
|
|
674
|
+
font-size: 14px;
|
|
675
|
+
line-height: 1.5;
|
|
331
676
|
}
|
|
332
677
|
|
|
333
|
-
|
|
334
|
-
:
|
|
335
|
-
|
|
336
|
-
|
|
678
|
+
.file-navigation {
|
|
679
|
+
display: flex;
|
|
680
|
+
justify-content: center;
|
|
681
|
+
align-items: center;
|
|
682
|
+
margin-top: 15px;
|
|
337
683
|
}
|
|
338
684
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
685
|
+
.file-counter {
|
|
686
|
+
margin: 0 15px;
|
|
687
|
+
font-size: 14px;
|
|
688
|
+
color: #606266;
|
|
342
689
|
}
|
|
343
690
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
691
|
+
.current-directory {
|
|
692
|
+
display: flex;
|
|
693
|
+
align-items: center;
|
|
694
|
+
margin-bottom: 15px;
|
|
695
|
+
padding: 8px 12px;
|
|
696
|
+
background-color: #f5f7fa;
|
|
697
|
+
border-radius: 4px;
|
|
698
|
+
font-family: monospace;
|
|
347
699
|
}
|
|
348
700
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
701
|
+
.current-directory .el-icon {
|
|
702
|
+
margin-right: 8px;
|
|
703
|
+
color: #409eff;
|
|
352
704
|
}
|
|
353
705
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
706
|
+
.current-directory span {
|
|
707
|
+
flex-grow: 1;
|
|
708
|
+
word-break: break-all;
|
|
709
|
+
margin-right: 10px;
|
|
357
710
|
}
|
|
358
711
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
712
|
+
.browser-current-path {
|
|
713
|
+
margin-bottom: 10px;
|
|
714
|
+
font-size: 14px;
|
|
715
|
+
color: #606266;
|
|
716
|
+
background-color: #f5f7fa;
|
|
717
|
+
padding: 8px 12px;
|
|
718
|
+
border-radius: 4px;
|
|
719
|
+
font-family: monospace;
|
|
720
|
+
word-break: break-all;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.browser-error {
|
|
724
|
+
margin-bottom: 10px;
|
|
725
|
+
color: #f56c6c;
|
|
726
|
+
padding: 8px 12px;
|
|
727
|
+
background-color: #fef0f0;
|
|
728
|
+
border-radius: 4px;
|
|
362
729
|
}
|
|
363
730
|
|
|
364
|
-
|
|
365
|
-
|
|
731
|
+
.directory-browser {
|
|
732
|
+
padding: 10px;
|
|
733
|
+
max-height: 400px;
|
|
734
|
+
overflow-y: auto;
|
|
366
735
|
}
|
|
367
736
|
|
|
368
|
-
.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
737
|
+
.browser-nav {
|
|
738
|
+
margin-bottom: 10px;
|
|
739
|
+
display: flex;
|
|
740
|
+
justify-content: space-between;
|
|
372
741
|
}
|
|
373
742
|
|
|
374
|
-
|
|
375
|
-
|
|
743
|
+
.directory-items {
|
|
744
|
+
list-style: none;
|
|
745
|
+
padding: 0;
|
|
746
|
+
margin: 0;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.directory-item {
|
|
750
|
+
padding: 8px 12px;
|
|
751
|
+
margin-bottom: 5px;
|
|
752
|
+
border-radius: 4px;
|
|
753
|
+
cursor: pointer;
|
|
376
754
|
display: flex;
|
|
377
|
-
justify-content: center;
|
|
378
755
|
align-items: center;
|
|
379
|
-
margin-top: 15px;
|
|
380
|
-
gap: 10px;
|
|
381
756
|
}
|
|
382
757
|
|
|
383
|
-
.
|
|
384
|
-
|
|
758
|
+
.directory-item:hover {
|
|
759
|
+
background-color: #f5f7fa;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.directory-item.directory {
|
|
763
|
+
color: #409eff;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.directory-item.file {
|
|
385
767
|
color: #606266;
|
|
386
768
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
gap: 8px;
|
|
769
|
+
|
|
770
|
+
.directory-item .el-icon {
|
|
771
|
+
margin-right: 10px;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.directory-item span {
|
|
394
775
|
font-family: monospace;
|
|
776
|
+
word-break: break-all;
|
|
395
777
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
778
|
+
|
|
779
|
+
.directory-buttons {
|
|
780
|
+
display: flex;
|
|
781
|
+
gap: 10px;
|
|
782
|
+
margin-top: 10px;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/* 移除按钮左侧的内边距 */
|
|
786
|
+
.no-padding-left {
|
|
787
|
+
padding-left: 8px !important;
|
|
788
|
+
}
|
|
789
|
+
</style>
|
|
790
|
+
|
|
791
|
+
<!-- 添加非scoped样式,使diff格式化样式对动态内容生效 -->
|
|
792
|
+
<style>
|
|
793
|
+
.diff-header {
|
|
794
|
+
font-weight: bold;
|
|
795
|
+
background-color: #e6f1fc;
|
|
796
|
+
padding: 3px;
|
|
797
|
+
margin: 5px 0;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.diff-old-file, .diff-new-file {
|
|
801
|
+
color: #888;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.diff-hunk-header {
|
|
805
|
+
color: #6f42c1;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.diff-added {
|
|
809
|
+
background-color: #e6ffed;
|
|
810
|
+
color: #28a745;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
.diff-removed {
|
|
814
|
+
background-color: #ffeef0;
|
|
815
|
+
color: #d73a49;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.diff-context {
|
|
819
|
+
color: #444;
|
|
402
820
|
}
|
|
403
821
|
</style>
|