From 0171c3ca4d206ded05734eb76687f12bbdf3d339 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Thu, 7 Aug 2025 20:18:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(initialization):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E6=93=8D=E4=BD=9C=E5=92=8C?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E6=9D=83=E9=99=90=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增配置文件保存、加载和重置功能 - 添加管理员权限检查和重启为管理员的功能 - 实现 pip 包管理器安装功能 - 优化初始化流程,自动检测并安装依赖 --- frontend/dist-electron/main.js | 107 ++- frontend/dist-electron/preload.js | 8 + frontend/electron/main.ts | 119 ++- frontend/electron/preload.ts | 10 + frontend/electron/services/pythonService.ts | 44 + .../components/initialization/BackendStep.vue | 264 ++++++ .../initialization/DependenciesStep.vue | 263 ++++++ .../src/components/initialization/GitStep.vue | 63 ++ .../src/components/initialization/PipStep.vue | 64 ++ .../components/initialization/PythonStep.vue | 274 ++++++ .../components/initialization/ServiceStep.vue | 63 ++ .../components/initialization/ThemeStep.vue | 145 ++++ frontend/src/router/index.ts | 12 +- frontend/src/utils/config.ts | 163 ++++ frontend/src/views/InitializationNew.vue | 806 ++++++++++++++++++ 15 files changed, 2397 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/initialization/BackendStep.vue create mode 100644 frontend/src/components/initialization/DependenciesStep.vue create mode 100644 frontend/src/components/initialization/GitStep.vue create mode 100644 frontend/src/components/initialization/PipStep.vue create mode 100644 frontend/src/components/initialization/PythonStep.vue create mode 100644 frontend/src/components/initialization/ServiceStep.vue create mode 100644 frontend/src/components/initialization/ThemeStep.vue create mode 100644 frontend/src/utils/config.ts create mode 100644 frontend/src/views/InitializationNew.vue diff --git a/frontend/dist-electron/main.js b/frontend/dist-electron/main.js index faf9dc4..e24a5de 100644 --- a/frontend/dist-electron/main.js +++ b/frontend/dist-electron/main.js @@ -36,10 +36,48 @@ Object.defineProperty(exports, "__esModule", { value: true }); const electron_1 = require("electron"); const path = __importStar(require("path")); const fs = __importStar(require("fs")); +const child_process_1 = require("child_process"); const environmentService_1 = require("./services/environmentService"); const downloadService_1 = require("./services/downloadService"); const pythonService_1 = require("./services/pythonService"); const gitService_1 = require("./services/gitService"); +// 检查是否以管理员权限运行 +function isRunningAsAdmin() { + try { + // 在Windows上,尝试写入系统目录来检查管理员权限 + if (process.platform === 'win32') { + const testPath = path.join(process.env.WINDIR || 'C:\\Windows', 'temp', 'admin-test.tmp'); + try { + fs.writeFileSync(testPath, 'test'); + fs.unlinkSync(testPath); + return true; + } + catch { + return false; + } + } + return true; // 非Windows系统暂时返回true + } + catch { + return false; + } +} +// 重新以管理员权限启动应用 +function restartAsAdmin() { + if (process.platform === 'win32') { + const exePath = process.execPath; + const args = process.argv.slice(1); + // 使用PowerShell以管理员权限启动 + (0, child_process_1.spawn)('powershell', [ + '-Command', + `Start-Process -FilePath "${exePath}" -ArgumentList "${args.join(' ')}" -Verb RunAs` + ], { + detached: true, + stdio: 'ignore' + }); + electron_1.app.quit(); + } +} let mainWindow = null; function createWindow() { mainWindow = new electron_1.BrowserWindow({ @@ -109,6 +147,10 @@ electron_1.ipcMain.handle('download-python', async (event, mirror = 'tsinghua') const appRoot = (0, environmentService_1.getAppRoot)(); return (0, pythonService_1.downloadPython)(appRoot, mirror); }); +electron_1.ipcMain.handle('install-pip', async () => { + const appRoot = (0, environmentService_1.getAppRoot)(); + return (0, pythonService_1.installPipPackage)(appRoot); +}); electron_1.ipcMain.handle('install-dependencies', async (event, mirror = 'tsinghua') => { const appRoot = (0, environmentService_1.getAppRoot)(); return (0, pythonService_1.installDependencies)(appRoot, mirror); @@ -130,6 +172,54 @@ electron_1.ipcMain.handle('update-backend', async (event, repoUrl = 'https://git const appRoot = (0, environmentService_1.getAppRoot)(); return (0, gitService_1.cloneBackend)(appRoot, repoUrl); // 使用相同的逻辑,会自动判断是pull还是clone }); +// 配置文件操作 +electron_1.ipcMain.handle('save-config', async (event, config) => { + try { + const appRoot = (0, environmentService_1.getAppRoot)(); + const configDir = path.join(appRoot, 'config'); + const configPath = path.join(configDir, 'frontend_config.json'); + // 确保config目录存在 + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); + } + fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8'); + console.log(`配置已保存到: ${configPath}`); + } + catch (error) { + console.error('保存配置文件失败:', error); + throw error; + } +}); +electron_1.ipcMain.handle('load-config', async () => { + try { + const appRoot = (0, environmentService_1.getAppRoot)(); + const configPath = path.join(appRoot, 'config', 'frontend_config.json'); + if (fs.existsSync(configPath)) { + const config = fs.readFileSync(configPath, 'utf8'); + console.log(`从文件加载配置: ${configPath}`); + return JSON.parse(config); + } + return null; + } + catch (error) { + console.error('加载配置文件失败:', error); + return null; + } +}); +electron_1.ipcMain.handle('reset-config', async () => { + try { + const appRoot = (0, environmentService_1.getAppRoot)(); + const configPath = path.join(appRoot, 'config', 'frontend_config.json'); + if (fs.existsSync(configPath)) { + fs.unlinkSync(configPath); + console.log(`配置文件已删除: ${configPath}`); + } + } + catch (error) { + console.error('重置配置文件失败:', error); + throw error; + } +}); // 日志文件操作 electron_1.ipcMain.handle('save-logs-to-file', async (event, logs) => { try { @@ -164,8 +254,23 @@ electron_1.ipcMain.handle('load-logs-from-file', async () => { return null; } }); +// 管理员权限相关 +electron_1.ipcMain.handle('check-admin', () => { + return isRunningAsAdmin(); +}); +electron_1.ipcMain.handle('restart-as-admin', () => { + restartAsAdmin(); +}); // 应用生命周期 -electron_1.app.whenReady().then(createWindow); +electron_1.app.whenReady().then(() => { + // 检查管理员权限 + if (!isRunningAsAdmin()) { + console.log('应用未以管理员权限运行'); + // 在生产环境中,可以选择是否强制要求管理员权限 + // 这里先创建窗口,让用户选择是否重新启动 + } + createWindow(); +}); electron_1.app.on('window-all-closed', () => { if (process.platform !== 'darwin') electron_1.app.quit(); diff --git a/frontend/dist-electron/preload.js b/frontend/dist-electron/preload.js index a0de46a..a6ff6f1 100644 --- a/frontend/dist-electron/preload.js +++ b/frontend/dist-electron/preload.js @@ -12,11 +12,19 @@ electron_1.contextBridge.exposeInMainWorld('electronAPI', { // 初始化相关API checkEnvironment: () => electron_1.ipcRenderer.invoke('check-environment'), downloadPython: (mirror) => electron_1.ipcRenderer.invoke('download-python', mirror), + installPip: () => electron_1.ipcRenderer.invoke('install-pip'), downloadGit: () => electron_1.ipcRenderer.invoke('download-git'), installDependencies: (mirror) => electron_1.ipcRenderer.invoke('install-dependencies', mirror), cloneBackend: (repoUrl) => electron_1.ipcRenderer.invoke('clone-backend', repoUrl), updateBackend: (repoUrl) => electron_1.ipcRenderer.invoke('update-backend', repoUrl), startBackend: () => electron_1.ipcRenderer.invoke('start-backend'), + // 管理员权限相关 + checkAdmin: () => electron_1.ipcRenderer.invoke('check-admin'), + restartAsAdmin: () => electron_1.ipcRenderer.invoke('restart-as-admin'), + // 配置文件操作 + saveConfig: (config) => electron_1.ipcRenderer.invoke('save-config', config), + loadConfig: () => electron_1.ipcRenderer.invoke('load-config'), + resetConfig: () => electron_1.ipcRenderer.invoke('reset-config'), // 日志文件操作 saveLogsToFile: (logs) => electron_1.ipcRenderer.invoke('save-logs-to-file', logs), loadLogsFromFile: () => electron_1.ipcRenderer.invoke('load-logs-from-file'), diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index 474c37f..51182f1 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -1,11 +1,51 @@ import { app, BrowserWindow, ipcMain, dialog } from 'electron' import * as path from 'path' import * as fs from 'fs' +import { spawn } from 'child_process' import { getAppRoot, checkEnvironment } from './services/environmentService' import { setMainWindow as setDownloadMainWindow } from './services/downloadService' -import { setMainWindow as setPythonMainWindow, downloadPython, installDependencies, startBackend } from './services/pythonService' +import { setMainWindow as setPythonMainWindow, downloadPython, installPipPackage, installDependencies, startBackend } from './services/pythonService' import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService' +// 检查是否以管理员权限运行 +function isRunningAsAdmin(): boolean { + try { + // 在Windows上,尝试写入系统目录来检查管理员权限 + if (process.platform === 'win32') { + const testPath = path.join(process.env.WINDIR || 'C:\\Windows', 'temp', 'admin-test.tmp') + try { + fs.writeFileSync(testPath, 'test') + fs.unlinkSync(testPath) + return true + } catch { + return false + } + } + return true // 非Windows系统暂时返回true + } catch { + return false + } +} + +// 重新以管理员权限启动应用 +function restartAsAdmin(): void { + if (process.platform === 'win32') { + const exePath = process.execPath + const args = process.argv.slice(1) + + // 使用PowerShell以管理员权限启动 + spawn('powershell', [ + '-Command', + `Start-Process -FilePath "${exePath}" -ArgumentList "${args.join(' ')}" -Verb RunAs` + ], { + detached: true, + stdio: 'ignore' + }) + + app.quit() + } +} + let mainWindow: BrowserWindow | null = null function createWindow() { @@ -83,6 +123,11 @@ ipcMain.handle('download-python', async (event, mirror = 'tsinghua') => { return downloadPython(appRoot, mirror) }) +ipcMain.handle('install-pip', async () => { + const appRoot = getAppRoot() + return installPipPackage(appRoot) +}) + ipcMain.handle('install-dependencies', async (event, mirror = 'tsinghua') => { const appRoot = getAppRoot() return installDependencies(appRoot, mirror) @@ -109,6 +154,59 @@ ipcMain.handle('update-backend', async (event, repoUrl = 'https://github.com/DLm return cloneBackend(appRoot, repoUrl) // 使用相同的逻辑,会自动判断是pull还是clone }) +// 配置文件操作 +ipcMain.handle('save-config', async (event, config) => { + try { + const appRoot = getAppRoot() + const configDir = path.join(appRoot, 'config') + const configPath = path.join(configDir, 'frontend_config.json') + + // 确保config目录存在 + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }) + } + + fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8') + console.log(`配置已保存到: ${configPath}`) + } catch (error) { + console.error('保存配置文件失败:', error) + throw error + } +}) + +ipcMain.handle('load-config', async () => { + try { + const appRoot = getAppRoot() + const configPath = path.join(appRoot, 'config', 'frontend_config.json') + + if (fs.existsSync(configPath)) { + const config = fs.readFileSync(configPath, 'utf8') + console.log(`从文件加载配置: ${configPath}`) + return JSON.parse(config) + } + + return null + } catch (error) { + console.error('加载配置文件失败:', error) + return null + } +}) + +ipcMain.handle('reset-config', async () => { + try { + const appRoot = getAppRoot() + const configPath = path.join(appRoot, 'config', 'frontend_config.json') + + if (fs.existsSync(configPath)) { + fs.unlinkSync(configPath) + console.log(`配置文件已删除: ${configPath}`) + } + } catch (error) { + console.error('重置配置文件失败:', error) + throw error + } +}) + // 日志文件操作 ipcMain.handle('save-logs-to-file', async (event, logs: string) => { try { @@ -147,8 +245,25 @@ ipcMain.handle('load-logs-from-file', async () => { } }) +// 管理员权限相关 +ipcMain.handle('check-admin', () => { + return isRunningAsAdmin() +}) + +ipcMain.handle('restart-as-admin', () => { + restartAsAdmin() +}) + // 应用生命周期 -app.whenReady().then(createWindow) +app.whenReady().then(() => { + // 检查管理员权限 + if (!isRunningAsAdmin()) { + console.log('应用未以管理员权限运行') + // 在生产环境中,可以选择是否强制要求管理员权限 + // 这里先创建窗口,让用户选择是否重新启动 + } + createWindow() +}) app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() diff --git a/frontend/electron/preload.ts b/frontend/electron/preload.ts index b660896..3a18edf 100644 --- a/frontend/electron/preload.ts +++ b/frontend/electron/preload.ts @@ -13,12 +13,22 @@ contextBridge.exposeInMainWorld('electronAPI', { // 初始化相关API checkEnvironment: () => ipcRenderer.invoke('check-environment'), downloadPython: (mirror?: string) => ipcRenderer.invoke('download-python', mirror), + installPip: () => ipcRenderer.invoke('install-pip'), downloadGit: () => ipcRenderer.invoke('download-git'), installDependencies: (mirror?: string) => ipcRenderer.invoke('install-dependencies', mirror), cloneBackend: (repoUrl?: string) => ipcRenderer.invoke('clone-backend', repoUrl), updateBackend: (repoUrl?: string) => ipcRenderer.invoke('update-backend', repoUrl), startBackend: () => ipcRenderer.invoke('start-backend'), + // 管理员权限相关 + checkAdmin: () => ipcRenderer.invoke('check-admin'), + restartAsAdmin: () => ipcRenderer.invoke('restart-as-admin'), + + // 配置文件操作 + saveConfig: (config: any) => ipcRenderer.invoke('save-config', config), + loadConfig: () => ipcRenderer.invoke('load-config'), + resetConfig: () => ipcRenderer.invoke('reset-config'), + // 日志文件操作 saveLogsToFile: (logs: string) => ipcRenderer.invoke('save-logs-to-file', logs), loadLogsFromFile: () => ipcRenderer.invoke('load-logs-from-file'), diff --git a/frontend/electron/services/pythonService.ts b/frontend/electron/services/pythonService.ts index 3e747a5..17ee9ed 100644 --- a/frontend/electron/services/pythonService.ts +++ b/frontend/electron/services/pythonService.ts @@ -399,6 +399,50 @@ export async function installDependencies(appRoot: string, mirror = 'tsinghua'): } } +// 导出pip安装函数 +export async function installPipPackage(appRoot: string): Promise<{ success: boolean; error?: string }> { + try { + const pythonPath = path.join(appRoot, 'environment', 'python') + + if (!fs.existsSync(pythonPath)) { + throw new Error('Python环境不存在,请先安装Python') + } + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'pip', + progress: 0, + status: 'installing', + message: '正在安装pip...' + }) + } + + await installPip(pythonPath, appRoot) + + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'pip', + progress: 100, + status: 'completed', + message: 'pip安装完成' + }) + } + + return { success: true } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + if (mainWindow) { + mainWindow.webContents.send('download-progress', { + type: 'pip', + progress: 0, + status: 'error', + message: `pip安装失败: ${errorMessage}` + }) + } + return { success: false, error: errorMessage } + } +} + // 启动后端 export async function startBackend(appRoot: string): Promise<{ success: boolean; error?: string }> { try { diff --git a/frontend/src/components/initialization/BackendStep.vue b/frontend/src/components/initialization/BackendStep.vue new file mode 100644 index 0000000..122b798 --- /dev/null +++ b/frontend/src/components/initialization/BackendStep.vue @@ -0,0 +1,264 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/initialization/DependenciesStep.vue b/frontend/src/components/initialization/DependenciesStep.vue new file mode 100644 index 0000000..7534f9b --- /dev/null +++ b/frontend/src/components/initialization/DependenciesStep.vue @@ -0,0 +1,263 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/initialization/GitStep.vue b/frontend/src/components/initialization/GitStep.vue new file mode 100644 index 0000000..44444ee --- /dev/null +++ b/frontend/src/components/initialization/GitStep.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/initialization/PipStep.vue b/frontend/src/components/initialization/PipStep.vue new file mode 100644 index 0000000..e7c697f --- /dev/null +++ b/frontend/src/components/initialization/PipStep.vue @@ -0,0 +1,64 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/initialization/PythonStep.vue b/frontend/src/components/initialization/PythonStep.vue new file mode 100644 index 0000000..bc4d09a --- /dev/null +++ b/frontend/src/components/initialization/PythonStep.vue @@ -0,0 +1,274 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/initialization/ServiceStep.vue b/frontend/src/components/initialization/ServiceStep.vue new file mode 100644 index 0000000..225fec3 --- /dev/null +++ b/frontend/src/components/initialization/ServiceStep.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/initialization/ThemeStep.vue b/frontend/src/components/initialization/ThemeStep.vue new file mode 100644 index 0000000..6208bd1 --- /dev/null +++ b/frontend/src/components/initialization/ThemeStep.vue @@ -0,0 +1,145 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 6ea72f7..111dc9a 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -11,7 +11,7 @@ const routes: RouteRecordRaw[] = [ { path: '/initialization', name: 'Initialization', - component: () => import('../views/Initialization.vue'), + component: () => import('../views/InitializationNew.vue'), meta: { title: '初始化' }, }, { @@ -87,16 +87,18 @@ const router = createRouter({ routes, }) +import { isAppInitialized } from '@/utils/config' + // 添加路由守卫,确保在生产环境中也能正确进入初始化页面 -router.beforeEach((to, from, next) => { +router.beforeEach(async (to, from, next) => { console.log('路由守卫:', { to: to.path, from: from.path }) // 如果访问的不是初始化页面,且没有初始化标记,则重定向到初始化页面 if (to.path !== '/initialization') { - const isInitialized = localStorage.getItem('app-initialized') - console.log('检查初始化状态:', isInitialized) + const initialized = await isAppInitialized() + console.log('检查初始化状态:', initialized) - if (!isInitialized) { + if (!initialized) { console.log('应用未初始化,重定向到初始化页面') next('/initialization') return diff --git a/frontend/src/utils/config.ts b/frontend/src/utils/config.ts new file mode 100644 index 0000000..e181bb0 --- /dev/null +++ b/frontend/src/utils/config.ts @@ -0,0 +1,163 @@ +import { createComponentLogger } from './logger' +import type { ThemeMode, ThemeColor } from '@/composables/useTheme' + +const logger = createComponentLogger('Config') + +export interface FrontendConfig { + // 基础配置 + isFirstLaunch: boolean + init: boolean + lastUpdateCheck?: string + + // 主题设置 + themeMode: ThemeMode + themeColor: ThemeColor + + // 镜像源设置 + selectedGitMirror: string + selectedPythonMirror: string + selectedPipMirror: string + + // 安装状态 + pythonInstalled?: boolean + gitInstalled?: boolean + backendExists?: boolean + dependenciesInstalled?: boolean + pipInstalled?: boolean +} + +const DEFAULT_CONFIG: FrontendConfig = { + isFirstLaunch: true, + init: false, + themeMode: 'system', + themeColor: 'blue', + selectedGitMirror: 'github', + selectedPythonMirror: 'tsinghua', + selectedPipMirror: 'tsinghua', + pythonInstalled: false, + gitInstalled: false, + backendExists: false, + dependenciesInstalled: false, + pipInstalled: false +} + +// 读取配置(内部使用,不触发保存) +async function getConfigInternal(): Promise { + try { + // 优先从文件读取配置 + const fileConfig = await window.electronAPI.loadConfig() + if (fileConfig) { + console.log('从文件加载配置:', fileConfig) + return { ...DEFAULT_CONFIG, ...fileConfig } + } + + // 如果文件不存在,尝试从localStorage迁移 + const localConfig = localStorage.getItem('app-config') + const themeConfig = localStorage.getItem('theme-settings') + + let config = { ...DEFAULT_CONFIG } + + if (localConfig) { + const parsed = JSON.parse(localConfig) + config = { ...config, ...parsed } + console.log('从localStorage迁移配置:', parsed) + } + + if (themeConfig) { + const parsed = JSON.parse(themeConfig) + config.themeMode = parsed.themeMode || 'system' + config.themeColor = parsed.themeColor || 'blue' + console.log('从localStorage迁移主题配置:', parsed) + } + + return config + } catch (error) { + console.error('读取配置失败:', error) + logger.error('读取配置失败', error) + return { ...DEFAULT_CONFIG } + } +} + +// 读取配置(公共接口) +export async function getConfig(): Promise { + const config = await getConfigInternal() + + // 如果是从localStorage迁移的配置,保存到文件并清理localStorage + const hasLocalStorage = localStorage.getItem('app-config') || localStorage.getItem('theme-settings') + if (hasLocalStorage) { + try { + await window.electronAPI.saveConfig(config) + localStorage.removeItem('app-config') + localStorage.removeItem('theme-settings') + localStorage.removeItem('app-initialized') + console.log('配置已从localStorage迁移到文件') + } catch (error) { + console.error('迁移配置失败:', error) + } + } + + return config +} + +// 保存配置 +export async function saveConfig(config: Partial): Promise { + try { + console.log('开始保存配置:', config) + const currentConfig = await getConfigInternal() // 使用内部函数避免递归 + const newConfig = { ...currentConfig, ...config } + console.log('合并后的配置:', newConfig) + await window.electronAPI.saveConfig(newConfig) + console.log('配置保存成功') + logger.info('配置已保存', newConfig) + } catch (error) { + console.error('保存配置失败:', error) + logger.error('保存配置失败', error) + throw error + } +} + +// 重置配置 +export async function resetConfig(): Promise { + try { + await window.electronAPI.resetConfig() + localStorage.removeItem('app-config') + localStorage.removeItem('theme-settings') + localStorage.removeItem('app-initialized') + logger.info('配置已重置') + } catch (error) { + logger.error('重置配置失败', error) + } +} + +// 检查是否已初始化 +export async function isAppInitialized(): Promise { + const config = await getConfig() + console.log('isAppInitialized 检查配置:', config) + console.log('init 字段值:', config.init) + console.log('init === true:', config.init === true) + return config.init === true +} + +// 检查是否第一次启动 +export async function isFirstLaunch(): Promise { + const config = await getConfig() + return config.isFirstLaunch === true +} + +// 设置初始化完成 +export async function setInitialized(value: boolean = true): Promise { + await saveConfig({ init: value, isFirstLaunch: false }) +} + +// 保存主题设置 +export async function saveThemeConfig(themeMode: ThemeMode, themeColor: ThemeColor): Promise { + await saveConfig({ themeMode, themeColor }) +} + +// 保存镜像源设置 +export async function saveMirrorConfig(gitMirror: string, pythonMirror?: string, pipMirror?: string): Promise { + const config: Partial = { selectedGitMirror: gitMirror } + if (pythonMirror) config.selectedPythonMirror = pythonMirror + if (pipMirror) config.selectedPipMirror = pipMirror + await saveConfig(config) +} \ No newline at end of file diff --git a/frontend/src/views/InitializationNew.vue b/frontend/src/views/InitializationNew.vue new file mode 100644 index 0000000..cf3b342 --- /dev/null +++ b/frontend/src/views/InitializationNew.vue @@ -0,0 +1,806 @@ + + + + + \ No newline at end of file