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,17 +1,15 @@
1
1
  <script setup lang="ts">
2
- import { ref, onMounted, defineEmits, computed, watch } from "vue";
3
- import { ElMessage } from "element-plus";
4
- import { Setting } from "@element-plus/icons-vue";
5
-
6
- const emit = defineEmits(["commit-success", "push-success"]);
2
+ import { ref, onMounted, computed, watch } from "vue";
3
+ import { ElMessage, ElMessageBox } from "element-plus";
4
+ import { Setting, Edit } from "@element-plus/icons-vue";
5
+ import { useGitLogStore } from "../stores/gitLogStore";
6
+ import { useGitStore } from "../stores/gitStore";
7
+
8
+ const gitLogStore = useGitLogStore();
9
+ const gitStore = useGitStore();
7
10
  const commitMessage = ref("");
8
- const commitBtnText = ref("提交");
9
- const pushBtnText = ref("推送到远程");
10
- const isCommitting = ref(false);
11
11
  const isPushing = ref(false);
12
12
  // 添加提交并推送的状态变量
13
- const isCommitAndPushing = ref(false);
14
- const commitAndPushBtnText = ref("提交并推送");
15
13
  const placeholder = ref("输入提交信息...");
16
14
  // 添加默认提交信息变量
17
15
  const defaultCommitMessage = ref("");
@@ -27,11 +25,19 @@ const descriptionTemplates = ref<string[]>([]);
27
25
  // 添加对话框可见性变量
28
26
  const descriptionDialogVisible = ref(false);
29
27
  const newTemplateName = ref("");
28
+ // 添加模板编辑相关变量
29
+ const isEditingDescription = ref(false);
30
+ const originalDescriptionTemplate = ref("");
31
+ const editingDescriptionIndex = ref(-1);
30
32
 
31
33
  // 作用域模板相关变量
32
34
  const scopeTemplates = ref<string[]>([]);
33
35
  const scopeDialogVisible = ref(false);
34
36
  const newScopeTemplate = ref("");
37
+ // 添加作用域模板编辑相关变量
38
+ const isEditingScope = ref(false);
39
+ const originalScopeTemplate = ref("");
40
+ const editingScopeIndex = ref(-1);
35
41
 
36
42
  // 跳过钩子
37
43
  const skipHooks = ref(false);
@@ -82,6 +88,19 @@ const finalCommitMessage = computed(() => {
82
88
  return message;
83
89
  });
84
90
 
91
+ // 计算Git命令预览
92
+ const gitCommandPreview = computed(() => {
93
+ // 基本命令
94
+ let command = `git commit -m "${finalCommitMessage.value}"`
95
+
96
+ // 如果跳过钩子开关打开,添加 --no-verify 参数
97
+ if (skipHooks.value) {
98
+ command += ' --no-verify'
99
+ }
100
+
101
+ return command
102
+ });
103
+
85
104
  // 加载配置
86
105
  async function loadConfig() {
87
106
  try {
@@ -119,51 +138,126 @@ async function saveDescriptionTemplate() {
119
138
  }
120
139
 
121
140
  try {
122
- // 检查是否已存在相同模板
123
- if (descriptionTemplates.value.includes(newTemplateName.value)) {
124
- ElMessage({
125
- message: "该模板已存在",
126
- type: "warning",
127
- });
128
- return;
129
- }
141
+ // 判断是编辑还是新建
142
+ if (isEditingDescription.value) {
143
+ // 编辑现有模板
144
+ await updateDescriptionTemplate();
145
+ } else {
146
+ // 新建模板
147
+ // 检查是否已存在相同模板
148
+ if (descriptionTemplates.value.includes(newTemplateName.value)) {
149
+ ElMessage({
150
+ message: "该模板已存在",
151
+ type: "warning",
152
+ });
153
+ return;
154
+ }
130
155
 
131
- // 添加到本地数组
132
- descriptionTemplates.value.push(newTemplateName.value);
156
+ // 添加到本地数组
157
+ descriptionTemplates.value.push(newTemplateName.value);
158
+
159
+ // 保存到服务器
160
+ const response = await fetch("/api/config/save-template", {
161
+ method: "POST",
162
+ headers: {
163
+ "Content-Type": "application/json",
164
+ },
165
+ body: JSON.stringify({
166
+ template: newTemplateName.value,
167
+ type: "description",
168
+ }),
169
+ });
133
170
 
134
- // 保存到服务器
135
- const response = await fetch("/api/config/save-template", {
136
- method: "POST",
137
- headers: {
138
- "Content-Type": "application/json",
139
- },
140
- body: JSON.stringify({
141
- template: newTemplateName.value,
142
- type: "description",
143
- }),
171
+ const result = await response.json();
172
+ if (result.success) {
173
+ ElMessage({
174
+ message: "模板保存成功!",
175
+ type: "success",
176
+ });
177
+ newTemplateName.value = "";
178
+ } else {
179
+ ElMessage({
180
+ message: "模板保存失败: " + result.error,
181
+ type: "error",
182
+ });
183
+ }
184
+ }
185
+ } catch (error) {
186
+ ElMessage({
187
+ message: "模板保存失败: " + (error as Error).message,
188
+ type: "error",
144
189
  });
190
+ }
191
+ }
145
192
 
146
- const result = await response.json();
147
- if (result.success) {
148
- ElMessage({
149
- message: "模板保存成功!",
150
- type: "success",
151
- });
152
- newTemplateName.value = "";
153
- } else {
154
- ElMessage({
155
- message: "模板保存失败: " + result.error,
156
- type: "error",
193
+ // 编辑描述模板
194
+ async function updateDescriptionTemplate() {
195
+ try {
196
+ // 先从本地数组中更新
197
+ if (editingDescriptionIndex.value >= 0) {
198
+ // 保存原模板和新模板
199
+ const oldTemplate = originalDescriptionTemplate.value;
200
+ const newTemplate = newTemplateName.value;
201
+
202
+ // 更新本地数组
203
+ descriptionTemplates.value[editingDescriptionIndex.value] = newTemplate;
204
+
205
+ // 调用API更新服务器
206
+ const response = await fetch("/api/config/update-template", {
207
+ method: "POST",
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ },
211
+ body: JSON.stringify({
212
+ oldTemplate,
213
+ newTemplate,
214
+ type: "description",
215
+ }),
157
216
  });
217
+
218
+ const result = await response.json();
219
+ if (result.success) {
220
+ ElMessage({
221
+ message: "模板更新成功!",
222
+ type: "success",
223
+ });
224
+
225
+ // 重置编辑状态
226
+ isEditingDescription.value = false;
227
+ originalDescriptionTemplate.value = "";
228
+ editingDescriptionIndex.value = -1;
229
+ newTemplateName.value = "";
230
+ } else {
231
+ ElMessage({
232
+ message: "模板更新失败: " + result.error,
233
+ type: "error",
234
+ });
235
+ }
158
236
  }
159
237
  } catch (error) {
160
238
  ElMessage({
161
- message: "模板保存失败: " + (error as Error).message,
239
+ message: "模板更新失败: " + (error as Error).message,
162
240
  type: "error",
163
241
  });
164
242
  }
165
243
  }
166
244
 
245
+ // 开始编辑描述模板
246
+ function startEditDescriptionTemplate(template: string, index: number) {
247
+ isEditingDescription.value = true;
248
+ originalDescriptionTemplate.value = template;
249
+ editingDescriptionIndex.value = index;
250
+ newTemplateName.value = template;
251
+ }
252
+
253
+ // 取消编辑描述模板
254
+ function cancelEditDescriptionTemplate() {
255
+ isEditingDescription.value = false;
256
+ originalDescriptionTemplate.value = "";
257
+ editingDescriptionIndex.value = -1;
258
+ newTemplateName.value = "";
259
+ }
260
+
167
261
  // 保存作用域模板
168
262
  async function saveScopeTemplate() {
169
263
  if (!newScopeTemplate.value.trim()) {
@@ -175,51 +269,126 @@ async function saveScopeTemplate() {
175
269
  }
176
270
 
177
271
  try {
178
- // 检查是否已存在相同模板
179
- if (scopeTemplates.value.includes(newScopeTemplate.value)) {
180
- ElMessage({
181
- message: "该模板已存在",
182
- type: "warning",
183
- });
184
- return;
185
- }
272
+ // 判断是编辑还是新建
273
+ if (isEditingScope.value) {
274
+ // 编辑现有模板
275
+ await updateScopeTemplate();
276
+ } else {
277
+ // 新建模板
278
+ // 检查是否已存在相同模板
279
+ if (scopeTemplates.value.includes(newScopeTemplate.value)) {
280
+ ElMessage({
281
+ message: "该模板已存在",
282
+ type: "warning",
283
+ });
284
+ return;
285
+ }
186
286
 
187
- // 添加到本地数组
188
- scopeTemplates.value.push(newScopeTemplate.value);
287
+ // 添加到本地数组
288
+ scopeTemplates.value.push(newScopeTemplate.value);
289
+
290
+ // 保存到服务器
291
+ const response = await fetch("/api/config/save-template", {
292
+ method: "POST",
293
+ headers: {
294
+ "Content-Type": "application/json",
295
+ },
296
+ body: JSON.stringify({
297
+ template: newScopeTemplate.value,
298
+ type: "scope",
299
+ }),
300
+ });
189
301
 
190
- // 保存到服务器
191
- const response = await fetch("/api/config/save-template", {
192
- method: "POST",
193
- headers: {
194
- "Content-Type": "application/json",
195
- },
196
- body: JSON.stringify({
197
- template: newScopeTemplate.value,
198
- type: "scope",
199
- }),
302
+ const result = await response.json();
303
+ if (result.success) {
304
+ ElMessage({
305
+ message: "作用域模板保存成功!",
306
+ type: "success",
307
+ });
308
+ newScopeTemplate.value = "";
309
+ } else {
310
+ ElMessage({
311
+ message: "作用域模板保存失败: " + result.error,
312
+ type: "error",
313
+ });
314
+ }
315
+ }
316
+ } catch (error) {
317
+ ElMessage({
318
+ message: "作用域模板保存失败: " + (error as Error).message,
319
+ type: "error",
200
320
  });
321
+ }
322
+ }
201
323
 
202
- const result = await response.json();
203
- if (result.success) {
204
- ElMessage({
205
- message: "作用域模板保存成功!",
206
- type: "success",
207
- });
208
- newScopeTemplate.value = "";
209
- } else {
210
- ElMessage({
211
- message: "作用域模板保存失败: " + result.error,
212
- type: "error",
324
+ // 更新作用域模板
325
+ async function updateScopeTemplate() {
326
+ try {
327
+ // 先从本地数组中更新
328
+ if (editingScopeIndex.value >= 0) {
329
+ // 保存原模板和新模板
330
+ const oldTemplate = originalScopeTemplate.value;
331
+ const newTemplate = newScopeTemplate.value;
332
+
333
+ // 更新本地数组
334
+ scopeTemplates.value[editingScopeIndex.value] = newTemplate;
335
+
336
+ // 调用API更新服务器
337
+ const response = await fetch("/api/config/update-template", {
338
+ method: "POST",
339
+ headers: {
340
+ "Content-Type": "application/json",
341
+ },
342
+ body: JSON.stringify({
343
+ oldTemplate,
344
+ newTemplate,
345
+ type: "scope",
346
+ }),
213
347
  });
348
+
349
+ const result = await response.json();
350
+ if (result.success) {
351
+ ElMessage({
352
+ message: "作用域模板更新成功!",
353
+ type: "success",
354
+ });
355
+
356
+ // 重置编辑状态
357
+ isEditingScope.value = false;
358
+ originalScopeTemplate.value = "";
359
+ editingScopeIndex.value = -1;
360
+ newScopeTemplate.value = "";
361
+ } else {
362
+ ElMessage({
363
+ message: "作用域模板更新失败: " + result.error,
364
+ type: "error",
365
+ });
366
+ }
214
367
  }
215
368
  } catch (error) {
216
369
  ElMessage({
217
- message: "作用域模板保存失败: " + (error as Error).message,
370
+ message: "作用域模板更新失败: " + (error as Error).message,
218
371
  type: "error",
219
372
  });
220
373
  }
221
374
  }
222
375
 
376
+ // 开始编辑作用域模板
377
+ function startEditScopeTemplate(template: string, index: number) {
378
+ isEditingScope.value = true;
379
+ originalScopeTemplate.value = template;
380
+ editingScopeIndex.value = index;
381
+ newScopeTemplate.value = template;
382
+ }
383
+
384
+ // 取消编辑作用域模板
385
+ function cancelEditScopeTemplate() {
386
+ isEditingScope.value = false;
387
+ originalScopeTemplate.value = "";
388
+ editingScopeIndex.value = -1;
389
+ newScopeTemplate.value = "";
390
+ }
391
+
223
392
  // 删除描述模板
224
393
  async function deleteDescriptionTemplate(template: string) {
225
394
  try {
@@ -324,231 +493,184 @@ function openScopeSettings() {
324
493
  scopeDialogVisible.value = true;
325
494
  }
326
495
 
327
-
328
- // 从localStorage加载标准化提交设置
329
- function loadCommitPreference() {
330
- const savedPreference = localStorage.getItem("zen-gitsync-standard-commit");
331
- if (savedPreference !== null) {
332
- isStandardCommit.value = savedPreference === "true";
333
- }
334
-
335
- // 加载跳过钩子设置
336
- const savedSkipHooks = localStorage.getItem("zen-gitsync-skip-hooks");
337
- if (savedSkipHooks !== null) {
338
- skipHooks.value = savedSkipHooks === "true";
496
+ // 添加文件到暂存区 (git add)
497
+ async function addToStage() {
498
+ try {
499
+ const result = await gitLogStore.addToStage();
500
+ if (result) {
501
+ // 触发状态更新事件
502
+ gitLogStore.fetchStatus();
503
+ }
504
+ } catch (error) {
505
+ ElMessage({
506
+ message: `添加文件失败: ${(error as Error).message}`,
507
+ type: "error",
508
+ });
339
509
  }
340
510
  }
341
511
 
342
- // 提交更改
512
+ // 提交更改 (git commit)
343
513
  async function commitChanges() {
344
- const message = finalCommitMessage.value;
345
- if (!message && isStandardCommit.value && !commitDescription.value) {
514
+ if (!finalCommitMessage.value.trim()) {
346
515
  ElMessage({
347
- message: "请输入提交描述",
516
+ message: "提交信息不能为空",
348
517
  type: "warning",
349
518
  });
350
519
  return;
351
520
  }
352
521
 
353
522
  try {
354
- isCommitting.value = true;
355
- commitBtnText.value = "提交中...";
356
-
357
- // 先执行 git add .
358
- const addResponse = await fetch("/api/add", {
359
- method: "POST",
360
- });
361
-
362
- const addResult = await addResponse.json();
363
- if (!addResult.success) {
364
- ElMessage({
365
- message: "添加文件失败: " + addResult.error,
366
- type: "error",
367
- });
368
- return;
369
- }
370
-
371
- const response = await fetch("/api/commit", {
372
- method: "POST",
373
- headers: {
374
- "Content-Type": "application/json",
375
- },
376
- body: JSON.stringify({
377
- message,
378
- // 添加一个标志,表示消息包含换行符
379
- hasNewlines: message.includes("\n"),
380
- // 添加 no-verify 选项
381
- noVerify: skipHooks.value,
382
- }),
383
- });
523
+ // 使用Store提交更改
524
+ const result = await gitLogStore.commitChanges(finalCommitMessage.value, skipHooks.value);
384
525
 
385
- const result = await response.json();
386
- if (result.success) {
387
- // 清空输入
388
- if (isStandardCommit.value) {
389
- commitDescription.value = "";
390
- commitBody.value = "";
391
- commitFooter.value = "";
392
- } else {
393
- commitMessage.value = "";
394
- }
526
+ if (result) {
527
+ // 清空提交信息
528
+ clearCommitFields();
395
529
 
396
- ElMessage({
397
- message: "提交成功!",
398
- type: "success",
399
- });
400
- // 发出提交成功事件
401
- emit("commit-success");
402
- } else {
403
- ElMessage({
404
- message: "提交失败: " + result.error,
405
- type: "error",
406
- });
530
+ // 触发成功事件
531
+ gitLogStore.fetchStatus();
532
+ gitLogStore.fetchLog();
407
533
  }
408
534
  } catch (error) {
409
535
  ElMessage({
410
- message: "提交失败: " + (error as Error).message,
536
+ message: `提交失败: ${(error as Error).message}`,
411
537
  type: "error",
412
538
  });
413
- } finally {
414
- isCommitting.value = false;
415
- commitBtnText.value = "提交";
416
539
  }
417
540
  }
418
541
 
419
- // 推送更改
420
- async function pushChanges() {
542
+ // 推送到远程 (git push)
543
+ async function pushToRemote() {
421
544
  try {
422
- isPushing.value = true;
423
- pushBtnText.value = "推送中...";
424
-
425
- const response = await fetch("/api/push", {
426
- method: "POST",
427
- });
428
-
429
- const result = await response.json();
430
- if (result.success) {
431
- ElMessage({
432
- message: "推送成功!",
433
- type: "success",
434
- });
435
- // 发出推送成功事件
436
- emit("push-success");
437
- } else {
438
- ElMessage({
439
- message: "推送失败: " + result.error,
440
- type: "error",
441
- });
545
+ isPushing.value = true
546
+ // 使用Store推送更改
547
+ const result = await gitLogStore.pushToRemote();
548
+
549
+ if (result) {
550
+ // 触发成功事件
551
+ gitStore.getCurrentBranch();
552
+ gitLogStore.fetchLog();
442
553
  }
443
554
  } catch (error) {
444
555
  ElMessage({
445
- message: "推送失败: " + (error as Error).message,
556
+ message: `推送失败: ${(error as Error).message}`,
446
557
  type: "error",
447
558
  });
448
559
  } finally {
449
- isPushing.value = false;
450
- pushBtnText.value = "推送到远程";
560
+ isPushing.value = false
451
561
  }
452
562
  }
453
563
 
454
- // 提交并推送更改
455
- async function commitAndPush() {
456
- const message = finalCommitMessage.value;
457
- if (!message && isStandardCommit.value && !commitDescription.value) {
564
+ // 添加并提交 (git add + git commit)
565
+ async function addAndCommit() {
566
+ if (!finalCommitMessage.value.trim()) {
458
567
  ElMessage({
459
- message: "请输入提交描述",
568
+ message: "提交信息不能为空",
460
569
  type: "warning",
461
570
  });
462
571
  return;
463
572
  }
464
573
 
465
574
  try {
466
- isCommitAndPushing.value = true;
467
- commitAndPushBtnText.value = "处理中...";
575
+ await gitLogStore.addAndCommit(finalCommitMessage.value, skipHooks.value);
468
576
 
469
- // 先执行 git add .
470
- const addResponse = await fetch("/api/add", {
471
- method: "POST",
577
+ // 清空提交信息
578
+ clearCommitFields();
579
+
580
+ // 触发成功事件
581
+ gitLogStore.fetchStatus();
582
+ gitLogStore.fetchLog();
583
+ } catch (error) {
584
+ ElMessage({
585
+ message: `暂存并提交失败: ${(error as Error).message}`,
586
+ type: "error",
472
587
  });
473
-
474
- const addResult = await addResponse.json();
475
- if (!addResult.success) {
476
- ElMessage({
477
- message: "添加文件失败: " + addResult.error,
478
- type: "error",
479
- });
480
- return;
481
- }
588
+ }
589
+ }
482
590
 
483
- // 再提交
484
- const commitResponse = await fetch("/api/commit", {
485
- method: "POST",
486
- headers: {
487
- "Content-Type": "application/json",
488
- },
489
- body: JSON.stringify({
490
- message,
491
- // 添加一个标志,表示消息包含换行符
492
- hasNewlines: message.includes("\n"),
493
- // 添加 no-verify 选项
494
- noVerify: skipHooks.value,
495
- }),
591
+ // 添加、提交并推送 (git add + git commit + git push)
592
+ async function addCommitAndPush() {
593
+ if (!finalCommitMessage.value.trim()) {
594
+ ElMessage({
595
+ message: "提交信息不能为空",
596
+ type: "warning",
496
597
  });
598
+ return;
599
+ }
497
600
 
498
- const commitResult = await commitResponse.json();
499
- if (!commitResult.success) {
500
- ElMessage({
501
- message: "提交失败: " + commitResult.error,
502
- type: "error",
503
- });
504
- return;
505
- }
601
+ try {
602
+ await gitLogStore.addCommitAndPush(finalCommitMessage.value, skipHooks.value);
506
603
 
507
- // 清空输入
508
- if (isStandardCommit.value) {
509
- commitDescription.value = "";
510
- commitBody.value = "";
511
- commitFooter.value = "";
512
- } else {
513
- commitMessage.value = "";
514
- }
515
604
 
516
- // 再推送
517
- const pushResponse = await fetch("/api/push", {
518
- method: "POST",
519
- });
605
+ // 清空提交信息
606
+ clearCommitFields();
607
+
608
+ // 触发成功事件
609
+ gitStore.getCurrentBranch();
610
+ gitLogStore.fetchLog();
520
611
 
521
- const pushResult = await pushResponse.json();
522
- if (pushResult.success) {
523
- commitMessage.value = "";
524
- ElMessage({
525
- message: "提交并推送成功!",
526
- type: "success",
527
- });
528
- // 发出提交和推送成功事件
529
- emit("commit-success");
530
- emit("push-success");
531
- } else {
532
- ElMessage({
533
- message: "推送失败: " + pushResult.error,
534
- type: "error",
535
- });
536
- }
537
612
  } catch (error) {
538
613
  ElMessage({
539
- message: "操作失败: " + (error as Error).message,
614
+ message: `暂存、提交并推送失败: ${(error as Error).message}`,
540
615
  type: "error",
541
616
  });
542
617
  } finally {
543
- isCommitAndPushing.value = false;
544
- commitAndPushBtnText.value = "提交并推送";
545
618
  }
546
619
  }
547
620
 
621
+ // 重置到远程分支 (git reset --hard origin/branch)
622
+ async function resetToRemote() {
623
+ try {
624
+ await ElMessageBox.confirm(
625
+ `确定要重置当前分支 "${gitStore.currentBranch}" 到远程状态吗?这将丢失所有未推送的提交和本地更改。`,
626
+ '重置到远程分支',
627
+ {
628
+ confirmButtonText: '确定',
629
+ cancelButtonText: '取消',
630
+ type: 'warning'
631
+ }
632
+ );
633
+
634
+ const result = await gitLogStore.resetToRemote(gitStore.currentBranch);
635
+ if (result) {
636
+ // 触发状态更新事件
637
+ gitLogStore.fetchStatus();
638
+ // 更新提交历史
639
+ gitLogStore.fetchLog();
640
+ }
641
+ } catch (error) {
642
+ // 用户取消操作,不显示错误
643
+ if ((error as any) !== 'cancel') {
644
+ ElMessage({
645
+ message: `重置到远程分支失败: ${(error as Error).message}`,
646
+ type: 'error'
647
+ });
648
+ }
649
+ }
650
+ }
651
+
652
+ // 清空提交字段
653
+ function clearCommitFields() {
654
+ commitMessage.value = "";
655
+ commitDescription.value = "";
656
+ commitBody.value = "";
657
+ commitFooter.value = "";
658
+ }
548
659
 
549
660
  onMounted(() => {
550
661
  loadConfig();
551
- loadCommitPreference();
662
+
663
+ // 从 localStorage 中获取标准化提交设置
664
+ const savedStandardCommit = localStorage.getItem("zen-gitsync-standard-commit");
665
+ if (savedStandardCommit !== null) {
666
+ isStandardCommit.value = savedStandardCommit === "true";
667
+ }
668
+
669
+ // 从 localStorage 中获取跳过钩子设置
670
+ const savedSkipHooks = localStorage.getItem("zen-gitsync-skip-hooks");
671
+ if (savedSkipHooks !== null) {
672
+ skipHooks.value = savedSkipHooks === "true";
673
+ }
552
674
  });
553
675
  </script>
554
676
 
@@ -556,178 +678,200 @@ onMounted(() => {
556
678
  <div class="card">
557
679
  <h2>提交更改</h2>
558
680
 
559
- <div class="commit-options">
560
- <div class="commit-mode-toggle">
561
- <el-switch
562
- v-model="isStandardCommit"
563
- active-text="标准化提交"
564
- inactive-text="普通提交"
565
- />
681
+ <div class="layout-container">
682
+ <!-- 如果没有配置Git用户信息,显示提示 -->
683
+ <div v-if="gitStore.userName === '' || gitStore.userEmail === ''" class="git-config-warning">
684
+ <el-alert
685
+ title="Git用户信息未配置"
686
+ type="warning"
687
+ :closable="false"
688
+ show-icon
689
+ >
690
+ <p>您需要配置Git用户名和邮箱才能提交代码。请使用以下命令配置:</p>
691
+ <pre class="config-command">git config --global user.name "Your Name"
692
+ git config --global user.email "your.email@example.com"</pre>
693
+ </el-alert>
566
694
  </div>
695
+
696
+ <!-- 正常的提交区域,仅在Git用户信息已配置时显示 -->
697
+ <template v-else>
698
+ <!-- 左侧:提交表单 -->
699
+ <div class="commit-section">
700
+ <div class="commit-options">
701
+ <div class="options-row">
702
+ <div class="commit-mode-toggle">
703
+ <el-switch v-model="isStandardCommit" active-text="标准化提交" inactive-text="普通提交" />
704
+ </div>
567
705
 
568
- <div class="no-verify-toggle">
569
- <el-tooltip content="跳过 Git 钩子检查 (--no-verify)" placement="top">
570
- <el-switch v-model="skipHooks" active-text="跳过钩子 (--no-verify)" />
571
- </el-tooltip>
572
- </div>
573
- </div>
706
+ <div class="no-verify-toggle">
707
+ <el-tooltip content="跳过 Git 钩子检查 (--no-verify)" placement="top">
708
+ <el-switch v-model="skipHooks" active-text="跳过钩子 (--no-verify)" />
709
+ </el-tooltip>
710
+ </div>
711
+ </div>
712
+ </div>
713
+
714
+ <!-- 普通提交表单 -->
715
+ <div v-if="!isStandardCommit" class="commit-form">
716
+ <el-input v-model="commitMessage" :placeholder="placeholder" clearable />
717
+ </div>
718
+
719
+ <!-- 标准化提交表单 -->
720
+ <div v-else class="standard-commit-form">
721
+ <div class="standard-commit-header">
722
+ <el-select v-model="commitType" placeholder="提交类型" class="type-select" clearable>
723
+ <el-option v-for="item in commitTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
724
+ </el-select>
725
+
726
+ <div class="scope-container">
727
+ <el-input v-model="commitScope" placeholder="作用域(可选)" class="scope-input" clearable />
728
+ <el-button type="primary" :icon="Setting" circle size="small" class="settings-button"
729
+ @click="openScopeSettings">
730
+ </el-button>
731
+ </div>
574
732
 
575
- <!-- 普通提交表单 -->
576
- <div v-if="!isStandardCommit" class="commit-form">
577
- <el-input v-model="commitMessage" :placeholder="placeholder" clearable />
578
- <el-button
579
- type="primary"
580
- @click="commitChanges"
581
- :loading="isCommitting"
582
- >{{ commitBtnText }}</el-button
583
- >
584
- </div>
733
+ <div class="description-container">
734
+ <el-input v-model="commitDescription" placeholder="简短描述(必填)" class="description-input" clearable />
735
+ <el-button type="primary" :icon="Setting" circle size="small" class="settings-button"
736
+ @click="openDescriptionSettings">
737
+ </el-button>
738
+ </div>
739
+ </div>
585
740
 
586
- <!-- 标准化提交表单 -->
587
- <div v-else class="standard-commit-form">
588
- <div class="standard-commit-header">
589
- <el-select
590
- v-model="commitType"
591
- placeholder="提交类型"
592
- class="type-select"
593
- clearable
594
- >
595
- <el-option
596
- v-for="item in commitTypeOptions"
597
- :key="item.value"
598
- :label="item.label"
599
- :value="item.value"
600
- />
601
- </el-select>
602
-
603
- <div class="scope-container">
604
- <el-input
605
- v-model="commitScope"
606
- placeholder="作用域(可选)"
607
- class="scope-input"
608
- clearable
609
- />
610
- <el-button
611
- type="primary"
612
- :icon="Setting"
613
- circle
614
- size="small"
615
- class="settings-button"
616
- @click="openScopeSettings"
617
- >
618
- </el-button>
619
- </div>
741
+ <el-input v-model="commitBody" type="textarea" :rows="4" placeholder="正文(可选):详细描述本次提交的内容和原因" class="body-input"
742
+ clearable />
620
743
 
621
- <div class="description-container">
622
- <el-input
623
- v-model="commitDescription"
624
- placeholder="简短描述(必填)"
625
- class="description-input"
626
- clearable
627
- />
628
- <el-button
629
- type="primary"
630
- :icon="Setting"
631
- circle
632
- size="small"
633
- class="settings-button"
634
- @click="openDescriptionSettings"
635
- >
636
- </el-button>
637
- </div>
638
- </div>
744
+ <el-input v-model="commitFooter" placeholder="页脚(可选):如 Closes #123" class="footer-input" clearable />
639
745
 
640
- <el-input
641
- v-model="commitBody"
642
- type="textarea"
643
- :rows="4"
644
- placeholder="正文(可选):详细描述本次提交的内容和原因"
645
- class="body-input"
646
- clearable
647
- />
648
-
649
- <el-input
650
- v-model="commitFooter"
651
- placeholder="页脚(可选):如 Closes #123"
652
- class="footer-input"
653
- clearable
654
- />
655
-
656
- <div class="preview-section">
657
- <div class="preview-title">预览:</div>
658
- <pre class="preview-content">{{ finalCommitMessage }}</pre>
659
- </div>
746
+ <div class="preview-section">
747
+ <div class="preview-title">提交信息预览:</div>
748
+ <pre class="preview-content">{{ finalCommitMessage }}</pre>
660
749
 
661
- <el-button
662
- type="primary"
663
- @click="commitChanges"
664
- :loading="isCommitting"
665
- >{{ commitBtnText }}</el-button
666
- >
667
- </div>
750
+ <div class="preview-title" style="margin-top: 10px;">Git命令预览:</div>
751
+ <pre class="preview-content code-command">{{ gitCommandPreview }}</pre>
752
+ </div>
753
+ </div>
754
+ </div>
668
755
 
669
- <div class="button-group">
670
- <el-button type="success" @click="pushChanges" :loading="isPushing">{{
671
- pushBtnText
672
- }}</el-button>
673
- <el-button
674
- type="warning"
675
- @click="commitAndPush"
676
- :loading="isCommitAndPushing"
677
- >{{ commitAndPushBtnText }}</el-button
678
- >
756
+ <!-- 右侧:操作区域 -->
757
+ <div class="actions-section">
758
+ <h3>Git 操作</h3>
759
+ <div class="action-groups">
760
+ <div class="action-group">
761
+ <div class="group-title">基础操作</div>
762
+ <div class="group-buttons">
763
+ <el-button
764
+ type="primary"
765
+ @click="addToStage"
766
+ :loading="gitLogStore.isAddingFiles"
767
+ class="action-button"
768
+ >
769
+ 暂存更改
770
+ <span class="command-text">git add .</span>
771
+ </el-button>
772
+
773
+ <el-button
774
+ type="primary"
775
+ @click="commitChanges"
776
+ :loading="gitLogStore.isLoadingStatus"
777
+ class="action-button"
778
+ >
779
+ 提交
780
+ <span class="command-text">git commit</span>
781
+ </el-button>
782
+
783
+ <el-button
784
+ type="success"
785
+ @click="pushToRemote"
786
+ :loading="gitLogStore.isPushing"
787
+ class="action-button push-button"
788
+ >
789
+ 推送
790
+ <span class="command-text">git push</span>
791
+ </el-button>
792
+ </div>
793
+ </div>
794
+
795
+ <div class="action-group">
796
+ <div class="group-title">组合操作</div>
797
+ <div class="group-buttons">
798
+ <el-button
799
+ type="warning"
800
+ @click="addAndCommit"
801
+ :loading="gitLogStore.isAddingFiles || gitLogStore.isCommiting"
802
+ class="action-button"
803
+ >
804
+ 暂存并提交
805
+ <span class="command-text">git add + commit</span>
806
+ </el-button>
807
+
808
+ <el-button
809
+ type="danger"
810
+ @click="addCommitAndPush"
811
+ :loading="gitLogStore.isAddingFiles || gitLogStore.isCommiting || gitLogStore.isPushing"
812
+ class="action-button"
813
+ >
814
+ 一键推送
815
+ <span class="command-text command-text-long">git add + commit + push</span>
816
+ </el-button>
817
+ </div>
818
+ </div>
819
+
820
+ <div class="action-group">
821
+ <div class="group-title">重置操作</div>
822
+ <div class="group-buttons">
823
+ <!-- <el-button
824
+ type="info"
825
+ @click="resetHead"
826
+ :loading="gitLogStore.isResetting"
827
+ :icon="Refresh"
828
+ class="action-button reset-button"
829
+ >
830
+ 重置暂存区
831
+ <span class="command-text">git reset HEAD</span>
832
+ </el-button> -->
833
+
834
+ <el-button
835
+ type="info"
836
+ @click="resetToRemote"
837
+ :loading="gitLogStore.isResetting"
838
+ class="action-button reset-button"
839
+ >
840
+ 重置到远程
841
+ <span class="command-text command-text-long">git reset --hard origin/branch</span>
842
+ </el-button>
843
+ </div>
844
+ </div>
845
+ </div>
846
+ </div>
847
+ </template>
679
848
  </div>
680
849
 
681
850
  <!-- 简短描述设置弹窗 -->
682
- <el-dialog
683
- title="简短描述模板设置"
684
- v-model="descriptionDialogVisible"
685
- width="80vw"
686
- style="height: 80vh"
687
- >
851
+ <el-dialog title="简短描述模板设置" v-model="descriptionDialogVisible" width="80vw" style="height: 80vh">
688
852
  <div class="template-container">
689
853
  <div class="template-form">
690
- <el-input
691
- v-model="newTemplateName"
692
- placeholder="输入新模板内容"
693
- class="template-input"
694
- clearable
695
- />
696
- <el-button
697
- type="primary"
698
- @click="saveDescriptionTemplate"
699
- :disabled="!newTemplateName.trim()"
700
- >添加模板</el-button
701
- >
854
+ <el-input v-model="newTemplateName" :placeholder="isEditingDescription ? '编辑模板内容' : '输入新模板内容'"
855
+ class="template-input" clearable />
856
+ <div class="template-form-buttons">
857
+ <el-button v-if="isEditingDescription" @click="cancelEditDescriptionTemplate">取消</el-button>
858
+ <el-button type="primary" @click="saveDescriptionTemplate" :disabled="!newTemplateName.trim()">{{
859
+ isEditingDescription ? '更新模板' : '添加模板' }}</el-button>
860
+ </div>
702
861
  </div>
703
862
 
704
863
  <div class="template-list">
705
864
  <h3>已保存模板</h3>
706
- <el-empty
707
- v-if="descriptionTemplates.length === 0"
708
- description="暂无保存的模板"
709
- />
710
- <el-card
711
- v-for="(template, index) in descriptionTemplates"
712
- :key="index"
713
- class="template-item"
714
- >
865
+ <el-empty v-if="descriptionTemplates.length === 0" description="暂无保存的模板" />
866
+ <el-card v-for="(template, index) in descriptionTemplates" :key="index" class="template-item">
715
867
  <!-- 两端对齐 -->
716
868
  <el-row justify="space-between" align="middle" style="width: 100%">
717
869
  <div class="template-content">{{ template }}</div>
718
870
  <div class="template-actions">
719
- <el-button
720
- type="primary"
721
- size="small"
722
- @click="useTemplate(template)"
723
- >使用</el-button
724
- >
725
- <el-button
726
- type="danger"
727
- size="small"
728
- @click="deleteDescriptionTemplate(template)"
729
- >删除</el-button
730
- >
871
+ <el-button type="primary" size="small" @click="useTemplate(template)">使用</el-button>
872
+ <el-button type="warning" size="small" :icon="Edit"
873
+ @click="startEditDescriptionTemplate(template, index)">编辑</el-button>
874
+ <el-button type="danger" size="small" @click="deleteDescriptionTemplate(template)">删除</el-button>
731
875
  </div>
732
876
  </el-row>
733
877
  </el-card>
@@ -736,54 +880,29 @@ onMounted(() => {
736
880
  </el-dialog>
737
881
 
738
882
  <!-- 作用域设置弹窗 -->
739
- <el-dialog
740
- title="作用域模板设置"
741
- v-model="scopeDialogVisible"
742
- width="80%"
743
- style="height: 80vh"
744
- >
883
+ <el-dialog title="作用域模板设置" v-model="scopeDialogVisible" width="80%" style="height: 80vh">
745
884
  <div class="template-container">
746
885
  <div class="template-form">
747
- <el-input
748
- v-model="newScopeTemplate"
749
- placeholder="输入新作用域模板"
750
- class="template-input"
751
- clearable
752
- />
753
- <el-button
754
- type="primary"
755
- @click="saveScopeTemplate"
756
- :disabled="!newScopeTemplate.trim()"
757
- >添加模板</el-button
758
- >
886
+ <el-input v-model="newScopeTemplate" :placeholder="isEditingScope ? '编辑作用域模板内容' : '输入新作用域模板'"
887
+ class="template-input" clearable />
888
+ <div class="template-form-buttons">
889
+ <el-button v-if="isEditingScope" @click="cancelEditScopeTemplate">取消</el-button>
890
+ <el-button type="primary" @click="saveScopeTemplate" :disabled="!newScopeTemplate.trim()">{{ isEditingScope
891
+ ? '更新模板' : '添加模板' }}</el-button>
892
+ </div>
759
893
  </div>
760
894
 
761
895
  <div class="template-list">
762
896
  <h3>已保存作用域</h3>
763
- <el-empty
764
- v-if="scopeTemplates.length === 0"
765
- description="暂无保存的作用域"
766
- />
767
- <el-card
768
- v-for="(template, index) in scopeTemplates"
769
- :key="index"
770
- class="template-item"
771
- >
897
+ <el-empty v-if="scopeTemplates.length === 0" description="暂无保存的作用域" />
898
+ <el-card v-for="(template, index) in scopeTemplates" :key="index" class="template-item">
772
899
  <el-row justify="space-between" align="middle" style="width: 100%">
773
900
  <div class="template-content">{{ template }}</div>
774
901
  <div class="template-actions">
775
- <el-button
776
- type="primary"
777
- size="small"
778
- @click="useScopeTemplate(template)"
779
- >使用</el-button
780
- >
781
- <el-button
782
- type="danger"
783
- size="small"
784
- @click="deleteScopeTemplate(template)"
785
- >删除</el-button
786
- >
902
+ <el-button type="primary" size="small" @click="useScopeTemplate(template)">使用</el-button>
903
+ <el-button type="warning" size="small" :icon="Edit"
904
+ @click="startEditScopeTemplate(template, index)">编辑</el-button>
905
+ <el-button type="danger" size="small" @click="deleteScopeTemplate(template)">删除</el-button>
787
906
  </div>
788
907
  </el-row>
789
908
  </el-card>
@@ -794,65 +913,197 @@ onMounted(() => {
794
913
  </template>
795
914
 
796
915
  <style scoped>
916
+ .card {
917
+ background-color: white;
918
+ border-radius: 5px;
919
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
920
+ margin-bottom: 20px;
921
+ padding: 20px;
922
+ }
923
+
924
+ .layout-container {
925
+ display: flex;
926
+ gap: 20px;
927
+ }
928
+
929
+ .commit-section {
930
+ flex: 1;
931
+ min-width: 0; /* 防止子元素撑开 */
932
+ }
933
+
934
+ .actions-section {
935
+ width: 300px;
936
+ flex-shrink: 0;
937
+ }
938
+
939
+ .actions-section h3 {
940
+ margin-top: 0;
941
+ margin-bottom: 15px;
942
+ padding-bottom: 10px;
943
+ border-bottom: 1px solid #dcdfe6;
944
+ font-size: 18px;
945
+ color: #303133;
946
+ font-weight: 500;
947
+ }
948
+
797
949
  .commit-form {
798
950
  display: flex;
799
951
  margin-bottom: 15px;
800
952
  gap: 10px;
801
953
  }
802
- .button-group {
954
+
955
+ .git-actions {
956
+ margin-top: 20px;
957
+ }
958
+
959
+ .action-groups {
803
960
  display: flex;
804
- gap: 10px;
961
+ flex-direction: column;
962
+ gap: 15px;
805
963
  }
806
- .commit-mode-toggle {
807
- margin-bottom: 15px;
964
+
965
+ .action-group {
966
+ background-color: #f9f9f9;
967
+ border-radius: 8px;
968
+ padding: 12px 15px;
969
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
970
+ border-left: 4px solid #409EFF;
971
+ overflow: hidden; /* 确保子元素不会溢出 */
972
+ }
973
+
974
+ .action-group:nth-child(2) {
975
+ border-left-color: #E6A23C;
976
+ }
977
+
978
+ .action-group:nth-child(3) {
979
+ border-left-color: #909399;
980
+ }
981
+
982
+ .group-title {
983
+ font-size: 14px;
984
+ font-weight: bold;
985
+ margin-bottom: 10px;
986
+ color: #606266;
987
+ text-align: left;
988
+ display: block;
989
+ position: relative;
990
+ padding-left: 10px;
991
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
992
+ padding-bottom: 8px;
993
+ }
994
+
995
+ .group-buttons {
996
+ display: flex;
997
+ flex-direction: column;
998
+ gap: 8px;
999
+ padding: 0 2px;
1000
+ }
1001
+
1002
+ .action-button {
1003
+ position: relative;
1004
+ padding: 14px 0 24px 0;
1005
+ width: 100%;
1006
+ display: flex;
1007
+ flex-direction: column;
1008
+ align-items: center;
1009
+ justify-content: center;
1010
+ height: auto;
1011
+ text-align: center;
1012
+ font-size: 16px;
1013
+ border-radius: 6px;
1014
+ border: none;
1015
+ }
1016
+
1017
+ .action-button:hover {
1018
+ transform: translateY(-2px);
1019
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
1020
+ }
1021
+
1022
+ .action-button:active {
1023
+ transform: translateY(0);
1024
+ }
1025
+
1026
+ .action-button :deep(.el-button__content) {
1027
+ display: flex;
1028
+ flex-direction: column;
1029
+ align-items: center;
1030
+ justify-content: center;
1031
+ width: 100%;
1032
+ }
1033
+
1034
+ .action-button :deep(.el-icon) {
1035
+ margin-right: 0;
1036
+ margin-bottom: 4px;
1037
+ font-size: 18px;
808
1038
  }
1039
+
1040
+ .command-text {
1041
+ position: absolute;
1042
+ bottom: 6px;
1043
+ font-size: 14px;
1044
+ font-family: monospace;
1045
+ width: 100%;
1046
+ text-align: center;
1047
+ left: 0;
1048
+ white-space: nowrap;
1049
+
1050
+ }
1051
+
809
1052
  .standard-commit-form {
810
1053
  display: flex;
811
1054
  flex-direction: column;
812
1055
  gap: 15px;
813
1056
  margin-bottom: 15px;
814
1057
  }
1058
+
815
1059
  .standard-commit-header {
816
1060
  display: flex;
1061
+ flex-direction: column;
817
1062
  gap: 10px;
818
1063
  width: 100%;
819
1064
  }
1065
+
820
1066
  .type-select {
821
- width: 120px;
822
- flex-shrink: 0;
1067
+ width: 100%;
823
1068
  }
1069
+
824
1070
  .scope-container {
825
1071
  display: flex;
826
1072
  align-items: center;
827
1073
  gap: 5px;
828
- flex-grow: 0;
829
- width: 200px;
1074
+ width: 100%;
830
1075
  }
1076
+
831
1077
  .scope-input {
832
1078
  flex-grow: 1;
833
1079
  }
1080
+
834
1081
  .description-container {
835
1082
  display: flex;
836
1083
  align-items: center;
837
1084
  gap: 5px;
838
- flex-grow: 1;
1085
+ width: 100%;
839
1086
  }
1087
+
840
1088
  .description-input {
841
1089
  flex-grow: 1;
842
- min-width: 200px;
843
1090
  }
1091
+
844
1092
  .settings-button {
845
1093
  flex-shrink: 0;
846
1094
  }
1095
+
847
1096
  .preview-section {
848
1097
  background-color: #f5f7fa;
849
1098
  padding: 10px;
850
1099
  border-radius: 4px;
851
1100
  }
1101
+
852
1102
  .preview-title {
853
1103
  font-weight: bold;
854
1104
  margin-bottom: 5px;
855
1105
  }
1106
+
856
1107
  .preview-content {
857
1108
  white-space: pre-wrap;
858
1109
  font-family: monospace;
@@ -861,6 +1112,7 @@ onMounted(() => {
861
1112
  background-color: #ebeef5;
862
1113
  border-radius: 4px;
863
1114
  }
1115
+
864
1116
  .template-container {
865
1117
  display: flex;
866
1118
  flex-direction: column;
@@ -876,24 +1128,30 @@ onMounted(() => {
876
1128
  flex: 1;
877
1129
  overflow-y: auto;
878
1130
  }
1131
+
879
1132
  .template-input {
880
1133
  flex-grow: 1;
881
1134
  }
1135
+
882
1136
  .template-list {
883
1137
  overflow-y: auto;
884
1138
  height: 100%;
885
1139
  }
1140
+
886
1141
  .template-item {
887
1142
  margin-bottom: 10px;
888
1143
  }
1144
+
889
1145
  .template-item:hover {
890
1146
  background-color: #f5f7fa;
891
1147
  }
1148
+
892
1149
  .template-content {
893
1150
  flex-grow: 1;
894
1151
  margin-right: 10px;
895
1152
  word-break: break-all;
896
1153
  }
1154
+
897
1155
  .template-actions {
898
1156
  display: flex;
899
1157
  gap: 5px;
@@ -901,4 +1159,81 @@ onMounted(() => {
901
1159
  min-width: 120px;
902
1160
  flex-shrink: 0;
903
1161
  }
1162
+
1163
+ .options-row {
1164
+ display: flex;
1165
+ justify-content: space-between;
1166
+ align-items: center;
1167
+ margin-bottom: 15px;
1168
+ }
1169
+
1170
+ .code-command {
1171
+ background-color: #2d2d2d;
1172
+ color: #f8f8f2;
1173
+ font-family: 'Courier New', Courier, monospace;
1174
+ padding: 10px;
1175
+ border-radius: 4px;
1176
+ overflow-x: auto;
1177
+ white-space: pre;
1178
+ font-size: 14px;
1179
+ }
1180
+
1181
+ @media (max-width: 768px) {
1182
+ .layout-container {
1183
+ flex-direction: column;
1184
+ }
1185
+
1186
+ .actions-section {
1187
+ width: 100%;
1188
+ }
1189
+
1190
+ .group-buttons {
1191
+ flex-direction: row;
1192
+ flex-wrap: wrap;
1193
+ }
1194
+
1195
+ .action-button {
1196
+ flex: 1;
1197
+ min-width: 120px;
1198
+ }
1199
+ }
1200
+
1201
+ .git-config-warning {
1202
+ width: 100%;
1203
+ }
1204
+
1205
+ .config-command {
1206
+ background-color: #2d2d2d;
1207
+ color: #f8f8f2;
1208
+ font-family: 'Courier New', Courier, monospace;
1209
+ padding: 10px;
1210
+ border-radius:
1211
+ 4px;
1212
+ margin-top: 10px;
1213
+ white-space: pre;
1214
+ }
1215
+
1216
+ /* 特定按钮样式 */
1217
+ .push-button {
1218
+ background-color: #67c23a;
1219
+ border-color: #67c23a;
1220
+ }
1221
+
1222
+ .push-button:hover {
1223
+ background-color: #85ce61;
1224
+ border-color: #85ce61;
1225
+ }
1226
+
1227
+ .reset-button {
1228
+ background-color: #909399;
1229
+ border-color: #909399;
1230
+ }
1231
+
1232
+ .reset-button:hover {
1233
+ background-color: #a6a9ad;
1234
+ border-color: #a6a9ad;
1235
+ }
1236
+ .el-button+.el-button {
1237
+ margin-left: 0;
1238
+ }
904
1239
  </style>