重构 monorepo 并完善网页端订阅与首页体验
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# Dark-theme empty state PNG — true alpha, colors visible on #363d45 panels.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$out = Join-Path $PSScriptRoot '..\frontend-web\assets\images\web\empty-state.png'
|
||||
$size = 320
|
||||
$pf = [System.Drawing.Imaging.PixelFormat]::Format32bppArgb
|
||||
$bmp = New-Object System.Drawing.Bitmap($size, $size, $pf)
|
||||
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
||||
$g.Clear([System.Drawing.Color]::FromArgb(0, 0, 0, 0))
|
||||
|
||||
function C($hex, $a = 255) {
|
||||
$h = $hex.TrimStart('#')
|
||||
return [System.Drawing.Color]::FromArgb($a, [Convert]::ToInt32($h.Substring(0,2),16), [Convert]::ToInt32($h.Substring(2,2),16), [Convert]::ToInt32($h.Substring(4,2),16))
|
||||
}
|
||||
|
||||
# Lighter tones for visibility on dark UI (--soon-text / --soon-border range)
|
||||
$cFace = C '#9aa3ad'
|
||||
$cFaceD = C '#7a8794'
|
||||
$cFaceDD = C '#6b7580'
|
||||
$cStroke = C '#b8c0c8'
|
||||
$cAccent = C '#009688'
|
||||
$cAccentH= C '#26a69a'
|
||||
$cShadow = C '#000000' 55
|
||||
|
||||
function Pt($x, $y) { New-Object System.Drawing.PointF $x, $y }
|
||||
|
||||
function FillPoly($pts, $fill, $stroke, $sw = 1.4) {
|
||||
$path = New-Object System.Drawing.Drawing2D.GraphicsPath
|
||||
$path.AddPolygon($pts)
|
||||
$g.FillPath((New-Object System.Drawing.SolidBrush $fill), $path)
|
||||
if ($stroke) {
|
||||
$pen = New-Object System.Drawing.Pen $stroke, $sw
|
||||
$pen.LineJoin = [System.Drawing.Drawing2D.LineJoin]::Round
|
||||
$g.DrawPath($pen, $path)
|
||||
$pen.Dispose()
|
||||
}
|
||||
$path.Dispose()
|
||||
}
|
||||
|
||||
# Ground shadow
|
||||
$sh = New-Object System.Drawing.Drawing2D.GraphicsPath
|
||||
$sh.AddEllipse(68, 252, 184, 26)
|
||||
$g.FillPath((New-Object System.Drawing.SolidBrush $cShadow), $sh)
|
||||
$sh.Dispose()
|
||||
|
||||
# Isometric open inbox
|
||||
FillPoly @((Pt 86 130),(Pt 160 88),(Pt 234 130),(Pt 160 172)) $cFaceD $cStroke
|
||||
FillPoly @((Pt 86 130),(Pt 160 172),(Pt 160 232),(Pt 86 192)) $cFace $cStroke
|
||||
FillPoly @((Pt 160 172),(Pt 234 130),(Pt 234 192),(Pt 160 232)) $cFaceDD $cStroke
|
||||
FillPoly @((Pt 86 192),(Pt 160 232),(Pt 234 192),(Pt 160 152)) (C '#5c6770') $cStroke
|
||||
|
||||
# Open flaps (lighter)
|
||||
FillPoly @((Pt 86 130),(Pt 52 148),(Pt 86 192),(Pt 120 176)) (C '#8a939c') $cStroke 1.1
|
||||
FillPoly @((Pt 234 130),(Pt 268 148),(Pt 234 192),(Pt 200 176)) (C '#8a939c') $cStroke 1.1
|
||||
FillPoly @((Pt 160 88),(Pt 124 70),(Pt 196 70)) (C '#8a939c') $cStroke 1.1
|
||||
|
||||
# Accent front edge
|
||||
$lip = New-Object System.Drawing.Drawing2D.GraphicsPath
|
||||
$lip.AddLines(@((Pt 86 192),(Pt 160 232),(Pt 234 192)))
|
||||
$pa = New-Object System.Drawing.Pen $cAccent, 3
|
||||
$pa.StartCap = [System.Drawing.Drawing2D.LineCap]::Round
|
||||
$pa.EndCap = [System.Drawing.Drawing2D.LineCap]::Round
|
||||
$g.DrawPath($pa, $lip)
|
||||
$pa.Dispose(); $lip.Dispose()
|
||||
|
||||
# Inner dashed lines (empty hint)
|
||||
$pd = New-Object System.Drawing.Pen (C '#b8c0c8' 100), 1.6
|
||||
$pd.DashStyle = [System.Drawing.Drawing2D.DashStyle]::Dash
|
||||
$g.DrawLine($pd, 114, 166, 206, 166)
|
||||
$g.DrawLine($pd, 124, 182, 196, 182)
|
||||
$pd.Dispose()
|
||||
|
||||
# Accent center mark
|
||||
$g.FillEllipse((New-Object System.Drawing.SolidBrush $cAccentH), 152, 200, 16, 16)
|
||||
$g.DrawEllipse((New-Object System.Drawing.Pen (C '#ffffff' 80), 1.2), 152, 200, 16, 16)
|
||||
|
||||
# Floating doc sheet
|
||||
FillPoly @((Pt 208 104),(Pt 254 82),(Pt 258 114),(Pt 212 136)) (C '#7a8794') $cStroke 1.1
|
||||
$pl = New-Object System.Drawing.Pen (C '#b8c0c8' 140), 1.2
|
||||
$g.DrawLine($pl, 216, 108, 246, 92)
|
||||
$g.DrawLine($pl, 216, 116, 240, 102)
|
||||
$g.DrawLine($pl, 216, 124, 234, 112)
|
||||
$pl.Dispose()
|
||||
|
||||
$g.Dispose()
|
||||
$dir = Split-Path $out -Parent
|
||||
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
$bmp.Save($out, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$bmp.Dispose()
|
||||
Write-Host "OK $((Get-Item $out).FullName) $((Get-Item $out).Length) bytes"
|
||||
@@ -0,0 +1,192 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
rem Double-click (cmd /c): re-launch under cmd /k so the window stays open
|
||||
if /i not "%~1"=="__run__" (
|
||||
echo %CMDCMDLINE% | find /i " /c " >nul
|
||||
if not errorlevel 1 (
|
||||
cmd /k "%~f0" __run__ %*
|
||||
exit /b
|
||||
)
|
||||
)
|
||||
shift
|
||||
|
||||
set "ROOT=%~dp0.."
|
||||
cd /d "%ROOT%" || goto :fail_cd
|
||||
|
||||
set "COMPOSE=docker\docker-compose.yml"
|
||||
set "ENV_FILE=docker\.env"
|
||||
set "COMPOSE_PROJECT=soondesign"
|
||||
set "ACTION=%~1"
|
||||
set "WEB_PORT=8100"
|
||||
set "ELECTRON_PORT=8101"
|
||||
set "BACKEND_PORT=8102"
|
||||
|
||||
if /i "%ACTION%"=="stop" goto :do_stop
|
||||
if /i "%ACTION%"=="reset" goto :do_reset
|
||||
goto :do_start
|
||||
|
||||
:compose
|
||||
docker compose -p "%COMPOSE_PROJECT%" -f "%COMPOSE%" --env-file "%ENV_FILE%" %*
|
||||
exit /b !ERRORLEVEL!
|
||||
|
||||
:compose_noenv
|
||||
docker compose -p "%COMPOSE_PROJECT%" -f "%COMPOSE%" %*
|
||||
exit /b !ERRORLEVEL!
|
||||
|
||||
:do_stop
|
||||
if exist "%ENV_FILE%" (call :compose down) else (call :compose_noenv down)
|
||||
echo.
|
||||
echo [OK] Stopped project %COMPOSE_PROJECT%. DB volume kept.
|
||||
exit /b 0
|
||||
|
||||
:do_reset
|
||||
if exist "%ENV_FILE%" (call :compose down -v) else (call :compose_noenv down -v)
|
||||
echo.
|
||||
echo [OK] Project %COMPOSE_PROJECT% stopped. DB volume removed.
|
||||
exit /b 0
|
||||
|
||||
:do_start
|
||||
echo [SoonDesign] Starting local Docker environment...
|
||||
echo [INFO] Compose project: %COMPOSE_PROJECT%
|
||||
echo.
|
||||
|
||||
docker info >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Docker is not running. Please start Docker Desktop first.
|
||||
goto :fail
|
||||
)
|
||||
|
||||
if not exist "soonModels\manifest.json" (
|
||||
echo [ERROR] Missing soonModels\manifest.json
|
||||
echo Project root: %CD%
|
||||
goto :fail
|
||||
)
|
||||
|
||||
if not exist "docker\docker-compose.yml" (
|
||||
echo [ERROR] Missing docker\docker-compose.yml
|
||||
goto :fail
|
||||
)
|
||||
|
||||
if not exist "%ENV_FILE%" (
|
||||
if not exist "docker\.env.example" (
|
||||
echo [ERROR] Missing docker\.env.example
|
||||
goto :fail
|
||||
)
|
||||
copy /Y "docker\.env.example" "%ENV_FILE%" >nul
|
||||
)
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "docker\sync-config.ps1" -EnvFile "%ENV_FILE%"
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Config sync failed.
|
||||
goto :fail
|
||||
)
|
||||
|
||||
for /f "usebackq eol=# tokens=1,* delims==" %%A in ("%ENV_FILE%") do (
|
||||
if /i "%%A"=="WEB_PORT" set "WEB_PORT=%%B"
|
||||
if /i "%%A"=="ELECTRON_PORT" set "ELECTRON_PORT=%%B"
|
||||
if /i "%%A"=="BACKEND_PORT" set "BACKEND_PORT=%%B"
|
||||
)
|
||||
|
||||
echo [INFO] Removing legacy compose project "docker" if present...
|
||||
docker compose -p docker -f "%COMPOSE%" --env-file "%ENV_FILE%" down >nul 2>&1
|
||||
docker network rm docker_default >nul 2>&1
|
||||
call :compose down >nul 2>&1
|
||||
|
||||
echo [INFO] Ports: Web=%WEB_PORT% Electron=%ELECTRON_PORT% API=%BACKEND_PORT%
|
||||
echo [INFO] Starting containers - project %COMPOSE_PROJECT%...
|
||||
call :compose up -d
|
||||
set "DC_EXIT=!ERRORLEVEL!"
|
||||
if not "!DC_EXIT!"=="0" (
|
||||
echo.
|
||||
echo [ERROR] docker compose failed with exit code !DC_EXIT!.
|
||||
echo Ensure Docker Desktop is running.
|
||||
echo First API start may install pdo_mysql for about 1 minute.
|
||||
goto :fail
|
||||
)
|
||||
|
||||
echo [INFO] Waiting for API - first run may take up to 2 min for pdo_mysql install...
|
||||
set /a RETRY=0
|
||||
:wait_health
|
||||
set /a RETRY+=1
|
||||
curl.exe -sf "http://localhost:%BACKEND_PORT%/health" >nul 2>&1
|
||||
if not errorlevel 1 goto :ready
|
||||
if !RETRY! GEQ 60 goto :show_urls
|
||||
ping -n 3 127.0.0.1 >nul
|
||||
goto :wait_health
|
||||
|
||||
:ready
|
||||
echo [OK] Backend is ready.
|
||||
|
||||
:show_urls
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Project: %COMPOSE_PROJECT%
|
||||
echo Web: http://localhost:%WEB_PORT%/pages/index.web.html
|
||||
echo Admin: http://localhost:%WEB_PORT%/pages/admin/index.html
|
||||
echo Electron: http://localhost:%ELECTRON_PORT%/pages/index.html
|
||||
echo API: http://localhost:%BACKEND_PORT%/health
|
||||
echo ========================================
|
||||
echo Login: admin@local.test / admin123
|
||||
echo Stop: scripts\start.bat stop
|
||||
echo.
|
||||
|
||||
curl.exe -sf "http://localhost:%BACKEND_PORT%/health" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [WARN] API not ready yet. Check: docker logs soondesign-api
|
||||
goto :done_ok
|
||||
)
|
||||
|
||||
curl.exe -sf "http://localhost:%BACKEND_PORT%/api/v1/settings" >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [WARN] API routes not responding. Check: docker logs soondesign-api
|
||||
) else (
|
||||
echo [OK] API routes verified.
|
||||
)
|
||||
|
||||
start "" "http://localhost:%WEB_PORT%/pages/index.web.html"
|
||||
start "" "http://localhost:%WEB_PORT%/pages/admin/index.html"
|
||||
call :launch_electron
|
||||
goto :done_ok
|
||||
|
||||
:launch_electron
|
||||
where node >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [WARN] Node.js not found. Open Electron preview in browser instead.
|
||||
start "" "http://localhost:%ELECTRON_PORT%/pages/index.html"
|
||||
exit /b 0
|
||||
)
|
||||
if not exist "frontend-electron\package.json" (
|
||||
echo [WARN] frontend-electron not found. Opening browser preview.
|
||||
start "" "http://localhost:%ELECTRON_PORT%/pages/index.html"
|
||||
exit /b 0
|
||||
)
|
||||
if not exist "frontend-electron\node_modules\electron" (
|
||||
echo [INFO] Installing Electron dependencies - first run only...
|
||||
pushd frontend-electron
|
||||
call npm install --no-audit --no-fund
|
||||
if errorlevel 1 (
|
||||
popd
|
||||
echo [WARN] npm install failed. Opening browser preview.
|
||||
start "" "http://localhost:%ELECTRON_PORT%/pages/index.html"
|
||||
exit /b 0
|
||||
)
|
||||
popd
|
||||
)
|
||||
echo [INFO] Launching Electron desktop app...
|
||||
start "SoonDesign Desktop" /D "%CD%\frontend-electron" cmd /c "npm start"
|
||||
exit /b 0
|
||||
|
||||
:fail_cd
|
||||
echo [ERROR] Cannot cd to project root: %ROOT%
|
||||
goto :fail
|
||||
|
||||
:fail
|
||||
echo.
|
||||
echo [FAILED] See messages above.
|
||||
exit /b 1
|
||||
|
||||
:done_ok
|
||||
echo.
|
||||
echo [DONE] SoonDesign is running under Docker project %COMPOSE_PROJECT%.
|
||||
exit /b 0
|
||||
Reference in New Issue
Block a user