From af15e86df697797ecc5c0ad43dcc183d27f02d9c Mon Sep 17 00:00:00 2001 From: zyj Date: Wed, 11 Mar 2026 10:06:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E5=A4=84=E7=90=86=E3=80=81=E6=8F=90=E4=BA=A4=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E3=80=81=E6=89=B9=E9=87=8F=E6=93=8D=E4=BD=9C=E5=8A=9F=E8=83=BD?= =?UTF-8?q?\n\n=E5=86=B2=E7=AA=81=E5=A4=84=E7=90=86:\n-=20=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E5=90=88=E5=B9=B6=E7=8A=B6=E6=80=81=E5=B9=B6=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=86=B2=E7=AA=81=E6=8F=90=E7=A4=BA=E6=A0=8F\n-=20?= =?UTF-8?q?=E5=86=B2=E7=AA=81=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E5=92=8C=E5=86=85=E5=AE=B9=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?\n-=20=E6=94=AF=E6=8C=81=E4=BF=9D=E5=AD=98=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E3=80=81=E6=A0=87=E8=AE=B0=E5=B7=B2=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E3=80=81=E4=B8=AD=E6=AD=A2=E5=90=88=E5=B9=B6=E3=80=81?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=90=88=E5=B9=B6\n\n=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=90=9C=E7=B4=A2:\n-=20=E6=94=AF=E6=8C=81=E6=8C=89=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E4=BF=A1=E6=81=AF=E5=85=B3=E9=94=AE=E5=AD=97=E5=92=8C?= =?UTF-8?q?=E4=BD=9C=E8=80=85=E6=90=9C=E7=B4=A2\n-=20=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E4=B8=8E=E5=AE=8C=E6=95=B4=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=98=BE=E7=A4=BA\n\n=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=93=8D=E4=BD=9C:\n-=20=E4=BE=A7=E8=BE=B9=E6=A0=8F=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=89=B9=E9=87=8F=E6=93=8D=E4=BD=9C=E5=85=A5=E5=8F=A3?= =?UTF-8?q?\n-=20=E5=B1=95=E7=A4=BA=E6=89=80=E6=9C=89=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=A6=82=E8=A7=88(=E5=88=86=E6=94=AF/=E5=8F=98=E6=9B=B4/?= =?UTF-8?q?=E6=9C=AA=E6=8E=A8=E9=80=81=E7=8A=B6=E6=80=81)\n-=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=89=B9=E9=87=8F=20Pull/Push=20=E5=B9=B6=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=89=A7=E8=A1=8C=E7=BB=93=E6=9E=9C"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/app/components/ContentArea.vue | 366 +++++++++++++++++- frontend/app/components/Sidebar.vue | 249 +++++++++++- .../GitPilot/internal/app/appservice.js | 176 +++++++-- .../zhuy1228/GitPilot/internal/app/index.js | 3 + .../zhuy1228/GitPilot/internal/app/models.js | 156 ++++++++ internal/app/service.go | 221 +++++++++++ internal/git/client.go | 73 ++++ 7 files changed, 1203 insertions(+), 41 deletions(-) diff --git a/frontend/app/components/ContentArea.vue b/frontend/app/components/ContentArea.vue index e10172a..e2d659b 100644 --- a/frontend/app/components/ContentArea.vue +++ b/frontend/app/components/ContentArea.vue @@ -31,6 +31,9 @@ import { GlobalOutlined, SaveOutlined, DownloadOutlined, + SearchOutlined, + WarningOutlined, + StopOutlined, } from '@ant-design/icons-vue' import { Modal, message } from 'ant-design-vue' import { AppService } from '../../bindings/github.com/zhuy1228/GitPilot/internal/app' @@ -100,6 +103,21 @@ const showSettings = ref(false) const settingsLoading = ref(false) const gitConfig = ref({ userName: '', userEmail: '' }) +// ---- 冲突处理 ---- +const isMerging = ref(false) +const conflictFiles = ref([]) +const selectedConflictFile = ref(null) +const conflictContent = ref('') +const conflictSaving = ref(false) + +// ---- 提交搜索 ---- +const searchKeyword = ref('') +const searchAuthor = ref('') +const searchLoading = ref(false) +const isSearchMode = ref(false) +const searchResults = ref([]) +const displayLogs = computed(() => isSearchMode.value ? searchResults.value : commitLogs.value) + // ---- 文件列表拖拽调整宽度 ---- const fileListWidth = ref(300) const MIN_FILELIST = 180 @@ -256,6 +274,20 @@ async function loadStatus() { loadingFiles.value = false } + // 检查合并冲突状态 + try { + isMerging.value = await AppService.IsMerging(props.project.path) + if (isMerging.value) { + const cFiles = await AppService.GetConflictFiles(props.project.path) + conflictFiles.value = Array.isArray(cFiles) ? cFiles : [] + } else { + conflictFiles.value = [] + selectedConflictFile.value = null + } + } catch (e) { + console.error('检查合并状态失败:', e) + } + // 第三步:并行加载分支列表 + 提交历史 loadBranches() if (activeTab.value === 'history') { @@ -794,6 +826,122 @@ async function saveSettings() { } } +// ---- 冲突处理 ---- +async function selectConflictFile(file) { + selectedConflictFile.value = file + conflictContent.value = '' + try { + const content = await AppService.GetConflictFileContent(props.project.path, file.filePath) + conflictContent.value = content + } catch (e) { + conflictContent.value = '读取冲突文件失败: ' + e + } +} + +async function saveConflictContent() { + if (!selectedConflictFile.value) return + conflictSaving.value = true + try { + await AppService.SaveConflictFile(props.project.path, selectedConflictFile.value.filePath, conflictContent.value) + message.success('冲突文件已保存') + } catch (e) { + Modal.error({ title: '保存失败', content: String(e) }) + } finally { + conflictSaving.value = false + } +} + +async function resolveConflictFiles(files) { + if (!props.project?.path) return + try { + const paths = files.map(f => f.filePath) + await AppService.ResolveConflictFile(props.project.path, paths) + message.success('已标记为已解决') + isMerging.value = await AppService.IsMerging(props.project.path) + if (isMerging.value) { + const cFiles = await AppService.GetConflictFiles(props.project.path) + conflictFiles.value = Array.isArray(cFiles) ? cFiles : [] + } else { + conflictFiles.value = [] + } + selectedConflictFile.value = null + await refreshFiles() + } catch (e) { + Modal.error({ title: '解决冲突失败', content: String(e) }) + } +} + +async function completeMerge() { + if (!props.project?.path) return + const msg = commitMessage.value.trim() || 'Merge completed' + commitLoading.value = true + try { + await AppService.CommitChanges(props.project.path, msg) + commitMessage.value = '' + isMerging.value = false + conflictFiles.value = [] + selectedConflictFile.value = null + await loadStatus() + } catch (e) { + Modal.error({ title: '完成合并失败', content: String(e) }) + } finally { + commitLoading.value = false + } +} + +async function handleAbortMerge() { + if (!props.project?.path) return + Modal.confirm({ + title: '确认中止合并', + icon: h(ExclamationCircleOutlined), + content: '中止合并将丢弃所有合并更改,回到合并之前的状态。确定继续?', + okText: '中止合并', + cancelText: '取消', + okButtonProps: { danger: true }, + async onOk() { + try { + await AppService.AbortMerge(props.project.path) + message.success('已中止合并') + isMerging.value = false + conflictFiles.value = [] + selectedConflictFile.value = null + await loadStatus() + } catch (e) { + Modal.error({ title: '中止合并失败', content: String(e) }) + } + }, + }) +} + +// ---- 提交搜索 ---- +async function searchCommits() { + if (!props.project?.path) return + if (!searchKeyword.value.trim() && !searchAuthor.value.trim()) return + searchLoading.value = true + isSearchMode.value = true + try { + const results = await AppService.SearchCommitLog( + props.project.path, + searchKeyword.value.trim(), + searchAuthor.value.trim(), + 100 + ) + searchResults.value = Array.isArray(results) ? results : [] + } catch (e) { + console.error('搜索提交失败:', e) + searchResults.value = [] + } finally { + searchLoading.value = false + } +} + +function clearSearch() { + isSearchMode.value = false + searchKeyword.value = '' + searchAuthor.value = '' + searchResults.value = [] +} + // ---- 提交历史 ---- async function loadCommitLog() { if (!props.project?.path) return @@ -1237,6 +1385,57 @@ watch(activeTab, (tab) => { + +
+
+ + 合并冲突 + + {{ conflictFiles.length }} 个文件需要解决 + + + + + + 中止 + + + 完成合并 + + +
+
+
+ + {{ cf.filePath }} +
+ + + + + +
+
+
+
+ + 全部标记为已解决 + +
+
+