diff --git a/.gitignore b/.gitignore index 73b8e09..7ae43d4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ Thumbs.db *.asar -data/ \ No newline at end of file +data/ +service/ \ No newline at end of file diff --git a/README.md b/README.md index bca3842..809a1e4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- 轻松管理 PHP、MySQL、Nginx、Redis 等服务,告别繁琐的手动配置 + 轻松管理 PHP、MySQL、Nginx、Redis、Node.js、Python 等服务,告别繁琐的手动配置

@@ -26,11 +26,35 @@ - + - - + + + + + + + + + + + + + + + + + + + + + + + + + +
仪表盘PHP管理PHP管理
仪表盘PHP 版本管理🏠 仪表盘🐘 PHP 版本管理
MySQL管理Nginx管理
🐬 MySQL 管理🌐 Nginx 管理
Redis管理Node.js管理
🔴 Redis 管理💚 Node.js 管理
Python管理设置
🐍 Python 管理⚙️ 设置
@@ -42,39 +66,72 @@ | ---------- | ---------------------------------------------------------- | | 多版本管理 | 支持同时安装 PHP 8.1、8.2、8.3、8.4、8.5 等多个版本 | | 一键切换 | 点击即可切换 PHP 版本,自动配置系统环境变量 | -| 扩展管理 | 可视化管理 PHP 扩展,一键启用/禁用 | +| 扩展管理 | 可视化管理 PHP 扩展,支持在线安装(从 PECL) | | 配置编辑 | 在线编辑 php.ini,无需手动查找配置文件 | | 自动配置 | 安装时自动启用常用扩展(curl、gd、mbstring、pdo_mysql 等) | +| Composer | 集成 Composer 管理,支持镜像源切换(阿里云、腾讯云等) | +| 下载源 | 从 [windows.php.net](https://windows.php.net) 官方下载 | ### 🐬 MySQL 管理 -| 功能 | 说明 | -| ---------- | -------------------------------- | -| 版本支持 | 支持 MySQL 5.7.x 和 8.0.x 系列 | -| 服务控制 | 启动、停止、重启 MySQL 服务 | -| 密码管理 | 一键修改 root 密码 | -| 配置编辑 | 在线编辑 my.ini 配置文件 | -| 自动初始化 | 安装时自动初始化数据库,开箱即用 | +| 功能 | 说明 | +| ---------- | -------------------------------------------------------------------- | +| 版本支持 | 支持 MySQL 5.7.x 和 8.0.x 系列 | +| 服务控制 | 启动、停止、重启 MySQL 服务 | +| 密码管理 | 一键修改 root 密码 | +| 配置编辑 | 在线编辑 my.ini 配置文件 | +| 自动初始化 | 安装时自动初始化数据库,开箱即用 | +| 下载源 | 从[阿里云镜像站](https://mirrors.aliyun.com/mysql/)下载,速度更快 | ### 🌐 Nginx 管理 -| 功能 | 说明 | -| ------------ | ------------------------------------ | -| 版本管理 | 支持多个 Nginx 版本,可随时切换 | -| 服务控制 | 启动、停止、重启、热重载配置 | -| 站点管理 | 可视化添加、删除、启用、禁用虚拟主机 | -| Laravel 支持 | 自动生成 Laravel 项目的伪静态配置 | -| SSL 证书 | 支持申请 Let's Encrypt 免费 SSL 证书 | -| 配置编辑 | 在线编辑 nginx.conf 主配置文件 | +| 功能 | 说明 | +| ------------ | --------------------------------------------- | +| 版本管理 | 支持多个 Nginx 版本,可随时切换 | +| 服务控制 | 启动、停止、重启、热重载配置 | +| 站点管理 | 可视化添加、删除、启用、禁用虚拟主机 | +| Laravel 支持 | 自动生成 Laravel 项目的伪静态配置 | +| SSL 证书 | 支持申请 Let's Encrypt 免费 SSL 证书 | +| 配置编辑 | 在线编辑 nginx.conf 主配置文件 | +| 下载源 | 从 [nginx.org](https://nginx.org) 官方下载 | ### 🔴 Redis 管理 -| 功能 | 说明 | -| ------------ | -------------------------------- | -| Windows 版本 | 使用 Windows 原生编译版 Redis | -| 服务控制 | 启动、停止、重启 Redis 服务 | -| 状态监控 | 实时查看运行状态、内存使用情况 | -| 配置编辑 | 在线编辑 redis.windows.conf 配置 | +| 功能 | 说明 | +| ------------ | -------------------------------------------------------------------------- | +| Windows 版本 | 使用 Windows 原生编译版 Redis | +| 服务控制 | 启动、停止、重启 Redis 服务 | +| 状态监控 | 实时查看运行状态、内存使用情况 | +| 配置编辑 | 在线编辑 redis.windows.conf 配置 | +| 下载源 | 从 [GitHub (redis-windows)](https://github.com/redis-windows/redis-windows) 下载 | + +### 💚 Node.js 管理 + +| 功能 | 说明 | +| ---------- | -------------------------------------------------------- | +| 多版本管理 | 支持同时安装多个 Node.js 版本 | +| LTS 支持 | 显示 LTS 版本和 Current 版本标识 | +| npm 集成 | 自动显示对应的 npm 版本 | +| 一键切换 | 快速切换默认 Node.js 版本,自动配置环境变量 | +| 下载源 | 从 [nodejs.org](https://nodejs.org) 官方下载 | + +### 🐍 Python 管理 + +| 功能 | 说明 | +| ---------- | -------------------------------------------------------- | +| 嵌入式版本 | 使用免安装的嵌入式版本,不影响系统环境 | +| pip 集成 | 自动配置 pip,支持安装 Python 包 | +| 多版本管理 | 支持同时安装多个 Python 版本 | +| 一键切换 | 快速切换默认 Python 版本 | +| 下载源 | 从 [python.org](https://www.python.org) 官方下载 | + +### 🔧 Git 管理 + +| 功能 | 说明 | +| ---------- | ------------------------------ | +| 版本管理 | 一键安装/卸载 Git for Windows | +| 配置管理 | 可视化配置用户名、邮箱等信息 | +| 环境变量 | 自动配置系统 PATH | ### 🌍 站点管理 @@ -89,6 +146,8 @@ - 📋 **Hosts 管理** - 可视化管理系统 hosts 文件 - 🌙 **深色/浅色主题** - 支持主题切换 - 📊 **服务状态监控** - 实时显示各服务运行状态 +- ⏳ **加载状态提示** - 版本列表加载时显示 Loading 状态 +- 📥 **下载源说明** - 清晰显示各软件的下载来源 ## 🛠️ 技术栈 @@ -146,6 +205,9 @@ phper/ │ ├── MysqlManager.ts # MySQL 服务管理器 │ ├── NginxManager.ts # Nginx 服务管理器 │ ├── RedisManager.ts # Redis 服务管理器 +│ ├── NodeManager.ts # Node.js 版本管理器 +│ ├── PythonManager.ts # Python 版本管理器 +│ ├── GitManager.ts # Git 管理器 │ ├── ServiceManager.ts # 开机自启服务管理器 │ └── HostsManager.ts # Hosts 文件管理器 │ @@ -163,6 +225,9 @@ phper/ │ ├── MysqlManager.vue # MySQL 管理 │ ├── NginxManager.vue # Nginx 管理 │ ├── RedisManager.vue # Redis 管理 +│ ├── NodeManager.vue # Node.js 管理 +│ ├── PythonManager.vue # Python 管理 +│ ├── GitManager.vue # Git 管理 │ ├── SitesManager.vue # 站点管理 │ ├── HostsManager.vue # Hosts 管理 │ └── Settings.vue # 设置 @@ -246,10 +311,15 @@ A: 进入对应服务管理页面,先停止服务,然后点击"卸载"按钮 ## 🔗 相关资源 -- [PHP for Windows](https://windows.php.net/download/) - PHP Windows 官方下载 -- [MySQL Downloads](https://dev.mysql.com/downloads/) - MySQL 官方下载 -- [Nginx](https://nginx.org/) - Nginx 官方网站 -- [Redis for Windows](https://github.com/redis-windows/redis-windows) - Windows 版 Redis +| 软件 | 下载源 | +| ------- | ---------------------------------------------------------------------- | +| PHP | [windows.php.net](https://windows.php.net/download/) - 官方 Windows 版 | +| MySQL | [阿里云镜像](https://mirrors.aliyun.com/mysql/) - 国内高速下载 | +| Nginx | [nginx.org](https://nginx.org/en/download.html) - 官方 Windows 版 | +| Redis | [GitHub redis-windows](https://github.com/redis-windows/redis-windows) | +| Node.js | [nodejs.org](https://nodejs.org/en/download/) - 官方下载 | +| Python | [python.org](https://www.python.org/downloads/windows/) - 嵌入式版本 | +| Git | [git-scm.com](https://git-scm.com/download/win) - 官方 Windows 版 | ## 📄 开源协议 diff --git a/docs/dashboard.png b/docs/dashboard.png new file mode 100644 index 0000000..1ecacf7 Binary files /dev/null and b/docs/dashboard.png differ diff --git a/docs/mysql.png b/docs/mysql.png new file mode 100644 index 0000000..2a5d115 Binary files /dev/null and b/docs/mysql.png differ diff --git a/docs/nginx.png b/docs/nginx.png new file mode 100644 index 0000000..3fd2b13 Binary files /dev/null and b/docs/nginx.png differ diff --git a/docs/nodejs.png b/docs/nodejs.png new file mode 100644 index 0000000..71c0890 Binary files /dev/null and b/docs/nodejs.png differ diff --git a/docs/php.png b/docs/php.png new file mode 100644 index 0000000..6d4c7bb Binary files /dev/null and b/docs/php.png differ diff --git a/docs/python.png b/docs/python.png new file mode 100644 index 0000000..f8df66c Binary files /dev/null and b/docs/python.png differ diff --git a/docs/redis.png b/docs/redis.png new file mode 100644 index 0000000..78006f9 Binary files /dev/null and b/docs/redis.png differ diff --git a/docs/setting.png b/docs/setting.png new file mode 100644 index 0000000..5b88e16 Binary files /dev/null and b/docs/setting.png differ diff --git a/electron/services/ConfigStore.ts b/electron/services/ConfigStore.ts index b2329ba..b914514 100644 --- a/electron/services/ConfigStore.ts +++ b/electron/services/ConfigStore.ts @@ -9,7 +9,9 @@ interface ConfigSchema { mysqlVersions: string[]; nginxVersions: string[]; redisVersions: string[]; + nodeVersions: string[]; activePhpVersion: string; + activeNodeVersion: string; autoStart: { nginx: boolean; mysql: boolean; @@ -63,7 +65,9 @@ export class ConfigStore { mysqlVersions: [], nginxVersions: [], redisVersions: [], + nodeVersions: [], activePhpVersion: "", + activeNodeVersion: "", autoStart: { nginx: false, mysql: false, diff --git a/electron/services/NginxManager.ts b/electron/services/NginxManager.ts index 53b8c0c..ce865bc 100644 --- a/electron/services/NginxManager.ts +++ b/electron/services/NginxManager.ts @@ -28,6 +28,8 @@ interface NginxStatus { export class NginxManager { private configStore: ConfigStore + private versionCache: { versions: AvailableNginxVersion[]; timestamp: number } | null = null + private readonly CACHE_TTL = 5 * 60 * 1000 // 5分钟缓存 constructor(configStore: ConfigStore) { this.configStore = configStore @@ -88,9 +90,109 @@ export class NginxManager { /** * 获取可用的 Nginx 版本列表 + * 动态从 nginx.org 获取 */ async getAvailableVersions(): Promise { - const versions: AvailableNginxVersion[] = [ + // 检查缓存 + if (this.versionCache && (Date.now() - this.versionCache.timestamp) < this.CACHE_TTL) { + console.log('使用缓存的 Nginx 版本列表') + return this.versionCache.versions + } + + let versions: AvailableNginxVersion[] = [] + + try { + console.log('从 nginx.org 获取版本列表...') + versions = await this.fetchNginxVersions() + console.log(`从 nginx.org 获取到 ${versions.length} 个 Nginx 版本`) + } catch (error: any) { + console.error('从 nginx.org 获取 Nginx 版本失败:', error.message) + } + + // 如果获取失败或为空,使用备用列表 + if (versions.length === 0) { + console.log('使用备用 Nginx 版本列表') + versions = this.getFallbackVersions() + } + + // 更新缓存 + this.versionCache = { versions, timestamp: Date.now() } + + return versions + } + + /** + * 从 nginx.org 下载页面获取版本列表 + */ + private async fetchNginxVersions(): Promise { + return new Promise((resolve, reject) => { + const options = { + hostname: 'nginx.org', + path: '/download/', + method: 'GET', + headers: { + 'User-Agent': 'PHPer-Dev-Manager' + }, + timeout: 15000 + } + + const request = http.request(options, (response) => { + let data = '' + response.on('data', chunk => data += chunk) + response.on('end', () => { + try { + const versions: AvailableNginxVersion[] = [] + // 解析 HTML 页面中的 Windows 版本链接 + // 格式: nginx-1.27.3.zip + const regex = /href="\/download\/nginx-(\d+\.\d+\.\d+)\.zip"/g + let match + const seen = new Set() + + while ((match = regex.exec(data)) !== null) { + const version = match[1] + if (!seen.has(version)) { + seen.add(version) + versions.push({ + version, + downloadUrl: `https://nginx.org/download/nginx-${version}.zip` + }) + } + } + + // 按版本号排序(降序) + versions.sort((a, b) => { + const aParts = a.version.split('.').map(Number) + const bParts = b.version.split('.').map(Number) + for (let i = 0; i < 3; i++) { + if (aParts[i] !== bParts[i]) { + return bParts[i] - aParts[i] + } + } + return 0 + }) + + // 只返回前 10 个版本 + resolve(versions.slice(0, 10)) + } catch (e) { + reject(e) + } + }) + }) + + request.on('error', reject) + request.on('timeout', () => { + request.destroy() + reject(new Error('请求超时')) + }) + request.end() + }) + } + + /** + * 备用版本列表 + */ + private getFallbackVersions(): AvailableNginxVersion[] { + return [ { version: '1.27.3', downloadUrl: 'https://nginx.org/download/nginx-1.27.3.zip' @@ -108,8 +210,6 @@ export class NginxManager { downloadUrl: 'https://nginx.org/download/nginx-1.24.0.zip' } ] - - return versions } /** diff --git a/electron/services/PythonManager.ts b/electron/services/PythonManager.ts index f0e363f..bc3dff7 100644 --- a/electron/services/PythonManager.ts +++ b/electron/services/PythonManager.ts @@ -24,6 +24,8 @@ interface AvailablePythonVersion { export class PythonManager { private configStore: ConfigStore + private versionCache: { versions: AvailablePythonVersion[]; timestamp: number } | null = null + private readonly CACHE_TTL = 5 * 60 * 1000 // 5分钟缓存 constructor(configStore: ConfigStore) { this.configStore = configStore @@ -56,7 +58,7 @@ export class PythonManager { } const dirs = readdirSync(pythonBasePath, { withFileTypes: true }) - + for (const dir of dirs) { if (dir.isDirectory() && dir.name.startsWith('python-')) { const version = dir.name.replace('python-', '') @@ -78,12 +80,131 @@ export class PythonManager { /** * 获取可用的 Python 版本列表 - * 使用 Python 嵌入式版本(免安装) + * 动态从 python.org 获取 Windows 嵌入式版本 */ async getAvailableVersions(): Promise { - // Python 嵌入式版本下载地址 - // https://www.python.org/downloads/windows/ - const versions: AvailablePythonVersion[] = [ + // 检查缓存 + if (this.versionCache && (Date.now() - this.versionCache.timestamp) < this.CACHE_TTL) { + console.log('使用缓存的 Python 版本列表') + // 过滤掉已安装的版本 + const installed = await this.getInstalledVersions() + const installedVersions = installed.map(v => v.version) + return this.versionCache.versions.filter(v => !installedVersions.includes(v.version)) + } + + let versions: AvailablePythonVersion[] = [] + + try { + console.log('从 python.org 获取版本列表...') + versions = await this.fetchPythonVersions() + console.log(`从 python.org 获取到 ${versions.length} 个 Python 版本`) + } catch (error: any) { + console.error('从 python.org 获取 Python 版本失败:', error.message) + } + + // 如果获取失败或为空,使用备用列表 + if (versions.length === 0) { + console.log('使用备用 Python 版本列表') + versions = this.getFallbackVersions() + } + + // 更新缓存 + this.versionCache = { versions, timestamp: Date.now() } + + // 过滤掉已安装的版本 + const installed = await this.getInstalledVersions() + const installedVersions = installed.map(v => v.version) + + return versions.filter(v => !installedVersions.includes(v.version)) + } + + /** + * 从 python.org API 获取版本列表 + */ + private async fetchPythonVersions(): Promise { + return new Promise((resolve, reject) => { + const options = { + hostname: 'www.python.org', + path: '/api/v2/downloads/release/?is_published=true&pre_release=false&page_size=50', + method: 'GET', + headers: { + 'User-Agent': 'PHPer-Dev-Manager', + 'Accept': 'application/json' + }, + timeout: 15000 + } + + const request = https.request(options, (response) => { + let data = '' + response.on('data', chunk => data += chunk) + response.on('end', () => { + try { + const json = JSON.parse(data) + const versions: AvailablePythonVersion[] = [] + const seen = new Set() + + if (json.results && Array.isArray(json.results)) { + for (const release of json.results) { + // 解析版本号,如 "Python 3.13.1" + const match = release.name?.match(/Python (\d+\.\d+\.\d+)/) + if (match) { + const version = match[1] + // 只获取 Python 3.8+ 版本 + const majorMinor = version.split('.').slice(0, 2).map(Number) + if (majorMinor[0] >= 3 && majorMinor[1] >= 8 && !seen.has(version)) { + seen.add(version) + versions.push({ + version, + downloadUrl: `https://www.python.org/ftp/python/${version}/python-${version}-embed-amd64.zip`, + type: 'embed' + }) + } + } + } + } + + // 按版本号排序(降序) + versions.sort((a, b) => { + const aParts = a.version.split('.').map(Number) + const bParts = b.version.split('.').map(Number) + for (let i = 0; i < 3; i++) { + if (aParts[i] !== bParts[i]) { + return bParts[i] - aParts[i] + } + } + return 0 + }) + + // 每个主版本只保留最新的一个 + const latestByMajorMinor = new Map() + for (const v of versions) { + const majorMinor = v.version.split('.').slice(0, 2).join('.') + if (!latestByMajorMinor.has(majorMinor)) { + latestByMajorMinor.set(majorMinor, v) + } + } + + resolve(Array.from(latestByMajorMinor.values())) + } catch (e) { + reject(e) + } + }) + }) + + request.on('error', reject) + request.on('timeout', () => { + request.destroy() + reject(new Error('请求超时')) + }) + request.end() + }) + } + + /** + * 备用版本列表 + */ + private getFallbackVersions(): AvailablePythonVersion[] { + return [ { version: '3.13.1', downloadUrl: 'https://www.python.org/ftp/python/3.13.1/python-3.13.1-embed-amd64.zip', @@ -110,12 +231,6 @@ export class PythonManager { type: 'embed' } ] - - // 过滤掉已安装的版本 - const installed = await this.getInstalledVersions() - const installedVersions = installed.map(v => v.version) - - return versions.filter(v => !installedVersions.includes(v.version)) } /** @@ -324,7 +439,7 @@ export class PythonManager { // 修改 python*._pth 文件以启用 pip const majorMinor = version.split('.').slice(0, 2).join('') const pthFile = join(pythonPath, `python${majorMinor}._pth`) - + if (existsSync(pthFile)) { const { readFileSync } = require('fs') let content = readFileSync(pthFile, 'utf-8') diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index ad3e2e9..3a4a3a8 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -376,8 +376,6 @@ const startService = async (service: Service) => { } else { ElMessage.error(result?.message || '启动失败') } - // 同步刷新全局状态 - await store.refreshServiceStatus() } catch (error: any) { ElMessage.error(error.message) } finally { @@ -396,8 +394,6 @@ const startPhpCgi = async (service: Service) => { } else { ElMessage.error(result?.message || '启动失败') } - // 同步刷新全局状态 - await store.refreshServiceStatus() } catch (error: any) { ElMessage.error(error.message) } finally { @@ -426,8 +422,6 @@ const stopService = async (service: Service) => { } else { ElMessage.error(result?.message || '停止失败') } - // 同步刷新全局状态 - await store.refreshServiceStatus() } catch (error: any) { ElMessage.error(error.message) } finally { @@ -446,8 +440,6 @@ const stopPhpCgi = async (service: Service) => { } else { ElMessage.error(result?.message || '停止失败') } - // 同步刷新全局状态 - await store.refreshServiceStatus() } catch (error: any) { ElMessage.error(error.message) } finally { @@ -475,8 +467,6 @@ const restartService = async (service: Service) => { } else { ElMessage.error(result?.message || '重启失败') } - // 同步刷新全局状态 - await store.refreshServiceStatus() } catch (error: any) { ElMessage.error(error.message) } finally { @@ -498,8 +488,6 @@ const restartPhpCgi = async (service: Service) => { } else { ElMessage.error(result?.message || '重启失败') } - // 同步刷新全局状态 - await store.refreshServiceStatus() } catch (error: any) { ElMessage.error(error.message) } finally { @@ -513,8 +501,8 @@ const startAllPhpCgi = async () => { const result = await window.electronAPI?.service.startAllPhpCgi() if (result?.success) { ElMessage.success('全部 PHP-CGI 已启动') - // 刷新全局状态 - await store.refreshServiceStatus() + // 更新所有 PHP-CGI 状态为运行中 + store.serviceStatus.phpCgi.forEach(p => p.running = true) } else { ElMessage.error(result?.message || '启动失败') } @@ -529,8 +517,8 @@ const stopAllPhpCgi = async () => { const result = await window.electronAPI?.service.stopAllPhpCgi() if (result?.success) { ElMessage.success('全部 PHP-CGI 已停止') - // 刷新全局状态 - await store.refreshServiceStatus() + // 更新所有 PHP-CGI 状态为已停止 + store.serviceStatus.phpCgi.forEach(p => p.running = false) } else { ElMessage.error(result?.message || '停止失败') } @@ -581,11 +569,9 @@ const setActiveNode = async (version: string) => { } } -onMounted(() => { - // 如果 store 未初始化,则刷新 - if (!store.lastUpdated) { - store.refreshAll() - } +onMounted(async () => { + // 总是刷新数据以确保数据最新 + await store.refreshAll() }) diff --git a/src/views/MysqlManager.vue b/src/views/MysqlManager.vue index c94c197..199a8d2 100644 --- a/src/views/MysqlManager.vue +++ b/src/views/MysqlManager.vue @@ -113,11 +113,19 @@ > - MySQL 将从阿里云镜像站下载,安装后自动设置 root 密码为 123456。 + MySQL 将从 阿里云镜像站 下载,速度较快。安装后 root 密码默认为 123456。 -

+
+ + 正在获取可用版本列表... +
+
+ 暂无可用版本 +
+
-
+
@@ -220,6 +228,7 @@