# ============================================================================= # 企微IT智能服务台 — 前端 dev server 一键启动脚本 # ============================================================================= # 作用:一次性后台启动 4 个前端 dev server(每个独立窗口 + 独立日志) # 用法:.\scripts\dev-frontend-start.ps1 # .\scripts\dev-frontend-start.ps1 -Frontend h5 # 只起一个 # .\scripts\dev-frontend-start.ps1 -Frontend h5,admin # 起多个 # .\scripts\dev-frontend-start.ps1 -Stop # 停掉所有 # 前置:已跑过 dev-frontend-install.ps1(每个前端有 node_modules) # 输出:日志写到 .pnpm-dev-.log;进程名 pnpm.exe(用 Get-Process pnpm 查) # 注意:启动顺序 h5 → agent → admin → portal,每个隔 4 秒 # ============================================================================= [CmdletBinding()] param( [string]$Frontend = '', # 空 = 全部 4 个;逗号分隔可多选: h5,agent,admin,portal [switch]$Stop # 切到停止模式 ) $ErrorActionPreference = 'Stop' $ProjectRoot = $PSScriptRoot | Split-Path -Parent Set-Location $ProjectRoot # -------------------------------------------------------------------------- # 自动把 npm 全局 bin 加进 PATH(只对本脚本生效) # 原因:某些用户(如 NVM 用户)系统 PATH 缺 npm global bin,pnpm 找不到 # -------------------------------------------------------------------------- $NpmGlobalBin = npm config get prefix 2>$null if ($NpmGlobalBin -and (Test-Path $NpmGlobalBin)) { if ($env:PATH -notlike "*$NpmGlobalBin*") { $env:PATH = "$env:PATH;$NpmGlobalBin" Write-Host " ℹ 已临时把 $NpmGlobalBin 加进 PATH(只对本次脚本生效)" -ForegroundColor DarkGray } } # -------------------------------------------------------------------------- # 前置检查 pnpm # -------------------------------------------------------------------------- $pnpmCmd = Get-Command pnpm -ErrorAction SilentlyContinue if (-not $pnpmCmd) { Write-Host " ✗ pnpm 没装或不在 PATH,请先跑 scripts\dev-frontend-install.ps1" -ForegroundColor Red exit 1 } # -------------------------------------------------------------------------- # 4 个前端清单(name -> port) # 端口必须跟每个前端 vite.config.ts 的 server.port 一致 # -------------------------------------------------------------------------- $AllFrontends = @( @{ Name = 'frontend-h5'; Port = 5174; Label = 'h5' }, @{ Name = 'frontend-agent'; Port = 5173; Label = 'agent' }, @{ Name = 'frontend-admin'; Port = 5175; Label = 'admin' }, @{ Name = 'frontend-portal'; Port = 5176; Label = 'portal' } ) # -------------------------------------------------------------------------- # 颜色函数(跟 dev-frontend-install.ps1 一致) # -------------------------------------------------------------------------- function Write-Step($msg) { Write-Host "`n$msg" -ForegroundColor Cyan } function Write-OK($msg) { Write-Host " ✓ $msg" -ForegroundColor Green } function Write-Warn($msg) { Write-Host " ⚠ $msg" -ForegroundColor Yellow } function Write-Err($msg) { Write-Host " ✗ $msg" -ForegroundColor Red } # -------------------------------------------------------------------------- # 工具函数 # -------------------------------------------------------------------------- function Get-FrontendByLabel($label) { foreach ($f in $AllFrontends) { if ($f.Label -eq $label -or $f.Name -eq $label) { return $f } } return $null } function Test-PortListening($port) { # 优先用 Get-NetTCPConnection(快);失败回退 Test-NetConnection try { $conn = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction Stop return ($null -ne $conn) } catch { try { $tnc = Test-NetConnection -ComputerName 'localhost' -Port $port -InformationLevel Quiet -WarningAction SilentlyContinue return $tnc } catch { return $false } } } function Get-PnpmPids() { # 拿到所有 pnpm 进程 PID(用作"停止所有") $procs = Get-Process -Name 'pnpm' -ErrorAction SilentlyContinue if ($null -eq $procs) { return @() } return @($procs | Select-Object -ExpandProperty Id) } function Get-NodePidsByCwd($dir) { # 拿到工作目录在 $dir 下的 node.exe 进程(Vite dev server) $procs = Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" -ErrorAction SilentlyContinue $pids = @() foreach ($p in $procs) { if ($p.CommandLine -and $p.CommandLine.Contains($dir)) { $pids += $p.ProcessId } } return $pids } # -------------------------------------------------------------------------- # 模式:停掉所有前端 # -------------------------------------------------------------------------- if ($Stop) { Write-Step "═══ 停掉所有前端 dev server ═══" # 先停 node.exe(它们是 Vite dev server 真正的工作进程) $killed = 0 foreach ($f in $AllFrontends) { $dir = Join-Path $ProjectRoot $f.Name $pids = Get-NodePidsByCwd $dir foreach ($pid in $pids) { try { Stop-Process -Id $pid -Force -ErrorAction Stop $killed++ } catch {} } } # 再停 pnpm 父进程(它会再起 node,所以得反过来先杀子再杀父?实际先父后子都行,这里杀剩的 pnpm) $pnpmPids = Get-PnpmPids foreach ($pid in $pnpmPids) { try { Stop-Process -Id $pid -Force -ErrorAction Stop $killed++ } catch {} } Write-OK "已停掉 $killed 个进程" # 兜底:再扫一遍端口确认空 Start-Sleep -Seconds 1 foreach ($f in $AllFrontends) { if (Test-PortListening $f.Port) { Write-Warn "$($f.Label) 端口 $($f.Port) 还占着,可能不是 pnpm 起的" } else { Write-OK "$($f.Label) 端口 $($f.Port) 已释放" } } Write-Host "`n下一步: .\scripts\dev-frontend-start.ps1 重启" -ForegroundColor Gray exit 0 } # -------------------------------------------------------------------------- # 解析 -Frontend 参数 # -------------------------------------------------------------------------- if ([string]::IsNullOrWhiteSpace($Frontend)) { $Selected = $AllFrontends } else { $Selected = @() foreach ($label in ($Frontend -split ',')) { $label = $label.Trim() $f = Get-FrontendByLabel $label if ($null -eq $f) { Write-Err "未知前端: $label(可选: h5, agent, admin, portal)" exit 1 } $Selected += $f } } # -------------------------------------------------------------------------- # 前置检查 # -------------------------------------------------------------------------- Write-Step "═══ 企微IT智能服务台 — 前端 dev server 启动 ═══" # 检查 pnpm try { $pnpmVer = pnpm --version Write-OK "pnpm 已装: $pnpmVer" } catch { Write-Err "pnpm 没装,请先跑: npm install -g pnpm" exit 1 } # 检查 Node 版本(警告,不阻塞) $nodeVer = node --version Write-Host " Node 版本: $nodeVer" if ($nodeVer -notmatch '^v(20\.|21\.|22\.)') { Write-Warn "前端要求 Node 20.x,你是 $nodeVer。多数情况能跑,出问题再装 Node 20。" } # 检查每个选中的前端是否有 node_modules foreach ($f in $Selected) { $dir = Join-Path $ProjectRoot $f.Name if (-not (Test-Path "$dir/package.json")) { Write-Err "找不到 $dir/package.json,跳过 $($f.Label)" # 用占位标记一下,在后面的循环里跳过 $f | Add-Member -NotePropertyName 'Skip' -NotePropertyValue $true -Force continue } if (-not (Test-Path "$dir/node_modules")) { Write-Warn "$($f.Label) 没有 node_modules,先跑 dev-frontend-install.ps1" $f | Add-Member -NotePropertyName 'Skip' -NotePropertyValue $true -Force } } # -------------------------------------------------------------------------- # 逐个后台启动 # -------------------------------------------------------------------------- $Started = @() foreach ($f in $Selected) { if ($f.Skip) { continue } $dir = Join-Path $ProjectRoot $f.Name $log = Join-Path $ProjectRoot ".pnpm-dev-$($f.Label).log" $port = $f.Port $label = $f.Label Write-Step "─── $label (port $port) ───" # 检查端口是否已被占(被占就跳过,不报错) if (Test-PortListening $port) { Write-Warn "端口 $port 已被占,$label 跳过(可能别人/上次没退干净在跑)" Write-Host " 看进程: Get-NetTCPConnection -LocalPort $port -State Listen" -ForegroundColor Gray Write-Host " 强制停: .\scripts\dev-frontend-start.ps1 -Stop" -ForegroundColor Gray continue } # 清空旧日志(每次启动都是干净的一页) if (Test-Path $log) { Remove-Item $log -Force } # 后台启动 Start-Process,WindowStyle Hidden(不弹黑窗) # 关键:必须用 cmd.exe /c 调 pnpm,因为 pnpm 是 .cmd 不是 .exe # 直接 Start-Process 'pnpm' 会报 "%1 不是有效的 Win32 应用程序" try { $proc = Start-Process -FilePath 'cmd.exe' ` -ArgumentList '/c', 'pnpm', 'dev' ` -WorkingDirectory $dir ` -WindowStyle Hidden ` -RedirectStandardOutput $log ` -RedirectStandardError "$log.err" ` -PassThru ` -ErrorAction Stop Write-OK "已起进程 PID=$($proc.Id),日志: $log" } catch { Write-Err "启动失败: $_" continue } # 等 3-5 秒,看端口起来没 $up = $false for ($i = 1; $i -le 8; $i++) { Start-Sleep -Seconds 1 if (Test-PortListening $port) { $up = $true break } } if ($up) { Write-OK "$label 端口 $port 已监听" $Started += $f } else { Write-Warn "$label 8 秒内未监听,看日志: $log" Write-Host " 常见原因: 依赖缺/Node 版本/端口冲突/语法错" -ForegroundColor Yellow } } # -------------------------------------------------------------------------- # 汇总表格 # -------------------------------------------------------------------------- Write-Step "═══ 启动汇总 ═══" if ($Started.Count -eq 0) { Write-Warn "没前端成功起来" } else { # 算每行最大宽度 $maxLabel = ($Started | ForEach-Object { $_.Label.Length } | Measure-Object -Maximum).Maximum $maxLabel = [Math]::Max($maxLabel, 5) Write-Host (" {0,-$maxLabel} {1,-8} {2}" -f 'NAME', 'PORT', 'URL') -ForegroundColor White Write-Host (" {0,-$maxLabel} {1,-8} {2}" -f ($maxLabel | ForEach-Object { '-' * $_ }), '----', '---') -ForegroundColor DarkGray foreach ($f in $Started) { $url = "http://localhost:$($f.Port)" Write-Host (" {0,-$maxLabel} {1,-8} {2}" -f $f.Label, $f.Port, $url) -ForegroundColor Green } Write-Host "`n 日志位置:" -ForegroundColor Cyan foreach ($f in $Started) { $log = Join-Path $ProjectRoot ".pnpm-dev-$($f.Label).log" Write-Host " $log" -ForegroundColor Gray } Write-Host "`n 实时跟日志(另开窗口跑):" -ForegroundColor Cyan Write-Host " Get-Content .pnpm-dev-h5.log -Wait" -ForegroundColor Gray Write-Host " Get-Content .pnpm-dev-agent.log -Wait" -ForegroundColor Gray Write-Host "`n 停掉所有前端:" -ForegroundColor Cyan Write-Host " .\scripts\dev-frontend-start.ps1 -Stop" -ForegroundColor Gray } Write-Host "`n下一步:浏览器开上面的 URL 就能用了" -ForegroundColor Cyan