feat: 初始化星元智灵人脸识别回收箱项目

This commit is contained in:
FinleyHsu
2026-07-28 10:48:03 +08:00
commit 32a31f0909
112 changed files with 28469 additions and 0 deletions
@@ -0,0 +1,93 @@
import { addAudit, ensureDatabase, loadConsoleData } from "@/db/store";
export const dynamic = "force-dynamic";
export async function GET() {
try {
return Response.json({ code: 0, data: await loadConsoleData() });
} catch (error) {
return Response.json(
{ code: 5001, message: error instanceof Error ? error.message : "数据库不可用" },
{ status: 500 },
);
}
}
export async function POST(request: Request) {
const db = await ensureDatabase();
const body = (await request.json()) as {
action?: string;
payload?: Record<string, unknown>;
};
const payload = body.payload ?? {};
const action = body.action ?? "";
if (action === "person.toggle") {
const id = String(payload.id ?? "");
await db
.prepare("UPDATE people SET active=CASE active WHEN 1 THEN 0 ELSE 1 END, updated_at=? WHERE id=?")
.bind(Date.now(), id)
.run();
await addAudit("管理员", "人员状态变更", `${id} 启用状态已切换`, "重要");
} else if (action === "person.workshop") {
const id = String(payload.id ?? "");
const workshop = String(payload.workshop ?? "");
await db.prepare("UPDATE people SET workshop=?, updated_at=? WHERE id=?").bind(workshop, Date.now(), id).run();
await addAudit("管理员", "修改人员车间", `${id} 调整到 ${workshop}`);
} else if (action === "person.face") {
const id = String(payload.id ?? "");
await db.prepare("UPDATE people SET face_status='已录入', face_version=face_version+1, sync_status='已同步', updated_at=? WHERE id=?").bind(Date.now(), id).run();
await addAudit("管理员", "更新人脸", `${id} 人脸版本已更新`, "重要");
} else if (action === "workshop.add") {
const name = String(payload.name ?? "").trim();
const location = String(payload.location ?? "—").trim();
const note = String(payload.note ?? "—").trim();
if (!name) return Response.json({ code: 1001, message: "车间名称不能为空" }, { status: 400 });
const row = await db.prepare("SELECT COUNT(*) AS total FROM workshops").first<{ total: number }>();
const code = `WS-${String(Number(row?.total ?? 0) + 1).padStart(2, "0")}`;
await db.prepare("INSERT INTO workshops(code,name,location,note) VALUES(?,?,?,?)").bind(code, name, location || "—", note || "—").run();
await addAudit("管理员", "新增车间", `${code} ${name}`, "重要");
} else if (action === "workshop.delete") {
const code = String(payload.code ?? "");
const workshop = await db.prepare("SELECT name FROM workshops WHERE code=?").bind(code).first<{ name: string }>();
if (workshop) {
const used = await db.prepare("SELECT COUNT(*) AS total FROM people WHERE workshop=?").bind(workshop.name).first<{ total: number }>();
if (Number(used?.total ?? 0) > 0) {
return Response.json({ code: 4001, message: "该车间仍有关联人员,不能删除" }, { status: 409 });
}
await db.prepare("DELETE FROM workshops WHERE code=?").bind(code).run();
await addAudit("管理员", "删除车间", `${code} ${workshop.name}`, "重要");
}
} else if (action === "tag.add") {
const name = String(payload.name ?? "未命名标签").trim() || "未命名标签";
const id = `E280-6102-${String(Math.floor(Math.random() * 9000) + 1000)}`;
await db.prepare("INSERT INTO rfid_tags(id,status,name,last_checked) VALUES(?,'未用',?,NULL)").bind(id, name).run();
await addAudit("管理员", "新增 RFID", `${id} ${name}`);
} else if (action === "tag.status") {
const id = String(payload.id ?? "");
const status = String(payload.status ?? "未用");
await db.prepare("UPDATE rfid_tags SET status=?, last_checked=? WHERE id=?").bind(status, Date.now(), id).run();
await addAudit("管理员", "RFID 状态变更", `${id}${status}`);
} else if (action === "alert.status") {
const id = String(payload.id ?? "");
const status = String(payload.status ?? "处理中");
await db.prepare("UPDATE alerts SET status=? WHERE id=?").bind(status, id).run();
await addAudit("管理员", "处理告警", `${id}${status}`, "重要");
} else if (action === "settings.save") {
const entries = Object.entries(payload);
for (const [key, value] of entries) {
await db
.prepare("INSERT INTO settings(key,value,updated_at) VALUES(?,?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at")
.bind(key, String(value), Date.now())
.run();
}
await addAudit("管理员", "更新系统设置", entries.map(([key]) => key).join("、"), "重要");
} else if (action === "mes.sync") {
await db.prepare("UPDATE people SET sync_status='已同步', updated_at=? WHERE sync_status<>'已同步'").bind(Date.now()).run();
await addAudit("管理员", "同步 MES 人员", "增量同步完成");
} else {
return Response.json({ code: 1001, message: "未知操作" }, { status: 400 });
}
return Response.json({ code: 0, message: "操作成功", data: await loadConsoleData() });
}
@@ -0,0 +1,121 @@
import { addAudit, ensureDatabase } from "@/db/store";
export const dynamic = "force-dynamic";
type RouteContext = { params: Promise<{ path: string[] }> };
function ok(data: unknown, requestId = crypto.randomUUID()) {
return Response.json({
code: 0,
message: "success",
requestId,
serverTime: Date.now(),
data,
});
}
function fail(code: number, message: string, status = 400) {
return Response.json(
{ code, message, requestId: crypto.randomUUID(), serverTime: Date.now(), data: null },
{ status },
);
}
export async function GET(request: Request, context: RouteContext) {
const db = await ensureDatabase();
const path = (await context.params).path;
const url = new URL(request.url);
if (path.length === 4 && path[0] === "devices" && path[2] === "people") {
const cursor = Number(url.searchParams.get("cursor") ?? 0);
const limit = Math.min(Number(url.searchParams.get("limit") ?? 200), 500);
const rows = await db
.prepare("SELECT id AS personId,name,workshop,role,active,face_status AS faceStatus,face_version AS faceVersion,updated_at AS updatedAt FROM people WHERE updated_at>? ORDER BY updated_at LIMIT ?")
.bind(cursor, limit)
.all();
const nextCursor = rows.results.reduce((value, row) => Math.max(value, Number(row.updatedAt ?? 0)), cursor);
return ok({ items: rows.results, nextCursor, hasMore: rows.results.length === limit });
}
if (path.length === 4 && path[0] === "devices" && path[2] === "faces") {
const person = await db
.prepare("SELECT id AS personId,name,face_status AS faceStatus,face_version AS faceVersion,updated_at AS updatedAt FROM people WHERE id=? AND active=1")
.bind(path[3])
.first();
if (!person) return fail(4002, "PERSON_DELETED", 404);
return ok({ ...person, featureEncoding: "server-managed", feature: null });
}
if (path.length === 2 && path[0] === "deliveries") {
const row = await db.prepare("SELECT * FROM deliveries WHERE record_id=?").bind(path[1]).first();
if (!row) return fail(3004, "DELIVERY_NOT_FOUND", 404);
return ok(row);
}
return fail(1001, "接口不存在", 404);
}
export async function POST(request: Request, context: RouteContext) {
const db = await ensureDatabase();
const path = (await context.params).path;
const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
if (path.join("/") === "auth/tokens") {
const deviceId = String(body.deviceId ?? body.deviceNo ?? "");
if (!deviceId) return fail(1001, "deviceId 必填");
return ok({ accessToken: `local-${deviceId}-${Date.now()}`, tokenType: "Bearer", expiresIn: 28_800 });
}
if (path.length === 3 && path[0] === "devices" && path[2] === "heartbeat") {
const deviceId = path[1];
const ip = String(body.ip ?? "0.0.0.0");
await db
.prepare("INSERT INTO devices(id,type,ip,status,last_seen,heartbeat_seconds,fill_percent,category) VALUES(?,?,?,'在线',?,30,0,'终端') ON CONFLICT(id) DO UPDATE SET status='在线',last_seen=excluded.last_seen,ip=excluded.ip")
.bind(deviceId, String(body.deviceType ?? "回收终端"), ip, Date.now())
.run();
return ok({ accepted: true, nextHeartbeatIn: 30 });
}
if (path.length === 3 && path[0] === "devices" && path[2] === "status") {
const deviceId = path[1];
const fillPercent = Number(body.fillPercent ?? body.fullRate ?? 0);
await db.prepare("UPDATE devices SET status=?,last_seen=?,fill_percent=? WHERE id=?").bind(String(body.status ?? "在线"), Date.now(), fillPercent, deviceId).run();
return ok({ accepted: true });
}
if (path.length === 3 && path[0] === "devices" && path[2] === "alerts") {
const deviceId = path[1];
const alerts = Array.isArray(body.alerts) ? body.alerts : [body];
for (const item of alerts as Array<Record<string, unknown>>) {
await db
.prepare("INSERT OR IGNORE INTO alerts(id,created_at,type,severity,source,reference,description,status) VALUES(?,?,?,?,?,?,?,'待处理')")
.bind(String(item.alarmId ?? crypto.randomUUID()), Number(item.reportedAt ?? Date.now()), String(item.type ?? "设备告警"), String(item.severity ?? "警告"), "设备上报", deviceId, String(item.message ?? "设备异常"))
.run();
}
return ok({ accepted: alerts.length });
}
if (path.length === 3 && path[0] === "devices" && path[2] === "deliveries") {
const deviceId = path[1];
const recordId = String(body.recordId ?? "");
if (!recordId) return fail(1001, "recordId 必填");
const weight = Number(body.weight ?? body.afterWeight ?? 0);
await db
.prepare("INSERT OR IGNORE INTO deliveries(record_id,device_id,created_at,employee_name,employee_no,machine_no,production_batch,garbage_type,weight,payload,status) VALUES(?,?,?,?,?,?,?,?,?,?,?)")
.bind(recordId, deviceId, Number(body.createdAt ?? Date.now()), String(body.employeeName ?? body.person ?? ""), String(body.employeeNo ?? ""), String(body.machineNo ?? ""), String(body.productionBatch ?? ""), Number(body.garbageType ?? body.typeCode ?? 0), weight, JSON.stringify(body), "已接收")
.run();
await db
.prepare("INSERT OR IGNORE INTO weighings(id,created_at,operator,machine,category,source,weight,event_id,status) VALUES(?,?,?,?,?,?,?,?,?)")
.bind(recordId, Number(body.createdAt ?? Date.now()), String(body.employeeName ?? body.person ?? "未知"), String(body.machineNo ?? "—"), `类型 ${String(body.garbageType ?? body.typeCode ?? 0)}`, deviceId, weight, String(body.eventId ?? recordId), "已确认")
.run();
await addAudit(deviceId, "投放数据上报", `${recordId} · ${weight}kg`);
return ok({ accepted: true, duplicate: false });
}
if (path.length === 3 && path[0] === "devices" && path[2] === "sync-acks") {
await addAudit(path[1], "同步结果上报", JSON.stringify(body).slice(0, 500));
return ok({ accepted: true });
}
return fail(1001, "接口不存在", 404);
}