zen-gitsync 2.0.4 → 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/components.d.ts +1 -0
- package/src/ui/client/src/App.vue +626 -186
- package/src/ui/client/src/components/CommitForm.vue +508 -401
- package/src/ui/client/src/components/GitStatus.vue +590 -224
- package/src/ui/client/src/components/LogList.vue +809 -83
- package/src/ui/client/src/stores/gitLogStore.ts +440 -125
- package/src/ui/client/src/stores/gitStore.ts +86 -1
- 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 +375 -44
- package/src/ui/public/assets/index-D5irnfho.css +0 -1
- package/src/ui/public/assets/index-DBck3u67.js +0 -8
- package/src/ui/public/assets/vendor-CdJ34PvS.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,15 +30,88 @@ 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
|
|
39
47
|
const maxScale = 1.5
|
|
40
48
|
const scaleStep = 0.1
|
|
41
49
|
|
|
50
|
+
// 添加日志被刷新的提示状态
|
|
51
|
+
const logRefreshed = ref(false)
|
|
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
|
+
|
|
42
115
|
// 加载提交历史
|
|
43
116
|
async function loadLog(all = false) {
|
|
44
117
|
// 从gitStore获取仓库状态
|
|
@@ -95,6 +168,11 @@ async function loadLog(all = false) {
|
|
|
95
168
|
|
|
96
169
|
console.log(`logsData长度: ${logsData.length}`) // 添加调试日志
|
|
97
170
|
|
|
171
|
+
// 设置刷新提示状态
|
|
172
|
+
logRefreshed.value = true
|
|
173
|
+
// 2秒后隐藏提示
|
|
174
|
+
setTimeout(() => { logRefreshed.value = false }, 2000)
|
|
175
|
+
|
|
98
176
|
// 加载完数据后渲染图表
|
|
99
177
|
if (showGraphView.value) {
|
|
100
178
|
setTimeout(renderGraph, 0)
|
|
@@ -337,10 +415,148 @@ function fitGraphToContainer() {
|
|
|
337
415
|
|
|
338
416
|
applyScale()
|
|
339
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
|
+
}
|
|
340
550
|
</script>
|
|
341
551
|
|
|
342
552
|
<template>
|
|
343
553
|
<div class="card">
|
|
554
|
+
<!-- 添加刷新提示,移到最外层 -->
|
|
555
|
+
<div v-if="logRefreshed" class="refresh-notification">
|
|
556
|
+
提交历史已刷新
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
<!-- 固定头部区域 -->
|
|
344
560
|
<div class="log-header">
|
|
345
561
|
<h2>提交历史</h2>
|
|
346
562
|
<div class="log-actions">
|
|
@@ -349,6 +565,9 @@ function fitGraphToContainer() {
|
|
|
349
565
|
size="small"
|
|
350
566
|
@click="toggleViewMode"
|
|
351
567
|
>
|
|
568
|
+
<template #icon>
|
|
569
|
+
<el-icon><component :is="showGraphView ? Document : TrendCharts" /></el-icon>
|
|
570
|
+
</template>
|
|
352
571
|
{{ showGraphView ? '表格视图' : '图表视图' }}
|
|
353
572
|
</el-button>
|
|
354
573
|
<el-button
|
|
@@ -357,113 +576,293 @@ function fitGraphToContainer() {
|
|
|
357
576
|
@click="toggleAllCommits"
|
|
358
577
|
:loading="isLoading"
|
|
359
578
|
>
|
|
579
|
+
<template #icon>
|
|
580
|
+
<el-icon><component :is="showAllCommits ? List : More" /></el-icon>
|
|
581
|
+
</template>
|
|
360
582
|
{{ showAllCommits ? '显示最近30条' : '显示所有提交' }}
|
|
361
583
|
</el-button>
|
|
362
584
|
<el-button
|
|
363
|
-
:icon="RefreshRight"
|
|
364
585
|
circle
|
|
365
586
|
size="small"
|
|
366
587
|
@click="refreshLog()"
|
|
367
588
|
:loading="isLoading"
|
|
368
|
-
|
|
589
|
+
:class="{ 'refresh-button-animated': logRefreshed }"
|
|
590
|
+
>
|
|
591
|
+
<el-icon><RefreshRight /></el-icon>
|
|
592
|
+
</el-button>
|
|
369
593
|
</div>
|
|
370
594
|
</div>
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
<div v-if="
|
|
375
|
-
|
|
376
|
-
|
|
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>
|
|
377
653
|
</div>
|
|
378
654
|
|
|
379
|
-
<!--
|
|
380
|
-
<div
|
|
381
|
-
<div class="
|
|
382
|
-
<
|
|
383
|
-
type="
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
@change="applyScale"
|
|
397
|
-
class="zoom-slider"
|
|
398
|
-
/>
|
|
399
|
-
|
|
400
|
-
<el-button
|
|
401
|
-
type="primary"
|
|
402
|
-
:icon="ZoomIn"
|
|
403
|
-
circle
|
|
404
|
-
size="small"
|
|
405
|
-
@click="zoomIn"
|
|
406
|
-
:disabled="graphScale >= maxScale"
|
|
407
|
-
/>
|
|
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>
|
|
408
672
|
|
|
409
|
-
<
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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>
|
|
416
688
|
</div>
|
|
417
689
|
|
|
418
|
-
|
|
419
|
-
|
|
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>
|
|
420
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>
|
|
421
772
|
</div>
|
|
422
|
-
|
|
423
|
-
<div ref="graphContainer" class="graph-container"></div>
|
|
424
773
|
</div>
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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>
|
|
430
829
|
</div>
|
|
431
|
-
<el-table :data="logs" style="width: 100%" stripe border v-loading="isLoading">
|
|
432
|
-
<el-table-column prop="hash" label="提交哈希" width="100" resizable />
|
|
433
|
-
<el-table-column prop="date" label="日期" width="180" resizable />
|
|
434
|
-
<el-table-column label="作者" width="200" resizable>
|
|
435
|
-
<template #default="scope">
|
|
436
|
-
{{ scope.row.author }} <{{ scope.row.email }}>
|
|
437
|
-
</template>
|
|
438
|
-
</el-table-column>
|
|
439
|
-
<el-table-column label="分支" width="180" resizable>
|
|
440
|
-
<template #default="scope">
|
|
441
|
-
<div v-if="scope.row.branch" class="branch-container">
|
|
442
|
-
<el-tag
|
|
443
|
-
v-for="(ref, index) in scope.row.branch.split(',')"
|
|
444
|
-
:key="index"
|
|
445
|
-
size="small"
|
|
446
|
-
:type="getBranchTagType(ref)"
|
|
447
|
-
class="branch-tag"
|
|
448
|
-
>
|
|
449
|
-
{{ formatBranchName(ref) }}
|
|
450
|
-
</el-tag>
|
|
451
|
-
</div>
|
|
452
|
-
</template>
|
|
453
|
-
</el-table-column>
|
|
454
|
-
<el-table-column prop="message" label="提交信息" min-width="250" />
|
|
455
|
-
</el-table>
|
|
456
830
|
</div>
|
|
457
|
-
</
|
|
831
|
+
</el-dialog>
|
|
458
832
|
</div>
|
|
459
833
|
</template>
|
|
460
834
|
|
|
461
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
|
+
|
|
462
848
|
.log-header {
|
|
463
849
|
display: flex;
|
|
464
850
|
justify-content: space-between;
|
|
465
851
|
align-items: center;
|
|
466
|
-
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;
|
|
467
866
|
}
|
|
468
867
|
|
|
469
868
|
.log-actions {
|
|
@@ -471,6 +870,28 @@ function fitGraphToContainer() {
|
|
|
471
870
|
gap: 8px;
|
|
472
871
|
}
|
|
473
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
|
+
|
|
474
895
|
.branch-container {
|
|
475
896
|
display: flex;
|
|
476
897
|
flex-wrap: wrap;
|
|
@@ -528,6 +949,311 @@ function fitGraphToContainer() {
|
|
|
528
949
|
font-size: 14px;
|
|
529
950
|
color: #606266;
|
|
530
951
|
}
|
|
952
|
+
|
|
953
|
+
.refresh-button-animated {
|
|
954
|
+
animation: pulse 1s;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.refresh-notification {
|
|
958
|
+
background-color: #f0f9eb;
|
|
959
|
+
color: #67c23a;
|
|
960
|
+
padding: 10px 15px;
|
|
961
|
+
border-radius: 8px;
|
|
962
|
+
font-size: 14px;
|
|
963
|
+
border-left: 4px solid #67c23a;
|
|
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;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
@keyframes pulse {
|
|
975
|
+
0% { transform: scale(1); }
|
|
976
|
+
50% { transform: scale(1.2); }
|
|
977
|
+
100% { transform: scale(1); }
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
@keyframes fadeOut {
|
|
981
|
+
0% { opacity: 0; transform: translateY(-20px); }
|
|
982
|
+
20% { opacity: 1; transform: translateY(0); }
|
|
983
|
+
80% { opacity: 1; }
|
|
984
|
+
100% { opacity: 0; }
|
|
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
|
+
}
|
|
531
1257
|
</style>
|
|
532
1258
|
|
|
533
1259
|
/* 添加表格列调整样式 */
|