Compare commits
3 Commits
v0.2.0
...
3d6874f79e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d6874f79e | ||
|
|
d6dbd8834b | ||
|
|
beb40688ce |
@@ -9,7 +9,7 @@ info:
|
|||||||
description: "Git Repository Management Tool"
|
description: "Git Repository Management Tool"
|
||||||
copyright: "(c) 2025, GitPilot"
|
copyright: "(c) 2025, GitPilot"
|
||||||
comments: "GitPilot - Git Repository Manager"
|
comments: "GitPilot - Git Repository Manager"
|
||||||
version: "0.1.0"
|
version: "0.3.0"
|
||||||
|
|
||||||
dev_mode:
|
dev_mode:
|
||||||
root_path: .
|
root_path: .
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.gitpilot.app</string>
|
<string>com.gitpilot.app</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>0.2.0</string>
|
<string>0.3.0</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>Git Repository Management Tool</string>
|
<string>Git Repository Management Tool</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.2.0</string>
|
<string>0.3.0</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>icons</string>
|
<string>icons</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"fixed": {
|
"fixed": {
|
||||||
"file_version": "0.2.0"
|
"file_version": "0.3.0"
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
"0000": {
|
"0000": {
|
||||||
"ProductVersion": "0.2.0",
|
"ProductVersion": "0.3.0",
|
||||||
"CompanyName": "GitPilot",
|
"CompanyName": "GitPilot",
|
||||||
"FileDescription": "Git Repository Management Tool",
|
"FileDescription": "Git Repository Management Tool",
|
||||||
"LegalCopyright": "© 2025, GitPilot",
|
"LegalCopyright": "© 2025, GitPilot",
|
||||||
|
|||||||
376
frontend/app/components/GitCheck.vue
Normal file
376
frontend/app/components/GitCheck.vue
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import {
|
||||||
|
WarningOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
LoadingOutlined,
|
||||||
|
CloseCircleOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
import { AppService } from '../../bindings/github.com/zhuy1228/GitPilot/internal/app'
|
||||||
|
import { Events } from '@wailsio/runtime'
|
||||||
|
|
||||||
|
const emit = defineEmits(['ready'])
|
||||||
|
|
||||||
|
const checking = ref(true)
|
||||||
|
const gitInstalled = ref(false)
|
||||||
|
const gitVersion = ref('')
|
||||||
|
const gitPath = ref('')
|
||||||
|
|
||||||
|
// 安装相关
|
||||||
|
const installDir = ref('C:\\Program Files\\Git')
|
||||||
|
const installing = ref(false)
|
||||||
|
const installPhase = ref('') // downloading | installing | done | error
|
||||||
|
const installPercent = ref(0)
|
||||||
|
const installMessage = ref('')
|
||||||
|
|
||||||
|
let unsubscribe = null
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// 监听安装进度事件
|
||||||
|
unsubscribe = Events.On('git-install-progress', (event) => {
|
||||||
|
const data = event.data?.[0] || event.data
|
||||||
|
if (data) {
|
||||||
|
installPhase.value = data.phase
|
||||||
|
installPercent.value = data.percent
|
||||||
|
installMessage.value = data.message
|
||||||
|
if (data.phase === 'done') {
|
||||||
|
installing.value = false
|
||||||
|
gitInstalled.value = true
|
||||||
|
setTimeout(() => emit('ready'), 1500)
|
||||||
|
} else if (data.phase === 'error') {
|
||||||
|
installing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查 Git
|
||||||
|
try {
|
||||||
|
const status = await AppService.CheckGitInstalled()
|
||||||
|
gitInstalled.value = status.installed
|
||||||
|
gitVersion.value = status.version || ''
|
||||||
|
gitPath.value = status.path || ''
|
||||||
|
if (status.installed) {
|
||||||
|
emit('ready')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('检查 Git 失败:', e)
|
||||||
|
gitInstalled.value = false
|
||||||
|
} finally {
|
||||||
|
checking.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (unsubscribe) unsubscribe()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function selectInstallDir() {
|
||||||
|
try {
|
||||||
|
const dir = await AppService.SelectGitInstallDir()
|
||||||
|
if (dir) installDir.value = dir
|
||||||
|
} catch (e) {
|
||||||
|
console.error('选择目录失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startInstall() {
|
||||||
|
installing.value = true
|
||||||
|
installPhase.value = 'downloading'
|
||||||
|
installPercent.value = 0
|
||||||
|
installMessage.value = '准备下载...'
|
||||||
|
try {
|
||||||
|
await AppService.InstallGit(installDir.value)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('安装 Git 失败:', e)
|
||||||
|
installPhase.value = 'error'
|
||||||
|
installMessage.value = '安装失败: ' + String(e)
|
||||||
|
installing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 检查中 -->
|
||||||
|
<div v-if="checking" class="git-check-overlay">
|
||||||
|
<div class="git-check-card">
|
||||||
|
<LoadingOutlined :style="{ fontSize: '32px', color: 'var(--accent, #89b4fa)' }" spin />
|
||||||
|
<div class="git-check-title">正在检查 Git 环境...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Git 未安装 -->
|
||||||
|
<div v-else-if="!gitInstalled" class="git-check-overlay">
|
||||||
|
<div class="git-check-card install-card">
|
||||||
|
<div class="git-check-icon">
|
||||||
|
<WarningOutlined :style="{ fontSize: '48px', color: '#fab387' }" />
|
||||||
|
</div>
|
||||||
|
<div class="git-check-title">未检测到 Git</div>
|
||||||
|
<div class="git-check-desc">GitPilot 需要 Git 才能正常工作,请安装 Git 后使用。</div>
|
||||||
|
|
||||||
|
<!-- 安装进度 -->
|
||||||
|
<template v-if="installing || installPhase === 'done'">
|
||||||
|
<div class="install-progress">
|
||||||
|
<div v-if="installPhase === 'downloading'" class="progress-section">
|
||||||
|
<div class="progress-bar-bg">
|
||||||
|
<div class="progress-bar-fill" :style="{ width: installPercent + '%' }"></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-text">
|
||||||
|
<DownloadOutlined /> {{ installMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="installPhase === 'installing'" class="progress-section">
|
||||||
|
<div class="progress-bar-bg">
|
||||||
|
<div class="progress-bar-fill installing-anim" style="width: 100%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-text">
|
||||||
|
<LoadingOutlined spin /> {{ installMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="installPhase === 'done'" class="progress-section done">
|
||||||
|
<CheckCircleOutlined :style="{ fontSize: '24px', color: '#a6e3a1' }" />
|
||||||
|
<div class="progress-text success">{{ installMessage }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="installPhase === 'error'" class="progress-section error">
|
||||||
|
<CloseCircleOutlined :style="{ fontSize: '24px', color: '#f38ba8' }" />
|
||||||
|
<div class="progress-text error-text">{{ installMessage }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 安装表单 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="install-form">
|
||||||
|
<div class="install-field">
|
||||||
|
<label>安装路径</label>
|
||||||
|
<div class="install-dir-input">
|
||||||
|
<input
|
||||||
|
v-model="installDir"
|
||||||
|
class="dir-input"
|
||||||
|
placeholder="C:\Program Files\Git"
|
||||||
|
/>
|
||||||
|
<button class="dir-btn" @click="selectInstallDir" title="选择目录">
|
||||||
|
<FolderOpenOutlined />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="install-actions">
|
||||||
|
<button class="install-btn primary" @click="startInstall">
|
||||||
|
<DownloadOutlined /> 自动下载并安装 Git
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="install-hint">
|
||||||
|
将从 GitHub 下载 Git for Windows 安装包(约 65MB),需保持网络连接。
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.git-check-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--bg-primary, #1e1e2e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.git-check-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 40px;
|
||||||
|
max-width: 480px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-card {
|
||||||
|
background: var(--bg-secondary, #181825);
|
||||||
|
border: 1px solid var(--border-color, #313244);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40px 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.git-check-icon {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.git-check-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary, #cdd6f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.git-check-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-muted, #6c7086);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-form {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-field label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary, #a6adc8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-dir-input {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--bg-primary, #1e1e2e);
|
||||||
|
border: 1px solid var(--border-color, #313244);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-primary, #cdd6f4);
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-input:focus {
|
||||||
|
border-color: var(--accent, #89b4fa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-btn {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--bg-primary, #1e1e2e);
|
||||||
|
border: 1px solid var(--border-color, #313244);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--accent, #89b4fa);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-btn:hover {
|
||||||
|
background: var(--bg-hover, #313244);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-actions {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color, #313244);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-btn.primary {
|
||||||
|
background: var(--accent, #89b4fa);
|
||||||
|
color: #1e1e2e;
|
||||||
|
border-color: var(--accent, #89b4fa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-btn.primary:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted, #6c7086);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 安装进度 */
|
||||||
|
.install-progress {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-section.done,
|
||||||
|
.progress-section.error {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
background: var(--bg-primary, #1e1e2e);
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent, #89b4fa);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-fill.installing-anim {
|
||||||
|
background: linear-gradient(90deg, var(--accent, #89b4fa) 0%, #b4befe 50%, var(--accent, #89b4fa) 100%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary, #a6adc8);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text.success {
|
||||||
|
color: #a6e3a1;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text.error-text {
|
||||||
|
color: #f38ba8;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
import { ref, onBeforeUnmount } from 'vue'
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
const selectedProject = ref(null)
|
const selectedProject = ref(null)
|
||||||
|
const gitReady = ref(false)
|
||||||
|
|
||||||
|
function onGitReady() {
|
||||||
|
gitReady.value = true
|
||||||
|
}
|
||||||
|
|
||||||
function onSelectProject(project) {
|
function onSelectProject(project) {
|
||||||
selectedProject.value = project
|
selectedProject.value = project
|
||||||
@@ -43,7 +48,11 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="main-layout">
|
<!-- Git 环境检查 -->
|
||||||
|
<GitCheck v-if="!gitReady" @ready="onGitReady" />
|
||||||
|
|
||||||
|
<!-- 主界面 -->
|
||||||
|
<div v-else class="main-layout">
|
||||||
<Sidebar :selected-project="selectedProject" :style="{ width: sidebarWidth + 'px' }" @select-project="onSelectProject" />
|
<Sidebar :selected-project="selectedProject" :style="{ width: sidebarWidth + 'px' }" @select-project="onSelectProject" />
|
||||||
<div class="resize-handle" @mousedown="startSidebarResize"></div>
|
<div class="resize-handle" @mousedown="startSidebarResize"></div>
|
||||||
<ContentArea :project="selectedProject" />
|
<ContentArea :project="selectedProject" />
|
||||||
|
|||||||
@@ -86,6 +86,16 @@ export function BatchPush(paths) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CheckGitInstalled 检查 Git 是否已安装
|
||||||
|
* @returns {$CancellablePromise<$models.GitStatus>}
|
||||||
|
*/
|
||||||
|
export function CheckGitInstalled() {
|
||||||
|
return $Call.ByID(464620426).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType2($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CheckoutRemoteBranch 检出远程分支到本地
|
* CheckoutRemoteBranch 检出远程分支到本地
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
@@ -196,7 +206,7 @@ export function FetchProject(path) {
|
|||||||
*/
|
*/
|
||||||
export function GetAllProjectOverview() {
|
export function GetAllProjectOverview() {
|
||||||
return $Call.ByID(2453130151).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(2453130151).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType3($result);
|
return $$createType4($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +216,7 @@ export function GetAllProjectOverview() {
|
|||||||
*/
|
*/
|
||||||
export function GetAppSettings() {
|
export function GetAppSettings() {
|
||||||
return $Call.ByID(428589026).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(428589026).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType5($result);
|
return $$createType6($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +227,7 @@ export function GetAppSettings() {
|
|||||||
*/
|
*/
|
||||||
export function GetBranches(path) {
|
export function GetBranches(path) {
|
||||||
return $Call.ByID(1686190192, path).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(1686190192, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType7($result);
|
return $$createType8($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +260,7 @@ export function GetCommitFileDiff(path, hash, filePath) {
|
|||||||
*/
|
*/
|
||||||
export function GetCommitFiles(path, hash) {
|
export function GetCommitFiles(path, hash) {
|
||||||
return $Call.ByID(2420707876, path, hash).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(2420707876, path, hash).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType9($result);
|
return $$createType10($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +272,7 @@ export function GetCommitFiles(path, hash) {
|
|||||||
*/
|
*/
|
||||||
export function GetCommitLog(path, count) {
|
export function GetCommitLog(path, count) {
|
||||||
return $Call.ByID(1281870789, path, count).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(1281870789, path, count).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType11($result);
|
return $$createType12($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +293,7 @@ export function GetConflictFileContent(projectPath, filePath) {
|
|||||||
*/
|
*/
|
||||||
export function GetConflictFiles(path) {
|
export function GetConflictFiles(path) {
|
||||||
return $Call.ByID(2446806137, path).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(2446806137, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType13($result);
|
return $$createType14($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +333,7 @@ export function GetFileDiffStaged(projectPath, filePath) {
|
|||||||
*/
|
*/
|
||||||
export function GetGitGlobalConfig() {
|
export function GetGitGlobalConfig() {
|
||||||
return $Call.ByID(154811497).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(154811497).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType15($result);
|
return $$createType16($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +344,7 @@ export function GetGitGlobalConfig() {
|
|||||||
*/
|
*/
|
||||||
export function GetPlatformInfo(name) {
|
export function GetPlatformInfo(name) {
|
||||||
return $Call.ByID(2668095547, name).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(2668095547, name).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType17($result);
|
return $$createType18($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +355,7 @@ export function GetPlatformInfo(name) {
|
|||||||
*/
|
*/
|
||||||
export function GetProjectChangedFiles(path) {
|
export function GetProjectChangedFiles(path) {
|
||||||
return $Call.ByID(2302591462, path).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(2302591462, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType19($result);
|
return $$createType20($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +366,7 @@ export function GetProjectChangedFiles(path) {
|
|||||||
*/
|
*/
|
||||||
export function GetProjectStatus(path) {
|
export function GetProjectStatus(path) {
|
||||||
return $Call.ByID(3451089027, path).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(3451089027, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType21($result);
|
return $$createType22($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +376,7 @@ export function GetProjectStatus(path) {
|
|||||||
*/
|
*/
|
||||||
export function GetProjectTree() {
|
export function GetProjectTree() {
|
||||||
return $Call.ByID(651189689).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(651189689).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType23($result);
|
return $$createType24($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +387,7 @@ export function GetProjectTree() {
|
|||||||
*/
|
*/
|
||||||
export function GetRemoteBranches(path) {
|
export function GetRemoteBranches(path) {
|
||||||
return $Call.ByID(994796370, path).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(994796370, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType7($result);
|
return $$createType8($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +398,7 @@ export function GetRemoteBranches(path) {
|
|||||||
*/
|
*/
|
||||||
export function GetStashList(path) {
|
export function GetStashList(path) {
|
||||||
return $Call.ByID(1948257945, path).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(1948257945, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType25($result);
|
return $$createType26($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,7 +409,7 @@ export function GetStashList(path) {
|
|||||||
*/
|
*/
|
||||||
export function GetTags(path) {
|
export function GetTags(path) {
|
||||||
return $Call.ByID(3979169621, path).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(3979169621, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType27($result);
|
return $$createType28($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,10 +421,20 @@ export function GetTags(path) {
|
|||||||
*/
|
*/
|
||||||
export function GetUserInfo(platform, username) {
|
export function GetUserInfo(platform, username) {
|
||||||
return $Call.ByID(2674655707, platform, username).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(2674655707, platform, username).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType29($result);
|
return $$createType30($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InstallGit 下载并安装 Git
|
||||||
|
* installDir: 用户选择的安装目录(Windows 有效)
|
||||||
|
* @param {string} installDir
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function InstallGit(installDir) {
|
||||||
|
return $Call.ByID(1052641569, installDir);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IsMerging 检查是否处于合并状态
|
* IsMerging 检查是否处于合并状态
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
@@ -545,7 +565,7 @@ export function SaveConflictFile(projectPath, filePath, content) {
|
|||||||
*/
|
*/
|
||||||
export function SearchCommitLog(path, keyword, author, maxCount) {
|
export function SearchCommitLog(path, keyword, author, maxCount) {
|
||||||
return $Call.ByID(2874450917, path, keyword, author, maxCount).then(/** @type {($result: any) => any} */(($result) => {
|
return $Call.ByID(2874450917, path, keyword, author, maxCount).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
return $$createType11($result);
|
return $$createType12($result);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,6 +577,14 @@ export function SelectDirectory() {
|
|||||||
return $Call.ByID(2318416763);
|
return $Call.ByID(2318416763);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SelectGitInstallDir 打开文件夹选择器让用户选择 Git 安装路径
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function SelectGitInstallDir() {
|
||||||
|
return $Call.ByID(2941074074);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {application$0.App | null} app
|
* @param {application$0.App | null} app
|
||||||
* @returns {$CancellablePromise<void>}
|
* @returns {$CancellablePromise<void>}
|
||||||
@@ -697,31 +725,32 @@ export function UpdateUser(platform, oldUsername, newUsername, token) {
|
|||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = $models.BatchPullResult.createFrom;
|
const $$createType0 = $models.BatchPullResult.createFrom;
|
||||||
const $$createType1 = $Create.Array($$createType0);
|
const $$createType1 = $Create.Array($$createType0);
|
||||||
const $$createType2 = $models.ProjectOverview.createFrom;
|
const $$createType2 = $models.GitStatus.createFrom;
|
||||||
const $$createType3 = $Create.Array($$createType2);
|
const $$createType3 = $models.ProjectOverview.createFrom;
|
||||||
const $$createType4 = config$0.Settings.createFrom;
|
const $$createType4 = $Create.Array($$createType3);
|
||||||
const $$createType5 = $Create.Nullable($$createType4);
|
const $$createType5 = config$0.Settings.createFrom;
|
||||||
const $$createType6 = $models.BranchInfo.createFrom;
|
const $$createType6 = $Create.Nullable($$createType5);
|
||||||
const $$createType7 = $Create.Array($$createType6);
|
const $$createType7 = $models.BranchInfo.createFrom;
|
||||||
const $$createType8 = $models.CommitFileInfo.createFrom;
|
const $$createType8 = $Create.Array($$createType7);
|
||||||
const $$createType9 = $Create.Array($$createType8);
|
const $$createType9 = $models.CommitFileInfo.createFrom;
|
||||||
const $$createType10 = $models.CommitLog.createFrom;
|
const $$createType10 = $Create.Array($$createType9);
|
||||||
const $$createType11 = $Create.Array($$createType10);
|
const $$createType11 = $models.CommitLog.createFrom;
|
||||||
const $$createType12 = $models.ConflictFileInfo.createFrom;
|
const $$createType12 = $Create.Array($$createType11);
|
||||||
const $$createType13 = $Create.Array($$createType12);
|
const $$createType13 = $models.ConflictFileInfo.createFrom;
|
||||||
const $$createType14 = $models.GitConfig.createFrom;
|
const $$createType14 = $Create.Array($$createType13);
|
||||||
const $$createType15 = $Create.Nullable($$createType14);
|
const $$createType15 = $models.GitConfig.createFrom;
|
||||||
const $$createType16 = $models.PlatformInfo.createFrom;
|
const $$createType16 = $Create.Nullable($$createType15);
|
||||||
const $$createType17 = $Create.Nullable($$createType16);
|
const $$createType17 = $models.PlatformInfo.createFrom;
|
||||||
const $$createType18 = $models.FileInfo.createFrom;
|
const $$createType18 = $Create.Nullable($$createType17);
|
||||||
const $$createType19 = $Create.Array($$createType18);
|
const $$createType19 = $models.FileInfo.createFrom;
|
||||||
const $$createType20 = $models.ProjectStatus.createFrom;
|
const $$createType20 = $Create.Array($$createType19);
|
||||||
const $$createType21 = $Create.Nullable($$createType20);
|
const $$createType21 = $models.ProjectStatus.createFrom;
|
||||||
const $$createType22 = $models.TreeNode.createFrom;
|
const $$createType22 = $Create.Nullable($$createType21);
|
||||||
const $$createType23 = $Create.Array($$createType22);
|
const $$createType23 = $models.TreeNode.createFrom;
|
||||||
const $$createType24 = $models.StashInfo.createFrom;
|
const $$createType24 = $Create.Array($$createType23);
|
||||||
const $$createType25 = $Create.Array($$createType24);
|
const $$createType25 = $models.StashInfo.createFrom;
|
||||||
const $$createType26 = $models.TagInfo.createFrom;
|
const $$createType26 = $Create.Array($$createType25);
|
||||||
const $$createType27 = $Create.Array($$createType26);
|
const $$createType27 = $models.TagInfo.createFrom;
|
||||||
const $$createType28 = $models.UserInfo.createFrom;
|
const $$createType28 = $Create.Array($$createType27);
|
||||||
const $$createType29 = $Create.Nullable($$createType28);
|
const $$createType29 = $models.UserInfo.createFrom;
|
||||||
|
const $$createType30 = $Create.Nullable($$createType29);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export {
|
|||||||
ConflictFileInfo,
|
ConflictFileInfo,
|
||||||
FileInfo,
|
FileInfo,
|
||||||
GitConfig,
|
GitConfig,
|
||||||
|
GitStatus,
|
||||||
PlatformInfo,
|
PlatformInfo,
|
||||||
ProjectOverview,
|
ProjectOverview,
|
||||||
ProjectStatus,
|
ProjectStatus,
|
||||||
|
|||||||
@@ -328,6 +328,51 @@ export class GitConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GitStatus Git 安装状态
|
||||||
|
*/
|
||||||
|
export class GitStatus {
|
||||||
|
/**
|
||||||
|
* Creates a new GitStatus instance.
|
||||||
|
* @param {Partial<GitStatus>} [$$source = {}] - The source object to create the GitStatus.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("installed" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this["installed"] = false;
|
||||||
|
}
|
||||||
|
if (!("version" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["version"] = "";
|
||||||
|
}
|
||||||
|
if (!("path" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["path"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new GitStatus instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {GitStatus}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new GitStatus(/** @type {Partial<GitStatus>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PlatformInfo 平台信息
|
* PlatformInfo 平台信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
241
internal/app/gitsetup.go
Normal file
241
internal/app/gitsetup.go
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zhuy1228/GitPilot/internal/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitStatus Git 安装状态
|
||||||
|
type GitStatus struct {
|
||||||
|
Installed bool `json:"installed"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitInstallProgress 安装进度
|
||||||
|
type GitInstallProgress struct {
|
||||||
|
Phase string `json:"phase"` // downloading, installing, done, error
|
||||||
|
Percent float64 `json:"percent"` // 下载百分比 0-100
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckGitInstalled 检查 Git 是否已安装
|
||||||
|
func (s *AppService) CheckGitInstalled() GitStatus {
|
||||||
|
path, err := exec.LookPath("git")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Git 未安装:", err)
|
||||||
|
return GitStatus{Installed: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取版本号
|
||||||
|
cmd := exec.Command("git", "--version")
|
||||||
|
hideWindowCmd(cmd)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("获取 Git 版本失败:", err)
|
||||||
|
return GitStatus{Installed: true, Path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
version := strings.TrimSpace(string(output))
|
||||||
|
// "git version 2.43.0.windows.1" -> "2.43.0"
|
||||||
|
version = strings.TrimPrefix(version, "git version ")
|
||||||
|
if idx := strings.Index(version, ".windows"); idx > 0 {
|
||||||
|
version = version[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
return GitStatus{
|
||||||
|
Installed: true,
|
||||||
|
Version: version,
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectGitInstallDir 打开文件夹选择器让用户选择 Git 安装路径
|
||||||
|
func (s *AppService) SelectGitInstallDir() (string, error) {
|
||||||
|
if s.app == nil {
|
||||||
|
return "", fmt.Errorf("应用未初始化")
|
||||||
|
}
|
||||||
|
path, err := s.app.Dialog.OpenFile().
|
||||||
|
CanChooseDirectories(true).
|
||||||
|
CanChooseFiles(false).
|
||||||
|
SetTitle("选择 Git 安装路径").
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("打开文件夹选择器失败: %w", err)
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGitDownloadURL 根据平台返回 Git 下载地址
|
||||||
|
func getGitDownloadURL() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
if runtime.GOARCH == "arm64" {
|
||||||
|
return "https://github.com/git-for-windows/git/releases/download/v2.49.0.windows.1/Git-2.49.0-arm64.exe"
|
||||||
|
}
|
||||||
|
return "https://github.com/git-for-windows/git/releases/download/v2.49.0.windows.1/Git-2.49.0-64-bit.exe"
|
||||||
|
case "darwin":
|
||||||
|
return "" // macOS 建议使用 brew 或 Xcode CLI
|
||||||
|
default:
|
||||||
|
return "" // Linux 建议使用包管理器
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallGit 下载并安装 Git
|
||||||
|
// installDir: 用户选择的安装目录(Windows 有效)
|
||||||
|
func (s *AppService) InstallGit(installDir string) error {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
return fmt.Errorf("自动安装仅支持 Windows,请使用系统包管理器安装 Git")
|
||||||
|
}
|
||||||
|
|
||||||
|
url := getGitDownloadURL()
|
||||||
|
if url == "" {
|
||||||
|
return fmt.Errorf("无法获取 Git 下载地址")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 下载安装包
|
||||||
|
s.emitInstallProgress("downloading", 0, "正在下载 Git 安装包...")
|
||||||
|
log.Println("开始下载 Git:", url)
|
||||||
|
|
||||||
|
tmpDir := os.TempDir()
|
||||||
|
installerPath := filepath.Join(tmpDir, "Git-Installer.exe")
|
||||||
|
|
||||||
|
err := s.downloadFile(url, installerPath)
|
||||||
|
if err != nil {
|
||||||
|
s.emitInstallProgress("error", 0, "下载失败: "+err.Error())
|
||||||
|
return fmt.Errorf("下载 Git 安装包失败: %w", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(installerPath)
|
||||||
|
|
||||||
|
// 2. 静默安装
|
||||||
|
s.emitInstallProgress("installing", 100, "正在安装 Git,请稍候...")
|
||||||
|
log.Println("开始安装 Git 到:", installDir)
|
||||||
|
|
||||||
|
// Git for Windows 静默安装参数
|
||||||
|
args := []string{
|
||||||
|
"/VERYSILENT",
|
||||||
|
"/NORESTART",
|
||||||
|
"/NOCANCEL",
|
||||||
|
"/SP-",
|
||||||
|
"/CLOSEAPPLICATIONS",
|
||||||
|
"/RESTARTAPPLICATIONS",
|
||||||
|
"/COMPONENTS=icons,ext,ext\\shellhere,ext\\guihere,gitlfs,assoc,assoc_sh,autoupdate",
|
||||||
|
}
|
||||||
|
if installDir != "" {
|
||||||
|
args = append(args, "/DIR="+installDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(installerPath, args...)
|
||||||
|
hideWindowCmd(cmd)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
s.emitInstallProgress("error", 0, "安装失败: "+err.Error())
|
||||||
|
return fmt.Errorf("安装 Git 失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 验证安装
|
||||||
|
time.Sleep(2 * time.Second) // 等待 PATH 更新
|
||||||
|
status := s.CheckGitInstalled()
|
||||||
|
if !status.Installed {
|
||||||
|
// 尝试在指定目录查找
|
||||||
|
gitExe := filepath.Join(installDir, "bin", "git.exe")
|
||||||
|
if _, err := os.Stat(gitExe); err == nil {
|
||||||
|
s.emitInstallProgress("done", 100, "Git 安装成功!路径: "+gitExe)
|
||||||
|
// 重新初始化 gitClient
|
||||||
|
s.reinitGitClient()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.emitInstallProgress("error", 0, "安装完成但未检测到 Git,请重启应用重试")
|
||||||
|
return fmt.Errorf("安装完成但未检测到 Git")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.emitInstallProgress("done", 100, "Git 安装成功!版本: "+status.Version)
|
||||||
|
s.reinitGitClient()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reinitGitClient 重新初始化 Git 客户端
|
||||||
|
func (s *AppService) reinitGitClient() {
|
||||||
|
s.gitClient = git.NewGitClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
// emitInstallProgress 向前端发送安装进度事件
|
||||||
|
func (s *AppService) emitInstallProgress(phase string, percent float64, message string) {
|
||||||
|
if s.app != nil {
|
||||||
|
s.app.Event.Emit("git-install-progress", GitInstallProgress{
|
||||||
|
Phase: phase,
|
||||||
|
Percent: percent,
|
||||||
|
Message: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadFile 下载文件并通过事件报告进度
|
||||||
|
func (s *AppService) downloadFile(url, dest string) error {
|
||||||
|
client := &http.Client{Timeout: 10 * time.Minute}
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
totalSize := resp.ContentLength
|
||||||
|
var downloaded int64
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
lastReport := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, readErr := resp.Body.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
_, writeErr := out.Write(buf[:n])
|
||||||
|
if writeErr != nil {
|
||||||
|
return writeErr
|
||||||
|
}
|
||||||
|
downloaded += int64(n)
|
||||||
|
|
||||||
|
// 每 300ms 报告一次进度
|
||||||
|
if time.Since(lastReport) > 300*time.Millisecond {
|
||||||
|
var pct float64
|
||||||
|
if totalSize > 0 {
|
||||||
|
pct = float64(downloaded) / float64(totalSize) * 100
|
||||||
|
}
|
||||||
|
sizeMB := float64(downloaded) / 1024 / 1024
|
||||||
|
msg := fmt.Sprintf("正在下载... %.1f MB", sizeMB)
|
||||||
|
if totalSize > 0 {
|
||||||
|
totalMB := float64(totalSize) / 1024 / 1024
|
||||||
|
msg = fmt.Sprintf("正在下载... %.1f / %.1f MB", sizeMB, totalMB)
|
||||||
|
}
|
||||||
|
s.emitInstallProgress("downloading", pct, msg)
|
||||||
|
lastReport = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if readErr != nil {
|
||||||
|
if readErr == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return readErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
8
internal/app/hidewindow_other.go
Normal file
8
internal/app/hidewindow_other.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import "os/exec"
|
||||||
|
|
||||||
|
// hideWindowCmd 非 Windows 平台无需处理
|
||||||
|
func hideWindowCmd(cmd *exec.Cmd) {}
|
||||||
14
internal/app/hidewindow_windows.go
Normal file
14
internal/app/hidewindow_windows.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hideWindowCmd 在 Windows 上隐藏子进程的控制台窗口
|
||||||
|
func hideWindowCmd(cmd *exec.Cmd) {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: true,
|
||||||
|
CreationFlags: 0x08000000,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user