zen-gitsync 2.0.3 → 2.0.5

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,105 +1,200 @@
1
1
  <script setup lang="ts">
2
- import { ref, onMounted, defineExpose } from 'vue'
3
- import { ElTable, ElTableColumn, ElTag, ElButton } from 'element-plus'
4
- import { RefreshRight } from '@element-plus/icons-vue'
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'
5
5
  import 'element-plus/dist/index.css'
6
6
  import { createGitgraph } from '@gitgraph/js'
7
+ import { useGitLogStore } from '../stores/gitLogStore'
8
+ import { useGitStore } from '../stores/gitStore'
7
9
 
8
10
  interface LogItem {
9
11
  hash: string
10
12
  date: string
11
13
  author: string
12
- email: string // 添加邮箱字段
14
+ email: string
13
15
  message: string
14
- branch?: string // 添加分支信息字段
15
- parents?: string[] // 添加父提交信息
16
+ branch?: string
17
+ parents?: string[]
16
18
  }
17
19
 
18
- const logs = ref<LogItem[]>([])
20
+ // 使用Git状态和日志Store
21
+ const gitLogStore = useGitLogStore()
22
+ const gitStore = useGitStore()
23
+
24
+ // 获取日志数据
25
+ let logsData: LogItem[] = []
26
+ const logs = ref<LogItem[]>(logsData)
19
27
  const errorMessage = ref('')
20
- const isLoading = ref(false)
28
+ // 定义本地加载状态,而不是依赖于computed
29
+ const localLoading = ref(false)
30
+ const isLoading = computed(() => gitLogStore.isLoadingLog || localLoading.value)
21
31
  const showAllCommits = ref(false)
22
32
  const totalCommits = ref(0)
23
- const showGraphView = ref(true) // 控制是否显示图表视图
33
+ const showGraphView = ref(true)
24
34
  const graphContainer = ref<HTMLElement | null>(null)
25
35
 
36
+ // 添加图表缩放控制
37
+ const graphScale = ref(1)
38
+ const minScale = 0.5
39
+ const maxScale = 1.5
40
+ const scaleStep = 0.1
41
+
42
+ // 添加日志被刷新的提示状态
43
+ const logRefreshed = ref(false)
44
+
26
45
  // 加载提交历史
27
46
  async function loadLog(all = false) {
47
+ // 从gitStore获取仓库状态
48
+ const gitStore = useGitStore()
49
+
50
+ // 检查是否是Git仓库
51
+ if (!gitStore.isGitRepo) {
52
+ errorMessage.value = '当前目录不是Git仓库'
53
+ return
54
+ }
55
+
28
56
  try {
29
- isLoading.value = true
30
57
  showAllCommits.value = all
31
- // 修改API调用,获取更详细的提交信息,包括父提交
58
+
59
+ // 设置本地加载状态
60
+ localLoading.value = true
61
+
62
+ // 保留graph参数,但服务器端其实不做特殊处理
63
+ // 这样可以兼容之前的代码,避免大量修改
32
64
  const url = all ? '/api/log?all=true&graph=true' : '/api/log?graph=true'
65
+ console.log(`加载日志数据: ${url}`)
66
+
33
67
  const response = await fetch(url)
34
- logs.value = await response.json()
35
- totalCommits.value = logs.value.length
36
- errorMessage.value = ''
68
+ const data = await response.json()
69
+
70
+ // 清空现有数据
71
+ logsData.length = 0
72
+
73
+ // 更新本地数据
74
+ if (Array.isArray(data)) {
75
+ // 新的API格式,直接返回数组
76
+ console.log(`日志加载完成: 共${data.length}条记录`)
77
+
78
+ // 填充logsData
79
+ data.forEach((item: LogItem) => logsData.push(item))
80
+
81
+ totalCommits.value = data.length
82
+ } else if (data.log && Array.isArray(data.log)) {
83
+ // 旧版API格式,兼容处理
84
+ console.log(`日志加载完成: 共${data.log.length}条记录`)
85
+
86
+ // 填充logsData
87
+ data.log.forEach((item: LogItem) => logsData.push(item))
88
+
89
+ totalCommits.value = data.log.length
90
+ } else {
91
+ console.error('未知的日志数据格式:', data)
92
+ errorMessage.value = '日志数据格式错误'
93
+ return
94
+ }
95
+
96
+ // 确保logs.value也更新
97
+ logs.value = [...logsData]
98
+
99
+ console.log(`logsData长度: ${logsData.length}`) // 添加调试日志
100
+
101
+ // 设置刷新提示状态
102
+ logRefreshed.value = true
103
+ // 2秒后隐藏提示
104
+ setTimeout(() => { logRefreshed.value = false }, 2000)
37
105
 
38
106
  // 加载完数据后渲染图表
39
107
  if (showGraphView.value) {
40
108
  setTimeout(renderGraph, 0)
41
109
  }
110
+
111
+ errorMessage.value = ''
42
112
  } catch (error) {
43
- errorMessage.value = '加载提交历史失败: ' + (error as Error).message
113
+ errorMessage.value = '加载提交历史失败: ' + (error instanceof Error ? error.message : String(error))
114
+ console.error('加载日志失败:', error)
44
115
  } finally {
45
- isLoading.value = false
116
+ // 重置本地加载状态
117
+ localLoading.value = false
46
118
  }
47
119
  }
48
120
 
49
121
  // 渲染Git图表
50
122
  async function renderGraph() {
51
- if (!graphContainer.value || logs.value.length === 0) return
52
-
53
- // 清空容器
54
- graphContainer.value.innerHTML = ''
123
+ console.log(`开始渲染图表...数据长度: ${logsData.length}`)
55
124
 
56
- // 获取当前分支
57
- const branchResponse = await fetch('/api/branch')
58
- const { branch: currentBranch } = await branchResponse.json()
59
-
60
- // 创建gitgraph实例
61
- const gitgraph = createGitgraph(graphContainer.value, {
62
- // 自定义选项
63
- // @ts-ignore: true
64
- orientation: 'vertical-reverse', // 从上到下的方向
65
- // @ts-ignore: true
66
- template: 'metro', // 使用metro模板
67
- author: '提交者 <committer@example.com>'
68
- })
125
+ if (!graphContainer.value) {
126
+ console.error('图表容器未找到')
127
+ return
128
+ }
69
129
 
70
- // 处理分支和提交数据
71
- // 注意:这里的实现是简化的,实际需要根据API返回的数据结构调整
72
- const branches: Record<string, any> = {}
73
- const mainBranch = gitgraph.branch(currentBranch || 'main') // 使用API获取的分支或默认main
74
- branches[currentBranch || 'main'] = mainBranch
130
+ if (logsData.length === 0) {
131
+ console.error('没有日志数据可渲染')
132
+ return
133
+ }
75
134
 
76
- // 简化示例 - 实际实现需要根据API返回的数据结构调整
77
- logs.value.forEach(commit => {
78
- // 这里需要根据实际数据结构构建分支图
79
- let currentBranch = mainBranch
135
+ try {
136
+ // 清空容器
137
+ graphContainer.value.innerHTML = ''
80
138
 
81
- // 如果有分支信息,使用对应的分支
82
- if (commit.branch) {
83
- const branchName = formatBranchName(commit.branch.split(',')[0])
84
- if (!branches[branchName]) {
85
- branches[branchName] = gitgraph.branch(branchName)
86
- }
87
- currentBranch = branches[branchName]
88
- }
139
+ console.log(`创建gitgraph实例,分支: ${gitStore.currentBranch || 'main'}`)
89
140
 
90
- // 创建提交,添加邮箱信息
91
- currentBranch.commit({
92
- hash: commit.hash,
93
- subject: commit.message,
94
- author: `${commit.author} <${commit.email}>`
141
+ // 创建gitgraph实例
142
+ const gitgraph = createGitgraph(graphContainer.value, {
143
+ // 自定义选项
144
+ orientation: 'vertical-reverse' as any, // 使用类型断言解决类型错误
145
+ template: 'metro' as any, // 使用类型断言解决类型错误
146
+ author: '提交者 <committer@example.com>'
95
147
  })
96
- })
148
+
149
+ // 处理分支和提交数据
150
+ const branches: Record<string, any> = {}
151
+ const mainBranch = gitgraph.branch(gitStore.currentBranch || 'main')
152
+ branches[gitStore.currentBranch || 'main'] = mainBranch
153
+
154
+ console.log(`开始创建提交图...共${logsData.length}条提交`)
155
+
156
+ // 简化示例 - 实际实现需要根据API返回的数据结构调整
157
+ logsData.forEach((commit, index) => {
158
+ // 这里需要根据实际数据结构构建分支图
159
+ let currentBranch = mainBranch
160
+
161
+ // 如果有分支信息,使用对应的分支
162
+ if (commit.branch) {
163
+ const branchName = formatBranchName(commit.branch.split(',')[0])
164
+ if (!branches[branchName]) {
165
+ branches[branchName] = gitgraph.branch(branchName)
166
+ }
167
+ currentBranch = branches[branchName]
168
+ }
169
+
170
+ // 创建提交,添加邮箱信息
171
+ currentBranch.commit({
172
+ hash: commit.hash,
173
+ subject: commit.message,
174
+ author: `${commit.author} <${commit.email}>`
175
+ })
176
+
177
+ if (index % 10 === 0) {
178
+ console.log(`已渲染 ${index + 1}/${logsData.length} 个提交`)
179
+ }
180
+ })
181
+
182
+ console.log('图表渲染完成')
183
+
184
+ // 确保渲染完成后调用自适应缩放
185
+ setTimeout(() => {
186
+ fitGraphToContainer()
187
+ }, 100)
188
+ } catch (error) {
189
+ console.error('渲染图表失败:', error)
190
+ errorMessage.value = '渲染图表失败: ' + (error instanceof Error ? error.message : String(error))
191
+ }
97
192
  }
98
193
 
99
194
  // 切换视图模式
100
195
  function toggleViewMode() {
101
196
  showGraphView.value = !showGraphView.value
102
- if (showGraphView.value && logs.value.length > 0) {
197
+ if (showGraphView.value && logsData.length > 0) {
103
198
  // 延迟执行以确保DOM已更新
104
199
  setTimeout(renderGraph, 0)
105
200
  }
@@ -110,14 +205,146 @@ function toggleAllCommits() {
110
205
  loadLog(!showAllCommits.value)
111
206
  }
112
207
 
208
+ // 格式化分支名(实现该函数,因为在模板中调用了)
209
+ function formatBranchName(ref: string) {
210
+ // 处理HEAD、远程分支等情况
211
+ if (ref.includes('HEAD -> ')) {
212
+ return ref.replace('HEAD -> ', '')
213
+ }
214
+ if (ref.includes('origin/')) {
215
+ return ref
216
+ }
217
+ return ref.trim()
218
+ }
219
+
220
+ // 获取分支标签类型(实现该函数,因为在模板中调用了)
221
+ function getBranchTagType(ref: string) {
222
+ if (ref.includes('HEAD')) return 'success'
223
+ if (ref.includes('origin/')) return 'warning'
224
+ return 'info'
225
+ }
226
+
113
227
  onMounted(() => {
114
- loadLog()
228
+ // 检查gitLogStore中是否已有数据
229
+ if (gitStore.isGitRepo) {
230
+ if (gitLogStore.log.length > 0) {
231
+ // 如果已经有数据,直接使用现有数据
232
+ console.log('使用已加载的日志数据')
233
+
234
+ // 清空并填充logsData
235
+ logsData.length = 0
236
+ gitLogStore.log.forEach(item => logsData.push(item))
237
+
238
+ // 由于TypeScript类型错误,我们直接设置totalCommits而不是使用logs.value.length
239
+ totalCommits.value = gitLogStore.log.length
240
+
241
+ // 确保视图被渲染
242
+ if (showGraphView.value) {
243
+ setTimeout(() => {
244
+ console.log(`准备渲染图表,数据长度: ${logsData.length}`)
245
+ renderGraph()
246
+ }, 100)
247
+ }
248
+ } else {
249
+ // 否则加载数据
250
+ console.log('初始加载日志数据')
251
+ loadLog()
252
+ }
253
+ } else {
254
+ errorMessage.value = '当前目录不是Git仓库'
255
+ }
256
+ })
257
+
258
+ // 简化刷新函数,只需调用loadLog即可
259
+ const refreshLog = () => {
260
+ if (!gitStore.isGitRepo) {
261
+ errorMessage.value = '当前目录不是Git仓库'
262
+ return
263
+ }
264
+ loadLog(showAllCommits.value)
265
+ }
266
+
267
+ // 监听store中的日志变化
268
+ watch(() => gitLogStore.log, (newLogs) => {
269
+ console.log('监听到gitLogStore.log变化,更新图表数据')
270
+
271
+ // 清空logsData
272
+ logsData.length = 0
273
+
274
+ // 重新填充数据
275
+ newLogs.forEach((item: LogItem) => logsData.push(item))
276
+
277
+ // 更新计数器
278
+ totalCommits.value = newLogs.length
279
+
280
+ // 尝试解决logs.value赋值问题
281
+ try {
282
+ // @ts-ignore - 忽略TypeScript错误
283
+ logs.value = [...logsData]
284
+ } catch (error) {
285
+ console.warn('无法更新logs.value:', error)
286
+ }
287
+
288
+ console.log(`数据更新完成,准备渲染图表(${logsData.length}条记录)`)
289
+
290
+ if (showGraphView.value && logsData.length > 0) {
291
+ setTimeout(renderGraph, 0)
292
+ }
115
293
  })
116
- const refreshLog = () => loadLog(showAllCommits.value)
294
+
117
295
  // 暴露方法给父组件
118
296
  defineExpose({
119
297
  refreshLog
120
298
  })
299
+
300
+ // 增加/减少缩放比例
301
+ function zoomIn() {
302
+ if (graphScale.value < maxScale) {
303
+ graphScale.value = Math.min(maxScale, graphScale.value + scaleStep)
304
+ applyScale()
305
+ }
306
+ }
307
+
308
+ function zoomOut() {
309
+ if (graphScale.value > minScale) {
310
+ graphScale.value = Math.max(minScale, graphScale.value - scaleStep)
311
+ applyScale()
312
+ }
313
+ }
314
+
315
+ // 应用缩放比例
316
+ function applyScale() {
317
+ if (!graphContainer.value) return
318
+
319
+ const svgElement = graphContainer.value.querySelector('svg')
320
+ if (svgElement) {
321
+ svgElement.style.transform = `scale(${graphScale.value})`
322
+ svgElement.style.transformOrigin = 'top left'
323
+ }
324
+ }
325
+
326
+ // 自适应图表大小
327
+ function fitGraphToContainer() {
328
+ if (!graphContainer.value) return
329
+
330
+ const svgElement = graphContainer.value.querySelector('svg')
331
+ if (!svgElement) return
332
+
333
+ // 获取SVG和容器的宽度
334
+ const svgWidth = svgElement.getBoundingClientRect().width / graphScale.value
335
+ const containerWidth = graphContainer.value.clientWidth
336
+
337
+ // 计算合适的缩放比例
338
+ if (svgWidth > containerWidth) {
339
+ // 如果SVG宽度大于容器,需要缩小
340
+ graphScale.value = Math.max(minScale, containerWidth / svgWidth)
341
+ } else {
342
+ // 否则恢复到默认比例
343
+ graphScale.value = 1
344
+ }
345
+
346
+ applyScale()
347
+ }
121
348
  </script>
122
349
 
123
350
  <template>
@@ -138,7 +365,7 @@ defineExpose({
138
365
  @click="toggleAllCommits"
139
366
  :loading="isLoading"
140
367
  >
141
- {{ showAllCommits ? '显示最近100条' : '显示所有提交' }}
368
+ {{ showAllCommits ? '显示最近30条' : '显示所有提交' }}
142
369
  </el-button>
143
370
  <el-button
144
371
  :icon="RefreshRight"
@@ -146,23 +373,74 @@ defineExpose({
146
373
  size="small"
147
374
  @click="refreshLog()"
148
375
  :loading="isLoading"
376
+ :class="{ 'refresh-button-animated': logRefreshed }"
149
377
  />
150
378
  </div>
151
379
  </div>
152
380
  <div v-if="errorMessage">{{ errorMessage }}</div>
153
381
  <div v-else>
382
+ <!-- 添加刷新提示 -->
383
+ <div v-if="logRefreshed" class="refresh-notification">
384
+ 提交历史已刷新
385
+ </div>
386
+
154
387
  <!-- 图表视图 -->
155
388
  <div v-if="showGraphView" class="graph-view">
156
- <div class="commit-count" v-if="logs.length > 0">
157
- 显示 {{ logs.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(最近100条)' }}
389
+ <div class="commit-count" v-if="logsData.length > 0">
390
+ 显示 {{ logsData.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(最近30条)' }}
158
391
  </div>
392
+
393
+ <!-- 添加缩放控制 -->
394
+ <div class="graph-controls">
395
+ <div class="zoom-controls">
396
+ <el-button
397
+ type="primary"
398
+ :icon="ZoomOut"
399
+ circle
400
+ size="small"
401
+ @click="zoomOut"
402
+ :disabled="graphScale <= minScale"
403
+ />
404
+
405
+ <el-slider
406
+ v-model="graphScale"
407
+ :min="minScale"
408
+ :max="maxScale"
409
+ :step="scaleStep"
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
+ />
422
+
423
+ <el-button
424
+ type="primary"
425
+ size="small"
426
+ @click="fitGraphToContainer"
427
+ >
428
+ 自适应大小
429
+ </el-button>
430
+ </div>
431
+
432
+ <div class="scale-info">
433
+ 缩放: {{ Math.round(graphScale * 100) }}%
434
+ </div>
435
+ </div>
436
+
159
437
  <div ref="graphContainer" class="graph-container"></div>
160
438
  </div>
161
439
 
162
440
  <!-- 表格视图 -->
163
441
  <div v-else>
164
442
  <div class="commit-count" v-if="logs.length > 0">
165
- 显示 {{ logs.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(最近100条)' }}
443
+ 显示 {{ logs.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(最近30条)' }}
166
444
  </div>
167
445
  <el-table :data="logs" style="width: 100%" stripe border v-loading="isLoading">
168
446
  <el-table-column prop="hash" label="提交哈希" width="100" resizable />
@@ -232,37 +510,67 @@ defineExpose({
232
510
  border-radius: 4px;
233
511
  padding: 10px;
234
512
  background-color: #fff;
235
- /* transform: scale(0.8); */
513
+ position: relative;
514
+ }
515
+
516
+ .graph-container svg {
517
+ transform-origin: top left;
518
+ transition: transform 0.2s ease;
236
519
  }
237
520
 
238
521
  .graph-view {
239
522
  width: 100%;
240
523
  }
241
- </style>
242
524
 
243
- <script lang="ts">
244
- // 辅助函数:格式化分支名称
245
- function formatBranchName(ref: string) {
246
- // 移除 "HEAD -> " 前缀
247
- ref = ref.trim().replace(/^HEAD\s*->\s*/, '')
248
-
249
- // 移除 "origin/" 前缀
250
- ref = ref.replace(/^origin\//, '')
251
-
252
- // 移除 "tag: " 前缀,但保留标签名
253
- ref = ref.replace(/^tag:\s*/, '')
254
-
255
- return ref.trim()
525
+ .graph-controls {
526
+ display: flex;
527
+ justify-content: space-between;
528
+ align-items: center;
529
+ margin-bottom: 10px;
256
530
  }
257
531
 
258
- // 辅助函数:根据分支类型返回不同的标签类型
259
- function getBranchTagType(ref: string) {
260
- if (ref.includes('HEAD')) return 'success'
261
- if (ref.includes('tag:')) return 'warning'
262
- if (ref.includes('origin/')) return 'info'
263
- return 'primary' // 修改空字符串为有效的type值
532
+ .zoom-controls {
533
+ display: flex;
534
+ gap: 8px;
264
535
  }
265
- </script>
536
+
537
+ .zoom-slider {
538
+ width: 200px;
539
+ }
540
+
541
+ .scale-info {
542
+ font-size: 14px;
543
+ color: #606266;
544
+ }
545
+
546
+ .refresh-button-animated {
547
+ animation: pulse 1s;
548
+ }
549
+
550
+ .refresh-notification {
551
+ background-color: #f0f9eb;
552
+ color: #67c23a;
553
+ padding: 8px;
554
+ border-radius: 4px;
555
+ margin-bottom: 10px;
556
+ text-align: center;
557
+ font-size: 14px;
558
+ border-left: 4px solid #67c23a;
559
+ animation: fadeOut 2s forwards;
560
+ }
561
+
562
+ @keyframes pulse {
563
+ 0% { transform: scale(1); }
564
+ 50% { transform: scale(1.2); }
565
+ 100% { transform: scale(1); }
566
+ }
567
+
568
+ @keyframes fadeOut {
569
+ 0% { opacity: 1; }
570
+ 70% { opacity: 1; }
571
+ 100% { opacity: 0; }
572
+ }
573
+ </style>
266
574
 
267
575
  /* 添加表格列调整样式 */
268
576
  .el-table .el-table__cell .cell {
@@ -1,4 +1,7 @@
1
1
  import { createApp } from 'vue'
2
+ import { createPinia } from 'pinia'
2
3
  import App from './App.vue'
4
+
3
5
  const app = createApp(App)
6
+ app.use(createPinia())
4
7
  app.mount('#app')