feat(app): pnpm 一键启动 + Flutter Web 编译修复
1. 新增 pnpm start:dev / pnpm start:stop 命令 - scripts/dev.mjs: 跨平台启动脚本(后端+管理端+学生端) - scripts/stop.mjs: 端口清理停止脚本 - 根 package.json 定义 pnpm 脚本 2. 修复 Flutter Web 编译(Isar 3.x + flutter_secure_storage 不兼容) - isar_database: 条件导出,Web 用空 stub - isar_journal_repository: 条件导出,Web 用空 stub - sync_engine: 条件导出,Web 用内存队列(无 Isar 持久化) - 移除 flutter_secure_storage(v9 web 插件用 dart:html) - 新增 SecureTokenStore 接口 + shared_preferences 实现 - auth_repository 改用 SecureTokenStore 接口
This commit is contained in:
111
scripts/stop.mjs
Normal file
111
scripts/stop.mjs
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 暖记开发环境停止脚本 — 清理所有服务进程
|
||||
*
|
||||
* 用法:
|
||||
* pnpm start:stop
|
||||
*/
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
const IS_WIN = process.platform === "win32";
|
||||
|
||||
// ===== 配置 =====
|
||||
const SERVICES = [
|
||||
{ port: 3000, name: "后端 (Rust/Axum)" },
|
||||
{ port: 5174, name: "管理端前端 (React/Vite)" },
|
||||
{ port: 8080, name: "学生端前端 (Flutter Web)" },
|
||||
];
|
||||
|
||||
// ===== 颜色输出 =====
|
||||
const log = {
|
||||
info: (msg) => console.log(`\x1b[34m[INFO]\x1b[0m ${msg}`),
|
||||
ok: (msg) => console.log(`\x1b[32m[OK]\x1b[0m ${msg}`),
|
||||
warn: (msg) => console.log(`\x1b[33m[WARN]\x1b[0m ${msg}`),
|
||||
};
|
||||
|
||||
// ===== 工具函数 =====
|
||||
|
||||
function findPidsOnPort(port) {
|
||||
try {
|
||||
if (IS_WIN) {
|
||||
const out = execSync(
|
||||
`netstat -ano | findstr :${port} | findstr LISTENING`,
|
||||
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
||||
);
|
||||
return [
|
||||
...new Set(
|
||||
out
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((l) => l.trim().split(/\s+/).pop())
|
||||
.filter((p) => p && p !== "0")
|
||||
),
|
||||
];
|
||||
}
|
||||
const out = execSync(`lsof -ti :${port}`, {
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return out.trim().split("\n").filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function killPid(pid) {
|
||||
try {
|
||||
if (IS_WIN) {
|
||||
execSync(`taskkill /F /PID ${pid}`, { stdio: "pipe" });
|
||||
} else {
|
||||
execSync(`kill -9 ${pid}`, { stdio: "pipe" });
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function killPort(port, name) {
|
||||
const pids = findPidsOnPort(port);
|
||||
if (pids.length === 0) {
|
||||
log.info(`${name} — 端口 ${port} 空闲`);
|
||||
return;
|
||||
}
|
||||
for (const pid of pids) {
|
||||
if (killPid(pid)) {
|
||||
log.ok(`${name} — 已停止 (PID: ${pid}, 端口: ${port})`);
|
||||
} else {
|
||||
log.warn(`${name} — 无法停止 PID: ${pid}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 主流程 =====
|
||||
|
||||
console.log();
|
||||
console.log("\x1b[1m\x1b[36m ═══ 暖记 — 停止所有服务 ═══\x1b[0m");
|
||||
console.log();
|
||||
|
||||
log.info("正在停止所有暖记服务...");
|
||||
|
||||
for (const { port, name } of SERVICES) {
|
||||
killPort(port, name);
|
||||
}
|
||||
|
||||
// Windows: 额外清理 erp-server.exe
|
||||
if (IS_WIN) {
|
||||
try {
|
||||
execSync("taskkill /F /IM erp-server.exe", { stdio: "pipe" });
|
||||
log.ok("已停止 erp-server.exe");
|
||||
} catch {
|
||||
// 没有运行中的进程,忽略
|
||||
}
|
||||
}
|
||||
|
||||
// Windows: 清理可能的残留 node 进程(仅清理 pnpm 启动的)
|
||||
// 注意:不盲目杀所有 node 进程,只清理端口的即可
|
||||
|
||||
console.log();
|
||||
log.ok("所有服务已停止");
|
||||
console.log();
|
||||
Reference in New Issue
Block a user