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,8 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { ref, onMounted, computed } from 'vue'
2
+ import { ref, onMounted, computed, watch } from 'vue'
3
3
  import { ElMessage, ElMessageBox } from 'element-plus'
4
4
  // import { io } from 'socket.io-client'
5
- import { Refresh, ArrowLeft, ArrowRight, Folder, Document, ArrowUp, RefreshRight } from '@element-plus/icons-vue'
5
+ import { Refresh, ArrowLeft, ArrowRight, Folder, Document, ArrowUp, RefreshRight, Check, Close } from '@element-plus/icons-vue'
6
6
  import { useGitLogStore } from '../stores/gitLogStore'
7
7
  import { useGitStore } from '../stores/gitStore'
8
8
 
@@ -16,10 +16,11 @@ const props = defineProps({
16
16
 
17
17
  const gitLogStore = useGitLogStore()
18
18
  const gitStore = useGitStore()
19
- const status = ref('加载中...')
19
+ // 移除本地status定义,直接使用store中的statusText
20
+ // const status = ref('加载中...')
20
21
  // const socket = io()
21
22
  const isRefreshing = computed(() => gitLogStore.isLoadingStatus)
22
- const fileList = ref<{path: string, type: string}[]>([])
23
+ // 移除本地fileList定义,改用store中的fileList
23
24
  const selectedFile = ref('')
24
25
  const diffContent = ref('')
25
26
  const diffDialogVisible = ref(false)
@@ -37,29 +38,6 @@ const directoryItems = ref<{name: string, path: string, type: string}[]>([])
37
38
  const isBrowsing = ref(false)
38
39
  const browseErrorMessage = ref('')
39
40
 
40
- // 解析 git status 输出,提取文件及类型
41
- function parseStatus(statusText: string) {
42
- if (statusText === undefined) return
43
- const lines = statusText.split('\n')
44
- const files: {path: string, type: string}[] = []
45
- for (const line of lines) {
46
- // 匹配常见的 git status --porcelain 格式
47
- // M: 修改, A: 新增, D: 删除, ??: 未跟踪
48
- const match = line.match(/^([ MADRCU\?]{2})\s+(.+)$/)
49
- if (match) {
50
- let type = ''
51
- const code = match[1].trim()
52
- if (code === 'M' || code === 'MM' || code === 'AM' || code === 'RM') type = 'modified'
53
- else if (code === 'A' || code === 'AA') type = 'added'
54
- else if (code === 'D' || code === 'AD' || code === 'DA') type = 'deleted'
55
- else if (code === '??') type = 'untracked'
56
- else type = 'other'
57
- files.push({ path: match[2], type })
58
- }
59
- }
60
- fileList.value = files
61
- }
62
-
63
41
  const currentDirectory = ref(props.initialDirectory || '');
64
42
  async function loadStatus() {
65
43
  try {
@@ -72,26 +50,17 @@ async function loadStatus() {
72
50
 
73
51
  // 如果不是Git仓库,直接显示提示并返回
74
52
  if (!gitStore.isGitRepo) {
75
- status.value = '当前目录不是一个Git仓库'
76
- fileList.value = []
77
53
  return
78
54
  }
79
55
 
80
- // 直接获取Git状态,不再通过store调用
81
- const response = await fetch('/api/status')
82
- const data = await response.json()
83
- status.value = data.status
84
-
85
- const response_porcelain = await fetch('/api/status_porcelain')
86
- const data_porcelain = await response_porcelain.json()
87
- parseStatus(data_porcelain.status)
56
+ // 使用gitLogStore获取Git状态
57
+ await gitLogStore.fetchStatus()
58
+
88
59
  ElMessage({
89
60
  message: 'Git 状态已刷新',
90
61
  type: 'success',
91
62
  })
92
63
  } catch (error) {
93
- status.value = '加载状态失败: ' + (error as Error).message
94
- fileList.value = []
95
64
  ElMessage({
96
65
  message: '刷新失败: ' + (error as Error).message,
97
66
  type: 'error',
@@ -145,7 +114,7 @@ async function getFileDiff(filePath: string) {
145
114
  isLoadingDiff.value = true
146
115
  selectedFile.value = filePath
147
116
  // 设置当前文件索引
148
- currentFileIndex.value = fileList.value.findIndex(file => file.path === filePath)
117
+ currentFileIndex.value = gitLogStore.fileList.findIndex(file => file.path === filePath)
149
118
  const response = await fetch(`/api/diff?file=${encodeURIComponent(filePath)}`)
150
119
  const data = await response.json()
151
120
  diffContent.value = data.diff || '没有变更'
@@ -163,19 +132,19 @@ async function getFileDiff(filePath: string) {
163
132
 
164
133
  // 添加切换到上一个文件的方法
165
134
  async function goToPreviousFile() {
166
- if (fileList.value.length === 0 || currentFileIndex.value <= 0) return
135
+ if (gitLogStore.fileList.length === 0 || currentFileIndex.value <= 0) return
167
136
 
168
137
  const newIndex = currentFileIndex.value - 1
169
- const prevFile = fileList.value[newIndex]
138
+ const prevFile = gitLogStore.fileList[newIndex]
170
139
  await getFileDiff(prevFile.path)
171
140
  }
172
141
 
173
142
  // 添加切换到下一个文件的方法
174
143
  async function goToNextFile() {
175
- if (fileList.value.length === 0 || currentFileIndex.value >= fileList.value.length - 1) return
144
+ if (gitLogStore.fileList.length === 0 || currentFileIndex.value >= gitLogStore.fileList.length - 1) return
176
145
 
177
146
  const newIndex = currentFileIndex.value + 1
178
- const nextFile = fileList.value[newIndex]
147
+ const nextFile = gitLogStore.fileList[newIndex]
179
148
  await getFileDiff(nextFile.path)
180
149
  }
181
150
 
@@ -316,9 +285,6 @@ async function changeDirectory() {
316
285
  await loadStatus()
317
286
  } else {
318
287
  ElMessage.warning('当前目录不是一个Git仓库')
319
- status.value = '当前目录不是一个Git仓库'
320
- fileList.value = []
321
-
322
288
  // 清空Git相关状态
323
289
  gitStore.$reset() // 使用pinia的reset方法重置状态
324
290
  }
@@ -337,15 +303,14 @@ function handleFileClick(file: {path: string, type: string}) {
337
303
  getFileDiff(file.path)
338
304
  }
339
305
 
340
- // 文件类型标签显示
341
- function fileTypeLabel(type: string) {
342
- switch (type) {
343
- case 'added': return '新增';
344
- case 'modified': return '修改';
345
- case 'deleted': return '删除';
346
- case 'untracked': return '未跟踪';
347
- default: return '其他';
348
- }
306
+ // 暂存单个文件
307
+ async function stageFile(filePath: string) {
308
+ await gitLogStore.addFileToStage(filePath)
309
+ }
310
+
311
+ // 取消暂存单个文件
312
+ async function unstageFile(filePath: string) {
313
+ await gitLogStore.unstageFile(filePath)
349
314
  }
350
315
 
351
316
  // 刷新Git状态的方法
@@ -383,26 +348,57 @@ async function revertFileChanges(filePath: string) {
383
348
  // 刷新Git状态
384
349
  await loadStatus()
385
350
  } else {
386
- ElMessage.error(result.error || '撤回文件修改失败')
351
+ // 使用自定义错误信息,避免显示undefined
352
+ ElMessage.error(result.error ? `撤回失败: ${result.error}` : '撤回文件修改失败,请重试')
387
353
  }
388
354
  } catch (error) {
389
- // 用户取消或发生错误
390
- if ((error as Error).message !== 'cancel') {
391
- ElMessage.error(`撤回文件修改失败: ${(error as Error).message}`)
355
+ // 用户取消操作不显示错误
356
+ if ((error as any) === 'cancel' || (error as Error).message === 'cancel') {
357
+ // 用户取消操作,不做任何处理,也不显示错误
358
+ return
359
+ }
360
+
361
+ // 其他错误情况才显示错误消息
362
+ // 避免显示undefined错误信息
363
+ const errorMessage = (error as Error).message || '未知错误';
364
+ if (errorMessage !== 'undefined') {
365
+ ElMessage.error(`撤回文件修改失败: ${errorMessage}`)
366
+ } else {
367
+ ElMessage.error('撤回文件修改失败,请重试')
392
368
  }
393
369
  }
394
370
  }
395
371
 
372
+ // 提取文件名和目录
373
+ function getFileName(path: string): string {
374
+ const parts = path.split('/')
375
+ return parts[parts.length - 1]
376
+ }
377
+
378
+ function getFileDirectory(path: string): string {
379
+ const parts = path.split('/')
380
+ if (parts.length <= 1) return ''
381
+
382
+ // 保留所有除最后一个部分的路径
383
+ return parts.slice(0, -1).join('/')
384
+ }
385
+
396
386
  onMounted(() => {
397
387
  // App.vue已经加载了Git相关数据,此时只需加载状态
398
388
  // 如果已有初始目录,则只需加载状态
399
389
  loadStatus()
400
390
  })
401
391
 
392
+ // 监听autoUpdateEnabled的变化,手动调用toggleAutoUpdate
393
+ watch(() => gitLogStore.autoUpdateEnabled, (newValue, oldValue) => {
394
+ console.log(`自动更新状态变更: ${oldValue} -> ${newValue}`)
395
+ // 调用store中的方法来实现服务器通信功能
396
+ gitLogStore.toggleAutoUpdate()
397
+ }, { immediate: false })
398
+
402
399
  // onUnmounted(() => {
403
400
  // socket.disconnect()
404
401
  // })
405
-
406
402
  // 暴露刷新方法给父组件
407
403
  defineExpose({
408
404
  refreshStatus
@@ -411,47 +407,165 @@ defineExpose({
411
407
 
412
408
  <template>
413
409
  <div class="card">
414
- <div class="current-directory">
415
- <el-icon><Folder /></el-icon>
416
- <span>{{ currentDirectory }}</span>
417
- <el-button type="primary" size="small" @click="openDirectoryDialog" plain>
418
- 切换目录
419
- </el-button>
420
- </div>
421
410
  <div class="status-header">
422
- <h2>Git 状态(git status)</h2>
423
- <el-button
424
- type="primary"
425
- :icon="Refresh"
426
- circle
427
- size="small"
428
- @click="refreshStatus"
429
- :loading="isRefreshing"
430
- />
411
+ <h2>Git 状态</h2>
412
+ <div class="header-actions">
413
+ <el-tooltip
414
+ :content="gitLogStore.autoUpdateEnabled ? '禁用自动更新文件状态' : '启用自动更新文件状态'"
415
+ placement="top"
416
+ :hide-after="1000"
417
+ >
418
+ <el-switch
419
+ v-model="gitLogStore.autoUpdateEnabled"
420
+ style="--el-switch-on-color: #67C23A; --el-switch-off-color: #909399; margin-right: 10px;"
421
+ inline-prompt
422
+ :active-icon="Check"
423
+ :inactive-icon="Close"
424
+ class="auto-update-switch"
425
+ />
426
+ </el-tooltip>
427
+ <el-tooltip content="刷新状态" placement="top" :hide-after="1000">
428
+ <el-button
429
+ type="primary"
430
+ :icon="Refresh"
431
+ circle
432
+ size="small"
433
+ @click="refreshStatus"
434
+ :loading="isRefreshing"
435
+ />
436
+ </el-tooltip>
437
+ </div>
431
438
  </div>
432
- <div class="status-box">{{ status }}</div>
433
- <!-- 颜色区分不同类型文件 -->
434
- <div v-if="fileList.length" class="file-list">
435
- <div
436
- v-for="file in fileList"
437
- :key="file.path"
438
- :class="['file-item', file.type]"
439
- >
440
- <div class="file-info" @click="handleFileClick(file)">
441
- <span class="file-type">{{ fileTypeLabel(file.type) }}</span>
442
- <span class="file-path">{{ file.path }}</span>
439
+
440
+ <div class="card-content">
441
+ <div class="current-directory">
442
+ <el-icon><Folder /></el-icon>
443
+ <span>{{ currentDirectory }}</span>
444
+ <el-button type="primary" size="small" @click="openDirectoryDialog" plain>
445
+ 切换目录
446
+ </el-button>
447
+ </div>
448
+
449
+ <div v-if="!gitStore.isGitRepo" class="status-box">
450
+ 当前目录不是一个Git仓库
451
+ </div>
452
+
453
+ <!-- 现代化、简洁的文件列表 -->
454
+ <div v-if="gitLogStore.fileList.length" class="file-list-container">
455
+ <!-- 分组显示文件 -->
456
+ <div v-if="gitLogStore.fileList.some(f => f.type === 'added')" class="file-group">
457
+ <div class="file-group-header">已暂存的更改</div>
458
+ <div class="file-list">
459
+ <div
460
+ v-for="file in gitLogStore.fileList.filter(f => f.type === 'added')"
461
+ :key="file.path"
462
+ class="file-item"
463
+ @click="handleFileClick(file)"
464
+ >
465
+ <div class="file-info">
466
+ <div class="file-path-container">
467
+ <span class="file-name">{{ getFileName(file.path) }}</span>
468
+ <span class="file-directory">{{ getFileDirectory(file.path) }}</span>
469
+ </div>
470
+ </div>
471
+ <div class="file-actions">
472
+ <el-tooltip content="取消暂存" placement="top" :hide-after="1000">
473
+ <el-button
474
+ type="warning"
475
+ size="small"
476
+ circle
477
+ @click.stop="unstageFile(file.path)"
478
+ >-</el-button>
479
+ </el-tooltip>
480
+ </div>
481
+ </div>
482
+ </div>
443
483
  </div>
444
- <div class="file-actions">
445
- <el-tooltip content="撤回修改" placement="top" :hide-after="1000">
446
- <el-button
447
- type="danger"
448
- size="small"
449
- :icon="RefreshRight"
450
- circle
451
- @click.stop="revertFileChanges(file.path)"
452
- />
453
- </el-tooltip>
484
+
485
+ <div v-if="gitLogStore.fileList.some(f => f.type === 'modified' || f.type === 'deleted')" class="file-group">
486
+ <div class="file-group-header">未暂存的更改</div>
487
+ <div class="file-list">
488
+ <div
489
+ v-for="file in gitLogStore.fileList.filter(f => f.type === 'modified' || f.type === 'deleted')"
490
+ :key="file.path"
491
+ class="file-item"
492
+ @click="handleFileClick(file)"
493
+ >
494
+ <div class="file-info">
495
+ <div class="file-status-indicator" :class="file.type"></div>
496
+ <div class="file-path-container">
497
+ <span class="file-name">{{ getFileName(file.path) }}</span>
498
+ <span class="file-directory">{{ getFileDirectory(file.path) }}</span>
499
+ </div>
500
+ </div>
501
+ <div class="file-actions">
502
+ <el-tooltip content="添加到暂存区" placement="top" :hide-after="1000">
503
+ <el-button
504
+ type="success"
505
+ size="small"
506
+ circle
507
+ @click.stop="stageFile(file.path)"
508
+ >+</el-button>
509
+ </el-tooltip>
510
+ <el-tooltip content="撤回修改" placement="top" :hide-after="1000">
511
+ <el-button
512
+ type="danger"
513
+ size="small"
514
+ :icon="RefreshRight"
515
+ circle
516
+ @click.stop="revertFileChanges(file.path)"
517
+ />
518
+ </el-tooltip>
519
+ </div>
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <div v-if="gitLogStore.fileList.some(f => f.type === 'untracked')" class="file-group">
525
+ <div class="file-group-header">未跟踪的文件</div>
526
+ <div class="file-list">
527
+ <div
528
+ v-for="file in gitLogStore.fileList.filter(f => f.type === 'untracked')"
529
+ :key="file.path"
530
+ class="file-item"
531
+ @click="handleFileClick(file)"
532
+ >
533
+ <div class="file-info">
534
+ <div class="file-status-indicator untracked"></div>
535
+ <div class="file-path-container">
536
+ <span class="file-name">{{ getFileName(file.path) }}</span>
537
+ <span class="file-directory">{{ getFileDirectory(file.path) }}</span>
538
+ </div>
539
+ </div>
540
+ <div class="file-actions">
541
+ <el-tooltip content="添加到暂存区" placement="top" :hide-after="1000">
542
+ <el-button
543
+ type="success"
544
+ size="small"
545
+ circle
546
+ @click.stop="stageFile(file.path)"
547
+ >+</el-button>
548
+ </el-tooltip>
549
+ <el-tooltip content="删除文件" placement="top" :hide-after="1000">
550
+ <el-button
551
+ type="danger"
552
+ size="small"
553
+ :icon="Close"
554
+ circle
555
+ @click.stop="revertFileChanges(file.path)"
556
+ />
557
+ </el-tooltip>
558
+ </div>
559
+ </div>
560
+ </div>
561
+ </div>
562
+ </div>
563
+
564
+ <div v-else-if="gitStore.isGitRepo" class="empty-status">
565
+ <div class="empty-icon">
566
+ <el-icon><Document /></el-icon>
454
567
  </div>
568
+ <div class="empty-text">没有检测到任何更改</div>
455
569
  </div>
456
570
  </div>
457
571
 
@@ -514,18 +628,20 @@ defineExpose({
514
628
  </div>
515
629
 
516
630
  <!-- 目录内容列表 -->
517
- <ul class="directory-items">
518
- <li
519
- v-for="item in directoryItems"
520
- :key="item.path"
521
- :class="['directory-item', item.type]"
522
- @click="selectDirectoryItem(item)"
523
- >
524
- <el-icon v-if="item.type === 'directory'"><Folder /></el-icon>
525
- <el-icon v-else><Document /></el-icon>
526
- <span>{{ item.name }}</span>
527
- </li>
528
- </ul>
631
+ <div class="directory-items-container">
632
+ <ul class="directory-items">
633
+ <li
634
+ v-for="item in directoryItems"
635
+ :key="item.path"
636
+ :class="['directory-item', item.type]"
637
+ @click="selectDirectoryItem(item)"
638
+ >
639
+ <el-icon v-if="item.type === 'directory'"><Folder /></el-icon>
640
+ <el-icon v-else><Document /></el-icon>
641
+ <span>{{ item.name }}</span>
642
+ </li>
643
+ </ul>
644
+ </div>
529
645
  </div>
530
646
  </el-dialog>
531
647
 
@@ -535,6 +651,7 @@ defineExpose({
535
651
  :title="`文件差异: ${selectedFile}`"
536
652
  width="80%"
537
653
  destroy-on-close
654
+ class="diff-dialog"
538
655
  >
539
656
  <div v-loading="isLoadingDiff" class="diff-content">
540
657
  <div v-if="diffContent" v-html="formatDiff(diffContent)" class="diff-formatted"></div>
@@ -546,14 +663,14 @@ defineExpose({
546
663
  <el-button
547
664
  :icon="ArrowLeft"
548
665
  @click="goToPreviousFile"
549
- :disabled="currentFileIndex <= 0 || fileList.length === 0"
666
+ :disabled="currentFileIndex <= 0 || gitLogStore.fileList.length === 0"
550
667
  circle
551
668
  />
552
- <span class="file-counter">{{ currentFileIndex + 1 }} / {{ fileList.length }}</span>
669
+ <span class="file-counter">{{ currentFileIndex + 1 }} / {{ gitLogStore.fileList.length }}</span>
553
670
  <el-button
554
671
  :icon="ArrowRight"
555
672
  @click="goToNextFile"
556
- :disabled="currentFileIndex >= fileList.length - 1 || fileList.length === 0"
673
+ :disabled="currentFileIndex >= gitLogStore.fileList.length - 1 || gitLogStore.fileList.length === 0"
557
674
  circle
558
675
  />
559
676
  </div>
@@ -565,215 +682,418 @@ defineExpose({
565
682
  .card {
566
683
  background-color: #fff;
567
684
  border-radius: 8px;
568
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
569
- padding: 20px;
570
- margin-bottom: 20px;
685
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
686
+ border: 1px solid rgba(0, 0, 0, 0.03);
687
+ height: 100%;
688
+ width: 100%;
689
+ display: flex;
690
+ flex-direction: column;
691
+ overflow: hidden;
571
692
  }
572
693
 
573
694
  .status-header {
574
695
  display: flex;
575
696
  justify-content: space-between;
576
697
  align-items: center;
577
- margin-bottom: 10px;
698
+ padding: 8px 16px;
699
+ border-bottom: 1px solid #f0f0f0;
700
+ height: 36px;
578
701
  }
579
702
 
580
703
  .status-header h2 {
581
704
  margin: 0;
705
+ font-size: 16px;
706
+ font-weight: 500;
707
+ color: #303133;
708
+ }
709
+
710
+ .header-actions {
711
+ display: flex;
712
+ align-items: center;
713
+ }
714
+
715
+ .card-content {
716
+ padding: 15px;
717
+ overflow-y: auto;
718
+ flex: 1;
719
+ display: flex;
720
+ flex-direction: column;
721
+ }
722
+
723
+ .current-directory {
724
+ display: flex;
725
+ align-items: center;
726
+ margin-bottom: 16px;
727
+ padding: 10px;
728
+ background-color: #f8f9fa;
729
+ border-radius: 6px;
730
+ font-family: monospace;
731
+ border: 1px solid #f0f0f0;
732
+ }
733
+
734
+ .current-directory .el-icon {
735
+ margin-right: 8px;
736
+ color: #409eff;
737
+ }
738
+
739
+ .current-directory span {
740
+ flex-grow: 1;
741
+ word-break: break-all;
742
+ margin-right: 10px;
582
743
  }
583
744
 
584
745
  .status-box {
585
746
  white-space: pre-wrap;
586
747
  font-family: monospace;
587
- background-color: #f5f7fa;
588
- padding: 15px;
589
- border-radius: 4px;
590
- margin-bottom: 15px;
591
- max-height: 300px;
748
+ background-color: #f8f9fa;
749
+ padding: 16px;
750
+ border-radius: 6px;
751
+ margin-bottom: 20px;
752
+ max-height: 200px;
592
753
  overflow-y: auto;
754
+ border: 1px solid #f0f0f0;
755
+ font-size: 14px;
756
+ line-height: 1.5;
757
+ }
758
+
759
+ /* 现代化的文件列表容器 */
760
+ .file-list-container {
761
+ flex: 1;
762
+ overflow: hidden;
763
+ display: flex;
764
+ flex-direction: column;
765
+ margin-bottom: 0;
766
+ gap: 12px;
767
+ height: calc(100% - 70px); /* 给底部留出些空间 */
768
+ }
769
+
770
+ .file-group {
771
+ background-color: #f8f9fa;
772
+ border-radius: 6px;
773
+ overflow: hidden;
774
+ border: 1px solid #ebeef5;
775
+ margin-bottom: 12px;
776
+ display: flex;
777
+ flex-direction: column;
778
+ }
779
+
780
+ /* 让每个文件组根据内容自动增长 */
781
+ .file-group {
782
+ flex: 0 1 auto; /* 不主动增长,但允许缩小,基于内容大小 */
783
+ }
784
+
785
+ /* 最后一个分组可以吸收剩余空间 */
786
+ .file-group:last-child {
787
+ margin-bottom: 0;
788
+ flex: 1 1 auto; /* 可以增长占用剩余空间 */
789
+ }
790
+
791
+ .file-group-header {
792
+ font-size: 14px;
793
+ font-weight: bold;
794
+ padding: 8px 12px;
795
+ background-color: #f0f2f5;
796
+ color: #606266;
797
+ border-bottom: 1px solid #ebeef5;
798
+ flex-shrink: 0; /* 防止header被压缩 */
593
799
  }
594
800
 
595
801
  .file-list {
596
- max-height: 300px;
597
802
  overflow-y: auto;
803
+ min-height: 40px; /* 最小高度 */
804
+ flex-grow: 1; /* 允许文件列表在文件组内扩展 */
805
+ padding: 0;
806
+ margin: 0;
807
+ scrollbar-width: thin; /* Firefox */
808
+ scrollbar-color: rgba(144, 147, 153, 0.3) transparent; /* Firefox */
809
+ }
810
+
811
+ /* Webkit浏览器的滚动条样式 */
812
+ .file-list::-webkit-scrollbar {
813
+ width: 6px;
814
+ height: 6px;
815
+ }
816
+
817
+ .file-list::-webkit-scrollbar-thumb {
818
+ background-color: rgba(144, 147, 153, 0.3);
819
+ border-radius: 4px;
820
+ }
821
+
822
+ .file-list::-webkit-scrollbar-thumb:hover {
823
+ background-color: rgba(144, 147, 153, 0.5);
824
+ }
825
+
826
+ .file-list::-webkit-scrollbar-track {
827
+ background-color: transparent;
828
+ }
829
+
830
+ /* 让每个文件列表自适应高度,使其在容器中更好地分配空间 */
831
+ .file-list:empty {
832
+ display: none; /* 如果列表为空,不显示 */
833
+ }
834
+
835
+ /* 当没有足够的项目填充列表时,禁用滚动 */
836
+ .file-list:has(.empty-file-group) {
837
+ overflow-y: hidden;
838
+ }
839
+
840
+ /* 替换为更兼容的选择器 */
841
+ /* 改用直接为empty-file-group父容器添加样式 */
842
+ .empty-file-container {
843
+ overflow-y: hidden !important; /* 强制禁用滚动 */
844
+ display: flex;
845
+ flex-direction: column;
846
+ align-items: stretch;
847
+ flex: 1;
848
+ }
849
+
850
+ .empty-file-group {
851
+ padding: 16px;
852
+ text-align: center;
853
+ color: #909399;
854
+ font-size: 13px;
855
+ font-style: italic;
856
+ display: flex;
857
+ align-items: center;
858
+ justify-content: center;
859
+ min-height: 50px; /* 增加最小高度 */
860
+ background-color: #f8f9fa;
861
+ border-radius: 4px;
862
+ margin: 8px;
863
+ }
864
+
865
+ /* 自定义每个分组的展开逻辑 */
866
+ /* 已暂存文件分组 - 保持小巧 */
867
+ .file-group:nth-child(1) {
868
+ flex: 0 1 auto;
869
+ }
870
+
871
+ /* 未暂存更改分组 - 稍微大一些 */
872
+ .file-group:nth-child(2) {
873
+ flex: 0 1 auto;
874
+ }
875
+
876
+ /* 未跟踪文件分组 - 可以占据剩余空间 */
877
+ .file-group:nth-child(3) {
878
+ flex: 1 1 auto;
598
879
  }
599
880
 
600
881
  .file-item {
601
882
  padding: 8px 12px;
602
- margin-bottom: 5px;
603
- border-radius: 4px;
604
- cursor: pointer;
605
883
  display: flex;
606
884
  align-items: center;
607
885
  justify-content: space-between;
886
+ border-bottom: 1px solid #ebeef5;
887
+ cursor: pointer;
888
+ transition: background-color 0.2s;
889
+ }
890
+
891
+ .file-item:last-child {
892
+ border-bottom: none;
608
893
  }
609
894
 
610
895
  .file-item:hover {
611
- background-color: #f5f7fa;
896
+ background-color: #ecf5ff;
612
897
  }
613
898
 
614
899
  .file-info {
615
900
  display: flex;
616
901
  align-items: center;
617
902
  flex-grow: 1;
903
+ overflow: hidden;
904
+ white-space: nowrap;
905
+ gap: 8px;
618
906
  }
619
907
 
620
- .file-actions {
621
- margin-left: 10px;
622
- opacity: 0.5;
623
- transition: opacity 0.2s;
908
+ .file-status-indicator {
909
+ width: 6px;
910
+ height: 6px;
911
+ border-radius: 50%;
912
+ background-color: #409eff;
913
+ flex-shrink: 0;
624
914
  }
625
915
 
626
- .file-item:hover .file-actions {
627
- opacity: 1;
916
+ .file-status-indicator.added {
917
+ background-color: #67c23a;
628
918
  }
629
919
 
630
- .file-type {
631
- font-size: 12px;
632
- padding: 2px 6px;
633
- border-radius: 10px;
634
- margin-right: 10px;
635
- flex-shrink: 0;
920
+ .file-status-indicator.modified {
921
+ background-color: #409eff;
636
922
  }
637
923
 
638
- .added .file-type {
639
- background-color: #e1f3d8;
640
- color: #67c23a;
924
+ .file-status-indicator.deleted {
925
+ background-color: #f56c6c;
641
926
  }
642
927
 
643
- .modified .file-type {
644
- background-color: #e6f1fc;
645
- color: #409eff;
928
+ .file-status-indicator.untracked {
929
+ background-color: #e6a23c;
646
930
  }
647
931
 
648
- .deleted .file-type {
649
- background-color: #fef0f0;
650
- color: #f56c6c;
932
+ .file-path-container {
933
+ display: flex;
934
+ flex-direction: column;
935
+ overflow: hidden;
651
936
  }
652
937
 
653
- .untracked .file-type {
654
- background-color: #fdf6ec;
655
- color: #e6a23c;
938
+ .file-name {
939
+ font-weight: 500;
940
+ color: #303133;
941
+ overflow: hidden;
942
+ text-overflow: ellipsis;
943
+ line-height: 1.2;
656
944
  }
657
945
 
658
- .file-path {
659
- font-family: monospace;
660
- word-break: break-all;
946
+ .file-directory {
947
+ font-size: 12px;
948
+ color: #909399;
949
+ overflow: hidden;
950
+ text-overflow: ellipsis;
951
+ line-height: 1.2;
661
952
  }
662
953
 
663
- .diff-content {
664
- font-family: monospace;
665
- white-space: pre-wrap;
666
- max-height: 60vh;
667
- overflow-y: auto;
668
- padding: 10px;
669
- background-color: #f5f7fa;
670
- border-radius: 4px;
954
+ .file-actions {
955
+ display: flex;
956
+ gap: 5px;
957
+ opacity: 0;
958
+ transition: opacity 0.2s;
671
959
  }
672
960
 
673
- .diff-formatted {
674
- font-size: 14px;
675
- line-height: 1.5;
961
+ .file-item:hover .file-actions {
962
+ opacity: 1;
676
963
  }
677
964
 
678
- .file-navigation {
965
+ .empty-status {
679
966
  display: flex;
680
- justify-content: center;
967
+ flex-direction: column;
681
968
  align-items: center;
969
+ justify-content: center;
970
+ height: calc(100% - 70px);
971
+ background-color: #f8f9fa;
972
+ border-radius: 6px;
973
+ border: 1px solid #ebeef5;
682
974
  margin-top: 15px;
683
975
  }
684
976
 
685
- .file-counter {
686
- margin: 0 15px;
977
+ .empty-icon {
978
+ font-size: 32px;
979
+ color: #c0c4cc;
980
+ margin-bottom: 10px;
981
+ }
982
+
983
+ .empty-text {
984
+ color: #909399;
687
985
  font-size: 14px;
688
- color: #606266;
689
986
  }
690
987
 
691
- .current-directory {
988
+ /* 添加针对空内容区域的样式 */
989
+ .card-content:empty {
692
990
  display: flex;
693
991
  align-items: center;
694
- margin-bottom: 15px;
695
- padding: 8px 12px;
696
- background-color: #f5f7fa;
697
- border-radius: 4px;
698
- font-family: monospace;
699
- }
700
-
701
- .current-directory .el-icon {
702
- margin-right: 8px;
703
- color: #409eff;
992
+ justify-content: center;
993
+ background-color: #f8f9fa;
994
+ border-radius: 6px;
995
+ border: 1px dashed #dcdfe6;
996
+ color: #909399;
997
+ height: 100%;
704
998
  }
705
999
 
706
- .current-directory span {
707
- flex-grow: 1;
708
- word-break: break-all;
709
- margin-right: 10px;
1000
+ .card-content:empty::after {
1001
+ content: '没有Git状态信息可显示';
1002
+ font-size: 14px;
710
1003
  }
711
1004
 
1005
+ /* 添加目录浏览相关样式 */
712
1006
  .browser-current-path {
713
- margin-bottom: 10px;
714
- font-size: 14px;
715
- color: #606266;
716
- background-color: #f5f7fa;
717
- padding: 8px 12px;
718
- border-radius: 4px;
719
1007
  font-family: monospace;
720
- word-break: break-all;
1008
+ margin-bottom: 15px;
1009
+ padding: 10px;
1010
+ background-color: #f5f7fa;
1011
+ border-radius: 6px;
1012
+ overflow-x: auto;
1013
+ white-space: nowrap;
1014
+ border: 1px solid #ebeef5;
721
1015
  }
722
1016
 
723
1017
  .browser-error {
724
- margin-bottom: 10px;
725
1018
  color: #f56c6c;
726
- padding: 8px 12px;
1019
+ margin: 10px 0;
1020
+ padding: 10px;
727
1021
  background-color: #fef0f0;
728
1022
  border-radius: 4px;
729
- }
730
-
731
- .directory-browser {
732
- padding: 10px;
733
- max-height: 400px;
734
- overflow-y: auto;
1023
+ border-left: 3px solid #f56c6c;
735
1024
  }
736
1025
 
737
1026
  .browser-nav {
1027
+ display: flex;
1028
+ gap: 10px;
738
1029
  margin-bottom: 10px;
1030
+ }
1031
+
1032
+ .no-padding-left {
1033
+ padding-left: 12px;
1034
+ }
1035
+
1036
+ .directory-browser {
1037
+ height: 400px;
1038
+ border: 1px solid #ebeef5;
1039
+ border-radius: 6px;
1040
+ overflow: hidden;
739
1041
  display: flex;
740
- justify-content: space-between;
1042
+ flex-direction: column;
1043
+ }
1044
+
1045
+ .directory-items-container {
1046
+ flex: 1;
1047
+ overflow-y: auto;
1048
+ background-color: #f8f9fa;
1049
+ padding: 10px;
741
1050
  }
742
1051
 
743
1052
  .directory-items {
744
1053
  list-style: none;
745
1054
  padding: 0;
746
1055
  margin: 0;
1056
+ display: grid;
1057
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
1058
+ gap: 10px;
747
1059
  }
748
1060
 
749
1061
  .directory-item {
750
- padding: 8px 12px;
751
- margin-bottom: 5px;
752
- border-radius: 4px;
753
- cursor: pointer;
754
1062
  display: flex;
755
1063
  align-items: center;
1064
+ padding: 8px;
1065
+ cursor: pointer;
1066
+ border-radius: 4px;
1067
+ background-color: white;
1068
+ border: 1px solid #ebeef5;
1069
+ transition: all 0.2s;
1070
+ overflow: hidden;
1071
+ white-space: nowrap;
1072
+ text-overflow: ellipsis;
1073
+ gap: 5px;
756
1074
  }
757
1075
 
758
1076
  .directory-item:hover {
759
- background-color: #f5f7fa;
1077
+ background-color: #ecf5ff;
1078
+ border-color: #c6e2ff;
760
1079
  }
761
1080
 
762
1081
  .directory-item.directory {
1082
+ background-color: #f0f7ff;
763
1083
  color: #409eff;
764
1084
  }
765
1085
 
766
- .directory-item.file {
767
- color: #606266;
1086
+ .directory-item .el-icon {
1087
+ margin-right: 5px;
1088
+ flex-shrink: 0;
768
1089
  }
769
1090
 
770
- .directory-item .el-icon {
771
- margin-right: 10px;
1091
+ .directory-item.directory .el-icon {
1092
+ color: #409eff;
772
1093
  }
773
1094
 
774
- .directory-item span {
775
- font-family: monospace;
776
- word-break: break-all;
1095
+ .directory-item.file .el-icon {
1096
+ color: #909399;
777
1097
  }
778
1098
 
779
1099
  .directory-buttons {
@@ -781,41 +1101,87 @@ defineExpose({
781
1101
  gap: 10px;
782
1102
  margin-top: 10px;
783
1103
  }
784
-
785
- /* 移除按钮左侧的内边距 */
786
- .no-padding-left {
787
- padding-left: 8px !important;
788
- }
789
1104
  </style>
790
1105
 
791
- <!-- 添加非scoped样式,使diff格式化样式对动态内容生效 -->
1106
+ <!-- scoped样式,使diff格式化样式对动态内容生效 -->
792
1107
  <style>
793
1108
  .diff-header {
794
1109
  font-weight: bold;
795
1110
  background-color: #e6f1fc;
796
- padding: 3px;
797
- margin: 5px 0;
1111
+ padding: 5px;
1112
+ margin: 8px 0;
1113
+ border-radius: 4px;
1114
+ color: #0366d6;
1115
+ border-bottom: 1px solid #c8e1ff;
798
1116
  }
799
1117
 
800
1118
  .diff-old-file, .diff-new-file {
801
- color: #888;
1119
+ color: #586069;
1120
+ padding: 2px 5px;
1121
+ font-family: monospace;
1122
+ }
1123
+
1124
+ .diff-old-file {
1125
+ color: #cb2431;
1126
+ }
1127
+
1128
+ .diff-new-file {
1129
+ color: #22863a;
802
1130
  }
803
1131
 
804
1132
  .diff-hunk-header {
805
1133
  color: #6f42c1;
1134
+ background-color: #f1f8ff;
1135
+ padding: 2px 5px;
1136
+ margin: 5px 0;
1137
+ border-radius: 3px;
1138
+ font-family: monospace;
806
1139
  }
807
1140
 
808
1141
  .diff-added {
809
1142
  background-color: #e6ffed;
810
- color: #28a745;
1143
+ color: #22863a;
1144
+ padding: 0 5px;
1145
+ border-left: 4px solid #22863a;
1146
+ font-family: monospace;
1147
+ display: block;
1148
+ margin: 2px 0;
811
1149
  }
812
1150
 
813
1151
  .diff-removed {
814
1152
  background-color: #ffeef0;
815
- color: #d73a49;
1153
+ color: #cb2431;
1154
+ padding: 0 5px;
1155
+ border-left: 4px solid #cb2431;
1156
+ font-family: monospace;
1157
+ display: block;
1158
+ margin: 2px 0;
816
1159
  }
817
1160
 
818
1161
  .diff-context {
819
1162
  color: #444;
1163
+ padding: 0 5px;
1164
+ font-family: monospace;
1165
+ display: block;
1166
+ margin: 2px 0;
1167
+ background-color: #fafbfc;
1168
+ }
1169
+
1170
+ /* 增加自动更新开关的样式 */
1171
+ .auto-update-switch .el-switch__core {
1172
+ transition: all 0.3s ease-in-out;
1173
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
1174
+ }
1175
+
1176
+ .auto-update-switch .el-switch__core:hover {
1177
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
1178
+ }
1179
+
1180
+ .auto-update-switch.is-checked .el-switch__core {
1181
+ box-shadow: 0 2px 5px rgba(103, 194, 58, 0.3);
1182
+ }
1183
+
1184
+ .auto-update-switch.is-checked .el-switch__core:hover {
1185
+ box-shadow: 0 2px 8px rgba(103, 194, 58, 0.5);
820
1186
  }
821
1187
  </style>