更新
This commit is contained in:
@@ -17,9 +17,12 @@
|
|||||||
|
|
||||||
```bat
|
```bat
|
||||||
docker\sync-config.ps1 :: 同步 local.js 与 backend local.php(改端口后必跑)
|
docker\sync-config.ps1 :: 同步 local.js 与 backend local.php(改端口后必跑)
|
||||||
scripts\start.bat :: 启动
|
scripts\start.bat :: 启动(不销毁已有容器,适合日常开发)
|
||||||
scripts\start.bat stop :: 停止(保留数据库)
|
scripts\start.bat stop :: 停止(保留数据库)
|
||||||
scripts\start.bat reset :: 停止并清空数据库卷
|
scripts\start.bat reset :: 停止并清空数据库卷
|
||||||
|
scripts\start.bat rebuild :: 重建 API 镜像并重启 API 容器
|
||||||
|
scripts\start.bat status :: 查看容器与 /health
|
||||||
|
scripts\start.bat logs :: 跟踪 API 日志
|
||||||
```
|
```
|
||||||
|
|
||||||
生产环境编辑 `frontend-web/config/local.js`(从 `config/local.js.example` 复制);全量更新 frontend 时不要覆盖该文件。本地开发由 `docker/sync-config.ps1` 生成 `local.js`。
|
生产环境编辑 `frontend-web/config/local.js`(从 `config/local.js.example` 复制);全量更新 frontend 时不要覆盖该文件。本地开发由 `docker/sync-config.ps1` 生成 `local.js`。
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ services:
|
|||||||
start_period: 30s
|
start_period: 30s
|
||||||
|
|
||||||
api:
|
api:
|
||||||
image: php:8.2-cli-bookworm
|
build:
|
||||||
|
context: ./php
|
||||||
|
dockerfile: Dockerfile.dev
|
||||||
|
image: soondesign-api:dev
|
||||||
container_name: soondesign-api
|
container_name: soondesign-api
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
FROM php:8.2-cli-bookworm
|
||||||
|
|
||||||
|
RUN apt-get update -qq \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
|
||||||
|
libmariadb-dev-compat $PHPIZE_DEPS \
|
||||||
|
&& docker-php-ext-install -j"$(nproc)" pdo_mysql \
|
||||||
|
&& apt-get purge -y --auto-remove $PHPIZE_DEPS \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -1,22 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if ! php -m 2>/dev/null | grep -q pdo_mysql; then
|
|
||||||
echo "[api] installing pdo_mysql..."
|
|
||||||
apt-get update -qq
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
|
|
||||||
libmariadb-dev-compat $PHPIZE_DEPS
|
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_mysql
|
|
||||||
apt-get purge -y --auto-remove $PHPIZE_DEPS
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
fi
|
|
||||||
|
|
||||||
host="${SOON_DB_HOST:-mysql}"
|
host="${SOON_DB_HOST:-mysql}"
|
||||||
port="${SOON_DB_PORT:-3306}"
|
port="${SOON_DB_PORT:-3306}"
|
||||||
user="${SOON_DB_USER:-soondesign}"
|
user="${SOON_DB_USER:-soondesign}"
|
||||||
pass="${SOON_DB_PASS:-soondesign}"
|
pass="${SOON_DB_PASS:-soondesign}"
|
||||||
db="${SOON_DB_NAME:-soondesign}"
|
db="${SOON_DB_NAME:-soondesign}"
|
||||||
|
|
||||||
|
echo "[api] waiting for mysql..."
|
||||||
i=0
|
i=0
|
||||||
while [ "$i" -lt 60 ]; do
|
while [ "$i" -lt 60 ]; do
|
||||||
if php -r "
|
if php -r "
|
||||||
@@ -41,4 +32,5 @@ done
|
|||||||
php /docker/php/migrate-dev-schema.php || true
|
php /docker/php/migrate-dev-schema.php || true
|
||||||
php /docker/php/seed-dev-admin.php || true
|
php /docker/php/seed-dev-admin.php || true
|
||||||
|
|
||||||
|
echo "[api] listening on :8080"
|
||||||
exec php -S 0.0.0.0:8080 -t /var/www/public /docker/php/router-dev.php
|
exec php -S 0.0.0.0:8080 -t /var/www/public /docker/php/router-dev.php
|
||||||
|
|||||||
@@ -409,6 +409,7 @@ function addBackground() {
|
|||||||
|
|
||||||
function finishBack(backImg) {
|
function finishBack(backImg) {
|
||||||
if (backImg) {
|
if (backImg) {
|
||||||
|
backImg.objectCaching = true;
|
||||||
soonPlaceBgOnCanvas(backImg, '#canvas2');
|
soonPlaceBgOnCanvas(backImg, '#canvas2');
|
||||||
background_image2 = backImg;
|
background_image2 = backImg;
|
||||||
window.background_image2 = backImg;
|
window.background_image2 = backImg;
|
||||||
@@ -426,6 +427,7 @@ function addBackground() {
|
|||||||
if (!loadBg) return;
|
if (!loadBg) return;
|
||||||
loadBg('bg_front_1.png', function (frontImg) {
|
loadBg('bg_front_1.png', function (frontImg) {
|
||||||
if (!frontImg) return;
|
if (!frontImg) return;
|
||||||
|
frontImg.objectCaching = true;
|
||||||
frontImg.scale(1, 1);
|
frontImg.scale(1, 1);
|
||||||
soonPlaceBgOnCanvas(frontImg, '#canvas1');
|
soonPlaceBgOnCanvas(frontImg, '#canvas1');
|
||||||
background_image1 = frontImg;
|
background_image1 = frontImg;
|
||||||
@@ -436,10 +438,10 @@ function addBackground() {
|
|||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
recordObjs1.push(JSON.stringify(objs1));
|
recordObjs1.push(JSON.stringify(objs1));
|
||||||
recordJson1.push(canvas1.toJSON(TO_JSON_PROPERTIES));
|
recordJson1.push(canvas1.toJSON(TO_JSON_PROPERTIES));
|
||||||
loadBg('bg_back.png', finishBack, function () { loadBg('bg_back.png', finishBack); });
|
loadBg('bg_back.png', finishBack, function () { loadBg('bg_back.png', finishBack, null, 2, true); }, 2, true);
|
||||||
}, function () {
|
}, function () {
|
||||||
setTimeout(startLoad, 200);
|
setTimeout(startLoad, 200);
|
||||||
});
|
}, 2, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
whenReady('#canvas1', startLoad);
|
whenReady('#canvas1', startLoad);
|
||||||
@@ -1515,7 +1517,20 @@ function handleObjectMoving(e) {
|
|||||||
obj.set("angle", angleDeg);
|
obj.set("angle", angleDeg);
|
||||||
obj.set("flipped", flipped);
|
obj.set("flipped", flipped);
|
||||||
}
|
}
|
||||||
updateControls();
|
soonScheduleUpdateControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
var _soonUpdateControlsRaf = 0;
|
||||||
|
function soonScheduleUpdateControls() {
|
||||||
|
if (_soonUpdateControlsRaf) return;
|
||||||
|
_soonUpdateControlsRaf = requestAnimationFrame(function () {
|
||||||
|
_soonUpdateControlsRaf = 0;
|
||||||
|
window.updateControls();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function soonDesign1SetInteracting(on) {
|
||||||
|
window._soonDesign1Interacting = !!on;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:检查对象是否被锁定
|
// 辅助函数:检查对象是否被锁定
|
||||||
@@ -1623,17 +1638,28 @@ function initCanvasEvents() {
|
|||||||
_canvasEventsInitialized = true;
|
_canvasEventsInitialized = true;
|
||||||
|
|
||||||
[canvas1, canvas2].forEach(currentCanvas => {
|
[canvas1, canvas2].forEach(currentCanvas => {
|
||||||
|
var _mouseMoveRaf = 0;
|
||||||
|
var _mouseMovePending = null;
|
||||||
// 1. 鼠标移动
|
// 1. 鼠标移动
|
||||||
currentCanvas.on("mouse:move", (e) => {
|
currentCanvas.on("mouse:move", (e) => {
|
||||||
let offsetX = 0, offsetY = 0;
|
_mouseMovePending = { e: e, canvas: currentCanvas };
|
||||||
if (currentCanvas === canvas2 && typeof background_image !== 'undefined') {
|
if (_mouseMoveRaf) return;
|
||||||
offsetX = background_image.left;
|
_mouseMoveRaf = requestAnimationFrame(function () {
|
||||||
offsetY = background_image.top;
|
_mouseMoveRaf = 0;
|
||||||
}
|
var payload = _mouseMovePending;
|
||||||
if (e.pointer) {
|
if (!payload) return;
|
||||||
$("#posText").text("X: " + (e.pointer.x - offsetX).toFixed(0) + " Y: " + (e.pointer.y - offsetY).toFixed(0));
|
var moveEvent = payload.e;
|
||||||
}
|
var moveCanvas = payload.canvas;
|
||||||
updateRulerGuides(e.e);
|
let offsetX = 0, offsetY = 0;
|
||||||
|
if (moveCanvas === canvas2 && typeof background_image !== 'undefined') {
|
||||||
|
offsetX = background_image.left;
|
||||||
|
offsetY = background_image.top;
|
||||||
|
}
|
||||||
|
if (moveEvent.pointer) {
|
||||||
|
$("#posText").text("X: " + (moveEvent.pointer.x - offsetX).toFixed(0) + " Y: " + (moveEvent.pointer.y - offsetY).toFixed(0));
|
||||||
|
}
|
||||||
|
updateRulerGuides(moveEvent.e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. 鼠标移出
|
// 2. 鼠标移出
|
||||||
@@ -1726,12 +1752,19 @@ function initCanvasEvents() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
soonDesign1SetInteracting(true);
|
||||||
handleObjectMoving(e);
|
handleObjectMoving(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6. 变换事件
|
// 6. 变换事件
|
||||||
currentCanvas.on("object:rotating", updateControls);
|
currentCanvas.on("object:rotating", function () {
|
||||||
currentCanvas.on("object:scaling", updateControls);
|
soonDesign1SetInteracting(true);
|
||||||
|
soonScheduleUpdateControls();
|
||||||
|
});
|
||||||
|
currentCanvas.on("object:scaling", function () {
|
||||||
|
soonDesign1SetInteracting(true);
|
||||||
|
soonScheduleUpdateControls();
|
||||||
|
});
|
||||||
currentCanvas.on("object:rotated", () => { window.recordState(); });
|
currentCanvas.on("object:rotated", () => { window.recordState(); });
|
||||||
currentCanvas.on('text:editing:entered', () => { _textEdit = true; });
|
currentCanvas.on('text:editing:entered', () => { _textEdit = true; });
|
||||||
currentCanvas.on('text:editing:exited', () => { _textEdit = false; });
|
currentCanvas.on('text:editing:exited', () => { _textEdit = false; });
|
||||||
@@ -1739,38 +1772,41 @@ function initCanvasEvents() {
|
|||||||
|
|
||||||
// 7. 对象修改后
|
// 7. 对象修改后
|
||||||
currentCanvas.on('object:modified', function (event) {
|
currentCanvas.on('object:modified', function (event) {
|
||||||
if (currentCanvas.getActiveObjects().length > 1) return;
|
try {
|
||||||
let idx = getIndex(event.target);
|
if (currentCanvas.getActiveObjects().length > 1) return;
|
||||||
if (idx <= 0) return;
|
let idx = getIndex(event.target);
|
||||||
let objData = objs[idx - 1];
|
if (idx <= 0) return;
|
||||||
// 安全检查:确保 objData 存在且有 type 属性
|
let objData = objs[idx - 1];
|
||||||
if (!objData || typeof objData.type === 'undefined') {
|
// 安全检查:确保 objData 存在且有 type 属性
|
||||||
|
if (!objData || typeof objData.type === 'undefined') {
|
||||||
|
// 更新对象列表以同步锁状态
|
||||||
|
if (typeof window.updateList === 'function') {
|
||||||
|
window.updateList();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let target = event.target;
|
||||||
|
|
||||||
|
if ([3, 4, 10].includes(objData.type)) {
|
||||||
|
target.fontSize = (target.fontSize * target.scaleX).toFixed(0);
|
||||||
|
target.scaleX = 1; target.scaleY = 1;
|
||||||
|
target._clearCache();
|
||||||
|
if (objData.type == 3) $("#text_size").val(target.fontSize);
|
||||||
|
if (objData.type == 4) $("#out_text_size").val(target.fontSize);
|
||||||
|
if (objData.type == 10) $("#count_size").val(target.fontSize);
|
||||||
|
currentCanvas.renderAll();
|
||||||
|
} else if (objData.type == 5) {
|
||||||
|
target.width *= target.scaleX; target.height *= target.scaleY;
|
||||||
|
target.scaleX = 1; target.scaleY = 1;
|
||||||
|
}
|
||||||
|
window.recordState();
|
||||||
// 更新对象列表以同步锁状态
|
// 更新对象列表以同步锁状态
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.updateList === 'function') {
|
||||||
window.updateList();
|
window.updateList();
|
||||||
}
|
}
|
||||||
return;
|
} finally {
|
||||||
}
|
soonDesign1SetInteracting(false);
|
||||||
let target = event.target;
|
|
||||||
|
|
||||||
if ([3, 4, 10].includes(objData.type)) {
|
|
||||||
target.fontSize = (target.fontSize * target.scaleX).toFixed(0);
|
|
||||||
target.scaleX = 1; target.scaleY = 1;
|
|
||||||
target._clearCache();
|
|
||||||
if (objData.type == 3) $("#text_size").val(target.fontSize);
|
|
||||||
if (objData.type == 4) $("#out_text_size").val(target.fontSize);
|
|
||||||
if (objData.type == 10) $("#count_size").val(target.fontSize);
|
|
||||||
currentCanvas.renderAll();
|
|
||||||
updateControls();
|
updateControls();
|
||||||
} else if (objData.type == 5) {
|
|
||||||
target.width *= target.scaleX; target.height *= target.scaleY;
|
|
||||||
target.scaleX = 1; target.scaleY = 1;
|
|
||||||
updateControls();
|
|
||||||
}
|
|
||||||
window.recordState();
|
|
||||||
// 更新对象列表以同步锁状态
|
|
||||||
if (typeof window.updateList === 'function') {
|
|
||||||
window.updateList();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// ==================================================
|
// ==================================================
|
||||||
@@ -1829,6 +1865,8 @@ function initCanvasEvents() {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window._soonDesign1Interacting) return;
|
||||||
|
|
||||||
// 在遮罩之后重新绘制辅助线,确保辅助线在最上层
|
// 在遮罩之后重新绘制辅助线,确保辅助线在最上层
|
||||||
const objects = currentCanvas.getObjects();
|
const objects = currentCanvas.getObjects();
|
||||||
objects.forEach(obj => {
|
objects.forEach(obj => {
|
||||||
@@ -2042,6 +2080,7 @@ function initRulers() {
|
|||||||
|
|
||||||
// --- 3. 绘制标尺 (核心绘制循环) ---
|
// --- 3. 绘制标尺 (核心绘制循环) ---
|
||||||
function updateAndDraw() {
|
function updateAndDraw() {
|
||||||
|
if (window._soonDesign1Interacting) return;
|
||||||
// 每次重新获取尺寸,确保使用最新值
|
// 每次重新获取尺寸,确保使用最新值
|
||||||
// 使用多种方法尝试获取实际尺寸,确保准确
|
// 使用多种方法尝试获取实际尺寸,确保准确
|
||||||
let currentCanvasWidth = canvasWrapper.width() || canvasWrapper.outerWidth() || canvasWrapper[0].getBoundingClientRect().width || 800;
|
let currentCanvasWidth = canvasWrapper.width() || canvasWrapper.outerWidth() || canvasWrapper[0].getBoundingClientRect().width || 800;
|
||||||
|
|||||||
+111
-107
@@ -1,29 +1,34 @@
|
|||||||
@echo off
|
@echo off
|
||||||
setlocal EnableDelayedExpansion
|
setlocal EnableDelayedExpansion
|
||||||
|
|
||||||
rem Double-click (cmd /c): re-launch under cmd /k so the window stays open
|
set "SELF=%~f0"
|
||||||
if /i not "%~1"=="__run__" (
|
if /i "%~1"=="__run__" (
|
||||||
|
set "ACTION=%~2"
|
||||||
|
) else (
|
||||||
|
set "ACTION=%~1"
|
||||||
echo %CMDCMDLINE% | find /i " /c " >nul
|
echo %CMDCMDLINE% | find /i " /c " >nul
|
||||||
if not errorlevel 1 (
|
if not errorlevel 1 (
|
||||||
cmd /k "%~f0" __run__ %*
|
cmd /k "%SELF%" __run__ %*
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
shift
|
|
||||||
|
|
||||||
set "ROOT=%~dp0.."
|
set "ROOT=%~dp0.."
|
||||||
|
for %%I in ("%ROOT%") do set "ROOT=%%~fI"
|
||||||
cd /d "%ROOT%" || goto :fail_cd
|
cd /d "%ROOT%" || goto :fail_cd
|
||||||
|
|
||||||
set "COMPOSE=docker\docker-compose.yml"
|
set "COMPOSE=docker\docker-compose.yml"
|
||||||
set "ENV_FILE=docker\.env"
|
set "ENV_FILE=docker\.env"
|
||||||
set "COMPOSE_PROJECT=soondesign"
|
set "COMPOSE_PROJECT=soondesign"
|
||||||
set "ACTION=%~1"
|
|
||||||
set "WEB_PORT=8100"
|
set "WEB_PORT=8100"
|
||||||
set "ELECTRON_PORT=8101"
|
set "ELECTRON_PORT=8101"
|
||||||
set "BACKEND_PORT=8102"
|
set "BACKEND_PORT=8102"
|
||||||
|
|
||||||
if /i "%ACTION%"=="stop" goto :do_stop
|
if /i "%ACTION%"=="stop" goto :do_stop
|
||||||
if /i "%ACTION%"=="reset" goto :do_reset
|
if /i "%ACTION%"=="reset" goto :do_reset
|
||||||
|
if /i "%ACTION%"=="rebuild" goto :do_rebuild
|
||||||
|
if /i "%ACTION%"=="logs" goto :do_logs
|
||||||
|
if /i "%ACTION%"=="status" goto :do_status
|
||||||
goto :do_start
|
goto :do_start
|
||||||
|
|
||||||
:compose
|
:compose
|
||||||
@@ -34,6 +39,66 @@ exit /b !ERRORLEVEL!
|
|||||||
docker compose -p "%COMPOSE_PROJECT%" -f "%COMPOSE%" %*
|
docker compose -p "%COMPOSE_PROJECT%" -f "%COMPOSE%" %*
|
||||||
exit /b !ERRORLEVEL!
|
exit /b !ERRORLEVEL!
|
||||||
|
|
||||||
|
:load_ports
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:ensure_env
|
||||||
|
if not exist "%ROOT%\docker\docker-compose.yml" (
|
||||||
|
echo [ERROR] Missing docker\docker-compose.yml under %ROOT%
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
if not exist "%ROOT%\soonModels\" mkdir "%ROOT%\soonModels"
|
||||||
|
if not exist "%ENV_FILE%" (
|
||||||
|
if not exist "docker\.env.example" (
|
||||||
|
echo [ERROR] Missing docker\.env.example
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
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.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
call :load_ports
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:wait_api
|
||||||
|
echo [INFO] Waiting for API http://localhost:%BACKEND_PORT%/health ...
|
||||||
|
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 exit /b 0
|
||||||
|
if !RETRY! GEQ 90 (
|
||||||
|
echo [WARN] API not ready after ~3 min. Run: scripts\start.bat logs
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
set /a MOD=!RETRY! %% 5
|
||||||
|
if !MOD!==0 echo ... still waiting (!RETRY!/90)
|
||||||
|
ping -n 3 127.0.0.1 >nul
|
||||||
|
goto :wait_health
|
||||||
|
|
||||||
|
:print_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 Desktop: cd frontend-electron ^&^& npm start
|
||||||
|
echo Stop: scripts\start.bat stop
|
||||||
|
echo Logs: scripts\start.bat logs
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
:do_stop
|
:do_stop
|
||||||
if exist "%ENV_FILE%" (call :compose down) else (call :compose_noenv down)
|
if exist "%ENV_FILE%" (call :compose down) else (call :compose_noenv down)
|
||||||
echo.
|
echo.
|
||||||
@@ -46,6 +111,33 @@ echo.
|
|||||||
echo [OK] Project %COMPOSE_PROJECT% stopped. DB volume removed.
|
echo [OK] Project %COMPOSE_PROJECT% stopped. DB volume removed.
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|
||||||
|
:do_logs
|
||||||
|
docker logs -f --tail 80 soondesign-api
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:do_status
|
||||||
|
call :ensure_env
|
||||||
|
if errorlevel 1 goto :fail
|
||||||
|
docker ps --filter name=soondesign --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||||
|
echo.
|
||||||
|
curl.exe -sf "http://localhost:%BACKEND_PORT%/health" >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [WARN] API health check failed.
|
||||||
|
) else (
|
||||||
|
echo [OK] API health check passed.
|
||||||
|
)
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:do_rebuild
|
||||||
|
echo [SoonDesign] Rebuilding API image and restarting...
|
||||||
|
call :ensure_env
|
||||||
|
if errorlevel 1 goto :fail
|
||||||
|
if exist "%ENV_FILE%" (call :compose up -d --build --force-recreate api) else (call :compose_noenv up -d --build --force-recreate api)
|
||||||
|
if errorlevel 1 goto :fail
|
||||||
|
call :wait_api
|
||||||
|
call :print_urls
|
||||||
|
goto :done_ok
|
||||||
|
|
||||||
:do_start
|
:do_start
|
||||||
echo [SoonDesign] Starting local Docker environment...
|
echo [SoonDesign] Starting local Docker environment...
|
||||||
echo [INFO] Compose project: %COMPOSE_PROJECT%
|
echo [INFO] Compose project: %COMPOSE_PROJECT%
|
||||||
@@ -57,121 +149,33 @@ if errorlevel 1 (
|
|||||||
goto :fail
|
goto :fail
|
||||||
)
|
)
|
||||||
|
|
||||||
if not exist "soonModels\" mkdir soonModels
|
call :ensure_env
|
||||||
|
if errorlevel 1 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] Ports: Web=%WEB_PORT% Electron=%ELECTRON_PORT% API=%BACKEND_PORT%
|
||||||
echo [INFO] Starting containers - project %COMPOSE_PROJECT%...
|
echo [INFO] docker compose up -d (keeps running containers; use rebuild for fresh API image)
|
||||||
call :compose up -d
|
if exist "%ENV_FILE%" (call :compose up -d) else (call :compose_noenv up -d)
|
||||||
set "DC_EXIT=!ERRORLEVEL!"
|
if errorlevel 1 (
|
||||||
if not "!DC_EXIT!"=="0" (
|
echo [ERROR] docker compose failed. Is Docker Desktop running?
|
||||||
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
|
goto :fail
|
||||||
)
|
)
|
||||||
|
|
||||||
echo [INFO] Waiting for API - first run may take up to 2 min for pdo_mysql install...
|
call :wait_api
|
||||||
set /a RETRY=0
|
if errorlevel 1 goto :show_urls_warn
|
||||||
: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.
|
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
|
curl.exe -sf "http://localhost:%BACKEND_PORT%/api/v1/settings" >nul 2>&1
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo [WARN] API routes not responding. Check: docker logs soondesign-api
|
echo [WARN] API routes not responding. Run: scripts\start.bat logs
|
||||||
) else (
|
) else (
|
||||||
echo [OK] API routes verified.
|
echo [OK] API routes verified.
|
||||||
)
|
)
|
||||||
|
call :print_urls
|
||||||
start "" "http://localhost:%WEB_PORT%/pages/index.web.html"
|
|
||||||
start "" "http://localhost:%WEB_PORT%/pages/admin/index.html"
|
|
||||||
call :launch_electron
|
|
||||||
goto :done_ok
|
goto :done_ok
|
||||||
|
|
||||||
:launch_electron
|
:show_urls_warn
|
||||||
where node >nul 2>&1
|
call :print_urls
|
||||||
if errorlevel 1 (
|
goto :done_ok
|
||||||
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
|
:fail_cd
|
||||||
echo [ERROR] Cannot cd to project root: %ROOT%
|
echo [ERROR] Cannot cd to project root: %ROOT%
|
||||||
@@ -184,5 +188,5 @@ exit /b 1
|
|||||||
|
|
||||||
:done_ok
|
:done_ok
|
||||||
echo.
|
echo.
|
||||||
echo [DONE] SoonDesign is running under Docker project %COMPOSE_PROJECT%.
|
echo [DONE] SoonDesign dev stack is up (project %COMPOSE_PROJECT%).
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|||||||
Reference in New Issue
Block a user