feat(portal-shell): v2.0 P0 shadcn standardization + security + streaming + error handling

- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等
- Tailwind v4 + @theme inline,移除 tailwind.config.js
- React 19 use() + Suspense 流式渲染,首屏骨架秒出
- 三级错误边界:Route → Section → Widget 层层兜底
- 错误上报:useErrorReport → sendBeacon → /api/log mock 端点
- 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围
- 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99%
- notify 统一 Toast 封装,禁止业务直接 import sonner
- PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套)

验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
This commit is contained in:
SpecialX
2026-07-17 16:10:05 +08:00
parent f7e52b5b7f
commit 9cedf0c437
140 changed files with 10872 additions and 3192 deletions

54
scripts/dev-service.ps1 Normal file
View File

@@ -0,0 +1,54 @@
<#
.SYNOPSIS
加载 .env 并启动单个服务(内部辅助脚本)
.DESCRIPTION
从项目根 .env 加载环境变量到当前进程,然后启动指定服务。
用法powershell -File scripts\dev-service.ps1 <package-name>
例: powershell -File scripts\dev-service.ps1 @edu/iam-service
#>
param(
[Parameter(Mandatory=$true)]
[string]$Filter
)
$ProjectRoot = Split-Path -Parent $PSScriptRoot
$envFile = Join-Path $ProjectRoot ".env"
if (Test-Path $envFile) {
$envLines = Get-Content $envFile
foreach ($line in $envLines) {
$line = $line.Trim()
if (-not $line -or $line.StartsWith("#") -or -not $line.Contains("=")) { continue }
$idx = $line.IndexOf("=")
$key = $line.Substring(0, $idx).Trim()
$val = $line.Substring($idx + 1).Trim()
if ($val.StartsWith('"') -and $val.EndsWith('"')) { $val = $val.Substring(1, $val.Length - 2) }
if ($val.StartsWith("'") -and $val.EndsWith("'")) { $val = $val.Substring(1, $val.Length - 2) }
[Environment]::SetEnvironmentVariable($key, $val, "Process")
}
Write-Host "[dev-service] Loaded .env" -ForegroundColor Green
}
# 补充 dev 模式默认值
if (-not $env:DEV_MODE) { $env:DEV_MODE = "true" }
if (-not $env:OTEL_EXPORTER_OTLP_ENDPOINT) { $env:OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318" }
if (-not $env:NEO4J_URI) { $env:NEO4J_URI = "bolt://localhost:7687" }
if (-not $env:NEO4J_USER) { $env:NEO4J_USER = "neo4j" }
if (-not $env:CLICKHOUSE_URL) { $env:CLICKHOUSE_URL = "http://localhost:8123" }
if (-not $env:ES_URL) { $env:ES_URL = "http://localhost:9200" }
if (-not $env:ROUTER_AUTH_SECRET) { $env:ROUTER_AUTH_SECRET = "dev-router-secret" }
if (-not $env:CLASSES_SERVICE_URL) { $env:CLASSES_SERVICE_URL = "http://localhost:3001" }
if (-not $env:IAM_SERVICE_URL) { $env:IAM_SERVICE_URL = "http://localhost:3002" }
if (-not $env:APOLLO_ROUTER_URL) { $env:APOLLO_ROUTER_URL = "http://localhost:3000" }
if (-not $env:CORE_EDU_SERVICE_URL) { $env:CORE_EDU_SERVICE_URL = "http://localhost:3004" }
if (-not $env:CONTENT_SERVICE_URL) { $env:CONTENT_SERVICE_URL = "http://localhost:3005" }
if (-not $env:DATA_ANA_SERVICE_URL) { $env:DATA_ANA_SERVICE_URL = "http://localhost:3006" }
if (-not $env:MSG_SERVICE_URL) { $env:MSG_SERVICE_URL = "http://localhost:3007" }
if (-not $env:AI_SERVICE_URL) { $env:AI_SERVICE_URL = "http://localhost:3008" }
if (-not $env:CORS_ORIGINS) { $env:CORS_ORIGINS = "http://localhost:4000,http://localhost:4010" }
if (-not $env:JWKS_URL) { $env:JWKS_URL = "http://localhost:3002/v1/iam/.well-known/jwks.json" }
if (-not $env:PUSH_INTERNAL_TOKEN) { $env:PUSH_INTERNAL_TOKEN = "edu-internal-token" }
Write-Host "[dev-service] Starting $Filter with DEV_MODE=$($env:DEV_MODE)" -ForegroundColor Cyan
Set-Location $ProjectRoot
pnpm --filter $Filter dev