zen-gitsync 2.0.6 → 2.0.8
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/package.json +1 -1
- package/src/ui/client/components.d.ts +1 -0
- package/src/ui/client/src/components/CommitForm.vue +1032 -178
- package/src/ui/client/src/components/GitStatus.vue +572 -135
- package/src/ui/client/src/components/LogList.vue +907 -223
- package/src/ui/client/src/stores/gitLogStore.ts +25 -5
- package/src/ui/client/src/stores/gitStore.ts +129 -1
- package/src/ui/client/stats.html +1 -1
- package/src/ui/client/vite.config.ts +2 -0
- package/src/ui/public/assets/index-C3BbS3PG.css +1 -0
- package/src/ui/public/assets/index-P9BcPWc5.js +20 -0
- package/src/ui/public/assets/vendor-eqaTZKOh.js +45 -0
- package/src/ui/public/index.html +3 -3
- package/src/ui/server/index.js +473 -50
- package/src/ui/public/assets/index-DaPynzAr.js +0 -10
- package/src/ui/public/assets/index-qfIezZmd.css +0 -1
- package/src/ui/public/assets/vendor-BcSuWc8z.js +0 -45
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, onMounted, computed, watch } from 'vue'
|
|
3
|
-
import {
|
|
2
|
+
import { ref, onMounted, computed, watch, onBeforeUnmount, nextTick } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
ElTable,
|
|
5
|
+
ElTableColumn,
|
|
6
|
+
ElTag,
|
|
7
|
+
ElButton,
|
|
8
|
+
ElSlider,
|
|
9
|
+
ElDialog,
|
|
10
|
+
ElSelect,
|
|
11
|
+
ElOption,
|
|
12
|
+
ElDatePicker,
|
|
13
|
+
ElInput,
|
|
14
|
+
ElBadge,
|
|
15
|
+
ElMessage,
|
|
16
|
+
ElMessageBox
|
|
17
|
+
} from 'element-plus'
|
|
4
18
|
import { RefreshRight, ZoomIn, ZoomOut, Filter, Document, TrendCharts, List, More } from '@element-plus/icons-vue'
|
|
5
19
|
import 'element-plus/dist/index.css'
|
|
6
20
|
import { createGitgraph } from '@gitgraph/js'
|
|
@@ -33,6 +47,12 @@ const totalCommits = ref(0)
|
|
|
33
47
|
const showGraphView = ref(false)
|
|
34
48
|
const graphContainer = ref<HTMLElement | null>(null)
|
|
35
49
|
|
|
50
|
+
// 添加分页相关变量
|
|
51
|
+
const currentPage = ref(1)
|
|
52
|
+
const hasMoreData = ref(true)
|
|
53
|
+
const isLoadingMore = ref(false)
|
|
54
|
+
const loadTimerInterval = ref<number | null>(null)
|
|
55
|
+
|
|
36
56
|
// 添加提交详情弹窗相关变量
|
|
37
57
|
const commitDetailVisible = ref(false)
|
|
38
58
|
const selectedCommit = ref<LogItem | null>(null)
|
|
@@ -52,68 +72,28 @@ const logRefreshed = ref(false)
|
|
|
52
72
|
|
|
53
73
|
// 添加筛选相关变量
|
|
54
74
|
const filterVisible = ref(false)
|
|
55
|
-
const authorFilter = ref(
|
|
75
|
+
const authorFilter = ref<string[]>([])
|
|
56
76
|
const messageFilter = ref('')
|
|
57
77
|
const dateRangeFilter = ref<any>(null)
|
|
58
|
-
const availableAuthors =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
const availableAuthors = ref<string[]>([])
|
|
79
|
+
// 添加分支筛选
|
|
80
|
+
const branchFilter = ref<string[]>([])
|
|
81
|
+
const availableBranches = ref<string[]>([])
|
|
82
|
+
|
|
83
|
+
// 添加右键菜单相关变量
|
|
84
|
+
const contextMenuVisible = ref(false)
|
|
85
|
+
const contextMenuTop = ref(0)
|
|
86
|
+
const contextMenuLeft = ref(0)
|
|
87
|
+
const selectedContextCommit = ref<LogItem | null>(null)
|
|
68
88
|
|
|
69
89
|
// 应用筛选后的日志
|
|
70
90
|
const filteredLogs = computed(() => {
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
})
|
|
91
|
+
// 不再在前端进行筛选,直接使用加载的日志
|
|
92
|
+
return logs.value;
|
|
106
93
|
})
|
|
107
94
|
|
|
108
|
-
// 重置筛选条件
|
|
109
|
-
function resetFilters() {
|
|
110
|
-
authorFilter.value = ''
|
|
111
|
-
messageFilter.value = ''
|
|
112
|
-
dateRangeFilter.value = null
|
|
113
|
-
}
|
|
114
|
-
|
|
115
95
|
// 加载提交历史
|
|
116
|
-
async function loadLog(all = false) {
|
|
96
|
+
async function loadLog(all = false, page = 1) {
|
|
117
97
|
// 从gitStore获取仓库状态
|
|
118
98
|
const gitStore = useGitStore()
|
|
119
99
|
|
|
@@ -124,57 +104,86 @@ async function loadLog(all = false) {
|
|
|
124
104
|
}
|
|
125
105
|
|
|
126
106
|
try {
|
|
127
|
-
|
|
107
|
+
// 设置加载状态
|
|
108
|
+
if (page > 1) {
|
|
109
|
+
isLoadingMore.value = true
|
|
110
|
+
} else {
|
|
111
|
+
localLoading.value = true
|
|
112
|
+
}
|
|
128
113
|
|
|
129
|
-
|
|
130
|
-
localLoading.value = true
|
|
114
|
+
console.log(`加载提交历史: page=${page}, all=${all}`)
|
|
131
115
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
// 构建查询参数
|
|
117
|
+
const queryParams = new URLSearchParams()
|
|
118
|
+
queryParams.append('page', page.toString())
|
|
119
|
+
queryParams.append('all', all.toString())
|
|
136
120
|
|
|
137
|
-
|
|
138
|
-
|
|
121
|
+
// 添加筛选参数
|
|
122
|
+
if (authorFilter.value.length > 0) {
|
|
123
|
+
queryParams.append('author', authorFilter.value.join(','))
|
|
124
|
+
}
|
|
139
125
|
|
|
140
|
-
//
|
|
141
|
-
|
|
126
|
+
// 添加分支筛选参数
|
|
127
|
+
if (branchFilter.value.length > 0) {
|
|
128
|
+
queryParams.append('branch', branchFilter.value.join(','))
|
|
129
|
+
}
|
|
142
130
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
errorMessage.value = '日志数据格式错误'
|
|
131
|
+
if (messageFilter.value) {
|
|
132
|
+
queryParams.append('message', messageFilter.value)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (dateRangeFilter.value && Array.isArray(dateRangeFilter.value) && dateRangeFilter.value.length === 2) {
|
|
136
|
+
queryParams.append('dateFrom', dateRangeFilter.value[0])
|
|
137
|
+
queryParams.append('dateTo', dateRangeFilter.value[1])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 添加时间戳防止缓存
|
|
141
|
+
queryParams.append('_t', Date.now().toString())
|
|
142
|
+
|
|
143
|
+
const response = await fetch(`/api/log?${queryParams.toString()}`)
|
|
144
|
+
const result = await response.json()
|
|
145
|
+
|
|
146
|
+
// 确保result有正确的数据结构
|
|
147
|
+
if (!result || !result.data || !Array.isArray(result.data)) {
|
|
148
|
+
console.error('API返回的数据格式不正确:', result)
|
|
149
|
+
errorMessage.value = '加载提交历史失败: 服务器返回数据格式不正确'
|
|
163
150
|
return
|
|
164
151
|
}
|
|
165
152
|
|
|
166
|
-
|
|
153
|
+
const isLoadMore = page > 1
|
|
154
|
+
|
|
155
|
+
// 处理结果
|
|
156
|
+
// 如果是加载更多,追加数据,否则替换数据
|
|
157
|
+
if (isLoadMore) {
|
|
158
|
+
result.data.forEach((item: LogItem) => logsData.push(item))
|
|
159
|
+
} else {
|
|
160
|
+
logsData.length = 0
|
|
161
|
+
result.data.forEach((item: LogItem) => logsData.push(item))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 强制重新渲染列表
|
|
167
165
|
logs.value = [...logsData]
|
|
168
166
|
|
|
169
|
-
|
|
167
|
+
// 更新当前页码
|
|
168
|
+
currentPage.value = page
|
|
170
169
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
// 更新总数和分页标记
|
|
171
|
+
totalCommits.value = result.total || logsData.length
|
|
172
|
+
hasMoreData.value = result.hasMore === true
|
|
173
|
+
|
|
174
|
+
if (!hasMoreData.value) {
|
|
175
|
+
console.log('已加载所有提交记录')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 设置刷新提示状态(仅在初次加载时)
|
|
179
|
+
if (!isLoadMore) {
|
|
180
|
+
logRefreshed.value = true
|
|
181
|
+
// 2秒后隐藏提示
|
|
182
|
+
setTimeout(() => { logRefreshed.value = false }, 2000)
|
|
183
|
+
}
|
|
175
184
|
|
|
176
|
-
//
|
|
177
|
-
if (showGraphView.value) {
|
|
185
|
+
// 加载完数据后渲染图表(仅在初次加载时)
|
|
186
|
+
if (!isLoadMore && showGraphView.value) {
|
|
178
187
|
setTimeout(renderGraph, 0)
|
|
179
188
|
}
|
|
180
189
|
|
|
@@ -182,9 +191,18 @@ async function loadLog(all = false) {
|
|
|
182
191
|
} catch (error) {
|
|
183
192
|
errorMessage.value = '加载提交历史失败: ' + (error instanceof Error ? error.message : String(error))
|
|
184
193
|
console.error('加载日志失败:', error)
|
|
194
|
+
|
|
195
|
+
// 如果加载更多失败,标记没有更多数据
|
|
196
|
+
if (page > 1) {
|
|
197
|
+
hasMoreData.value = false
|
|
198
|
+
}
|
|
185
199
|
} finally {
|
|
186
|
-
//
|
|
187
|
-
|
|
200
|
+
// 重置加载状态
|
|
201
|
+
if (page > 1) {
|
|
202
|
+
isLoadingMore.value = false
|
|
203
|
+
} else {
|
|
204
|
+
localLoading.value = false
|
|
205
|
+
}
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
208
|
|
|
@@ -294,6 +312,59 @@ function getBranchTagType(ref: string) {
|
|
|
294
312
|
return 'info'
|
|
295
313
|
}
|
|
296
314
|
|
|
315
|
+
// 添加对表格实例的引用
|
|
316
|
+
const tableRef = ref<InstanceType<typeof ElTable> | null>(null)
|
|
317
|
+
const tableBodyWrapper = ref<HTMLElement | null>(null)
|
|
318
|
+
|
|
319
|
+
// 监听表格滚动事件的处理函数
|
|
320
|
+
function handleTableScroll(event: Event) {
|
|
321
|
+
if (showGraphView.value || !hasMoreData.value || isLoadingMore.value || isLoading.value) {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const target = event.target as HTMLElement
|
|
326
|
+
const { scrollTop, scrollHeight, clientHeight } = target
|
|
327
|
+
const scrollDistance = scrollHeight - scrollTop - clientHeight
|
|
328
|
+
|
|
329
|
+
// 调试信息
|
|
330
|
+
console.log('表格滚动:', {
|
|
331
|
+
scrollTop,
|
|
332
|
+
scrollHeight,
|
|
333
|
+
clientHeight,
|
|
334
|
+
scrollDistance
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// 当滚动到距离底部20px时触发加载
|
|
338
|
+
if (scrollDistance <= 20) {
|
|
339
|
+
console.log('已滚动到底部,加载更多数据')
|
|
340
|
+
loadMoreLogs()
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 设置表格滚动监听
|
|
345
|
+
function setupTableScrollListener() {
|
|
346
|
+
if (!tableRef.value) return
|
|
347
|
+
|
|
348
|
+
// 获取表格的body-wrapper
|
|
349
|
+
tableBodyWrapper.value = tableRef.value.$el.querySelector('.el-table__body-wrapper')
|
|
350
|
+
|
|
351
|
+
if (tableBodyWrapper.value) {
|
|
352
|
+
console.log('添加表格滚动监听')
|
|
353
|
+
tableBodyWrapper.value.addEventListener('scroll', handleTableScroll, true)
|
|
354
|
+
} else {
|
|
355
|
+
console.error('未找到表格的body-wrapper元素')
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 移除表格滚动监听
|
|
360
|
+
function removeTableScrollListener() {
|
|
361
|
+
if (tableBodyWrapper.value) {
|
|
362
|
+
console.log('移除表格滚动监听')
|
|
363
|
+
tableBodyWrapper.value.removeEventListener('scroll', handleTableScroll, true)
|
|
364
|
+
tableBodyWrapper.value = null
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
297
368
|
onMounted(() => {
|
|
298
369
|
// 检查gitLogStore中是否已有数据
|
|
299
370
|
if (gitStore.isGitRepo) {
|
|
@@ -320,9 +391,33 @@ onMounted(() => {
|
|
|
320
391
|
console.log('初始加载日志数据')
|
|
321
392
|
loadLog()
|
|
322
393
|
}
|
|
394
|
+
|
|
395
|
+
// 加载所有可能的作者列表
|
|
396
|
+
fetchAllAuthors()
|
|
397
|
+
|
|
398
|
+
// 加载所有可能的分支列表
|
|
399
|
+
fetchAllBranches()
|
|
323
400
|
} else {
|
|
324
401
|
errorMessage.value = '当前目录不是Git仓库'
|
|
325
402
|
}
|
|
403
|
+
|
|
404
|
+
// 在下一个tick中设置表格滚动监听
|
|
405
|
+
nextTick(() => {
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
setupTableScrollListener()
|
|
408
|
+
}, 500) // 给表格足够的时间来渲染
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
onBeforeUnmount(() => {
|
|
413
|
+
// 清除表格滚动监听
|
|
414
|
+
removeTableScrollListener()
|
|
415
|
+
|
|
416
|
+
// 清除定时器
|
|
417
|
+
if (loadTimerInterval.value !== null) {
|
|
418
|
+
window.clearInterval(loadTimerInterval.value)
|
|
419
|
+
loadTimerInterval.value = null
|
|
420
|
+
}
|
|
326
421
|
})
|
|
327
422
|
|
|
328
423
|
// 简化刷新函数,只需调用loadLog即可
|
|
@@ -331,36 +426,49 @@ const refreshLog = () => {
|
|
|
331
426
|
errorMessage.value = '当前目录不是Git仓库'
|
|
332
427
|
return
|
|
333
428
|
}
|
|
334
|
-
|
|
429
|
+
// 重置页码,重新加载第一页
|
|
430
|
+
currentPage.value = 1
|
|
431
|
+
hasMoreData.value = true
|
|
432
|
+
loadLog(showAllCommits.value, 1)
|
|
335
433
|
}
|
|
336
434
|
|
|
337
435
|
// 监听store中的日志变化
|
|
338
436
|
watch(() => gitLogStore.log, (newLogs) => {
|
|
339
437
|
console.log('监听到gitLogStore.log变化,更新图表数据')
|
|
340
438
|
|
|
341
|
-
// 清空logsData
|
|
342
|
-
logsData.length = 0
|
|
343
|
-
|
|
344
|
-
// 重新填充数据
|
|
345
|
-
newLogs.forEach((item: LogItem) => logsData.push(item))
|
|
346
|
-
|
|
347
|
-
// 更新计数器
|
|
348
|
-
totalCommits.value = newLogs.length
|
|
349
|
-
|
|
350
|
-
// 尝试解决logs.value赋值问题
|
|
351
439
|
try {
|
|
352
|
-
//
|
|
440
|
+
// 清空logsData
|
|
441
|
+
logsData.length = 0
|
|
442
|
+
|
|
443
|
+
// 重新填充数据,使用类型断言
|
|
444
|
+
if (Array.isArray(newLogs)) {
|
|
445
|
+
// @ts-ignore - 忽略类型校验
|
|
446
|
+
newLogs.forEach(item => item && logsData.push(item))
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 更新计数器
|
|
450
|
+
totalCommits.value = logsData.length
|
|
451
|
+
|
|
452
|
+
// 重置当前页为第1页
|
|
453
|
+
currentPage.value = 1
|
|
454
|
+
|
|
455
|
+
// 确保引用更新,触发UI重渲染
|
|
456
|
+
// @ts-ignore - 忽略类型校验
|
|
353
457
|
logs.value = [...logsData]
|
|
458
|
+
|
|
459
|
+
// 设置刷新提示
|
|
460
|
+
logRefreshed.value = true
|
|
461
|
+
setTimeout(() => { logRefreshed.value = false }, 2000)
|
|
462
|
+
|
|
463
|
+
console.log(`数据更新完成,共${logs.value.length}条记录,准备渲染图表`)
|
|
464
|
+
|
|
465
|
+
if (showGraphView.value && logsData.length > 0) {
|
|
466
|
+
setTimeout(renderGraph, 0)
|
|
467
|
+
}
|
|
354
468
|
} catch (error) {
|
|
355
|
-
console.
|
|
469
|
+
console.error('更新日志数据失败:', error)
|
|
356
470
|
}
|
|
357
|
-
|
|
358
|
-
console.log(`数据更新完成,准备渲染图表(${logsData.length}条记录)`)
|
|
359
|
-
|
|
360
|
-
if (showGraphView.value && logsData.length > 0) {
|
|
361
|
-
setTimeout(renderGraph, 0)
|
|
362
|
-
}
|
|
363
|
-
})
|
|
471
|
+
}, { immediate: true })
|
|
364
472
|
|
|
365
473
|
// 暴露方法给父组件
|
|
366
474
|
defineExpose({
|
|
@@ -417,7 +525,9 @@ function fitGraphToContainer() {
|
|
|
417
525
|
}
|
|
418
526
|
|
|
419
527
|
// 查看提交详情
|
|
420
|
-
async function viewCommitDetail(commit: LogItem) {
|
|
528
|
+
async function viewCommitDetail(commit: LogItem | null) {
|
|
529
|
+
if (!commit) return
|
|
530
|
+
|
|
421
531
|
selectedCommit.value = commit
|
|
422
532
|
commitDetailVisible.value = true
|
|
423
533
|
isLoadingCommitDetail.value = true
|
|
@@ -547,6 +657,232 @@ function formatCommitMessage(message: string) {
|
|
|
547
657
|
// 返回格式化后的提交信息,保留换行符
|
|
548
658
|
return message.trim();
|
|
549
659
|
}
|
|
660
|
+
|
|
661
|
+
// 加载更多日志
|
|
662
|
+
function loadMoreLogs() {
|
|
663
|
+
if (!hasMoreData.value || isLoadingMore.value || isLoading.value) return
|
|
664
|
+
|
|
665
|
+
console.log(`加载更多日志,页码: ${currentPage.value + 1}`)
|
|
666
|
+
loadLog(showAllCommits.value, currentPage.value + 1)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// 重置筛选条件并重新加载数据
|
|
670
|
+
function resetFilters() {
|
|
671
|
+
authorFilter.value = []
|
|
672
|
+
branchFilter.value = []
|
|
673
|
+
messageFilter.value = ''
|
|
674
|
+
dateRangeFilter.value = null
|
|
675
|
+
|
|
676
|
+
// 重置筛选后重新加载数据
|
|
677
|
+
currentPage.value = 1
|
|
678
|
+
loadLog(showAllCommits.value, 1)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// 应用筛选条件
|
|
682
|
+
function applyFilters() {
|
|
683
|
+
// 应用筛选时重置到第一页
|
|
684
|
+
currentPage.value = 1
|
|
685
|
+
loadLog(showAllCommits.value, 1)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// 添加获取所有作者的函数
|
|
689
|
+
async function fetchAllAuthors() {
|
|
690
|
+
try {
|
|
691
|
+
console.log('获取所有可用作者...')
|
|
692
|
+
const response = await fetch('/api/authors')
|
|
693
|
+
const result = await response.json()
|
|
694
|
+
|
|
695
|
+
if (result.success && Array.isArray(result.authors)) {
|
|
696
|
+
// 更新可用作者列表
|
|
697
|
+
availableAuthors.value = result.authors.sort()
|
|
698
|
+
console.log(`获取到${availableAuthors.value.length}位作者`)
|
|
699
|
+
} else {
|
|
700
|
+
// 如果获取作者列表失败,但正常获取了日志
|
|
701
|
+
// 从当前加载的日志中提取作者列表作为备选
|
|
702
|
+
console.warn('从API获取作者列表失败,将从现有日志中提取作者列表')
|
|
703
|
+
extractAuthorsFromLogs()
|
|
704
|
+
}
|
|
705
|
+
} catch (error) {
|
|
706
|
+
console.error('获取作者列表失败:', error)
|
|
707
|
+
// 从当前加载的日志中提取作者列表作为备选
|
|
708
|
+
extractAuthorsFromLogs()
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// 从已加载的日志中提取作者列表
|
|
713
|
+
function extractAuthorsFromLogs() {
|
|
714
|
+
const authors = new Set<string>()
|
|
715
|
+
logs.value.forEach(log => {
|
|
716
|
+
if (log.author) {
|
|
717
|
+
authors.add(log.author)
|
|
718
|
+
}
|
|
719
|
+
})
|
|
720
|
+
availableAuthors.value = Array.from(authors).sort()
|
|
721
|
+
console.log(`从现有日志中提取了${availableAuthors.value.length}位作者`)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// 添加获取所有分支的函数
|
|
725
|
+
async function fetchAllBranches() {
|
|
726
|
+
try {
|
|
727
|
+
console.log('获取所有可用分支...')
|
|
728
|
+
const response = await fetch('/api/branches')
|
|
729
|
+
const result = await response.json()
|
|
730
|
+
|
|
731
|
+
if (result.branches && Array.isArray(result.branches)) {
|
|
732
|
+
// 更新可用分支列表
|
|
733
|
+
availableBranches.value = result.branches.sort()
|
|
734
|
+
console.log(`获取到${availableBranches.value.length}个分支`)
|
|
735
|
+
} else {
|
|
736
|
+
console.warn('获取分支列表失败')
|
|
737
|
+
extractBranchesFromLogs()
|
|
738
|
+
}
|
|
739
|
+
} catch (error) {
|
|
740
|
+
console.error('获取分支列表失败:', error)
|
|
741
|
+
extractBranchesFromLogs()
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// 从日志中提取分支
|
|
746
|
+
function extractBranchesFromLogs() {
|
|
747
|
+
// 从当前加载的日志中提取唯一的分支名称
|
|
748
|
+
const branches = new Set<string>()
|
|
749
|
+
|
|
750
|
+
logs.value.forEach(log => {
|
|
751
|
+
if (log.branch) {
|
|
752
|
+
// 分支可能是逗号分隔的多个引用
|
|
753
|
+
const refs = log.branch.split(',')
|
|
754
|
+
refs.forEach(ref => {
|
|
755
|
+
const branch = formatBranchName(ref.trim())
|
|
756
|
+
if (branch) {
|
|
757
|
+
branches.add(branch)
|
|
758
|
+
}
|
|
759
|
+
})
|
|
760
|
+
}
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
availableBranches.value = Array.from(branches).sort()
|
|
764
|
+
console.log(`从日志中提取了${availableBranches.value.length}个分支`)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// 处理右键菜单事件
|
|
768
|
+
function handleContextMenu(row: LogItem, column: any, event: MouseEvent) {
|
|
769
|
+
console.log('handleContextMenu', row, column, event)
|
|
770
|
+
// 阻止默认右键菜单
|
|
771
|
+
event.preventDefault()
|
|
772
|
+
|
|
773
|
+
// 设置右键菜单位置
|
|
774
|
+
contextMenuTop.value = event.clientY
|
|
775
|
+
contextMenuLeft.value = event.clientX
|
|
776
|
+
|
|
777
|
+
// 设置选中的提交
|
|
778
|
+
selectedContextCommit.value = row
|
|
779
|
+
|
|
780
|
+
// 显示右键菜单
|
|
781
|
+
contextMenuVisible.value = true
|
|
782
|
+
|
|
783
|
+
// 点击其他地方时隐藏菜单
|
|
784
|
+
const hideContextMenu = () => {
|
|
785
|
+
contextMenuVisible.value = false
|
|
786
|
+
document.removeEventListener('click', hideContextMenu)
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// 添加点击监听器
|
|
790
|
+
setTimeout(() => {
|
|
791
|
+
document.addEventListener('click', hideContextMenu)
|
|
792
|
+
}, 0)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// 撤销提交 (Revert)
|
|
796
|
+
async function revertCommit(commit: LogItem | null) {
|
|
797
|
+
if (!commit) return
|
|
798
|
+
|
|
799
|
+
try {
|
|
800
|
+
// 询问确认
|
|
801
|
+
await ElMessageBox.confirm(
|
|
802
|
+
`确定要撤销提交 ${commit.hash.substring(0, 7)} 吗?这将创建一个新的提交来撤销这次提交的更改。`,
|
|
803
|
+
'撤销提交确认',
|
|
804
|
+
{
|
|
805
|
+
confirmButtonText: '确认',
|
|
806
|
+
cancelButtonText: '取消',
|
|
807
|
+
type: 'warning'
|
|
808
|
+
}
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
// 发送请求
|
|
812
|
+
const response = await fetch('/api/revert-commit', {
|
|
813
|
+
method: 'POST',
|
|
814
|
+
headers: {
|
|
815
|
+
'Content-Type': 'application/json'
|
|
816
|
+
},
|
|
817
|
+
body: JSON.stringify({ hash: commit.hash })
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
const result = await response.json()
|
|
821
|
+
|
|
822
|
+
if (result.success) {
|
|
823
|
+
ElMessage.success(result.message || '已成功撤销提交')
|
|
824
|
+
// 刷新日志
|
|
825
|
+
refreshLog()
|
|
826
|
+
// 刷新Git状态
|
|
827
|
+
gitLogStore.fetchStatus()
|
|
828
|
+
// 添加: 刷新分支状态
|
|
829
|
+
gitStore.getBranchStatus()
|
|
830
|
+
} else {
|
|
831
|
+
ElMessage.error(result.error || '撤销提交失败')
|
|
832
|
+
}
|
|
833
|
+
} catch (error: any) {
|
|
834
|
+
if (error !== 'cancel') {
|
|
835
|
+
console.error('撤销提交出错:', error)
|
|
836
|
+
ElMessage.error('撤销提交失败: ' + (error.message || error))
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Cherry-pick提交
|
|
842
|
+
async function cherryPickCommit(commit: LogItem | null) {
|
|
843
|
+
if (!commit) return
|
|
844
|
+
|
|
845
|
+
try {
|
|
846
|
+
// 询问确认
|
|
847
|
+
await ElMessageBox.confirm(
|
|
848
|
+
`确定要将提交 ${commit.hash.substring(0, 7)} Cherry-Pick 到当前分支吗?`,
|
|
849
|
+
'Cherry-Pick确认',
|
|
850
|
+
{
|
|
851
|
+
confirmButtonText: '确认',
|
|
852
|
+
cancelButtonText: '取消',
|
|
853
|
+
type: 'warning'
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
// 发送请求
|
|
858
|
+
const response = await fetch('/api/cherry-pick-commit', {
|
|
859
|
+
method: 'POST',
|
|
860
|
+
headers: {
|
|
861
|
+
'Content-Type': 'application/json'
|
|
862
|
+
},
|
|
863
|
+
body: JSON.stringify({ hash: commit.hash })
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
const result = await response.json()
|
|
867
|
+
|
|
868
|
+
if (result.success) {
|
|
869
|
+
ElMessage.success(result.message || '已成功Cherry-Pick提交')
|
|
870
|
+
// 刷新日志
|
|
871
|
+
refreshLog()
|
|
872
|
+
// 刷新Git状态
|
|
873
|
+
gitLogStore.fetchStatus()
|
|
874
|
+
// 添加: 刷新分支状态
|
|
875
|
+
gitStore.getBranchStatus()
|
|
876
|
+
} else {
|
|
877
|
+
ElMessage.error(result.error || 'Cherry-Pick提交失败')
|
|
878
|
+
}
|
|
879
|
+
} catch (error: any) {
|
|
880
|
+
if (error !== 'cancel') {
|
|
881
|
+
console.error('Cherry-Pick提交出错:', error)
|
|
882
|
+
ElMessage.error('Cherry-Pick提交失败: ' + (error.message || error))
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
550
886
|
</script>
|
|
551
887
|
|
|
552
888
|
<template>
|
|
@@ -558,8 +894,38 @@ function formatCommitMessage(message: string) {
|
|
|
558
894
|
|
|
559
895
|
<!-- 固定头部区域 -->
|
|
560
896
|
<div class="log-header">
|
|
561
|
-
<
|
|
897
|
+
<div class="header-left">
|
|
898
|
+
<h2>提交历史</h2>
|
|
899
|
+
<!-- <el-tag type="info" effect="plain" size="small" class="record-count" v-if="!showGraphView">
|
|
900
|
+
<template #icon>
|
|
901
|
+
<el-icon><Document /></el-icon>
|
|
902
|
+
</template>
|
|
903
|
+
{{ filteredLogs.length }}/{{ logs.length }}
|
|
904
|
+
<el-tag v-if="!showAllCommits" type="warning" size="small" effect="plain" style="margin-left: 5px">
|
|
905
|
+
分页加载 (每页100条)
|
|
906
|
+
</el-tag>
|
|
907
|
+
<el-tag v-else type="success" size="small" effect="plain" style="margin-left: 5px">
|
|
908
|
+
全部
|
|
909
|
+
</el-tag>
|
|
910
|
+
</el-tag> -->
|
|
911
|
+
</div>
|
|
912
|
+
|
|
562
913
|
<div class="log-actions">
|
|
914
|
+
<!-- 筛选按钮移到这里 -->
|
|
915
|
+
<el-button
|
|
916
|
+
v-if="!showGraphView"
|
|
917
|
+
:type="filterVisible ? 'primary' : 'default'"
|
|
918
|
+
size="small"
|
|
919
|
+
@click="filterVisible = !filterVisible"
|
|
920
|
+
>
|
|
921
|
+
<template #icon>
|
|
922
|
+
<el-icon><Filter /></el-icon>
|
|
923
|
+
</template>
|
|
924
|
+
筛选
|
|
925
|
+
<el-badge v-if="filteredLogs.length !== logs.length" :value="filteredLogs.length" class="filter-badge" />
|
|
926
|
+
</el-button>
|
|
927
|
+
|
|
928
|
+
<!-- 原有的按钮 -->
|
|
563
929
|
<el-button
|
|
564
930
|
type="primary"
|
|
565
931
|
size="small"
|
|
@@ -579,7 +945,7 @@ function formatCommitMessage(message: string) {
|
|
|
579
945
|
<template #icon>
|
|
580
946
|
<el-icon><component :is="showAllCommits ? List : More" /></el-icon>
|
|
581
947
|
</template>
|
|
582
|
-
{{ showAllCommits ? '
|
|
948
|
+
{{ showAllCommits ? '显示分页加载 (每页100条)' : '显示所有提交' }}
|
|
583
949
|
</el-button>
|
|
584
950
|
<el-button
|
|
585
951
|
circle
|
|
@@ -593,14 +959,90 @@ function formatCommitMessage(message: string) {
|
|
|
593
959
|
</div>
|
|
594
960
|
</div>
|
|
595
961
|
|
|
962
|
+
<!-- 筛选面板放在头部下方,但在内容区域之前 -->
|
|
963
|
+
<div v-if="filterVisible && !showGraphView" class="filter-panel-header">
|
|
964
|
+
<div class="filter-form">
|
|
965
|
+
<div class="filter-item">
|
|
966
|
+
<div class="filter-label">作者:</div>
|
|
967
|
+
<el-select
|
|
968
|
+
v-model="authorFilter"
|
|
969
|
+
placeholder="选择作者"
|
|
970
|
+
multiple
|
|
971
|
+
clearable
|
|
972
|
+
filterable
|
|
973
|
+
class="filter-input"
|
|
974
|
+
size="small"
|
|
975
|
+
>
|
|
976
|
+
<el-option
|
|
977
|
+
v-for="author in availableAuthors"
|
|
978
|
+
:key="author"
|
|
979
|
+
:label="author"
|
|
980
|
+
:value="author"
|
|
981
|
+
/>
|
|
982
|
+
</el-select>
|
|
983
|
+
</div>
|
|
984
|
+
|
|
985
|
+
<div class="filter-item">
|
|
986
|
+
<div class="filter-label">分支:</div>
|
|
987
|
+
<el-select
|
|
988
|
+
v-model="branchFilter"
|
|
989
|
+
placeholder="选择分支"
|
|
990
|
+
multiple
|
|
991
|
+
clearable
|
|
992
|
+
filterable
|
|
993
|
+
class="filter-input"
|
|
994
|
+
size="small"
|
|
995
|
+
>
|
|
996
|
+
<el-option
|
|
997
|
+
v-for="branch in availableBranches"
|
|
998
|
+
:key="branch"
|
|
999
|
+
:label="branch"
|
|
1000
|
+
:value="branch"
|
|
1001
|
+
/>
|
|
1002
|
+
</el-select>
|
|
1003
|
+
</div>
|
|
1004
|
+
|
|
1005
|
+
<div class="filter-item">
|
|
1006
|
+
<div class="filter-label">提交信息包含:</div>
|
|
1007
|
+
<el-input
|
|
1008
|
+
v-model="messageFilter"
|
|
1009
|
+
placeholder="关键词"
|
|
1010
|
+
clearable
|
|
1011
|
+
class="filter-input"
|
|
1012
|
+
size="small"
|
|
1013
|
+
/>
|
|
1014
|
+
</div>
|
|
1015
|
+
|
|
1016
|
+
<div class="filter-item">
|
|
1017
|
+
<div class="filter-label">日期范围:</div>
|
|
1018
|
+
<el-date-picker
|
|
1019
|
+
v-model="dateRangeFilter"
|
|
1020
|
+
type="daterange"
|
|
1021
|
+
range-separator="至"
|
|
1022
|
+
start-placeholder="开始日期"
|
|
1023
|
+
end-placeholder="结束日期"
|
|
1024
|
+
format="YYYY-MM-DD"
|
|
1025
|
+
value-format="YYYY-MM-DD"
|
|
1026
|
+
class="filter-input date-range"
|
|
1027
|
+
size="small"
|
|
1028
|
+
/>
|
|
1029
|
+
</div>
|
|
1030
|
+
|
|
1031
|
+
<div class="filter-actions">
|
|
1032
|
+
<el-button type="primary" size="small" @click="applyFilters">应用筛选</el-button>
|
|
1033
|
+
<el-button type="info" size="small" @click="resetFilters">重置</el-button>
|
|
1034
|
+
</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
</div>
|
|
1037
|
+
|
|
596
1038
|
<!-- 内容区域,添加上边距以避免被固定头部遮挡 -->
|
|
597
|
-
<div class="content-area">
|
|
1039
|
+
<div class="content-area" :class="{'with-filter': filterVisible && !showGraphView}">
|
|
598
1040
|
<div v-if="errorMessage">{{ errorMessage }}</div>
|
|
599
1041
|
<div v-else>
|
|
600
1042
|
<!-- 图表视图 -->
|
|
601
1043
|
<div v-if="showGraphView" class="graph-view">
|
|
602
1044
|
<div class="commit-count" v-if="logsData.length > 0">
|
|
603
|
-
显示 {{ logsData.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(
|
|
1045
|
+
显示 {{ logsData.length }} 条提交记录 {{ showAllCommits ? '(全部)' : '(分页加载,每页100条)' }}
|
|
604
1046
|
</div>
|
|
605
1047
|
|
|
606
1048
|
<!-- 添加缩放控制 -->
|
|
@@ -653,92 +1095,8 @@ function formatCommitMessage(message: string) {
|
|
|
653
1095
|
</div>
|
|
654
1096
|
|
|
655
1097
|
<!-- 表格视图 -->
|
|
656
|
-
<div v-else>
|
|
657
|
-
|
|
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>
|
|
672
|
-
|
|
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>
|
|
688
|
-
</div>
|
|
689
|
-
|
|
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>
|
|
739
|
-
</div>
|
|
740
|
-
|
|
741
|
-
<el-table :data="filteredLogs" style="width: 100%" stripe border v-loading="isLoading">
|
|
1098
|
+
<div v-else class="table-view-container">
|
|
1099
|
+
<el-table ref="tableRef" :data="filteredLogs" stripe border v-loading="isLoading" class="log-table" :empty-text="isLoading ? '加载中...' : '没有匹配的提交记录'" height="500" @row-contextmenu="handleContextMenu" >
|
|
742
1100
|
<el-table-column label="提交哈希" width="100" resizable>
|
|
743
1101
|
<template #default="scope">
|
|
744
1102
|
<span class="commit-hash" @click="viewCommitDetail(scope.row)">{{ scope.row.hash.substring(0, 7) }}</span>
|
|
@@ -769,6 +1127,26 @@ function formatCommitMessage(message: string) {
|
|
|
769
1127
|
</el-table-column>
|
|
770
1128
|
<el-table-column prop="message" label="提交信息" min-width="250" />
|
|
771
1129
|
</el-table>
|
|
1130
|
+
|
|
1131
|
+
<!-- 添加底部加载状态和加载更多按钮 -->
|
|
1132
|
+
<div v-if="!showAllCommits" class="load-more-container">
|
|
1133
|
+
<!-- 显示加载状态和页码信息 -->
|
|
1134
|
+
<div class="pagination-info">
|
|
1135
|
+
<span>第 {{ currentPage }} 页 {{ totalCommits > 0 ? `/ 共 ${Math.ceil(totalCommits / 100) || 1} 页` : '' }} (总计 {{ totalCommits }} 条记录)</span>
|
|
1136
|
+
</div>
|
|
1137
|
+
|
|
1138
|
+
<div v-if="isLoadingMore" class="loading-more">
|
|
1139
|
+
<div class="loading-spinner"></div>
|
|
1140
|
+
<span>加载更多...</span>
|
|
1141
|
+
</div>
|
|
1142
|
+
<div v-else-if="hasMoreData" class="load-more-button" @click="loadMoreLogs">
|
|
1143
|
+
<span>加载更多</span>
|
|
1144
|
+
</div>
|
|
1145
|
+
<div v-else class="no-more-data">
|
|
1146
|
+
<span>没有更多数据了</span>
|
|
1147
|
+
<span v-if="logs.length > 0" class="total-loaded">(已加载 {{ logs.length }} 条记录)</span>
|
|
1148
|
+
</div>
|
|
1149
|
+
</div>
|
|
772
1150
|
</div>
|
|
773
1151
|
</div>
|
|
774
1152
|
</div>
|
|
@@ -828,11 +1206,7 @@ function formatCommitMessage(message: string) {
|
|
|
828
1206
|
</div>
|
|
829
1207
|
</div>
|
|
830
1208
|
</div>
|
|
831
|
-
|
|
832
|
-
</div>
|
|
833
|
-
</template>
|
|
834
|
-
|
|
835
|
-
<style scoped>
|
|
1209
|
+
</el-dialog> </div> <!-- 添加右键菜单 --> <div v-show="contextMenuVisible" class="context-menu" :style="{ top: contextMenuTop + 'px', left: contextMenuLeft + 'px' }" > <div class="context-menu-item" @click="viewCommitDetail(selectedContextCommit)"> <i class="el-icon-view"></i> 查看详情 </div> <div class="context-menu-item" @click="revertCommit(selectedContextCommit)"> <i class="el-icon-delete"></i> 撤销提交 (Revert) </div> <div class="context-menu-item" @click="cherryPickCommit(selectedContextCommit)"> <i class="el-icon-edit"></i> Cherry-Pick 到当前分支 </div> </div></template><style scoped>
|
|
836
1210
|
.card {
|
|
837
1211
|
background-color: white;
|
|
838
1212
|
border-radius: 8px;
|
|
@@ -857,6 +1231,13 @@ function formatCommitMessage(message: string) {
|
|
|
857
1231
|
top: 0;
|
|
858
1232
|
z-index: 100;
|
|
859
1233
|
height: 36px;
|
|
1234
|
+
flex-shrink: 0; /* 防止头部被压缩 */
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.header-left {
|
|
1238
|
+
display: flex;
|
|
1239
|
+
align-items: center;
|
|
1240
|
+
gap: 8px;
|
|
860
1241
|
}
|
|
861
1242
|
|
|
862
1243
|
.log-header h2 {
|
|
@@ -871,11 +1252,23 @@ function formatCommitMessage(message: string) {
|
|
|
871
1252
|
}
|
|
872
1253
|
|
|
873
1254
|
.content-area {
|
|
874
|
-
padding: 0
|
|
875
|
-
overflow-y: auto;
|
|
1255
|
+
padding: 10px 0;
|
|
876
1256
|
flex: 1;
|
|
877
1257
|
min-height: 100px;
|
|
878
1258
|
height: calc(100% - 52px);
|
|
1259
|
+
display: flex;
|
|
1260
|
+
flex-direction: column;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.content-area.with-filter {
|
|
1264
|
+
height: calc(100% - 52px - 60px); /* 减去header高度和filter高度 */
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/* 确保内容区域内的直接子元素占满高度 */
|
|
1268
|
+
.content-area > div {
|
|
1269
|
+
flex: 1;
|
|
1270
|
+
display: flex;
|
|
1271
|
+
flex-direction: column;
|
|
879
1272
|
}
|
|
880
1273
|
|
|
881
1274
|
/* 优化表格区域 */
|
|
@@ -909,10 +1302,18 @@ function formatCommitMessage(message: string) {
|
|
|
909
1302
|
text-align: right;
|
|
910
1303
|
}
|
|
911
1304
|
|
|
1305
|
+
.graph-view {
|
|
1306
|
+
width: 100%;
|
|
1307
|
+
flex: 1;
|
|
1308
|
+
display: flex;
|
|
1309
|
+
flex-direction: column;
|
|
1310
|
+
overflow-y: auto;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
912
1313
|
.graph-container {
|
|
913
1314
|
width: 100%;
|
|
914
|
-
|
|
915
|
-
|
|
1315
|
+
flex: 1;
|
|
1316
|
+
min-height: 500px;
|
|
916
1317
|
border: 1px solid #ebeef5;
|
|
917
1318
|
border-radius: 4px;
|
|
918
1319
|
padding: 10px;
|
|
@@ -925,10 +1326,6 @@ function formatCommitMessage(message: string) {
|
|
|
925
1326
|
transition: transform 0.2s ease;
|
|
926
1327
|
}
|
|
927
1328
|
|
|
928
|
-
.graph-view {
|
|
929
|
-
width: 100%;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
1329
|
.graph-controls {
|
|
933
1330
|
display: flex;
|
|
934
1331
|
justify-content: space-between;
|
|
@@ -1181,7 +1578,7 @@ function formatCommitMessage(message: string) {
|
|
|
1181
1578
|
|
|
1182
1579
|
/* 减小对话框的顶部边距 */
|
|
1183
1580
|
:deep(.commit-detail-dialog) {
|
|
1184
|
-
--el-dialog-margin-top:
|
|
1581
|
+
--el-dialog-margin-top: 7vh;
|
|
1185
1582
|
}
|
|
1186
1583
|
|
|
1187
1584
|
.history-controls {
|
|
@@ -1190,6 +1587,17 @@ function formatCommitMessage(message: string) {
|
|
|
1190
1587
|
align-items: center;
|
|
1191
1588
|
margin-bottom: 15px;
|
|
1192
1589
|
padding: 0;
|
|
1590
|
+
position: sticky;
|
|
1591
|
+
top: 52px; /* 与 log-header 的高度匹配 */
|
|
1592
|
+
z-index: 90;
|
|
1593
|
+
background-color: white;
|
|
1594
|
+
padding: 5px 0;
|
|
1595
|
+
transition: box-shadow 0.3s ease, background-color 0.3s ease, padding 0.2s ease;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/* 当滚动时添加微妙的阴影效果 */
|
|
1599
|
+
.content-area:not(:hover) .history-controls:not(:hover) {
|
|
1600
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
1193
1601
|
}
|
|
1194
1602
|
|
|
1195
1603
|
.history-stats {
|
|
@@ -1220,6 +1628,19 @@ function formatCommitMessage(message: string) {
|
|
|
1220
1628
|
margin-bottom: 15px;
|
|
1221
1629
|
border: 1px solid #e4e7ed;
|
|
1222
1630
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
1631
|
+
position: sticky;
|
|
1632
|
+
top: 98px; /* history-controls的高度(36px) + padding(10px) + log-header的高度(52px) */
|
|
1633
|
+
z-index: 89; /* 比history-controls低一点 */
|
|
1634
|
+
transition: box-shadow 0.3s ease, background-color 0.3s ease, transform 0.2s ease;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/* 当滚动时增强视觉效果 */
|
|
1638
|
+
.content-area:not(:hover) .filter-panel:not(:hover) {
|
|
1639
|
+
box-shadow: 0 3px 16px rgba(0, 0, 0, 0.1);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
.filter-panel:hover {
|
|
1643
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
1223
1644
|
}
|
|
1224
1645
|
|
|
1225
1646
|
.filter-form {
|
|
@@ -1254,9 +1675,272 @@ function formatCommitMessage(message: string) {
|
|
|
1254
1675
|
align-items: center;
|
|
1255
1676
|
gap: 10px;
|
|
1256
1677
|
}
|
|
1678
|
+
|
|
1679
|
+
/* 表格样式 */
|
|
1680
|
+
.log-table {
|
|
1681
|
+
transition: margin-top 0.3s ease;
|
|
1682
|
+
min-height: 200px; /* 确保表格有最小高度 */
|
|
1683
|
+
height: 100%; /* 填充可用空间 */
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/* 为带筛选面板的表格添加顶部边距 */
|
|
1687
|
+
.log-table.has-filter {
|
|
1688
|
+
margin-top: 10px;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/* 当控件固定时增加表格上边距 */
|
|
1692
|
+
.log-table.has-sticky-controls {
|
|
1693
|
+
margin-top: 52px !important; /* controls 高度 + 一些额外空间 */
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/* 当筛选面板固定时再增加表格上边距 */
|
|
1697
|
+
.log-table.has-sticky-filter {
|
|
1698
|
+
margin-top: 140px !important; /* 筛选面板高度 + controls 高度 + 一些额外空间 */
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/* 表格为空时的样式 */
|
|
1702
|
+
.el-table__empty-block {
|
|
1703
|
+
min-height: 200px;
|
|
1704
|
+
justify-content: center;
|
|
1705
|
+
align-items: center;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/* 确保表格容器占满可用空间 */
|
|
1709
|
+
.content-area > div:not(.graph-view) {
|
|
1710
|
+
display: flex;
|
|
1711
|
+
flex-direction: column;
|
|
1712
|
+
height: 100%;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/* 表格视图容器 */
|
|
1716
|
+
.table-view-container {
|
|
1717
|
+
display: flex;
|
|
1718
|
+
flex-direction: column;
|
|
1719
|
+
flex: 1;
|
|
1720
|
+
height: 100%;
|
|
1721
|
+
overflow-y: auto;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
.log-table {
|
|
1725
|
+
width: 100%;
|
|
1726
|
+
flex: 1;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
/* 表格容器样式 */
|
|
1730
|
+
.table-view-container .el-table {
|
|
1731
|
+
flex: 1;
|
|
1732
|
+
width: 100%;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
.table-view-container .el-table__inner-wrapper {
|
|
1736
|
+
width: 100%;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.table-view-container .el-table__body-wrapper {
|
|
1740
|
+
width: 100%;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
.filter-panel.filter-sticky {
|
|
1744
|
+
background-color: rgba(245, 247, 250, 0.97);
|
|
1745
|
+
backdrop-filter: blur(4px);
|
|
1746
|
+
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
|
|
1747
|
+
border-top: none;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
.history-controls.controls-sticky {
|
|
1751
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1752
|
+
background-color: rgba(255, 255, 255, 0.98);
|
|
1753
|
+
backdrop-filter: blur(4px);
|
|
1754
|
+
padding: 8px 0;
|
|
1755
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/* 筛选面板头部样式 */
|
|
1759
|
+
.filter-panel-header {
|
|
1760
|
+
background-color: #f5f7fa;
|
|
1761
|
+
padding: 10px 16px;
|
|
1762
|
+
border-bottom: 1px solid #e4e7ed;
|
|
1763
|
+
position: sticky;
|
|
1764
|
+
top: 36px; /* 紧贴log-header下方 */
|
|
1765
|
+
z-index: 99;
|
|
1766
|
+
transition: all 0.3s ease;
|
|
1767
|
+
flex-shrink: 0;
|
|
1768
|
+
margin-bottom: 0; /* 移除底部边距 */
|
|
1769
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
.filter-panel-header .filter-form {
|
|
1773
|
+
display: flex;
|
|
1774
|
+
flex-wrap: wrap;
|
|
1775
|
+
gap: 10px;
|
|
1776
|
+
align-items: flex-end;
|
|
1777
|
+
justify-content: flex-start;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
.filter-panel-header .filter-item {
|
|
1781
|
+
margin-right: 12px;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
.filter-panel-header .filter-label {
|
|
1785
|
+
font-size: 12px;
|
|
1786
|
+
color: #606266;
|
|
1787
|
+
margin-bottom: 4px;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
.filter-panel-header .filter-input {
|
|
1791
|
+
width: 180px;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
.filter-panel-header .filter-input.date-range {
|
|
1795
|
+
width: 320px;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
.content-area.with-filter {
|
|
1799
|
+
height: calc(100% - 52px - 60px); /* 减去header高度和filter高度 */
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/* 记录计数标签 */
|
|
1803
|
+
.record-count {
|
|
1804
|
+
display: flex;
|
|
1805
|
+
align-items: center;
|
|
1806
|
+
height: 24px;
|
|
1807
|
+
padding-left: 8px;
|
|
1808
|
+
padding-right: 8px;
|
|
1809
|
+
margin-left: 8px;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
.record-count :deep(.el-icon) {
|
|
1813
|
+
margin-right: 4px;
|
|
1814
|
+
font-size: 12px;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
/* 表格视图容器简化 */
|
|
1818
|
+
.table-view-container {
|
|
1819
|
+
display: flex;
|
|
1820
|
+
flex-direction: column;
|
|
1821
|
+
flex: 1;
|
|
1822
|
+
min-height: 400px; /* 确保至少有一定高度 */
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
.log-table {
|
|
1826
|
+
width: 100%;
|
|
1827
|
+
height: 100%;
|
|
1828
|
+
min-height: 300px;
|
|
1829
|
+
flex: 1;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/* 重置或移除不再需要的样式 */
|
|
1833
|
+
.history-controls,
|
|
1834
|
+
.filter-panel {
|
|
1835
|
+
display: none; /* 隐藏原来的筛选组件 */
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
.log-table.has-filter,
|
|
1839
|
+
.log-table.has-sticky-controls,
|
|
1840
|
+
.log-table.has-sticky-filter {
|
|
1841
|
+
margin-top: 0 !important; /* 重置原来的边距 */
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/* 添加底部加载更多相关样式 */
|
|
1845
|
+
.load-more-container {
|
|
1846
|
+
display: flex;
|
|
1847
|
+
flex-direction: column;
|
|
1848
|
+
align-items: center;
|
|
1849
|
+
padding: 15px 0;
|
|
1850
|
+
border-top: 1px dashed #ebeef5;
|
|
1851
|
+
gap: 10px;
|
|
1852
|
+
display: none;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
.pagination-info {
|
|
1856
|
+
font-size: 13px;
|
|
1857
|
+
color: #909399;
|
|
1858
|
+
margin-bottom: 5px;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
.loading-more {
|
|
1862
|
+
display: flex;
|
|
1863
|
+
align-items: center;
|
|
1864
|
+
color: #909399;
|
|
1865
|
+
font-size: 14px;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
.loading-spinner {
|
|
1869
|
+
width: 20px;
|
|
1870
|
+
height: 20px;
|
|
1871
|
+
margin-right: 10px;
|
|
1872
|
+
border: 2px solid #dcdfe6;
|
|
1873
|
+
border-top-color: #409eff;
|
|
1874
|
+
border-radius: 50%;
|
|
1875
|
+
animation: spinner 1s linear infinite;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
@keyframes spinner {
|
|
1879
|
+
to {transform: rotate(360deg);}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
.load-more-button {
|
|
1883
|
+
cursor: pointer;
|
|
1884
|
+
color: #409eff;
|
|
1885
|
+
font-size: 14px;
|
|
1886
|
+
padding: 6px 16px;
|
|
1887
|
+
border: 1px solid #d9ecff;
|
|
1888
|
+
background-color: #ecf5ff;
|
|
1889
|
+
border-radius: 4px;
|
|
1890
|
+
transition: all 0.3s;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
.load-more-button:hover {
|
|
1894
|
+
background-color: #d9ecff;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
.no-more-data {
|
|
1898
|
+
color: #909399;
|
|
1899
|
+
font-size: 14px;
|
|
1900
|
+
font-style: italic;
|
|
1901
|
+
display: flex;
|
|
1902
|
+
flex-direction: column;
|
|
1903
|
+
align-items: center;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
.total-loaded {
|
|
1907
|
+
font-size: 12px;
|
|
1908
|
+
margin-top: 5px;
|
|
1909
|
+
color: #c0c4cc;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/* 右键菜单样式 */
|
|
1913
|
+
.context-menu {
|
|
1914
|
+
position: fixed;
|
|
1915
|
+
background: white;
|
|
1916
|
+
border: 1px solid #dcdfe6;
|
|
1917
|
+
border-radius: 4px;
|
|
1918
|
+
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
1919
|
+
padding: 8px 0;
|
|
1920
|
+
z-index: 3000;
|
|
1921
|
+
min-width: 180px;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
.context-menu-item {
|
|
1925
|
+
padding: 8px 16px;
|
|
1926
|
+
cursor: pointer;
|
|
1927
|
+
font-size: 14px;
|
|
1928
|
+
display: flex;
|
|
1929
|
+
align-items: center;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
.context-menu-item:hover {
|
|
1933
|
+
background-color: #f5f7fa;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
.context-menu-item i {
|
|
1937
|
+
margin-right: 8px;
|
|
1938
|
+
}
|
|
1257
1939
|
</style>
|
|
1258
1940
|
|
|
1259
1941
|
/* 添加表格列调整样式 */
|
|
1942
|
+
<style>
|
|
1260
1943
|
.el-table .el-table__cell .cell {
|
|
1261
1944
|
word-break: break-all;
|
|
1262
|
-
}
|
|
1945
|
+
}
|
|
1946
|
+
</style>
|