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.
@@ -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(true)
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, '&amp;')
510
+ .replace(/</g, '&lt;')
511
+ .replace(/>/g, '&gt;')
512
+ .replace(/"/g, '&quot;')
513
+ .replace(/'/g, '&#039;');
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
- <div v-if="errorMessage">{{ errorMessage }}</div>
372
- <div v-else>
373
- <!-- 图表视图 -->
374
- <div v-if="showGraphView" class="graph-view">
375
- <div class="commit-count" v-if="logsData.length > 0">
376
- 显示 {{ logsData.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(最近30条)' }}
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 class="graph-controls">
381
- <div class="zoom-controls">
382
- <el-button
383
- type="primary"
384
- :icon="ZoomOut"
385
- circle
386
- size="small"
387
- @click="zoomOut"
388
- :disabled="graphScale <= minScale"
389
- />
390
-
391
- <el-slider
392
- v-model="graphScale"
393
- :min="minScale"
394
- :max="maxScale"
395
- :step="scaleStep"
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
- <el-button
410
- type="primary"
411
- size="small"
412
- @click="fitGraphToContainer"
413
- >
414
- 自适应大小
415
- </el-button>
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
- <div class="scale-info">
419
- 缩放: {{ Math.round(graphScale * 100) }}%
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
- <div v-else>
428
- <div class="commit-count" v-if="logs.length > 0">
429
- 显示 {{ logs.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(最近30条)' }}
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 }} &lt;{{ selectedCommit.email }}&gt;</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 }} &lt;{{ scope.row.email }}&gt;
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
- </div>
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: 10px;
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
  /* 添加表格列调整样式 */