zen-gitsync 2.0.5 → 2.0.6
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/README.md +31 -2
- package/package.json +3 -1
- package/src/ui/client/src/App.vue +417 -123
- package/src/ui/client/src/components/CommitForm.vue +284 -211
- package/src/ui/client/src/components/GitStatus.vue +556 -171
- package/src/ui/client/src/components/LogList.vue +778 -94
- package/src/ui/client/src/stores/gitLogStore.ts +328 -13
- package/src/ui/client/stats.html +1 -1
- package/src/ui/public/assets/index-DaPynzAr.js +10 -0
- package/src/ui/public/assets/index-qfIezZmd.css +1 -0
- package/src/ui/public/assets/vendor-BcSuWc8z.js +45 -0
- package/src/ui/public/index.html +3 -3
- package/src/ui/server/index.js +287 -40
- package/src/ui/public/assets/index-CALk9kKc.js +0 -9
- package/src/ui/public/assets/index-D3zIiSNw.css +0 -1
- package/src/ui/public/assets/vendor-BfXVsoKv.js +0 -45
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted, computed, watch } from 'vue'
|
|
3
|
-
import { ElTable, ElTableColumn, ElTag, ElButton, ElSlider } from 'element-plus'
|
|
4
|
-
import { RefreshRight, ZoomIn, ZoomOut } from '@element-plus/icons-vue'
|
|
3
|
+
import { ElTable, ElTableColumn, ElTag, ElButton, ElSlider, ElDialog, ElSelect, ElOption, ElDatePicker, ElInput, ElBadge } from 'element-plus'
|
|
4
|
+
import { RefreshRight, ZoomIn, ZoomOut, Filter, Document, TrendCharts, List, More } from '@element-plus/icons-vue'
|
|
5
5
|
import 'element-plus/dist/index.css'
|
|
6
6
|
import { createGitgraph } from '@gitgraph/js'
|
|
7
7
|
import { useGitLogStore } from '../stores/gitLogStore'
|
|
@@ -30,9 +30,17 @@ const localLoading = ref(false)
|
|
|
30
30
|
const isLoading = computed(() => gitLogStore.isLoadingLog || localLoading.value)
|
|
31
31
|
const showAllCommits = ref(false)
|
|
32
32
|
const totalCommits = ref(0)
|
|
33
|
-
const showGraphView = ref(
|
|
33
|
+
const showGraphView = ref(false)
|
|
34
34
|
const graphContainer = ref<HTMLElement | null>(null)
|
|
35
35
|
|
|
36
|
+
// 添加提交详情弹窗相关变量
|
|
37
|
+
const commitDetailVisible = ref(false)
|
|
38
|
+
const selectedCommit = ref<LogItem | null>(null)
|
|
39
|
+
const commitFiles = ref<string[]>([])
|
|
40
|
+
const commitDiff = ref('')
|
|
41
|
+
const isLoadingCommitDetail = ref(false)
|
|
42
|
+
const selectedCommitFile = ref('')
|
|
43
|
+
|
|
36
44
|
// 添加图表缩放控制
|
|
37
45
|
const graphScale = ref(1)
|
|
38
46
|
const minScale = 0.5
|
|
@@ -42,6 +50,68 @@ const scaleStep = 0.1
|
|
|
42
50
|
// 添加日志被刷新的提示状态
|
|
43
51
|
const logRefreshed = ref(false)
|
|
44
52
|
|
|
53
|
+
// 添加筛选相关变量
|
|
54
|
+
const filterVisible = ref(false)
|
|
55
|
+
const authorFilter = ref('')
|
|
56
|
+
const messageFilter = ref('')
|
|
57
|
+
const dateRangeFilter = ref<any>(null)
|
|
58
|
+
const availableAuthors = computed(() => {
|
|
59
|
+
// 从日志中提取不重复的作者列表
|
|
60
|
+
const authors = new Set<string>()
|
|
61
|
+
logs.value.forEach(log => {
|
|
62
|
+
if (log.author) {
|
|
63
|
+
authors.add(log.author)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
return Array.from(authors).sort()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// 应用筛选后的日志
|
|
70
|
+
const filteredLogs = computed(() => {
|
|
71
|
+
if (!authorFilter.value && !messageFilter.value && !dateRangeFilter.value) {
|
|
72
|
+
return logs.value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return logs.value.filter(log => {
|
|
76
|
+
// 作者筛选
|
|
77
|
+
if (authorFilter.value && log.author !== authorFilter.value) {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 提交信息关键词筛选
|
|
82
|
+
if (messageFilter.value && !log.message.toLowerCase().includes(messageFilter.value.toLowerCase())) {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 日期范围筛选
|
|
87
|
+
if (dateRangeFilter.value && dateRangeFilter.value.length === 2) {
|
|
88
|
+
const logDate = new Date(log.date)
|
|
89
|
+
const [startDateStr, endDateStr] = dateRangeFilter.value
|
|
90
|
+
|
|
91
|
+
// 转换日期字符串为Date对象
|
|
92
|
+
const startDate = new Date(startDateStr)
|
|
93
|
+
const endDate = new Date(endDateStr)
|
|
94
|
+
|
|
95
|
+
// 设置时间为一天的开始和结束,确保包含完整日期
|
|
96
|
+
startDate.setHours(0, 0, 0, 0)
|
|
97
|
+
endDate.setHours(23, 59, 59, 999)
|
|
98
|
+
|
|
99
|
+
if (logDate < startDate || logDate > endDate) {
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// 重置筛选条件
|
|
109
|
+
function resetFilters() {
|
|
110
|
+
authorFilter.value = ''
|
|
111
|
+
messageFilter.value = ''
|
|
112
|
+
dateRangeFilter.value = null
|
|
113
|
+
}
|
|
114
|
+
|
|
45
115
|
// 加载提交历史
|
|
46
116
|
async function loadLog(all = false) {
|
|
47
117
|
// 从gitStore获取仓库状态
|
|
@@ -345,10 +415,148 @@ function fitGraphToContainer() {
|
|
|
345
415
|
|
|
346
416
|
applyScale()
|
|
347
417
|
}
|
|
418
|
+
|
|
419
|
+
// 查看提交详情
|
|
420
|
+
async function viewCommitDetail(commit: LogItem) {
|
|
421
|
+
selectedCommit.value = commit
|
|
422
|
+
commitDetailVisible.value = true
|
|
423
|
+
isLoadingCommitDetail.value = true
|
|
424
|
+
commitFiles.value = []
|
|
425
|
+
commitDiff.value = ''
|
|
426
|
+
selectedCommitFile.value = ''
|
|
427
|
+
|
|
428
|
+
// 调试输出当前提交对象的所有属性
|
|
429
|
+
console.log('提交详情对象:', JSON.stringify(commit, null, 2))
|
|
430
|
+
console.log('哈希值类型和长度:', typeof commit.hash, commit.hash ? commit.hash.length : 0)
|
|
431
|
+
console.log('提交信息类型和长度:', typeof commit.message, commit.message ? commit.message.length : 0)
|
|
432
|
+
console.log('提交分支:', commit.branch)
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
console.log(`获取提交详情: ${commit.hash}`)
|
|
436
|
+
|
|
437
|
+
// 确保哈希值有效
|
|
438
|
+
if (!commit.hash || commit.hash.length < 7) {
|
|
439
|
+
console.error('无效的提交哈希值:', commit.hash)
|
|
440
|
+
commitDiff.value = '无效的提交哈希值'
|
|
441
|
+
isLoadingCommitDetail.value = false
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 获取提交的变更文件列表
|
|
446
|
+
const filesResponse = await fetch(`/api/commit-files?hash=${commit.hash}`)
|
|
447
|
+
console.log('API响应状态: ', filesResponse.status)
|
|
448
|
+
const filesData = await filesResponse.json()
|
|
449
|
+
console.log('文件列表数据: ', filesData)
|
|
450
|
+
|
|
451
|
+
if (filesData.success && Array.isArray(filesData.files)) {
|
|
452
|
+
commitFiles.value = filesData.files
|
|
453
|
+
|
|
454
|
+
// 如果有文件,自动加载第一个文件的差异
|
|
455
|
+
if (commitFiles.value.length > 0) {
|
|
456
|
+
await getCommitFileDiff(commit.hash, commitFiles.value[0])
|
|
457
|
+
} else {
|
|
458
|
+
console.log('没有找到变更文件')
|
|
459
|
+
commitDiff.value = '该提交没有变更文件'
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
console.error('获取提交文件列表失败:', filesData.error || '未知错误')
|
|
463
|
+
commitDiff.value = `获取文件列表失败: ${filesData.error || '未知错误'}`
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error('获取提交详情失败:', error)
|
|
467
|
+
commitDiff.value = `获取提交详情失败: ${(error as Error).message}`
|
|
468
|
+
} finally {
|
|
469
|
+
isLoadingCommitDetail.value = false
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 获取提交中特定文件的差异
|
|
474
|
+
async function getCommitFileDiff(hash: string, filePath: string) {
|
|
475
|
+
isLoadingCommitDetail.value = true
|
|
476
|
+
selectedCommitFile.value = filePath
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
console.log(`获取文件差异: hash=${hash}, file=${filePath}`)
|
|
480
|
+
const diffResponse = await fetch(`/api/commit-file-diff?hash=${hash}&file=${encodeURIComponent(filePath)}`)
|
|
481
|
+
console.log('差异API响应状态: ', diffResponse.status)
|
|
482
|
+
const diffData = await diffResponse.json()
|
|
483
|
+
console.log('差异数据: ', diffData.success, typeof diffData.diff)
|
|
484
|
+
|
|
485
|
+
if (diffData.success) {
|
|
486
|
+
commitDiff.value = diffData.diff || '没有变更内容'
|
|
487
|
+
} else {
|
|
488
|
+
console.error('获取差异失败: ', diffData.error)
|
|
489
|
+
commitDiff.value = `获取差异失败: ${diffData.error || '未知错误'}`
|
|
490
|
+
}
|
|
491
|
+
} catch (error) {
|
|
492
|
+
console.error('获取文件差异失败:', error)
|
|
493
|
+
commitDiff.value = `获取差异失败: ${(error as Error).message}`
|
|
494
|
+
} finally {
|
|
495
|
+
isLoadingCommitDetail.value = false
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// 格式化差异内容,添加颜色
|
|
500
|
+
function formatDiff(diffText: string) {
|
|
501
|
+
if (!diffText) return '';
|
|
502
|
+
|
|
503
|
+
// 将差异内容按行分割
|
|
504
|
+
const lines = diffText.split('\n');
|
|
505
|
+
|
|
506
|
+
// 转义 HTML 标签的函数
|
|
507
|
+
function escapeHtml(text: string) {
|
|
508
|
+
return text
|
|
509
|
+
.replace(/&/g, '&')
|
|
510
|
+
.replace(/</g, '<')
|
|
511
|
+
.replace(/>/g, '>')
|
|
512
|
+
.replace(/"/g, '"')
|
|
513
|
+
.replace(/'/g, ''');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 为每行添加适当的 CSS 类
|
|
517
|
+
return lines.map(line => {
|
|
518
|
+
// 先转义 HTML 标签,再添加样式
|
|
519
|
+
const escapedLine = escapeHtml(line);
|
|
520
|
+
|
|
521
|
+
if (line.startsWith('diff --git')) {
|
|
522
|
+
return `<div class="diff-header">${escapedLine}</div>`;
|
|
523
|
+
} else if (line.startsWith('---')) {
|
|
524
|
+
return `<div class="diff-old-file">${escapedLine}</div>`;
|
|
525
|
+
} else if (line.startsWith('+++')) {
|
|
526
|
+
return `<div class="diff-new-file">${escapedLine}</div>`;
|
|
527
|
+
} else if (line.startsWith('@@')) {
|
|
528
|
+
return `<div class="diff-hunk-header">${escapedLine}</div>`;
|
|
529
|
+
} else if (line.startsWith('+')) {
|
|
530
|
+
return `<div class="diff-added">${escapedLine}</div>`;
|
|
531
|
+
} else if (line.startsWith('-')) {
|
|
532
|
+
return `<div class="diff-removed">${escapedLine}</div>`;
|
|
533
|
+
} else {
|
|
534
|
+
return `<div class="diff-context">${escapedLine}</div>`;
|
|
535
|
+
}
|
|
536
|
+
}).join('');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 格式化提交信息,支持多行显示
|
|
540
|
+
function formatCommitMessage(message: string) {
|
|
541
|
+
if (!message) return '(无提交信息)';
|
|
542
|
+
|
|
543
|
+
// 调试输出
|
|
544
|
+
console.log('格式化前的提交信息:', message)
|
|
545
|
+
console.log('提交信息中的换行符数量:', (message.match(/\n/g) || []).length)
|
|
546
|
+
|
|
547
|
+
// 返回格式化后的提交信息,保留换行符
|
|
548
|
+
return message.trim();
|
|
549
|
+
}
|
|
348
550
|
</script>
|
|
349
551
|
|
|
350
552
|
<template>
|
|
351
553
|
<div class="card">
|
|
554
|
+
<!-- 添加刷新提示,移到最外层 -->
|
|
555
|
+
<div v-if="logRefreshed" class="refresh-notification">
|
|
556
|
+
提交历史已刷新
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
<!-- 固定头部区域 -->
|
|
352
560
|
<div class="log-header">
|
|
353
561
|
<h2>提交历史</h2>
|
|
354
562
|
<div class="log-actions">
|
|
@@ -357,6 +565,9 @@ function fitGraphToContainer() {
|
|
|
357
565
|
size="small"
|
|
358
566
|
@click="toggleViewMode"
|
|
359
567
|
>
|
|
568
|
+
<template #icon>
|
|
569
|
+
<el-icon><component :is="showGraphView ? Document : TrendCharts" /></el-icon>
|
|
570
|
+
</template>
|
|
360
571
|
{{ showGraphView ? '表格视图' : '图表视图' }}
|
|
361
572
|
</el-button>
|
|
362
573
|
<el-button
|
|
@@ -365,119 +576,293 @@ function fitGraphToContainer() {
|
|
|
365
576
|
@click="toggleAllCommits"
|
|
366
577
|
:loading="isLoading"
|
|
367
578
|
>
|
|
579
|
+
<template #icon>
|
|
580
|
+
<el-icon><component :is="showAllCommits ? List : More" /></el-icon>
|
|
581
|
+
</template>
|
|
368
582
|
{{ showAllCommits ? '显示最近30条' : '显示所有提交' }}
|
|
369
583
|
</el-button>
|
|
370
584
|
<el-button
|
|
371
|
-
:icon="RefreshRight"
|
|
372
585
|
circle
|
|
373
586
|
size="small"
|
|
374
587
|
@click="refreshLog()"
|
|
375
588
|
:loading="isLoading"
|
|
376
589
|
:class="{ 'refresh-button-animated': logRefreshed }"
|
|
377
|
-
|
|
590
|
+
>
|
|
591
|
+
<el-icon><RefreshRight /></el-icon>
|
|
592
|
+
</el-button>
|
|
378
593
|
</div>
|
|
379
594
|
</div>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
<div v-if="
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
595
|
+
|
|
596
|
+
<!-- 内容区域,添加上边距以避免被固定头部遮挡 -->
|
|
597
|
+
<div class="content-area">
|
|
598
|
+
<div v-if="errorMessage">{{ errorMessage }}</div>
|
|
599
|
+
<div v-else>
|
|
600
|
+
<!-- 图表视图 -->
|
|
601
|
+
<div v-if="showGraphView" class="graph-view">
|
|
602
|
+
<div class="commit-count" v-if="logsData.length > 0">
|
|
603
|
+
显示 {{ logsData.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(最近30条)' }}
|
|
604
|
+
</div>
|
|
605
|
+
|
|
606
|
+
<!-- 添加缩放控制 -->
|
|
607
|
+
<div class="graph-controls">
|
|
608
|
+
<div class="zoom-controls">
|
|
609
|
+
<el-button
|
|
610
|
+
type="primary"
|
|
611
|
+
circle
|
|
612
|
+
size="small"
|
|
613
|
+
@click="zoomOut"
|
|
614
|
+
:disabled="graphScale <= minScale"
|
|
615
|
+
>
|
|
616
|
+
<el-icon><ZoomOut /></el-icon>
|
|
617
|
+
</el-button>
|
|
618
|
+
|
|
619
|
+
<el-slider
|
|
620
|
+
v-model="graphScale"
|
|
621
|
+
:min="minScale"
|
|
622
|
+
:max="maxScale"
|
|
623
|
+
:step="scaleStep"
|
|
624
|
+
@change="applyScale"
|
|
625
|
+
class="zoom-slider"
|
|
626
|
+
/>
|
|
627
|
+
|
|
628
|
+
<el-button
|
|
629
|
+
type="primary"
|
|
630
|
+
circle
|
|
631
|
+
size="small"
|
|
632
|
+
@click="zoomIn"
|
|
633
|
+
:disabled="graphScale >= maxScale"
|
|
634
|
+
>
|
|
635
|
+
<el-icon><ZoomIn /></el-icon>
|
|
636
|
+
</el-button>
|
|
637
|
+
|
|
638
|
+
<el-button
|
|
639
|
+
type="primary"
|
|
640
|
+
size="small"
|
|
641
|
+
@click="fitGraphToContainer"
|
|
642
|
+
>
|
|
643
|
+
自适应大小
|
|
644
|
+
</el-button>
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
<div class="scale-info">
|
|
648
|
+
缩放: {{ Math.round(graphScale * 100) }}%
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
|
|
652
|
+
<div ref="graphContainer" class="graph-container"></div>
|
|
391
653
|
</div>
|
|
392
654
|
|
|
393
|
-
<!--
|
|
394
|
-
<div
|
|
395
|
-
<div class="
|
|
396
|
-
<
|
|
397
|
-
type="
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
@change="applyScale"
|
|
411
|
-
class="zoom-slider"
|
|
412
|
-
/>
|
|
413
|
-
|
|
414
|
-
<el-button
|
|
415
|
-
type="primary"
|
|
416
|
-
:icon="ZoomIn"
|
|
417
|
-
circle
|
|
418
|
-
size="small"
|
|
419
|
-
@click="zoomIn"
|
|
420
|
-
:disabled="graphScale >= maxScale"
|
|
421
|
-
/>
|
|
655
|
+
<!-- 表格视图 -->
|
|
656
|
+
<div v-else>
|
|
657
|
+
<div class="history-controls">
|
|
658
|
+
<div class="history-stats">
|
|
659
|
+
<el-tag type="info" effect="plain" size="large" class="record-count">
|
|
660
|
+
<template #icon>
|
|
661
|
+
<el-icon><Document /></el-icon>
|
|
662
|
+
</template>
|
|
663
|
+
显示 {{ filteredLogs.length }}/{{ logs.length }} 条记录
|
|
664
|
+
<el-tag v-if="!showAllCommits" type="warning" size="small" effect="plain" style="margin-left: 5px">
|
|
665
|
+
最近30条
|
|
666
|
+
</el-tag>
|
|
667
|
+
<el-tag v-else type="success" size="small" effect="plain" style="margin-left: 5px">
|
|
668
|
+
全部
|
|
669
|
+
</el-tag>
|
|
670
|
+
</el-tag>
|
|
671
|
+
</div>
|
|
422
672
|
|
|
423
|
-
<
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
673
|
+
<div class="filter-actions">
|
|
674
|
+
<el-button
|
|
675
|
+
:type="filterVisible ? 'primary' : 'default'"
|
|
676
|
+
size="default"
|
|
677
|
+
@click="filterVisible = !filterVisible"
|
|
678
|
+
>
|
|
679
|
+
<template #icon>
|
|
680
|
+
<el-icon>
|
|
681
|
+
<Filter />
|
|
682
|
+
</el-icon>
|
|
683
|
+
</template>
|
|
684
|
+
筛选
|
|
685
|
+
<el-badge v-if="filteredLogs.length !== logs.length" :value="filteredLogs.length" class="filter-badge" />
|
|
686
|
+
</el-button>
|
|
687
|
+
</div>
|
|
430
688
|
</div>
|
|
431
689
|
|
|
432
|
-
|
|
433
|
-
|
|
690
|
+
<!-- 筛选条件面板 -->
|
|
691
|
+
<div v-if="filterVisible" class="filter-panel">
|
|
692
|
+
<div class="filter-form">
|
|
693
|
+
<div class="filter-item">
|
|
694
|
+
<div class="filter-label">作者:</div>
|
|
695
|
+
<el-select
|
|
696
|
+
v-model="authorFilter"
|
|
697
|
+
placeholder="选择作者"
|
|
698
|
+
clearable
|
|
699
|
+
filterable
|
|
700
|
+
class="filter-input"
|
|
701
|
+
>
|
|
702
|
+
<el-option
|
|
703
|
+
v-for="author in availableAuthors"
|
|
704
|
+
:key="author"
|
|
705
|
+
:label="author"
|
|
706
|
+
:value="author"
|
|
707
|
+
/>
|
|
708
|
+
</el-select>
|
|
709
|
+
</div>
|
|
710
|
+
|
|
711
|
+
<div class="filter-item">
|
|
712
|
+
<div class="filter-label">提交信息包含:</div>
|
|
713
|
+
<el-input
|
|
714
|
+
v-model="messageFilter"
|
|
715
|
+
placeholder="关键词"
|
|
716
|
+
clearable
|
|
717
|
+
class="filter-input"
|
|
718
|
+
/>
|
|
719
|
+
</div>
|
|
720
|
+
|
|
721
|
+
<div class="filter-item">
|
|
722
|
+
<div class="filter-label">日期范围:</div>
|
|
723
|
+
<el-date-picker
|
|
724
|
+
v-model="dateRangeFilter"
|
|
725
|
+
type="daterange"
|
|
726
|
+
range-separator="至"
|
|
727
|
+
start-placeholder="开始日期"
|
|
728
|
+
end-placeholder="结束日期"
|
|
729
|
+
format="YYYY-MM-DD"
|
|
730
|
+
value-format="YYYY-MM-DD"
|
|
731
|
+
class="filter-input date-range"
|
|
732
|
+
/>
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
<div class="filter-actions">
|
|
736
|
+
<el-button type="info" size="small" @click="resetFilters">重置</el-button>
|
|
737
|
+
</div>
|
|
738
|
+
</div>
|
|
434
739
|
</div>
|
|
740
|
+
|
|
741
|
+
<el-table :data="filteredLogs" style="width: 100%" stripe border v-loading="isLoading">
|
|
742
|
+
<el-table-column label="提交哈希" width="100" resizable>
|
|
743
|
+
<template #default="scope">
|
|
744
|
+
<span class="commit-hash" @click="viewCommitDetail(scope.row)">{{ scope.row.hash.substring(0, 7) }}</span>
|
|
745
|
+
</template>
|
|
746
|
+
</el-table-column>
|
|
747
|
+
<el-table-column prop="date" label="日期" width="120" resizable />
|
|
748
|
+
<el-table-column label="作者" width="120" resizable>
|
|
749
|
+
<template #default="scope">
|
|
750
|
+
<el-tooltip :content="scope.row.email" placement="top" :hide-after="1000">
|
|
751
|
+
<span class="author-name">{{ scope.row.author }}</span>
|
|
752
|
+
</el-tooltip>
|
|
753
|
+
</template>
|
|
754
|
+
</el-table-column>
|
|
755
|
+
<el-table-column label="分支" width="180" resizable>
|
|
756
|
+
<template #default="scope">
|
|
757
|
+
<div v-if="scope.row.branch" class="branch-container">
|
|
758
|
+
<el-tag
|
|
759
|
+
v-for="(ref, index) in scope.row.branch.split(',')"
|
|
760
|
+
:key="index"
|
|
761
|
+
size="small"
|
|
762
|
+
:type="getBranchTagType(ref)"
|
|
763
|
+
class="branch-tag"
|
|
764
|
+
>
|
|
765
|
+
{{ formatBranchName(ref) }}
|
|
766
|
+
</el-tag>
|
|
767
|
+
</div>
|
|
768
|
+
</template>
|
|
769
|
+
</el-table-column>
|
|
770
|
+
<el-table-column prop="message" label="提交信息" min-width="250" />
|
|
771
|
+
</el-table>
|
|
435
772
|
</div>
|
|
436
|
-
|
|
437
|
-
<div ref="graphContainer" class="graph-container"></div>
|
|
438
773
|
</div>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
774
|
+
</div>
|
|
775
|
+
|
|
776
|
+
<!-- 提交详情弹窗 -->
|
|
777
|
+
<el-dialog
|
|
778
|
+
v-model="commitDetailVisible"
|
|
779
|
+
:title="`提交详情: ${selectedCommit?.hash ? selectedCommit.hash.substring(0, 7) : '未知'}`"
|
|
780
|
+
width="80%"
|
|
781
|
+
destroy-on-close
|
|
782
|
+
class="commit-detail-dialog"
|
|
783
|
+
>
|
|
784
|
+
<div v-loading="isLoadingCommitDetail" class="commit-detail-container">
|
|
785
|
+
<!-- 提交基本信息 -->
|
|
786
|
+
<div v-if="selectedCommit" class="commit-info">
|
|
787
|
+
<div class="commit-info-header">
|
|
788
|
+
<div class="info-item">
|
|
789
|
+
<span class="item-label">哈希:</span>
|
|
790
|
+
<span class="item-value">{{ selectedCommit.hash }}</span>
|
|
791
|
+
</div>
|
|
792
|
+
<div class="info-item">
|
|
793
|
+
<span class="item-label">作者:</span>
|
|
794
|
+
<span class="item-value">{{ selectedCommit.author }} <{{ selectedCommit.email }}></span>
|
|
795
|
+
</div>
|
|
796
|
+
<div class="info-item">
|
|
797
|
+
<span class="item-label">日期:</span>
|
|
798
|
+
<span class="item-value">{{ selectedCommit.date }}</span>
|
|
799
|
+
</div>
|
|
800
|
+
</div>
|
|
801
|
+
<div class="commit-message-container">
|
|
802
|
+
<div class="message-label">提交信息:</div>
|
|
803
|
+
<div class="message-content" v-html="formatCommitMessage(selectedCommit.message).replace(/\n/g, '<br>')"></div>
|
|
804
|
+
</div>
|
|
805
|
+
</div>
|
|
806
|
+
|
|
807
|
+
<!-- 变更文件列表和差异 -->
|
|
808
|
+
<div class="commit-files-diff">
|
|
809
|
+
<div class="files-list">
|
|
810
|
+
<h3>变更文件</h3>
|
|
811
|
+
<el-empty v-if="commitFiles.length === 0" description="没有找到变更文件"></el-empty>
|
|
812
|
+
<ul v-else>
|
|
813
|
+
<li
|
|
814
|
+
v-for="file in commitFiles"
|
|
815
|
+
:key="file"
|
|
816
|
+
:class="{ 'active-file': file === selectedCommitFile }"
|
|
817
|
+
@click="getCommitFileDiff(selectedCommit!.hash, file)"
|
|
818
|
+
>
|
|
819
|
+
{{ file }}
|
|
820
|
+
</li>
|
|
821
|
+
</ul>
|
|
822
|
+
</div>
|
|
823
|
+
<div class="file-diff">
|
|
824
|
+
<h3 v-if="selectedCommitFile">文件差异: {{ selectedCommitFile }}</h3>
|
|
825
|
+
<h3 v-else>文件差异</h3>
|
|
826
|
+
<el-empty v-if="!commitDiff && !isLoadingCommitDetail" description="选择文件查看差异"></el-empty>
|
|
827
|
+
<div v-else-if="commitDiff" v-html="formatDiff(commitDiff)" class="diff-content"></div>
|
|
828
|
+
</div>
|
|
444
829
|
</div>
|
|
445
|
-
<el-table :data="logs" style="width: 100%" stripe border v-loading="isLoading">
|
|
446
|
-
<el-table-column prop="hash" label="提交哈希" width="100" resizable />
|
|
447
|
-
<el-table-column prop="date" label="日期" width="180" resizable />
|
|
448
|
-
<el-table-column label="作者" width="200" resizable>
|
|
449
|
-
<template #default="scope">
|
|
450
|
-
{{ scope.row.author }} <{{ scope.row.email }}>
|
|
451
|
-
</template>
|
|
452
|
-
</el-table-column>
|
|
453
|
-
<el-table-column label="分支" width="180" resizable>
|
|
454
|
-
<template #default="scope">
|
|
455
|
-
<div v-if="scope.row.branch" class="branch-container">
|
|
456
|
-
<el-tag
|
|
457
|
-
v-for="(ref, index) in scope.row.branch.split(',')"
|
|
458
|
-
:key="index"
|
|
459
|
-
size="small"
|
|
460
|
-
:type="getBranchTagType(ref)"
|
|
461
|
-
class="branch-tag"
|
|
462
|
-
>
|
|
463
|
-
{{ formatBranchName(ref) }}
|
|
464
|
-
</el-tag>
|
|
465
|
-
</div>
|
|
466
|
-
</template>
|
|
467
|
-
</el-table-column>
|
|
468
|
-
<el-table-column prop="message" label="提交信息" min-width="250" />
|
|
469
|
-
</el-table>
|
|
470
830
|
</div>
|
|
471
|
-
</
|
|
831
|
+
</el-dialog>
|
|
472
832
|
</div>
|
|
473
833
|
</template>
|
|
474
834
|
|
|
475
835
|
<style scoped>
|
|
836
|
+
.card {
|
|
837
|
+
background-color: white;
|
|
838
|
+
border-radius: 8px;
|
|
839
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
|
|
840
|
+
height: 100%;
|
|
841
|
+
width: 100%;
|
|
842
|
+
display: flex;
|
|
843
|
+
flex-direction: column;
|
|
844
|
+
border: 1px solid rgba(0, 0, 0, 0.03);
|
|
845
|
+
overflow: hidden;
|
|
846
|
+
}
|
|
847
|
+
|
|
476
848
|
.log-header {
|
|
477
849
|
display: flex;
|
|
478
850
|
justify-content: space-between;
|
|
479
851
|
align-items: center;
|
|
480
|
-
margin-bottom:
|
|
852
|
+
margin-bottom: 0;
|
|
853
|
+
padding: 8px 16px;
|
|
854
|
+
background-color: white;
|
|
855
|
+
border-bottom: 1px solid #f0f0f0;
|
|
856
|
+
position: sticky;
|
|
857
|
+
top: 0;
|
|
858
|
+
z-index: 100;
|
|
859
|
+
height: 36px;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.log-header h2 {
|
|
863
|
+
margin: 0;
|
|
864
|
+
font-size: 16px;
|
|
865
|
+
font-weight: 500;
|
|
481
866
|
}
|
|
482
867
|
|
|
483
868
|
.log-actions {
|
|
@@ -485,6 +870,28 @@ function fitGraphToContainer() {
|
|
|
485
870
|
gap: 8px;
|
|
486
871
|
}
|
|
487
872
|
|
|
873
|
+
.content-area {
|
|
874
|
+
padding: 0 20px 20px 20px;
|
|
875
|
+
overflow-y: auto;
|
|
876
|
+
flex: 1;
|
|
877
|
+
min-height: 100px;
|
|
878
|
+
height: calc(100% - 52px);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/* 优化表格区域 */
|
|
882
|
+
.el-table {
|
|
883
|
+
--el-table-border-color: #f0f0f0;
|
|
884
|
+
--el-table-header-bg-color: #f8f9fa;
|
|
885
|
+
border-radius: 4px;
|
|
886
|
+
overflow: hidden;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/* 统一按钮间距 */
|
|
890
|
+
.log-actions {
|
|
891
|
+
display: flex;
|
|
892
|
+
gap: 12px;
|
|
893
|
+
}
|
|
894
|
+
|
|
488
895
|
.branch-container {
|
|
489
896
|
display: flex;
|
|
490
897
|
flex-wrap: wrap;
|
|
@@ -550,13 +957,18 @@ function fitGraphToContainer() {
|
|
|
550
957
|
.refresh-notification {
|
|
551
958
|
background-color: #f0f9eb;
|
|
552
959
|
color: #67c23a;
|
|
553
|
-
padding:
|
|
554
|
-
border-radius:
|
|
555
|
-
margin-bottom: 10px;
|
|
556
|
-
text-align: center;
|
|
960
|
+
padding: 10px 15px;
|
|
961
|
+
border-radius: 8px;
|
|
557
962
|
font-size: 14px;
|
|
558
963
|
border-left: 4px solid #67c23a;
|
|
559
964
|
animation: fadeOut 2s forwards;
|
|
965
|
+
position: fixed;
|
|
966
|
+
top: 20px;
|
|
967
|
+
right: 20px;
|
|
968
|
+
z-index: 9999;
|
|
969
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
970
|
+
max-width: 300px;
|
|
971
|
+
text-align: center;
|
|
560
972
|
}
|
|
561
973
|
|
|
562
974
|
@keyframes pulse {
|
|
@@ -566,10 +978,282 @@ function fitGraphToContainer() {
|
|
|
566
978
|
}
|
|
567
979
|
|
|
568
980
|
@keyframes fadeOut {
|
|
569
|
-
0% { opacity:
|
|
570
|
-
|
|
981
|
+
0% { opacity: 0; transform: translateY(-20px); }
|
|
982
|
+
20% { opacity: 1; transform: translateY(0); }
|
|
983
|
+
80% { opacity: 1; }
|
|
571
984
|
100% { opacity: 0; }
|
|
572
985
|
}
|
|
986
|
+
|
|
987
|
+
.author-name {
|
|
988
|
+
cursor: pointer;
|
|
989
|
+
white-space: nowrap;
|
|
990
|
+
overflow: hidden;
|
|
991
|
+
text-overflow: ellipsis;
|
|
992
|
+
display: inline-block;
|
|
993
|
+
max-width: 100%;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
.commit-hash {
|
|
997
|
+
cursor: pointer;
|
|
998
|
+
color: #409EFF;
|
|
999
|
+
font-family: monospace;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.commit-hash:hover {
|
|
1003
|
+
text-decoration: underline;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
.commit-detail-container {
|
|
1007
|
+
display: flex;
|
|
1008
|
+
flex-direction: column;
|
|
1009
|
+
gap: 20px;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.commit-info {
|
|
1013
|
+
padding: 12px;
|
|
1014
|
+
background-color: #f5f7fa;
|
|
1015
|
+
border-radius: 8px;
|
|
1016
|
+
font-size: 14px;
|
|
1017
|
+
display: flex;
|
|
1018
|
+
flex-direction: column;
|
|
1019
|
+
gap: 12px;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.commit-info-header {
|
|
1023
|
+
display: flex;
|
|
1024
|
+
flex-wrap: wrap;
|
|
1025
|
+
gap: 15px;
|
|
1026
|
+
align-items: center;
|
|
1027
|
+
background-color: #fff;
|
|
1028
|
+
padding: 10px;
|
|
1029
|
+
border-radius: 4px;
|
|
1030
|
+
border: 1px solid #e4e7ed;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.info-item {
|
|
1034
|
+
display: flex;
|
|
1035
|
+
align-items: center;
|
|
1036
|
+
gap: 5px;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
.item-label {
|
|
1040
|
+
font-weight: bold;
|
|
1041
|
+
color: #606266;
|
|
1042
|
+
white-space: nowrap;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.item-value {
|
|
1046
|
+
color: #333;
|
|
1047
|
+
word-break: break-all;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.commit-message-container {
|
|
1051
|
+
display: flex;
|
|
1052
|
+
flex-direction: column;
|
|
1053
|
+
gap: 5px;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
.message-label {
|
|
1057
|
+
font-weight: bold;
|
|
1058
|
+
color: #606266;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.message-content {
|
|
1062
|
+
background-color: #fff;
|
|
1063
|
+
padding: 12px;
|
|
1064
|
+
border-radius: 4px;
|
|
1065
|
+
font-family: monospace;
|
|
1066
|
+
white-space: pre-wrap;
|
|
1067
|
+
border-left: 4px solid #409EFF;
|
|
1068
|
+
line-height: 1.5;
|
|
1069
|
+
border: 1px solid #e4e7ed;
|
|
1070
|
+
border-left: 4px solid #409EFF;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.commit-files-diff {
|
|
1074
|
+
margin-top: 5px;
|
|
1075
|
+
display: flex;
|
|
1076
|
+
gap: 20px;
|
|
1077
|
+
height: 60vh;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.files-list {
|
|
1081
|
+
width: 25%;
|
|
1082
|
+
overflow-y: auto;
|
|
1083
|
+
background-color: #f5f7fa;
|
|
1084
|
+
border-radius: 8px;
|
|
1085
|
+
padding: 10px;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.files-list h3 {
|
|
1089
|
+
margin-top: 0;
|
|
1090
|
+
padding-bottom: 10px;
|
|
1091
|
+
border-bottom: 1px solid #dcdfe6;
|
|
1092
|
+
font-size: 16px;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.files-list ul {
|
|
1096
|
+
list-style: none;
|
|
1097
|
+
padding: 0;
|
|
1098
|
+
margin: 0;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.files-list li {
|
|
1102
|
+
padding: 8px 10px;
|
|
1103
|
+
cursor: pointer;
|
|
1104
|
+
border-radius: 4px;
|
|
1105
|
+
margin-bottom: 5px;
|
|
1106
|
+
font-family: monospace;
|
|
1107
|
+
white-space: nowrap;
|
|
1108
|
+
overflow: hidden;
|
|
1109
|
+
text-overflow: ellipsis;
|
|
1110
|
+
font-size: 13px;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.files-list li:hover {
|
|
1114
|
+
background-color: #ecf5ff;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
.files-list li.active-file {
|
|
1118
|
+
background-color: #409eff;
|
|
1119
|
+
color: white;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.file-diff {
|
|
1123
|
+
flex: 1;
|
|
1124
|
+
display: flex;
|
|
1125
|
+
flex-direction: column;
|
|
1126
|
+
background-color: #f5f7fa;
|
|
1127
|
+
border-radius: 8px;
|
|
1128
|
+
padding: 10px;
|
|
1129
|
+
overflow: hidden;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.file-diff h3 {
|
|
1133
|
+
margin-top: 0;
|
|
1134
|
+
padding-bottom: 10px;
|
|
1135
|
+
border-bottom: 1px solid #dcdfe6;
|
|
1136
|
+
font-size: 16px;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.diff-content {
|
|
1140
|
+
flex: 1;
|
|
1141
|
+
overflow-y: auto;
|
|
1142
|
+
background-color: #fff;
|
|
1143
|
+
padding: 10px;
|
|
1144
|
+
border-radius: 4px;
|
|
1145
|
+
font-family: monospace;
|
|
1146
|
+
font-size: 13px;
|
|
1147
|
+
line-height: 1.5;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
.diff-header {
|
|
1151
|
+
font-weight: bold;
|
|
1152
|
+
color: #409EFF;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
.diff-old-file {
|
|
1156
|
+
color: #E6A23C;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.diff-new-file {
|
|
1160
|
+
color: #67C23A;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
.diff-hunk-header {
|
|
1164
|
+
font-weight: bold;
|
|
1165
|
+
color: #409EFF;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.diff-added {
|
|
1169
|
+
background-color: #f0f9eb;
|
|
1170
|
+
color: #67C23A;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.diff-removed {
|
|
1174
|
+
background-color: #fef2f2;
|
|
1175
|
+
color: #F56C6C;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
.diff-context {
|
|
1179
|
+
background-color: #f5f7fa;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/* 减小对话框的顶部边距 */
|
|
1183
|
+
:deep(.commit-detail-dialog) {
|
|
1184
|
+
--el-dialog-margin-top: 5vh;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.history-controls {
|
|
1188
|
+
display: flex;
|
|
1189
|
+
justify-content: space-between;
|
|
1190
|
+
align-items: center;
|
|
1191
|
+
margin-bottom: 15px;
|
|
1192
|
+
padding: 0;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.history-stats {
|
|
1196
|
+
display: flex;
|
|
1197
|
+
align-items: center;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.record-count {
|
|
1201
|
+
display: flex;
|
|
1202
|
+
align-items: center;
|
|
1203
|
+
height: 36px;
|
|
1204
|
+
padding-left: 15px;
|
|
1205
|
+
padding-right: 15px;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.record-count :deep(.el-icon) {
|
|
1209
|
+
margin-right: 6px;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
.filter-badge :deep(.el-badge__content) {
|
|
1213
|
+
background-color: #409EFF;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.filter-panel {
|
|
1217
|
+
background-color: #f5f7fa;
|
|
1218
|
+
border-radius: 8px;
|
|
1219
|
+
padding: 15px;
|
|
1220
|
+
margin-bottom: 15px;
|
|
1221
|
+
border: 1px solid #e4e7ed;
|
|
1222
|
+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.filter-form {
|
|
1226
|
+
display: flex;
|
|
1227
|
+
flex-wrap: wrap;
|
|
1228
|
+
gap: 15px;
|
|
1229
|
+
align-items: flex-end;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
.filter-item {
|
|
1233
|
+
display: flex;
|
|
1234
|
+
flex-direction: column;
|
|
1235
|
+
gap: 5px;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.filter-label {
|
|
1239
|
+
font-size: 13px;
|
|
1240
|
+
color: #606266;
|
|
1241
|
+
font-weight: bold;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.filter-input {
|
|
1245
|
+
width: 200px;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.filter-input.date-range {
|
|
1249
|
+
width: 350px;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
.filter-actions {
|
|
1253
|
+
display: flex;
|
|
1254
|
+
align-items: center;
|
|
1255
|
+
gap: 10px;
|
|
1256
|
+
}
|
|
573
1257
|
</style>
|
|
574
1258
|
|
|
575
1259
|
/* 添加表格列调整样式 */
|