From e7f898f3576b9aceb53a3e3a47ad7db20c10e9c7 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Thu, 7 Aug 2025 16:09:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(initialization):=20=E6=9B=B4=E6=96=B0=20Py?= =?UTF-8?q?thon=20=E7=89=88=E6=9C=AC=E5=B9=B6=E4=BC=98=E5=8C=96=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -将 Python 版本从 3.13.0 更改为3.12.0 - 添加路由守卫,确保在生产环境中也能正确进入初始化页面 - 在初始化页面中增加服务启动相关逻辑 - 优化环境检查和启动服务的代码结构 - 调整后端服务启动方式,增加调试用的强制启动功能 --- frontend/electron/services/pythonService.ts | 34 ++++-- frontend/src/router/index.ts | 23 +++- frontend/src/views/Initialization.vue | 120 +++++++++++++++++--- 3 files changed, 148 insertions(+), 29 deletions(-) diff --git a/frontend/electron/services/pythonService.ts b/frontend/electron/services/pythonService.ts index b36ce49..3e747a5 100644 --- a/frontend/electron/services/pythonService.ts +++ b/frontend/electron/services/pythonService.ts @@ -13,11 +13,11 @@ export function setMainWindow(window: BrowserWindow) { // Python镜像源URL映射 const pythonMirrorUrls = { - official: 'https://www.python.org/ftp/python/3.13.0/python-3.13.0-embed-amd64.zip', - tsinghua: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.13.0/python-3.13.0-embed-amd64.zip', - ustc: 'https://mirrors.ustc.edu.cn/python/3.13.0/python-3.13.0-embed-amd64.zip', - huawei: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.13.0/python-3.13.0-embed-amd64.zip', - aliyun: 'https://mirrors.aliyun.com/python-release/windows/python-3.13.0-embed-amd64.zip' + official: 'https://www.python.org/ftp/python/3.12.0/python-3.12.0-embed-amd64.zip', + tsinghua: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip', + ustc: 'https://mirrors.ustc.edu.cn/python/3.12.0/python-3.12.0-embed-amd64.zip', + huawei: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/python-3.12.0-embed-amd64.zip', + aliyun: 'https://mirrors.aliyun.com/python-release/windows/python-3.12.0-embed-amd64.zip' } // 检查pip是否已安装 @@ -200,7 +200,7 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise< const stats = fs.statSync(zipPath) console.log(`Python压缩包大小: ${stats.size} bytes (${(stats.size / 1024 / 1024).toFixed(2)} MB)`) - // Python 3.13.0嵌入式版本应该大约30MB,如果小于5MB可能是无效文件 + // Python 3.12.0嵌入式版本应该大约30MB,如果小于5MB可能是无效文件 if (stats.size < 5 * 1024 * 1024) { // 5MB fs.unlinkSync(zipPath) // 删除无效文件 throw new Error(`Python下载文件大小异常: ${stats.size} bytes (${(stats.size / 1024).toFixed(2)} KB),可能是镜像站返回的错误页面或无效文件`) @@ -233,7 +233,7 @@ export async function downloadPython(appRoot: string, mirror = 'ustc'): Promise< console.log(`删除临时文件: ${zipPath}`) // 启用 site-packages 支持 - const pthFile = path.join(pythonPath, 'python313._pth') + const pthFile = path.join(pythonPath, 'python312._pth') if (fs.existsSync(pthFile)) { let content = fs.readFileSync(pthFile, 'utf-8') content = content.replace(/^#import site/m, 'import site') @@ -404,7 +404,7 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean; try { const pythonPath = path.join(appRoot, 'environment', 'python', 'python.exe') const backendPath = path.join(appRoot) - const mainPyPath = path.join(backendPath, 'app','main.py') + const mainPyPath = path.join(backendPath,'main.py') // 检查文件是否存在 if (!fs.existsSync(pythonPath)) { @@ -414,9 +414,11 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean; throw new Error('后端主文件不存在') } + console.log(`启动后端指令: "${pythonPath}" "${mainPyPath}"(cwd: ${appRoot})`) + // 启动后端进程 const backendProcess = spawn(pythonPath, [mainPyPath], { - cwd: backendPath, + cwd: appRoot, stdio: 'pipe' }) @@ -433,7 +435,19 @@ export async function startBackend(appRoot: string): Promise<{ success: boolean; console.log('Backend output:', output) // 检查是否包含启动成功的标志 - if (output.includes('uvicorn') || output.includes('8000')) { + if (output.includes('Uvicorn running') || output.includes('8000')) { + clearTimeout(timeout) + resolve() + } + }) + + // ✅ 重要:也要监听 stderr + backendProcess.stderr?.on('data', (data) => { + const output = data.toString() + console.error('Backend error:', output) // 保留原有日志 + + // ✅ 在 stderr 中也检查启动标志 + if (output.includes('Uvicorn running') || output.includes('8000')) { clearTimeout(timeout) resolve() } diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index d2a7869..6ea72f7 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -4,7 +4,9 @@ import type { RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', - redirect: '/initialization', + redirect: () => { + return '/initialization' + }, }, { path: '/initialization', @@ -85,4 +87,23 @@ const router = createRouter({ routes, }) +// 添加路由守卫,确保在生产环境中也能正确进入初始化页面 +router.beforeEach((to, from, next) => { + console.log('路由守卫:', { to: to.path, from: from.path }) + + // 如果访问的不是初始化页面,且没有初始化标记,则重定向到初始化页面 + if (to.path !== '/initialization') { + const isInitialized = localStorage.getItem('app-initialized') + console.log('检查初始化状态:', isInitialized) + + if (!isInitialized) { + console.log('应用未初始化,重定向到初始化页面') + next('/initialization') + return + } + } + + next() +}) + export default router diff --git a/frontend/src/views/Initialization.vue b/frontend/src/views/Initialization.vue index 39c68d7..94296ac 100644 --- a/frontend/src/views/Initialization.vue +++ b/frontend/src/views/Initialization.vue @@ -65,7 +65,7 @@

Python 运行环境

-

需要安装 Python 3.13.0 运行环境(64位嵌入式版本)

+

需要安装 Python 3.12.0 运行环境(64位嵌入式版本)

+ + {{ canStartService ? '启动服务' : '请先完成前置步骤' }} + + + + + 强制启动(调试用) + + + + @@ -275,10 +298,10 @@ const dependenciesInstalled = ref(false) // 镜像源配置 const pythonMirrors = ref([ - { key: 'official', name: 'Python 官方', url: 'https://www.python.org/ftp/python/3.13.0/', speed: null as number | null }, - { key: 'tsinghua', name: '清华 TUNA 镜像', url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.13.0/', speed: null as number | null }, - { key: 'ustc', name: '中科大镜像', url: 'https://mirrors.ustc.edu.cn/python/3.13.0/', speed: null as number | null }, - { key: 'huawei', name: '华为云镜像', url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.13.0/', speed: null as number | null }, + { key: 'official', name: 'Python 官方', url: 'https://www.python.org/ftp/python/3.12.0/', speed: null as number | null }, + { key: 'tsinghua', name: '清华 TUNA 镜像', url: 'https://mirrors.tuna.tsinghua.edu.cn/python/3.12.0/', speed: null as number | null }, + // { key: 'ustc', name: '中科大镜像', url: 'https://mirrors.ustc.edu.cn/python/3.12.0/', speed: null as number | null }, + { key: 'huawei', name: '华为云镜像', url: 'https://mirrors.huaweicloud.com/repository/toolkit/python/3.12.0/', speed: null as number | null }, { key: 'aliyun', name: '阿里云镜像', url: 'https://mirrors.aliyun.com/python-release/windows/', speed: null as number | null } ]) @@ -287,7 +310,7 @@ const pipMirrors = ref([ { key: 'tsinghua', name: '清华大学', url: 'https://pypi.tuna.tsinghua.edu.cn/simple/', speed: null as number | null }, { key: 'aliyun', name: '阿里云', url: 'https://mirrors.aliyun.com/pypi/simple/', speed: null as number | null }, { key: 'douban', name: '豆瓣', url: 'https://pypi.douban.com/simple/', speed: null as number | null }, - { key: 'ustc', name: '中科大', url: 'https://pypi.mirrors.ustc.edu.cn/simple/', speed: null as number | null }, + // { key: 'ustc', name: '中科大', url: 'https://pypi.mirrors.ustc.edu.cn/simple/', speed: null as number | null }, { key: 'huawei', name: '华中科技大学', url: 'https://pypi.hustunique.com/simple/', speed: null as number | null } ]) @@ -311,18 +334,45 @@ const startingService = ref(false) const showServiceProgress = ref(false) const serviceProgress = ref(0) const serviceStatus = ref('准备启动后端服务...') +const serviceStarted = ref(false) // 新增:服务是否已启动成功 // 全局进度条状态 const globalProgress = ref(0) const globalProgressStatus = ref<'normal' | 'exception' | 'success'>('normal') const progressText = ref('') -const allCompleted = computed(() => - pythonInstalled.value && gitInstalled.value && backendExists.value && dependenciesInstalled.value -) +// 检查是否可以启动服务(前置条件都满足) +const canStartService = computed(() => { + const result = pythonInstalled.value && gitInstalled.value && backendExists.value && dependenciesInstalled.value + console.log('canStartService 计算结果:', { + pythonInstalled: pythonInstalled.value, + gitInstalled: gitInstalled.value, + backendExists: backendExists.value, + dependenciesInstalled: dependenciesInstalled.value, + result + }) + return result +}) -function jumpToLastStep() { +// 检查是否全部完成(包括服务启动) +const allCompleted = computed(() => { + const result = pythonInstalled.value && gitInstalled.value && backendExists.value && dependenciesInstalled.value && serviceStarted.value + console.log('allCompleted 计算结果:', { + pythonInstalled: pythonInstalled.value, + gitInstalled: gitInstalled.value, + backendExists: backendExists.value, + dependenciesInstalled: dependenciesInstalled.value, + serviceStarted: serviceStarted.value, + result + }) + return result +}) + +async function jumpToLastStep() { + console.log('跳转到第6步,先检查环境状态') currentStep.value = 5 + // 跳转到第6步时,重新检查环境状态 + await checkEnvironment() } function skipToHome(){ router.push('/home') @@ -465,25 +515,30 @@ function prevStep() { } async function nextStep() { + console.log('nextStep 被调用,当前步骤:', currentStep.value) isProcessing.value = true errorMessage.value = '' try { switch (currentStep.value) { case 0: // 主题设置 + console.log('执行主题设置') saveSettings() break case 1: // Python 环境 + console.log('执行Python环境安装') if (!pythonInstalled.value) { await installPython() } break case 2: // Git 工具 + console.log('执行Git工具安装') if (!gitInstalled.value) { await installGit() } break case 3: // 源码获取 + console.log('执行源码获取') if (!backendExists.value) { await cloneBackend() } else { @@ -491,11 +546,13 @@ async function nextStep() { } break case 4: // 依赖安装 + console.log('执行依赖安装') if (!dependenciesInstalled.value) { await installDependencies() } break case 5: // 启动服务 + console.log('执行启动服务') await startBackendService() break } @@ -506,6 +563,7 @@ async function nextStep() { await autoStartSpeedTest() } } catch (error) { + console.error('nextStep 执行出错:', error) errorMessage.value = error instanceof Error ? error.message : String(error) stepStatus.value = 'error' } finally { @@ -551,21 +609,28 @@ async function checkEnvironment() { const status = await window.electronAPI.checkEnvironment() logger.info('环境检查结果', status) + console.log('环境检查结果:', status) pythonInstalled.value = status.pythonExists gitInstalled.value = status.gitExists backendExists.value = status.backendExists dependenciesInstalled.value = status.dependenciesInstalled - // 如果所有环境都已准备好,跳到最后一步 + // 如果所有环境都已准备好,跳到最后一步,但不自动启动服务 if (status.isInitialized) { - logger.info('环境已初始化完成,跳转到启动服务步骤') + logger.info('环境已初始化完成,跳转到启动服务步骤(但不自动启动)') + console.log('环境已初始化完成,跳转到第6步') currentStep.value = 5 - await startBackendService() + // 移除自动启动服务的逻辑,让用户手动点击启动 + // await startBackendService() + } else { + logger.info('环境未完全初始化,停留在初始化页面') + console.log('环境未完全初始化,当前步骤:', currentStep.value) } } catch (error) { const errorMsg = `环境检查失败: ${error instanceof Error ? error.message : String(error)}` logger.error('环境检查失败', error) + console.error('环境检查失败:', error) errorMessage.value = errorMsg } } @@ -648,6 +713,7 @@ async function startBackendService() { if (result.success) { serviceProgress.value = 100 serviceStatus.value = '后端服务启动成功' + serviceStarted.value = true // 设置服务启动成功状态 stepStatus.value = 'finish' logger.info('后端服务启动成功') } else { @@ -656,6 +722,7 @@ async function startBackendService() { } } catch (error) { serviceStatus.value = '后端服务启动失败' + serviceStarted.value = false // 确保启动失败时状态正确 logger.error('后端服务启动异常', error) throw error } finally { @@ -663,8 +730,17 @@ async function startBackendService() { } } +// 强制启动服务(调试用) +async function forceStartService() { + console.log('强制启动服务(忽略前置条件检查)') + await startBackendService() +} + // 进入应用 function enterApp() { + // 设置初始化完成标记 + localStorage.setItem('app-initialized', 'true') + console.log('设置初始化完成标记,跳转到首页') router.push('/home') } @@ -699,12 +775,20 @@ function handleDownloadProgress(progress: DownloadProgress) { } onMounted(async () => { + console.log('初始化页面 onMounted 开始') loadSettings() - await checkEnvironment() - window.electronAPI.onDownloadProgress(handleDownloadProgress) - // 如果当前步骤需要测速,自动开始测速 - await autoStartSpeedTest() + // 添加延迟,确保页面完全加载后再检查环境 + setTimeout(async () => { + console.log('开始环境检查') + await checkEnvironment() + + // 如果当前步骤需要测速,自动开始测速 + await autoStartSpeedTest() + }, 100) + + window.electronAPI.onDownloadProgress(handleDownloadProgress) + console.log('初始化页面 onMounted 完成') }) onUnmounted(() => {