feat: 启动时检测 Git 安装状态,支持自动下载安装\n\n- 启动时自动检测 Git 是否已安装\n- 未安装时显示全屏提示界面\n- 支持用户选择安装路径\n- 自动从 GitHub 下载 Git for Windows 并静默安装\n- 实时显示下载进度和安装状态\n- 安装完成后自动重新初始化 Git 客户端"
This commit is contained in:
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 { On } 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 = 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>
|
||||
Reference in New Issue
Block a user