chore(scripts): 开发脚本支持 Flutter Windows 桌面端
- pnpm start:dev 默认在 Windows 上启动桌面端(非 Web) - 新增 pnpm start:dev:app:win 强制 Windows 桌面 - 新增 pnpm start:dev:app:web 强制 Chrome Web - SIGINT 清理时同时终止 nuanji_app.exe - 汇总输出区分桌面/Web 模式
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
"start:dev:backend": "node scripts/dev.mjs backend",
|
||||
"start:dev:admin": "node scripts/dev.mjs admin",
|
||||
"start:dev:app": "node scripts/dev.mjs app",
|
||||
"start:dev:app:win": "node scripts/dev.mjs app:win",
|
||||
"start:dev:app:web": "node scripts/dev.mjs app:web",
|
||||
"start:stop": "node scripts/stop.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
114
scripts/dev.mjs
114
scripts/dev.mjs
@@ -6,7 +6,9 @@
|
||||
* pnpm start:dev # 启动全部 (后端+管理端+学生端)
|
||||
* pnpm start:dev:backend # 只启动后端
|
||||
* pnpm start:dev:admin # 只启动管理端前端
|
||||
* pnpm start:dev:app # 只启动学生端 Flutter
|
||||
* pnpm start:dev:app # 只启动学生端(Windows桌面/Web 自动检测)
|
||||
* pnpm start:dev:app:win # 强制学生端 Windows 桌面
|
||||
* pnpm start:dev:app:web # 强制学生端 Web (Chrome)
|
||||
* pnpm start:stop # 停止所有服务
|
||||
*/
|
||||
|
||||
@@ -299,15 +301,68 @@ async function startAdmin() {
|
||||
return waitForPort(PORTS.admin, "管理端", 20, 2000);
|
||||
}
|
||||
|
||||
/** 启动学生端 Flutter Web */
|
||||
async function startApp() {
|
||||
/** 启动学生端 Flutter(Windows 桌面 或 Web) */
|
||||
async function startApp(mode = "auto") {
|
||||
const appDir = resolve(ROOT, "app");
|
||||
const flutterBin = IS_WIN ? "D:\\flutter\\bin\\flutter.bat" : "flutter";
|
||||
|
||||
// 决定运行平台:auto → Windows 上用桌面,其他用 Web
|
||||
const useWindows = mode === "windows" || (mode === "auto" && IS_WIN);
|
||||
|
||||
if (useWindows) {
|
||||
log.info("编译并启动 Flutter Windows 桌面端...");
|
||||
const child = spawn(`${flutterBin} run -d windows --dart-define=API_BASE_URL=http://localhost:${PORTS.backend}/api/v1 --dart-define=SSE_BASE_URL=http://localhost:${PORTS.backend}/api/v1`, {
|
||||
cwd: appDir,
|
||||
shell: true,
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
child.stdout?.on("data", (data) => {
|
||||
const lines = data.toString().trim().split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.trim()) console.log(color.yellow("[app-win]") + " " + line);
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data) => {
|
||||
const lines = data.toString().trim().split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.trim()) console.log(color.yellow("[app-win]") + " " + line);
|
||||
}
|
||||
});
|
||||
|
||||
child.on("error", (err) => {
|
||||
log.err(`学生端启动失败: ${err.message}`);
|
||||
});
|
||||
|
||||
// Windows 桌面不监听端口,等待进程启动即可
|
||||
// flutter run 会输出 "Syncing files" 表示就绪
|
||||
return new Promise((resolve) => {
|
||||
let ready = false;
|
||||
const check = (data) => {
|
||||
if (!ready && data.toString().includes("Syncing files to device Windows")) {
|
||||
ready = true;
|
||||
log.ok("学生端 (Windows 桌面) 已启动");
|
||||
resolve(true);
|
||||
}
|
||||
};
|
||||
child.stdout?.on("data", check);
|
||||
child.stderr?.on("data", check);
|
||||
// 超时保护
|
||||
setTimeout(() => {
|
||||
if (!ready) {
|
||||
ready = true;
|
||||
log.warn("学生端启动超时,但进程仍在运行");
|
||||
resolve(true);
|
||||
}
|
||||
}, 180_000);
|
||||
});
|
||||
} else {
|
||||
// Web 模式
|
||||
log.info("清理旧学生端进程...");
|
||||
killPort(PORTS.app, "学生端前端");
|
||||
|
||||
log.info("编译并启动 Flutter Web...");
|
||||
const appDir = resolve(ROOT, "app");
|
||||
const flutterBin = IS_WIN ? "D:\\flutter\\bin\\flutter.bat" : "flutter";
|
||||
|
||||
const child = spawn(`${flutterBin} run -d chrome --web-port=${PORTS.app} --dart-define=API_BASE_URL=http://localhost:${PORTS.backend}/api/v1 --dart-define=SSE_BASE_URL=http://localhost:${PORTS.backend}/api/v1`, {
|
||||
cwd: appDir,
|
||||
shell: true,
|
||||
@@ -317,14 +372,14 @@ async function startApp() {
|
||||
child.stdout?.on("data", (data) => {
|
||||
const lines = data.toString().trim().split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.trim()) console.log(color.yellow("[app]") + " " + line);
|
||||
if (line.trim()) console.log(color.yellow("[app-web]") + " " + line);
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data) => {
|
||||
const lines = data.toString().trim().split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.trim()) console.log(color.yellow("[app]") + " " + line);
|
||||
if (line.trim()) console.log(color.yellow("[app-web]") + " " + line);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -334,6 +389,7 @@ async function startApp() {
|
||||
|
||||
return waitForPort(PORTS.app, "学生端", 40, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 主流程 =====
|
||||
|
||||
@@ -351,13 +407,18 @@ function printBanner(services) {
|
||||
console.log();
|
||||
}
|
||||
|
||||
function printSummary() {
|
||||
function printSummary(appMode) {
|
||||
const isWindows = appMode === "windows" || (appMode === "auto" && IS_WIN);
|
||||
console.log();
|
||||
console.log(color.bold(color.green(" ═══ 暖记开发环境已启动 ═══")));
|
||||
console.log();
|
||||
console.log(` 后端 API: http://localhost:${PORTS.backend}`);
|
||||
console.log(` 管理端: http://localhost:${PORTS.admin} (admin/admin123)`);
|
||||
if (isWindows) {
|
||||
console.log(` 学生端: Flutter Windows 桌面`);
|
||||
} else {
|
||||
console.log(` 学生端: http://localhost:${PORTS.app}`);
|
||||
}
|
||||
console.log();
|
||||
console.log(color.gray(" 停止所有服务: pnpm start:stop"));
|
||||
console.log();
|
||||
@@ -365,15 +426,31 @@ function printSummary() {
|
||||
|
||||
async function main() {
|
||||
const arg = process.argv[2] || "all";
|
||||
const services =
|
||||
arg === "all"
|
||||
? ["backend", "admin", "app"]
|
||||
: [arg];
|
||||
|
||||
const validServices = ["backend", "admin", "app", "all"];
|
||||
// 解析参数 → 服务列表 + app 运行模式
|
||||
let services;
|
||||
let appMode = "auto"; // auto | windows | web
|
||||
|
||||
switch (arg) {
|
||||
case "all":
|
||||
services = ["backend", "admin", "app"];
|
||||
break;
|
||||
case "app:win":
|
||||
services = ["app"];
|
||||
appMode = "windows";
|
||||
break;
|
||||
case "app:web":
|
||||
services = ["app"];
|
||||
appMode = "web";
|
||||
break;
|
||||
default:
|
||||
services = [arg];
|
||||
}
|
||||
|
||||
const validServices = ["backend", "admin", "app", "all", "app:win", "app:web"];
|
||||
if (!validServices.includes(arg)) {
|
||||
log.err(`未知参数: ${arg}`);
|
||||
console.log("用法: pnpm start:dev [all|backend|admin|app]");
|
||||
console.log("用法: pnpm start:dev [all|backend|admin|app|app:win|app:web]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -401,7 +478,7 @@ async function main() {
|
||||
frontendPromises.push(startAdmin());
|
||||
}
|
||||
if (services.includes("app")) {
|
||||
frontendPromises.push(startApp());
|
||||
frontendPromises.push(startApp(appMode));
|
||||
}
|
||||
|
||||
if (frontendPromises.length > 0) {
|
||||
@@ -413,8 +490,8 @@ async function main() {
|
||||
}
|
||||
|
||||
// 打印汇总
|
||||
if (arg === "all") {
|
||||
printSummary();
|
||||
if (arg === "all" || arg === "app:win" || arg === "app:web") {
|
||||
printSummary(appMode);
|
||||
}
|
||||
|
||||
// 保持进程存活(不退出 Node,让子进程继续运行)
|
||||
@@ -427,6 +504,7 @@ async function main() {
|
||||
killPort(PORTS.app, "学生端前端");
|
||||
if (IS_WIN) {
|
||||
try { execSync("taskkill /F /IM erp-server.exe", { stdio: "pipe" }); } catch {}
|
||||
try { execSync("taskkill /F /IM nuanji_app.exe", { stdio: "pipe" }); } catch {}
|
||||
}
|
||||
log.ok("所有服务已停止");
|
||||
process.exit(0);
|
||||
|
||||
Reference in New Issue
Block a user