diff --git a/package-lock.json b/package-lock.json index af503c4..74039c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "express": "^5.0.1", "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", + "ramda": "^0.30.1", "ws": "^8.18.1" }, "devDependencies": { @@ -24,6 +25,7 @@ "@types/morgan": "^1.9.9", "@types/multer": "^1.4.12", "@types/node": "^22.13.4", + "@types/ramda": "^0.30.2", "@types/ws": "^8.5.14", "nodemon": "^3.1.9", "prisma": "^6.5.0", @@ -716,6 +718,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ramda": { + "version": "0.30.2", + "resolved": "https://registry.npmmirror.com/@types/ramda/-/ramda-0.30.2.tgz", + "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "types-ramda": "^0.30.1" + } + }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", @@ -2063,6 +2075,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmmirror.com/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", @@ -2472,6 +2494,13 @@ } } }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmmirror.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/type-is": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.0.tgz", @@ -2492,6 +2521,16 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/types-ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmmirror.com/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.7.3.tgz", diff --git a/package.json b/package.json index 98e3e88..61f7933 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "express": "^5.0.1", "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", + "ramda": "^0.30.1", "ws": "^8.18.1" }, "devDependencies": { @@ -32,6 +33,7 @@ "@types/morgan": "^1.9.9", "@types/multer": "^1.4.12", "@types/node": "^22.13.4", + "@types/ramda": "^0.30.2", "@types/ws": "^8.5.14", "nodemon": "^3.1.9", "prisma": "^6.5.0", diff --git a/public/main.js b/public/main.js index b81cd10..071d0c1 100644 --- a/public/main.js +++ b/public/main.js @@ -44,7 +44,7 @@ $("#endRecord").on("click", () => { $('#disconnect-ble').on("click", ()=> { $.ajax({ type: "POST", - url: "/api/cmd/disconnect-ble", + url: "/api/ble/disconnect", data: JSON.stringify({}), contentType: "application/json", success: res => { @@ -58,8 +58,8 @@ $('#disconnect-ble').on("click", ()=> { $('#connect-ble').on("click", ()=> { $.ajax({ type: "POST", - url: "/api/cmd/connect-ble", - data: JSON.stringify({}), + url: "/api/ble/connect", + data: JSON.stringify({id: "abc"}), contentType: "application/json", success: res => { console.log("Success", res); diff --git a/src/index.ts b/src/index.ts index 4970815..c57c17e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,10 +3,9 @@ import http from "http"; import bodyParser from "body-parser"; import morgan from "morgan"; import multer from "multer"; -import cors from 'cors'; +import cors from "cors"; import path from "path"; - import cmdRouter from "./routes/cmd"; import authRouter from "./routes/auth"; import measureTaskRouter from "./routes/measureTask"; @@ -16,6 +15,10 @@ import railRouter from "./routes/rail"; import calibrationRouter from "./routes/calibration"; import mobileRouter from "./routes/mobile"; import measureRouter from "./routes/measure"; +import bleRouter from "./routes/ble"; +import systemRouter from "./routes/system"; +import syncRouter from "./routes/sync"; +import recordRouter from "./routes/record"; // import { defaultStatus, StatusDatagram } from "./types/wsTypes"; import { WsProxy } from "./utils/wss"; @@ -30,16 +33,16 @@ app.use(cors()); const server = http.createServer(app); -WsProxy.addActionForClientConnect((ws) => { - // DeviceContext - ws.send( - JSON.stringify({ - messageType: "DeviceContext", - data: app.locals["context"], - path: "/deviceContext", - }) - ); -}) +WsProxy.addActionForClientConnect(ws => { + // DeviceContext + ws.send( + JSON.stringify({ + messageType: "DeviceContext", + data: app.locals["context"], + path: "/deviceContext", + }) + ); +}); // 在HTTP服务器上初始化WebSocket服务器 WsProxy.init(server); // const wss = new Server({ server }); @@ -61,37 +64,41 @@ app.use("/api/measurement-data", measureDataRouter); app.use("/api/standard-rail", railRouter); app.use("/api/calibration", calibrationRouter); app.use("/api/measure", measureRouter); +app.use("/api/ble", bleRouter); +app.use("/api/system", systemRouter); +app.use("/api/sync", syncRouter); +app.use("/api/record", recordRouter); const storage = multer.diskStorage({ destination: function (req, file, cb) { // 指定文件存储的目录 - cb(null, 'uploads/'); + cb(null, "uploads/"); }, filename: function (req, file, cb) { // 指定文件保存的文件名 - const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); - cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); - } + const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); + cb(null, file.fieldname + "-" + uniqueSuffix + path.extname(file.originalname)); + }, }); const upload = multer({ storage: storage }); // 创建上传文件的路由 -app.post('/upload', upload.single('file'), (req, res) => { +app.post("/upload", upload.single("file"), (req, res) => { if (!req.file) { - res.status(400).json({msg:'未收到文件'}); + res.status(400).json({ msg: "未收到文件" }); } else { - res.json({msg:'文件上传成功!文件路径:' + req.file.path}); - } + res.json({ msg: "文件上传成功!文件路径:" + req.file.path }); + } }); //@ts-ignore app.use((err, req, res, next) => { - console.error(err.stack); - res.status(500).send("Something broke!"); + console.error(err.stack); + res.status(500).send("Something broke!"); }); // 监听端口 const PORT = process.env.PORT || 8080; server.listen(PORT, () => { - console.log(`Server is listening on port ${PORT}`); + console.log(`Server is listening on port ${PORT}`); }); diff --git a/src/routes/ble.ts b/src/routes/ble.ts new file mode 100644 index 0000000..fe17f7b --- /dev/null +++ b/src/routes/ble.ts @@ -0,0 +1,68 @@ +import express from "express"; +import { delay, prismaClient } from "../utils/helper"; +const router = express.Router(); +import { WsProxy } from "../utils/wss"; + +let intervalId1: ReturnType; +let connectId = ""; + +router.post("/list/start", (req, res) => { + intervalId1 = setInterval(() => { + WsProxy.sendMobile({ + type: "ble-list", + data: [ + { + mac: "xyz", // 蓝牙设备的 MAC 地址(唯一标识) + name: "TEST-XYZ", // 蓝牙设备的可读名称(如型号/别名) + linked: connectId === "xyz", //该设备是否已链接 + }, + { + mac: "abc", // 蓝牙设备的 MAC 地址(唯一标识) + name: "TEST-ABC", // 蓝牙设备的可读名称(如型号/别名) + linked: connectId === "abc", //该设备是否已链接 + }, + ], + }); + }, 1000); + res.json({ success: true }); +}); +router.post("/list/stop", (req, res) => { + clearInterval(intervalId1); + res.json({ success: true }); +}); +router.post("/connect", (req, res) => { + const id = req.body.id; + connectId = id; + + setTimeout(() => { + WsProxy.sendMobile({ + type: "peripheral-status", + data: { + connected: true, //是否已连接蓝牙 + power: 60, //电量 + inclinatorX: 0.276, //x轴倾斜 + inclinatorY: 3.019, //y轴倾斜 + temperature: 32.026, //温度 + }, + }); + }); + res.json({ success: true }); +}); +router.post("/disconnect", (req, res) => { + connectId = ""; + setTimeout(() => { + WsProxy.sendMobile({ + type: "peripheral-status", + data: { + connected: false, //是否已连接蓝牙 + power: 60, //电量 + inclinatorX: 0.276, //x轴倾斜 + inclinatorY: 3.019, //y轴倾斜 + temperature: 32.026, //温度 + }, + }); + }); + res.json({ success: true }); +}); + +export default router; diff --git a/src/routes/record.ts b/src/routes/record.ts new file mode 100644 index 0000000..7453e7b --- /dev/null +++ b/src/routes/record.ts @@ -0,0 +1,33 @@ +import express from "express"; +import * as R from "ramda"; +import { delay, prismaClient } from "../utils/helper"; +const router = express.Router(); +import { WsProxy } from "../utils/wss"; +import { dataList, dataListFlat, dataListFlatMore } from "../utils/constant"; + +const settingObj = { + server: "", +}; + +router.post("/list", (req, res) => { + if (req.body.pageNum === 1) { + res.json({ success: true, data: { list: dataList } }); + } +}); + +router.post("/detail", (req, res) => { + const id = +req.body.id; + const item = R.flatten(dataList.map(item => item.records)).find(t => t.id === id); + res.json({ + success: true, + data: item || {}, + }); +}); + +router.post("/delete", (req, res) => { + const ids = req.body.ids; + + res.json({ success: true }); +}); + +export default router; diff --git a/src/routes/sync.ts b/src/routes/sync.ts new file mode 100644 index 0000000..b517ec8 --- /dev/null +++ b/src/routes/sync.ts @@ -0,0 +1,42 @@ +import express from "express"; +import { delay, prismaClient } from "../utils/helper"; +const router = express.Router(); +import { WsProxy } from "../utils/wss"; +import { dataListFlat, dataListFlatMore } from "../utils/constant"; + +const settingObj = { + server: "", +}; + +router.post("/list", (req, res) => { + if (req.body.pageNum === 1) { + res.json({ success: true, data: { list: dataListFlat } }); + } else { + res.json({ success: true, data: { list: dataListFlatMore } }); + } +}); + +router.post("/add", (req, res) => { + const ids = req.body.ids; + res.json({ success: true }); +}); + +router.post("/progress", (req, res) => { + res.json({ + success: true, + data: { + remaining: 123, // 剩余未同步数量 + fail: 1, // 同步失败数量 + total: 123, // 总数量 + finish: false, // 是否同步完成(true 表示全部完成) + }, + }); +}); + +router.post("/config/save", (req, res) => { + const server = req.body.server; + settingObj.server = server; + res.json({ success: true }); +}); + +export default router; diff --git a/src/routes/system.ts b/src/routes/system.ts new file mode 100644 index 0000000..e08e70d --- /dev/null +++ b/src/routes/system.ts @@ -0,0 +1,20 @@ +import express from "express"; +import { delay, prismaClient } from "../utils/helper"; +const router = express.Router(); +import { WsProxy } from "../utils/wss"; + +const settingObj = { + server: "", +}; + +router.post("/config", (req, res) => { + res.json({ success: true, data: settingObj }); +}); + +router.post("/config/save", (req, res) => { + const server = req.body.server; + settingObj.server = server; + res.json({ success: true }); +}); + +export default router; diff --git a/src/types/apiTypes.ts b/src/types/apiTypes.ts new file mode 100644 index 0000000..2bd89cf --- /dev/null +++ b/src/types/apiTypes.ts @@ -0,0 +1,15 @@ +export type Measurement = { + id: number; + name: string; + railId: number; + bureau: string; + line: string; + section: string; + direction: string; + createAt: string; // Date; + leftPoints: string; // json: 坐标数组 + rightPoints: string; // json: 坐标数组 + upload: boolean; + syncStatus: 'wait' | 'finish' | 'fail'; + }; + \ No newline at end of file diff --git a/src/utils/constant.ts b/src/utils/constant.ts new file mode 100644 index 0000000..25b93c4 --- /dev/null +++ b/src/utils/constant.ts @@ -0,0 +1,259 @@ +import { Measurement } from "../types/apiTypes"; + +export const dataListFlat: Measurement[] = [ + { + id: 1, + name: "测量名称1", + createAt: "2025-03-02 10:20", + line: "京沪线", + section: "A段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 2, + name: "测量名称2", + createAt: "2025-03-02 12:22", + line: "京沪线", + section: "B段", + direction: "下行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 3, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 4, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 5, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 6, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 7, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 8, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 9, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 10, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, +]; + +export const dataListFlatMore: Measurement[] = [ + { + id: 11, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, + { + id: 12, + name: "测量名称3", + createAt: "2025-03-02 12:20", + line: "京沪线", + section: "C段", + direction: "上行", + railId: 2, + leftPoints: "[]", + rightPoints: "[]", + bureau: "北京铁路局", + upload: false, + syncStatus: 'finish' + }, +]; + + +export const dataList: Array<{ date: string; records: Measurement[] }> = [ + { + date: '2025-03-02', + records: [ + { + id: 1, + name: '测量名称1', + createAt: '2025-03-02 10:20', + line: '京沪线', + section: 'A段', + direction: '上行', + railId: 2, + leftPoints: '[]', + rightPoints: '[]', + bureau: '北京铁路局', + upload: false, + syncStatus: 'finish' + }, + { + id: 2, + name: '测量名称2', + createAt: '2025-03-02 12:22', + line: '京沪线', + section: 'B段', + direction: '下行', + railId: 2, + leftPoints: '[]', + rightPoints: '[]', + bureau: '北京铁路局', + upload: false, + syncStatus: 'finish' + }, + { + id: 3, + name: '测量名称3', + createAt: '2025-03-02 12:20', + line: '京沪线', + section: 'C段', + direction: '上行', + railId: 2, + leftPoints: '[]', + rightPoints: '[]', + bureau: '北京铁路局', + upload: false, + syncStatus: 'finish' + }, + ], + }, + { + date: '2025-02-01', + records: [ + { + id: 4, + name: '测量名称2', + createAt: '2025-03-02 10:20', + line: '京沪线', + section: 'D段', + direction: '下行', + railId: 2, + leftPoints: '[]', + rightPoints: '[]', + bureau: '北京铁路局', + upload: false, + syncStatus: 'finish' + }, + { + id: 5, + name: '测量名称2', + createAt: '2025-03-02 10:20', + line: '京沪线', + section: 'E段', + direction: '下行', + railId: 2, + leftPoints: '[]', + rightPoints: '[]', + bureau: '北京铁路局', + upload: false, + syncStatus: 'finish' + }, + ], + }, + ]; \ No newline at end of file