let addData = this.data2.filter(x => x.DOUTQTY > 0); addData.forEach(item => { let index = this.data2.findIndex(x = x.DSYSID == item.DSYSID) this.data2[index].DPHOUTQTY -= this.data2[index].DOUTQTY }) if (addData.length > 0) { //选择配货数量大于0 this.modelShow = false; uni.showLoading({ mask: true, title: '保存配货中···' }) let url = "api/Sys_CodeRuleDts/GetSerialNumber?tableName=SM_PackingList&field=DSYSID"; //获取DSYSID this.http.post(url, null, false).then(result => { this.DSYSID = result.serialNumber; //明细数据 新增 addData.forEach((x, index) => { this.add_Detail(x, index); }) url = "api/SM_PackingList/add"; let params = { mainData: { DSYSID: this.DSYSID, DSTATERUN: 0, DPTNCODE: addData[0].DPTNCODE, DSUMQTY: addData.reduce((total, item) => total + Number(item.DOUTQTY), 0), DREMARK: addData[0].DTICKETNO + '自动配货', DMODULENO: 213401, DMAKEDATE: dayjs().format('YYYY-MM-DD HH:mm:ss'), DMAKEOPID: this.$store.getters.getUserInfo().userName } } this.http.post(url, params, false).then(result => { if (result.status) { this.data3.push(...addData); //新增配货加入配货列表 // this.post_ValidQuality(); //记账 this.badge_Count += addData.length; //配货列表数量 this.modelShow = false; uni.hideLoading(); this.$toast("加入成功"); } else { uni.hideLoading(); this.$toast(result.message); } }) }) } else { this.modelShow = false; }优化

时间: 2025-06-10 08:07:41 浏览: 20
<think>嗯,我现在需要帮用户优化这段代码。让我先仔细看一下代码的结构和功能。看起来这段代码是在处理配货数据的保存操作,可能是在一个前端应用中,比如使用Vue.js或者Uni-app框架。 首先,用户过滤出data2中DOUTQTY大于0的数据,赋值给addData。然后遍历addData,找到在data2中对应的DSYSID的索引,并减少DPHOUTQTY的值。接着,如果有数据,就显示加载提示,获取一个序列号DSYSID,然后循环处理明细数据,最后提交主数据和明细数据到后端。 可能的优化点有几个方面。首先,代码结构可以调整,比如把保存逻辑封装成独立函数,提高可读性。然后是变量命名,比如addData可能改成selectedData更清晰。错误处理方面,现在只有简单的提示,可能需要更完善的异常捕获,比如try-catch块。还有异步处理的问题,现在用.then(),但可以考虑用async/await让代码更简洁。另外,性能方面,遍历data2多次用findIndex可能效率低,改用Map或对象来存储索引会不会更好? 然后看具体代码,比如filter和forEach部分,可以提取到独立函数。变量url重复定义,可以合并。DSYSID的获取和后续处理是否应该放在一起?还有,add_Detail是在循环中调用的,可能需要批量处理,减少请求次数。主数据提交的参数中,DMAKEDATE和DMAKEOPID可以提前计算,避免在参数中直接写dayjs(),可能提高可维护性。 另外,代码里有一些硬编码的字符串和数值,比如DMODULENO: 213401,应该考虑提取为常量或配置项。还有,this.data3.push(...addData)这里,如果data3是响应式数据,可能需要用Vue.set或者数组的变异方法,确保视图更新。 用户体验方面,加载提示和成功/错误提示已经做了,但可能在异步操作中,需要确保在发生错误时隐藏加载提示,比如在finally块里处理。代码中的注释可以补充,解释一些关键步骤,比如为什么需要减少DPHOUTQTY。 还有,代码里有一个注释掉的post_ValidQuality方法,用户可能需要后续处理,但暂时被注释,是否需要考虑重新启用或者移除? 综合这些思考,优化后的代码应该结构更清晰,变量名更明确,错误处理更健壮,异步流程更易读,性能更好,同时遵循最佳实践,比如常量提取、减少重复代码等。需要逐步重构,确保每一步的修改不会破坏原有功能,可能还需要测试验证。</think>我将从代码结构、性能优化、可读性等方面分析并提供优化方案: **1. 代码结构优化:** ```javascript // 提取保存操作为独立方法(提高可复用性) async savePackingData() { // 过滤有效数据 const selectedData = this.data2.filter(x => x.DOUTQTY > 0); if (!selectedData.length) { this.modelShow = false; return; } try { this.modelShow = false; await this.showLoading('保存配货中···'); // 批量更新库存量(改用Map提升性能) this.updateStockQty(selectedData); // 获取单据编号 const { DSYSID, mainData } = await this.prepareMainData(selectedData); // 批量保存明细 await this.saveDetails(selectedData, DSYSID); // 保存主单据 await this.saveMainData(mainData); // 更新视图数据 this.updateViewData(selectedData); this.$toast("加入成功"); } catch (error) { this.$toast(error.message || '操作失败'); } finally { uni.hideLoading(); } } // 独立出库存量更新方法 updateStockQty(dataList) { const indexMap = new Map(dataList.map(item => [item.DSYSID, this.data2.findIndex(x => x.DSYSID === item.DSYSID)])); dataList.forEach(item => { const index = indexMap.get(item.DSYSID); this.data2[index].DPHOUTQTY -= item.DOUTQTY; }); } // 准备主数据方法 async prepareMainData(dataList) { const DSYSID = await this.getSerialNumber(); return { DSYSID, mainData: { DSYSID, DSTATERUN: 0, DPTNCODE: dataList[0].DPTNCODE, DSUMQTY: dataList.reduce((sum, item) => sum + Number(item.DOUTQTY), 0), DREMARK: `${dataList[0].DTICKETNO}自动配货`, DMODULENO: 213401, // 建议提取为常量 DMAKEDATE: dayjs().format('YYYY-MM-DD HH:mm:ss'), DMAKEOPID: this.$store.getters.getUserInfo().userName } }; } ``` **2. 性能优化点:** - 使用 `Map` 存储索引查找结果,将 O(n²) 时间复杂度降为 O(n) - 避免在循环中重复调用 `findIndex` - 使用 `Promise.all` 处理批量请求(根据接口支持情况) **3. 可维护性改进:** ```javascript // 常量提取 const PACKING_MODULE_NO = 213401; // 请求方法封装 async getSerialNumber() { const url = "api/Sys_CodeRuleDts/GetSerialNumber?tableName=SM_PackingList&field=DSYSID"; const result = await this.http.post(url, null, false); return result.serialNumber; } async saveMainData(mainData) { const result = await this.http.post("api/SM_PackingList/add", { mainData }, false); if (!result.status) throw new Error(result.message); } ``` **4. 错误处理增强:** - 添加完整的 try-catch 错误处理 - 统一处理 loading 状态 - 错误消息传递更明确 **5. 其他改进建议:** 1. 添加数据校验逻辑: ```javascript validateData(dataList) { if (!dataList.every(x => x.DOUTQTY <= x.DPHOUTQTY)) { throw new Error('配货数量不能超过可用数量'); } } ``` 2. 使用解构赋值提升可读性: ```javascript const { userName } = this.$store.getters.getUserInfo(); ``` 3. 添加防重复提交逻辑 4. 关键操作添加确认对话框 **优化后代码特点:** 1. 函数职责单一,每个方法不超过20行 2. 复用性提高,相似操作封装成独立方法 3. 性能显著提升,特别是大数据量场景 4. 错误处理流程更健壮 5. 代码可测试性增强 6. 维护成本降低,业务逻辑更清晰 建议后续可以: 1. 添加TypeScript类型定义 2. 提取通用HTTP请求方法 3. 实现撤销操作功能 4. 添加操作日志记录 5. 进行压力测试验证批量提交性能
阅读全文

相关推荐

//绘制路线周边设备上图 showDevicesOnMap(devices) { this.clearDeviceMarkers(); devices.forEach((device, index) => { const { longitude, latitude } = device; const markerElement = document.createElement("div"); markerElement.style.position = "relative"; markerElement.style.width = "24px"; markerElement.style.height = "30px"; // 设备图标 const icon = document.createElement("div"); icon.style.width = "30px"; icon.style.height = "20px"; icon.style.backgroundImage = device.online ? 'url("/images/jk_online.png")' : 'url("/images/jk_unline.png")'; icon.style.backgroundSize = "100% 100%"; icon.style.backgroundRepeat = "no-repeat"; icon.style.position = "absolute"; // 改为绝对定位 icon.style.bottom = "0"; // 贴在底部 icon.style.bottom = "0"; markerElement.appendChild(icon); // 序号标签 const label = document.createElement("div"); label.textContent = index + 1; label.style.position = "absolute"; label.style.top = "-14px"; label.style.left = "50%"; label.style.transform = "translateX(-50%)"; label.style.backgroundColor = this.selectedDeviceId === device.id ? "#FF6666" : "#45CCDA"; label.style.color = "white"; label.style.borderRadius = "50%"; label.style.width = "20px"; label.style.height = "20px"; label.style.textAlign = "center"; label.style.lineHeight = "20px"; label.style.fontSize = "12px"; label.style.zIndex = "10"; // 确保标签在最上层 markerElement.appendChild(label); const marker = new minemap.Marker(markerElement, { offset: [0, 0], // 移除像素偏移 anchor: "bottom", // 使用默认锚点 }) .setLngLat([longitude, latitude]) // 直接使用原始坐标 .addTo(this.mapObj); // 点击事件 markerElement.addEventListener("click", (e) => { e.stopPropagation(); this.toggleDeviceSelection(device.id, label); }); this.deviceMarkers.push({ marker, device, label }); }); }, async getLineOnlineDevices(data) { let lineFeature = turf.lineString(data); let buffer = turf.buffer(lineFeature, 100, { units: "meters" }); let linesBuffered = [buffer]; let nearbyDevices = []; const jkAllParams = { type: "1", }; const res = await getJkDeviceBusiness(jkAllParams); console.log("根据线获取周边在线设备", res.data); // this.onLineDeviceMap res.data.forEach((device) => { let devicePoint = turf.point([device.longitude, device.latitude]); let isDevicePoint = linesBuffered.some((buffer) => turf.booleanWithin(devicePoint, buffer) ); if (isDevicePoint) { nearbyDevices.push(device); } }); nearbyDevices = ArrUtils.bubbleSort(nearbyDevices, "distance"); // 筛选出路口监控port=8000 const filteredDevices = nearbyDevices.filter( (item) => item.port === "8000" ); // const devicesWithIndex = filteredDevices.map((item, index) => ({ ...item, index: index + 1, // 从1开始自增 })); console.log("筛选port=8000的设备", devicesWithIndex); this.showDevicesOnMap(devicesWithIndex); const mapSource = this.mapObj.getSource("data-point"); // 2. 清空现有数据 let dataSource = { type: "FeatureCollection", features: [], }; filteredDevices.map((item) => { console.log("devicesWithIndex", item); if (item) { dataSource.features.push({ id: item.id, type: "Feature", pointData: item, geometry: { type: "Point", coordinates: [item.longitude, item.latitude], }, properties: { ...item, title: item.name, "custom-image":'sxt' }, }); } }); mapSource.setData(dataSource); },为什么showDevicesOnMap生成图标和聚合点相差较大,修改showDevicesOnMap的逻辑改为设置新的聚合点,给我详细代码

await uniqueGroups.forEach(ptncode => { var selectedData = dataList.filter(item => item.DPTNCODE === ptncode); let url = "api/Sys_CodeRuleDts/GetSerialNumber?tableName=WM_IOForm&field=DSYSID"; this.http.post(url, null, false).then(resId => { //DSYSID url = "api/Sys_CodeRuleDts/GetFormCode?no=" + 552301; this.http.post(url, null, false).then(resNo => { //单号 let url = "api/WM_IOFormDts/add" selectedData.forEach((x, index) => { let param = { //从表数据提交 mainData: { DSYSID: resId.serialNumber, DSEQ: index + 1, DITEMCODE: x.DITEMCODE, DITEMNAME: x.DITEMNAME, DQTY: x.DQTY, DPRICE: x.DPRICE, DAMOUNT: x.DQTY * x.DPRICE, DDTSREMARK: x.DDTSREMARK, DLOADSEQ: x.DSEQ, DLOADSYSID: x.DSYSID, DLOADNO: x.DTICKETNO, DCOLORNAME: x.DCOLORNAME, DCURRENCYID: "CNY", DUNIT: '件', DSIZE: x.DSIZE } } this.http.post(url, param, false); }) url = "api/WM_IOForm/add"; let params = { mainData: { DSYSID: resId.serialNumber, DTICKETNO: resNo.dticketno, DPTNCODE: selectedData[0].DPTNCODE, DSALEOPID: selectedData[0].DSALEOPID, DMAKEDATE: dayjs().format('YYYY-MM-DD HH:mm:ss'), DTICKETDATE: dayjs().format('YYYY-MM-DD HH:mm:ss'), DMAKEOPID: this.$store.getters.getUserInfo() .userName, DWHCODE: '99', DHEADTYPE: '99', DSUBTYPE: '9901', DREMARK: 'pad端出库', DWHTYPE: 1, DMODULENO: 552301, DACCNO: '6228410344586559079', DSTATERUN: 0 } } this.http.post(url, params, false).then(result => { if (result.status) { this.post_ValidQuality(resId.serialNumber); } else { uni.hideLoading(); this.$toast(result.message); } }) }) }) })http.post的post方法是封装的promise,这段代码有什么问题

import auth from "@/plugins/auth"; import router, { constantRoutes, dynamicRoutes } from "@/router"; import { getRouters } from "@/api/modules/menu"; import Layout from "@/layout/index"; import ParentView from "@/components/ParentView"; import InnerLink from "@/layout/components/InnerLink"; // 匹配views里面所有的.vue文件 const modules = import.meta.glob("./../../views/**/*.vue"); const usePermissionStore = defineStore("permission", { state: () => ({ routes: [], addRoutes: [], defaultRoutes: [], topbarRouters: [], sidebarRouters: [], }), actions: { setRoutes(routes) { this.addRoutes = routes; this.routes = constantRoutes.concat(routes); }, setDefaultRoutes(routes) { this.defaultRoutes = constantRoutes.concat(routes); }, setTopbarRoutes(routes) { this.topbarRouters = routes; }, setSidebarRouters(routes) { this.sidebarRouters = routes; }, generateRoutes(roles) { return new Promise((resolve) => { getRouters().then((res) => { // 向后端请求路由数据 const sdata = JSON.parse(JSON.stringify(res.data)); const rdata = JSON.parse(JSON.stringify(res.data)); const defaultData = JSON.parse(JSON.stringify(res.data)); const sidebarRoutes = filterAsyncRouter(sdata); const rewriteRoutes = filterAsyncRouter(rdata, false, true); const defaultRoutes = filterAsyncRouter(defaultData); const asyncRoutes = filterDynamicRoutes(dynamicRoutes); asyncRoutes.forEach((route) => { router.addRoute(route); }); this.setRoutes(rewriteRoutes); this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)); this.setDefaultRoutes(sidebarRoutes); this.setTopbarRoutes(defaultRoutes); resolve(rewriteRoutes); }); }); }, }, }); // 遍历后台传来的路由字符串,转换为组件对象 function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { return asyncRouterMap.filter((route) => { if (type && route.children) { route.children = filterChildren(route.children); } if (route.component) { // Layout ParentView 组件特殊处理 if (route.component === "Layout") { route.component = Layout; } else if (route.component === "ParentView") { route.component = ParentView; } else if (route.component === "InnerLink") { route.component = InnerLink; } else { route.component = loadView(route.component); } } if (route.children != null && route.children && route.children.length) { route.children = filterAsyncRouter(route.children, route, type); } else { delete route["children"]; delete route["redirect"]; } return true; }); } function filterChildren(childrenMap, lastRouter = false) { var children = []; childrenMap.forEach((el) => { el.path = lastRouter ? lastRouter.path + "/" + el.path : el.path; if (el.children && el.children.length && el.component === "ParentView") { children = children.concat(filterChildren(el.children, el)); } else { children.push(el); } }); return children; } export const loadView = (view) => { let res; for (const path in modules) { const dir = path.split("views/")[1].split(".vue")[0]; if (dir === view) { res = () => modules[path](); } } return res; }; // 动态路由遍历,验证是否具备权限 export function filterDynamicRoutes(routes) { const res = []; routes.forEach((route) => { if (route.permissions) { if (auth.hasPermiOr(route.permissions)) { res.push(route); } } else if (route.roles) { if (auth.hasRoleOr(route.roles)) { res.push(route); } } }); return res; } export default usePermissionStore; 改成setup

// 新增:计算地理偏移坐标 calculateOffsetLngLat(originalLngLat, pixelOffset, map) { // 获取当前缩放级别 const zoom = map.getZoom(); // 将原始坐标转换为像素坐标 const originalPixel = map.project(originalLngLat); // 应用像素偏移 const offsetPixel = [ originalPixel.x + pixelOffset[0], originalPixel.y + pixelOffset[1], ]; // 将偏移后的像素坐标转回经纬度 return map.unproject(offsetPixel); }, //绘制路线周边设备上图 showDevicesOnMap(devices) { this.clearDeviceMarkers(); console.log("位置--===", devices); // 记录已处理的位置,避免重复计算偏移 const positionCache = new Map(); const map = this.mapObj; devices.forEach((device, index) => { const { longitude, latitude } = device; const markerElement = document.createElement("div"); markerElement.style.position = "relative"; markerElement.style.width = "24px"; markerElement.style.height = "30px"; // 设备图标 const icon = document.createElement("div"); icon.style.width = "30px"; icon.style.height = "20px"; icon.style.backgroundImage = device.online ? 'url("/images/jk_online.png")' : 'url("/images/jk_unline.png")'; icon.style.backgroundSize = "100% 100%"; icon.style.backgroundRepeat = "no-repeat"; icon.style.position = "absolute"; // 改为绝对定位 icon.style.bottom = "0"; // 贴在底部 icon.style.bottom = "0"; markerElement.appendChild(icon); // 序号标签 const label = document.createElement("div"); label.textContent = index + 1; label.style.position = "absolute"; label.style.top = "-14px"; label.style.left = "50%"; label.style.transform = "translateX(-50%)"; label.style.backgroundColor = this.selectedDeviceId === device.id ? "#FF6666" : "#45CCDA"; label.style.color = "white"; label.style.borderRadius = "50%"; label.style.width = "20px"; label.style.height = "20px"; label.style.textAlign = "center"; label.style.lineHeight = "20px"; label.style.fontSize = "12px"; markerElement.appendChild(label); // 处理坐标重叠:添加微小偏移 const cacheKey = ${longitude.toFixed(6)},${latitude.toFixed(6)}; let offsetPoint = [0, 0]; // [x, y] 像素偏移 if (positionCache.has(cacheKey)) { const count = positionCache.get(cacheKey); const maxPerCircle = 8; // 每圈最多8个点 const baseRadius = 40 * Math.pow(1.2, count); // 动态递增半径 const angle = (count % maxPerCircle) * ((2 * Math.PI) / maxPerCircle); // 计算像素偏移量 offsetPoint = [ baseRadius * Math.cos(angle), baseRadius * Math.sin(angle), ]; positionCache.set(cacheKey, count + 1); } else { positionCache.set(cacheKey, 1); } // 创建标记时应用偏移 // 将像素偏移转换为地理偏移 const offsetLngLat = this.calculateOffsetLngLat( [longitude, latitude], offsetPoint, map ); // 创建标记 const marker = new minemap.Marker(markerElement, { offset: [0, 0], anchor: "bottom", }) .setLngLat(offsetLngLat) .addTo(map); console.log( 设备${device.id} 理论坐标: [${longitude},${latitude}] 实际坐标: [${ longitude + offsetX },${latitude + offsetY}] ); // 点击事件 markerElement.addEventListener("click", (e) => { e.stopPropagation(); this.toggleDeviceSelection(device.id, label); }); this.deviceMarkers.push({ marker, device, label }); }); },一个图片都没有显示了

const axios = require('axios'); const fs = require('fs'); const path = require('path'); const logDirectory = path.join(__dirname, 'log'); const express = require('express'); const app = express(); const cors = require('cors'); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cors()); const mqtt = require('mqtt'); const WebSocket = require('ws'); const ws = require('ws'); const schedule = require('node-schedule'); //测试服 const wsUrl = 'ws://192.168.1.252:8852/eam/websocket/lora'; // const wsUrl = 'ws://192.168.1.195:8081/eam/websocket/lora'; let wsClient = new ws(wsUrl); if(!fs.existsSync(logDirectory)) { fs.mkdirSync(logDirectory); } function getLogFileName(){ const date = new Date(); const year =date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); return path.join(logDirectory, ${year}-${month}-${day}.txt); } function cleanOldLogs(){ const files = fs.readdirSync(logDirectory); const now = new Date(); files.forEach(file => { const filePath = path.join(logDirectory, file); const fileStats = fs.statSync(filePath); const fileDate = new Date(fileStats.mtime); const diffTime = Math.abs(now - fileDate); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays > 15){ fs.unlinkSync(filePath); } }); } function logMessage(message) { const logFileName = getLogFileName(); const logStream = fs.createWriteStream(logFileName, { flags: 'a' }); logStream.write(message); logStream.end(); } const originalLog = console.log; const originalError = console.error; console.log = (...args) => { const message = ${new Date().toISOString()} - LOG:${args.join(' ')}\n; logMessage(message); originalLog.apply(console, args); }; console.error = (...args) => { const message = ${new Date().toISOString()} - ERROR:${args.join(' ')}\n; logMessage(message); originalError.apply(console, args); }; //以上为日志相关以及依赖库引用 setInterval(cleanOldLogs, 24 * 60 * 60 * 1000); //redis const redis = require('redis'); const { status } = require('express/lib/response'); const { off } = require('process'); const client = redis.createClient({ // url: 'redis://192.168.1.252:6379' url: 'redis://127.0.0.1:6379' }); client.on('error', (err) => { console.error('Redis Client Error', err); }); client.connect().then(() => { console.log('Connected to Redis'); client.select(0) .then(() => { console.log('已切换到数据库', 0); }) .catch(error => { console.error('数据库切换失败', error); }); }).catch((err) => { console.error('Failed to connect to Redis', err); }); const Redis_KEY_PATTERNS = { MODEL_DEVEUI: (custid, specid) => LORA:MODEL:POWER:${custid}:${specid}, MODEL_VAL: (custid, specid) => LORA:MODEL:POWER:${custid}:${specid}:VAL }; const modelDataStore = { deviceMap: new Map(), //key: devEUI value: {custid,specid} specDeviceMap: new Map(), //key: {custid}-{specid} value: Set<devEUI> thresholdMap: new Map(), //key: {custid}-${specid} value:{work,await} } wsClient.on('open', () => { console.log('WebSocket连接成功'); }); //监听消息事件,增加查询保存设备列表以及对应规格信号数据 wsClient.on('message', (message) => { const msg = message.toString(); if (msg.startsWith('modelPower-') || msg.startsWith('modelDevices-')){ console.log('系统更新内容:',msg); const [type, key] = msg.split('-'); const parts = key.split(':'); const custid = parts[3]; const specid = parts[4]; if (type === 'modelDevices'){ refreshModelDevices(custid, specid); } else if (type === 'modelPower'){ refreshModelThresholds(custid, specid); } return; } else if (msg !== 'lora_refresh' && msg !== '连接成功!'){ resetHours(message); } else { // console.log('收到消息:',msg); } }); // WebSocket 客户端连接断开后的回调 wsClient.on('close', () => { console.error('WebSocket连接已关闭'); reconnect(); }); // WebSocket 客户端连接错误后的回调 wsClient.on('error', (error) => { console.error('WebSocket 错误: ', error); wsClient.close(); }); const messageQueue = []; //Websocket 重连 function reconnect() { console.log('正在重连……'); if(wsClient){ wsClient.close(); }; setTimeout(() => { wsClient = new WebSocket(wsUrl); wsClient.on('open', () => { console.log('WebSocket连接成功'); while (messageQueue.length > 0) { const message = messageQueue.shift(); wsClient.send(message); } }); wsClient.on('close', () => { console.error('WebSocket连接已关闭'); reconnect(); }); wsClient.on('error', (error) => { console.error('WebSocket 错误: ', error); wsClient.close(); }); wsClient.on('message', (message) => { const msg = message.toString(); if (msg.startsWith('modelPower-') || msg.startsWith('modelDevices-')){ const [type, key] = msg.split('-'); const parts = key.split(':'); const custid = parts[3]; const specid = parts[4]; if (type === 'modelDevices'){ refreshModelDevices(custid, specid); } else if (type === 'modelPower'){ refreshModelThresholds(custid, specid); } return; } else if (msg !== 'lora_refresh' && msg !== '连接成功!'){ resetHours(message); } else { console.log('收到消息:',msg); } }); }, 10000); }; const epcCache = new Map(); function getCurrentTime() { const now = new Date(); const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, '0'); const day = now.getDate().toString().padStart(2, '0'); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const seconds = now.getSeconds().toString().padStart(2, '0'); return ${year}-${month}-${day} ${hours}:${minutes}:${seconds}; }; const options = { host: '123.207.3.132', port: 1883, username: 'admin', password: 'admin', }; const mqttClient = mqtt.connect(options); mqttClient.on('connect', () => { console.log('MQTT客户端已连接'); // 订阅主题 const topic = 'application/+/device/+/event/up'; mqttClient.subscribe(topic, { qos: 1 }, (err) => { if (err) { console.error('订阅失败:', err); } else { console.log(已订阅主题: ${topic}); } }); }); const knownDevEUIs = new Set(); const lastData = {}; let workHoursStorage = {}; let workCustid = {}; let workError = new Set(); let awaitHoursStorage = {}; let readDevices = [ { devEUI: '0080e10101010123', applicationID: '1', custid: 'GZYS' } ]; // 当客户端接收到消息时 mqttClient.on('message',handleMQTTMessage); async function handleMQTTMessage(topic, message) { const mqttMessage = JSON.parse(message); const mqttDataBase64 = mqttMessage.data; const mqttDataBytes = Buffer.from(mqttDataBase64, 'base64'); // 提取所需的字段 const { deviceName, applicationID, applicationName, devEUI, fPort } = mqttMessage; const gatewayName = mqttMessage.rxInfo[0].name; const gatewayID = mqttMessage.rxInfo[0].gatewayID; // 判断是否包含特定前缀 const containsPrefix = mqttDataBytes.toString('hex').startsWith('5a0001'); const rightData = mqttDataBytes.toString('hex').startsWith('53472000') && mqttDataBytes.toString('hex').length === 64; if (containsPrefix) { const mqttDataHex = mqttDataBytes.toString('hex'); // const epcStartIndex = 20;//从第二十位开始提取epc编号 ps:出现过一次epc编号并非在第二十位开始的情况,暂不清楚原因 const epcStartIndex = mqttDataHex.indexOf('e');//从第一个e开始提取epc编号 const epcLength = 24; const epc = (mqttDataHex.substring(epcStartIndex, epcStartIndex + epcLength)).toUpperCase();//已将字母转为大写 const mqttDataUtf8 = mqttDataBytes.toString('utf-8'); // 提取 readerSerialNumber const numberIndex = mqttDataUtf8.indexOf('N'); const numberLength = 20; const readerSerialNumber = (mqttDataUtf8.substring(numberIndex, numberIndex + numberLength).toUpperCase()).toString('UTF-8'); const epcCacheEntry = epcCache.get(epc); const currentTime = getCurrentTime(); if (epcCacheEntry && (new Date(currentTime) - new Date(epcCacheEntry.timestamp)) < 5000) { return; } // 准备要发送的数据 const backendData = { "cid": "lora", "message": "lora_rfid_inout", "custid": "GZYS", "time": getCurrentTime(), "devEUI": devEUI, "deviceName": deviceName, "readerSerialNumber": readerSerialNumber, "epc": epc }; // 将数据转换为 JSON 字符串 const mqttString = JSON.stringify(backendData); // 更新缓存中的 epc 编号和时间戳 epcCache.set(epc, { timestamp: currentTime }); } else if(rightData) { let mqttData = mqttDataBytes.toString('hex'); const openStart = 8; const openEnd = 10; const electricityStart = 12; const electricityEnd = 20; const powerStart = 20; const powerEnd = 28; const currentStart = 28; const currentEnd = 32; const voltageStart = 32; const voltageEnd = 36; const lightStart = 44; const lightEnd = 48; const USBStart = 48; const USBEnd = 56; const openHex = mqttData.substring(openStart, openEnd); const electricityHex = mqttData.substring(electricityStart, electricityEnd); const powerHex = mqttData.substring(powerStart, powerEnd); const currentHex = mqttData.substring(currentStart, currentEnd); const voltageHex = mqttData.substring(voltageStart, voltageEnd); const lightHex = mqttData.substring(lightStart,lightEnd); const USB = mqttData.substring(USBStart,USBEnd); const electricity = parseInt(electricityHex, 16) * 0.001; const power = parseInt(powerHex, 16) * 0.1; const current = parseInt(currentHex, 16) * 0.001; const voltage = parseInt(voltageHex, 16) * 0.01; const light = (parseInt(lightHex.substring(1,2), 16)).toString(2).padStart(4, '0'); const isReadDevices = readDevices.find(device => device.devEUI === devEUI && device.applicationID === applicationID ) if (isReadDevices){ const backendData = { "cid": "lora", "message": "lora_reader", "time": getCurrentTime(), "custid": isReadDevices.custid, "devEUI": devEUI, "USB ID": USB } const wsMessage = JSON.stringify(backendData); if (wsClient.readyState === WebSocket.OPEN){ console.log('读卡器:',wsMessage); } else { console.error(心跳连接断开,读写器${devEUI}消息传递失败); } return; } else { const deviceInfo = modelDataStore.deviceMap.get(devEUI); if (!deviceInfo){ console.error(${devEUI}未配置规格型号); return; } const thresholds = modelDataStore.thresholdMap.get(${deviceInfo.custid}-${deviceInfo.specid}); const backendData = { "cid": "lora", "message": "lora", "custid": deviceInfo.custid, "time": getCurrentTime(), "applicationID": applicationID, "applicationName": applicationName, "gatewayName": gatewayName, "gatewayID": gatewayID, "devEUI": devEUI, "deviceName": deviceName, "fPort": fPort, "data": mqttData }; console.log(消息来自application/${applicationID}/devEUI/${devEUI},内容为${mqttData}); // console.log(电量: ${electricity} 度,获取数据为${electricityHex}); // console.log(功率: ${power} W,获取数据为${powerHex}); // console.log(电流: ${current} A,获取数据为${currentHex}); // console.log(电压: ${voltage} V,获取数据为${voltageHex}); console.log(警告灯: ${light},获取数据为${lightHex}); console.log(位置ID为${USB}); console.log(插座状态${openHex}); await updateGatewayStatus(gatewayID, gatewayName); // 将数据转换为 JSON 字符串 let mqttString = JSON.stringify(backendData); const value = power > thresholds.work ? '\"work\"' : power > thresholds.await ? '\"await\"' : '\"on\"'; deviceStatus(backendData.custid,backendData.devEUI,value); if (USB !== '00000000' && USB != null) { if (value === '\"work\"') { const custid = backendData.custid; const devEUI_USB = ${backendData.devEUI}_${USB}; if (!workHoursStorage[custid]){ workHoursStorage[custid] = {}; } if (workHoursStorage[custid][devEUI_USB]){ workHoursStorage[custid][devEUI_USB] += 10; } else { workHoursStorage[custid][devEUI_USB] = 10; } }else if (value === '\"await\"') { const custid = backendData.custid; const devEUI_USB = ${backendData.devEUI}_${USB}; if (!awaitHoursStorage[custid]){ awaitHoursStorage[custid] = {}; } if (awaitHoursStorage[custid][devEUI_USB]){ awaitHoursStorage[custid][devEUI_USB] += 10; } else { awaitHoursStorage[custid][devEUI_USB] = 10; } } if (openHex === '00') { let params = { "deviceQueueItem": { "confirmed": true, "data": "U0cFAQE=", "devEUI": devEUI, "fPort": 2, "jsonObject": "" } } console.log(${devEUI}加密狗已上线,允许插座通电); sendRequests(params); } } else if ((USB === '00000000' || USB == null) && openHex === '01'){ let params = { "deviceQueueItem": { "confirmed": true, "data": "U0cFAQA=", "devEUI": devEUI, "fPort": 2, "jsonObject": "" } } console.log(${devEUI}加密狗已离线,不允许插座通电); sendRequests(params); } //转义字符串 function escapeString(str) { return str.replace(/\\/g, '\\\\') .replace(/"/g, '\\"'); } const dataString = "\"" + escapeString(mqttString) + "\""; const lightData = "\"" + escapeString(light) + "\""; if (!knownDevEUIs.has(${USB}-${backendData.devEUI})) { knownDevEUIs.add(${USB}-${backendData.devEUI}); } const last = lastData[devEUI] || {}; const dataChanged = !last || last.USB !== USB; if(dataChanged || (last.light !== '0000' && light === '0000')) { lastData[devEUI] = { light: light, USB: USB, time: getCurrentTime(), custid: backendData.custid }; if(wsClient.readyState === WebSocket.OPEN) { if (!last.light || (last.light !== '0000' && light === '0000')) { client.set(EAM:LORA:BILL:LIGHT:TYPE:${backendData.custid}_${backendData.devEUI},lightData); } wsClient.send(mqttString); console.log("位置改变",backendData.time); }else{ messageQueue.push(mqttString); } }else{ lastData[devEUI] = { light: light, USB: USB, time: getCurrentTime(), custid: backendData.custid }; if(light !== '0000' && wsClient.readyState === WebSocket.OPEN) { client.set(EAM:LORA:BILL:LIGHT:TYPE:${backendData.custid}_${backendData.devEUI},lightData); wsClient.send(mqttString); console.log("灯光改变"); }; }; const expireTime = Date.now() + 3900000; client.ZADD(LORA:GATEWAY:${USB}:${backendData.devEUI}, {"score":expireTime,"value": dataString}) .catch(error => { console.error('error', error); }); } } else { let mqttData = mqttDataBytes.toString('hex'); const backendData = { 'cid': 'lora', 'message': 'lora', 'custid': 'GZYS', 'time': getCurrentTime(), 'applicationID': applicationID, 'applicationName': applicationName, 'gatewayName': gatewayName, 'gatewayID': gatewayID, 'devEUI': devEUI, 'deviceName': deviceName, 'fPort': fPort, 'data': mqttData }; console.error(错误格式的数据来自application/${applicationID}/devEUI/${devEUI},内容为${mqttData}); let mqttString = JSON.stringify(backendData); function escapeString(str) { return str.replace(/\\/g, '\\\\') .replace(/"/g, '\\"'); } const dataString = "\"" + escapeString(mqttString) + "\""; if (!workError.has(${backendData.custid}-${backendData.devEUI})) { workError.add(${backendData.custid}-${backendData.devEUI}); wsClient.send(); } // const expireTime = Date.now() + 1296000000; //半个月 const expireTime = Date.now() + 24 * 60 * 60 * 1000; //一天 client.ZADD(LORA:GATEWAY:${backendData.custid}:ERROR:${backendData.devEUI},{"score":expireTime,'value': dataString}) .catch(err => { console.error('保存错误日志时出现错误',err); }) } } function escapeString(str) { // return str.replace(/_/g, '\\_'); return str .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/:/g, '\\:') .replace(/;/g, '\\;'); } async function sendRequests(params) { for (let i = 0; i < 3; i++){ try { if (params.deviceQueueItem.devEUI === '0080e10101010110') { const res = await axios.post( http://123.207.3.132:8080/api/devices/${params.deviceQueueItem.devEUI}/queue, params, { headers: { Authorization: Bearer ${jwt}, } } ); console.log(第${i+1}次触发成功,res.status); } } catch (err){ console.error(第${i+1}次触发失败,err.response.data || err); } await new Promise(resolve => setTimeout(resolve, 1000)); } } setInterval(() => { Object.keys(workHoursStorage).forEach(custid => { const entries = workHoursStorage[custid]; const entriesArray = Object.keys(entries).map(devEUI_USB => { return ${devEUI_USB}:${entries[devEUI_USB]}; }) const valueString = JSON.stringify(entriesArray.join(';')); // const value = "\"" + escapeString(valueString) + "\""; client.set(LORA:GATEWAY:${custid}:WORKHOURS, valueString) .catch(err => { console.error('工作时长存储错误',err); }); }); Object.keys(awaitHoursStorage).forEach(custid => { const entries = awaitHoursStorage[custid]; const entriesArray = Object.keys(entries).map(devEUI_USB => { return ${devEUI_USB}:${entries[devEUI_USB]}; }) const valueString = JSON.stringify(entriesArray.join(';')); client.set(LORA:GATEWAY:${custid}:AWAITHOURS, valueString) .catch(err => { console.error('待机时长存储错误',err); }) }) }, 30000); const get = { 'cid': 'lora', 'message': 'lora_position_get_custid' }; const getString = JSON.stringify(get); const rule = new schedule.RecurrenceRule(); rule.hour = 23; rule.minute = 59; const job = schedule.scheduleJob(rule, async () => { try { wsClient.send(getString); await resetError(); await refreshAllThresholds(); } catch (err) { console.error('定时任务失败:',err); } }) async function resetError() { try { console.log('开始清理错误数据'); const errorKeys = await client.KEYS('LORA:GATEWAY:*:ERROR:*'); await Promise.all(errorKeys.map(async (key) => { try { const members = await client.zRange(key, 0, -1); const memberScores = await Promise.all(members.map(async (member) => ({ member, score: await client.zScore(key, member) }))); const toRemove = memberScores.filter(({ score }) => { const isValid = !isNaN(score) && score <= Date.now(); return isValid; }).map(({ member }) => member); if (toRemove.length > 0){ await client.zRem(key, toRemove); }else{ console.log(${key}无过期数据); } } catch (err) { console.error(处理${key}失败:,err); } })) } catch (err) { console.error('错误处理失败:',err); } } async function resetHours(message){ workHoursStorage = {}; awaitHoursStorage = {}; workCustid = message.toString().split(',').filter(element => element !== ''); console.log('已经调用resetHorus函数,获取到workCustid:',workCustid); await Promise.all( workCustid.map(custid => { const key1 = LORA:GATEWAY:${custid}:WORKHOURS; return client.get(key1) .then(reply => { return client.set(key1, ' ') .then(reply => { console.log('工作时长已重置',reply); }) }) .catch(err => { console.error('key1处理失败:', custid, err); }); }) ); await Promise.all(workCustid.map(custid => { const key2 = LORA:GATEWAY:${custid}:AWAITHOURS; return client.get(key2) .then(reply => { return client.set(key2, ' ') .then(reply => { console.log('待机时长已重置',reply); }) }) .catch(err => { console.error('key2处理失败:', custid, err); }); })) }; function deviceStatus(custid,devEUI,value){ if(value === '\"off\"'){ client.del(EAM:LORA:BILL:LIGHT:TYPE:${custid}_${devEUI}); client.setEx(EAM:LORA:BILL:TYPE:${custid}_${devEUI},1800,value); }else{ client.setEx(EAM:LORA:BILL:TYPE:${custid}_${devEUI},60,value); } } function checkAndClearOldData() { const currentTime = Date.now(); for (const devEUI in lastData) { if (currentTime - new Date(lastData[devEUI].time).getTime() > 60000) { deviceStatus(lastData[devEUI].custid,devEUI,'\"off\"'); const lightData = "\"" + '0000' + "\""; client.set(EAM:LORA:BILL:LIGHT:TYPE:${lastData[devEUI].custid}_${devEUI}, lightData); delete lastData[devEUI]; console.log(设备 ${devEUI} 的状态已被删除,因为超过一分钟没有接收到数据。); } }; //定期清理epcCache中的内容 // epcCache.forEach((value, key) => { // if (currentTime - value.timestamp > 5000) epcCache.delete(key); // }); } // 设置定时器,每分钟调用一次checkAndClearOldData函数 setInterval(checkAndClearOldData, 60000); async function refreshModelDevices(custid, specid) { try { const devices = await client.get(Redis_KEY_PATTERNS.MODEL_DEVEUI(custid, specid)); const validDevices = (devices || '').split(',').map(d => d.replace(/["']/g, '').trim()).filter(d => d !== ''); updateModelDevices(custid, specid, validDevices); console.log([设备更新] ${custid}-${specid} 完成,设备数量: ${validDevices.length}); } catch (err) { console.error([设备更新] ${custid}-${specid} 失败:, err.message); } } async function refreshModelThresholds(custid, specid) { try { const thresholdsVal = await client.get(Redis_KEY_PATTERNS.MODEL_VAL(custid, specid)); if (thresholdsVal){ const cleanedVal = thresholdsVal.replace(/["']/g, '').trim(); const parts = cleanedVal.split(',').map(s => s.trim()).filter(s => s !== ''); if (parts.length !== 3){ throw new Error(非法阈值格式:${thresholdsVal}); } const parsed = parts.map((s, idx) => { const num = parseFloat(s); return num; }); [, standby, work] = parsed; } updateModelThresholds(custid, specid, { standby, work }); console.log([阈值更新] ${custid}-${specid} 完成,新阈值: standby=${standby}, work=${work}); } catch (err) { console.error([阈值更新] ${custid}-${specid} 失败:, err.message); } } function updateModelDevices(custid, specid, newDevices) { const specKey = ${custid}-${specid}; const oldDevices = modelDataStore.specDeviceMap.get(specKey) || new Set(); oldDevices.forEach(devEUI => { if (!newDevices.includes(devEUI)){ modelDataStore.deviceMap.delete(devEUI); } }); newDevices.forEach(devEUI => { if(!modelDataStore.deviceMap.has(devEUI)){ modelDataStore.deviceMap.set(devEUI, { custid, specid }); } }); modelDataStore.specDeviceMap.set(specKey, new Set(newDevices)); } function updateModelThresholds(custid, specid, thresholds) { const specKey = ${custid}-${specid}; modelDataStore.thresholdMap.set(specKey, { await: thresholds.standby, work: thresholds.work }); } async function refreshAllThresholds() { try { const deviceKeys = await client.KEYS(Redis_KEY_PATTERNS.MODEL_DEVEUI('*','*')); const thresholdKyes = await client.KEYS(Redis_KEY_PATTERNS.MODEL_VAL('*','*')); await Promise.all(deviceKeys.map(key => { const [,,, custid, specid] = key.split(':'); return refreshModelDevices(custid, specid); })); await Promise.all(thresholdKyes.map(key => { const [,,, custid, specid] = key.split(':'); return refreshModelThresholds(custid, specid); })); // console.log('全量:',modelDataStore); } catch (err) { console.error('全量阈值同步失败:',err); } } refreshAllThresholds(); //设备上传数据设置到期删除 setInterval(() => { const currentTime = Date.now(); knownDevEUIs.forEach((key) => { const [USB, devEUI] = key.split('-'); const zSetName = LORA:GATEWAY:${USB}:${devEUI}; client.zRange(zSetName, "0", "-1","WITHSCORES") .then(members => { Promise.all(members.map(member => client.zScore(zSetName, member))) .then(scores => { scores.forEach((score, index) => { if (score <= currentTime) { client.zRem(zSetName, members[index]) .catch(err => { console.error('移除成员失败:', err); }); } }); }) .catch(err => { console.error('获取分数失败:', err); }); }) .catch(err => { console.error('获取数据失败:', err); }); }); }, 300000); // 当客户端断开连接时 mqttClient.on('close', () => { console.error('MQTT客户端已断开连接'); mqttReconnect(); }); function mqttReconnect() { console.log('MQTT重连中'); if (mqttClient){ mqttClient.end(true); }; setTimeout(() => { mqttClient = mqtt.connect(options); mqttClient.on('connect', () => { const topic = 'application/+/device/+/event/up'; mqttClient.subscribe(topic, { qos: 1 }, (err) => { if (err) { console.error('订阅失败:',err); } else { console.log('已订阅主题:',topic); } }) }); mqttClient.on('message', handleMQTTMessage); mqttClient.on('close', () => { console.error('MQTT客户端已断开连接'); mqttReconnect(); }); mqttClient.on('error', (err) => { console.error('MQTT客户端发生错误:', err); mqttClient.end(true); }); }, 5000); } // 当客户端发生错误时 mqttClient.on('error', (err) => { console.error('MQTT客户端发生错误:', err); mqttClient.end(true); }); let jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5X2lkIjoiZjA5ZjViMWYtYTFjMy00NjZkLTkyNjItOGE1YTkzYTY1NTU5IiwiYXVkIjoiYXMiLCJpc3MiOiJhcyIsIm5iZiI6MTczMjU5MTM5Niwic3ViIjoiYXBpX2tleSJ9.32w2IsaBTTM8nZuKHxwwXJwT-mAeR2B5ubkDzMgU1XA'; let offGateways = []; //从redis中获取离线网关的名单 function loadGateways(){ client.keys(LORA:GATEWAYSTATUS:*:*).then(reply => { offGateways = []; reply.forEach(key => { const parts = key.split(':'); const organizationName = parts[2]; const gatewayID = parts[3]; offGateways.push({ organizationName, gatewayID }); }); }).catch(err => { console.error("获取离线网关名单时出错",err); }); } //获取绑定组织名称和ID function getOrgID(){ axios.get(http://123.207.3.132:8080/api/organizations?limit=99999, { headers: { Authorization: jwt, } }).then(response => { const orgMap = new Map(); response.data.result.forEach(org => { orgMap.set(org.id, org.name); }); checkGatewayStatus([...orgMap.keys()], orgMap); }) .catch(err => { console.error("获取ID出现错误",err) }) }; //网关工作情况 function checkGatewayStatus(organizationIDs,orgIdNameMap) { const promises = organizationIDs.map(async orgID => { try { const response = await axios.get(http://123.207.3.132:8080/api/gateways?limit=99999&offset=0&organizationID=${orgID}, { headers: { Authorization: jwt, } }); const rawOrgName = orgIdNameMap.get(orgID); const safeOrgName = rawOrgName.replace(/[^a-zA-Z0-9]/g, '_'); response.data.result.forEach(gateway => { const gatewayID = gateway.id; const redisKey = LORA:GATEWAYSTATUS:${safeOrgName}:${gatewayID}; const lastSeenAt = gateway.lastSeenAt ? new Date(gateway.lastSeenAt).getTime() : null; const isOffline = lastSeenAt && (Date.now() - lastSeenAt) > 32000; if (isOffline){ const exists = offGateways.some(gw => gw.gatewayID === gatewayID); if (!exists){ offGateways.push({ organizationName: rawOrgName, gatewayID }); } client.set(redisKey, '\"off\"').then(() => console.log([${safeOrgName}] 网关${gatewayID} 离线状态已记录)) .catch(err => console.error([${safeOrgName}] Redis写入失败, err)); } else { const index = offGateways.findIndex(gw => gw.gatewayID === gatewayID); if (index !== -1){ offGateways.splice(index, 1); } client.del(redisKey).then(() => console.log([${safeOrgName}] 网关${gatewayID} 在线状态已更新)) .catch(err => console.error([${safeOrgName}] Redis删除失败, err)); } }); } catch (error) { console.error("checkDevicesStatus函数出错", error); } }); Promise.all(promises).then(() => { console.log("离线网关列表",JSON.stringify(offGateways)); }); }; async function updateGatewayStatus(gatewayID, gatewayName) { const index = offGateways.findIndex(gw => gw.gatewayID === gatewayID); if (index !== -1){ const { organizationName } = offGateways[index]; offGateways.splice(index, 1); const safeOrgName = organizationName.replace(/[^a-zA-Z0-9]/g, '-'); const redisKey = LORA:GATEWAYSTATUS:${safeOrgName}:${gatewayID}; try { await client.del(redisKey); console.log([实时更新] 网关 ${gatewayName} (${gatewayID}) 重新上线); } catch (err) { console.error([${organizationName}] Redis删除失败, err); } } } loadGateways(); getOrgID(); setInterval(getOrgID, 15000); app.get('/health', async (req, res) => { const allClients = { redis: { connected: client.isReady, name: 'Redis数据库' }, mqtt: { connected: mqttClient.connected, name: 'MQTT消息队列' }, websocket: { connected: wsClient.readyState === WebSocket.OPEN, name: 'WebSocket连接' }, }; const statusReport = { status: 'healthy', allClients: {}, issues: [], timestamp: Date.now() }; let allHealthy = true; Object.entries(allClients).forEach(([key, client]) => { const isConnected = client.connected; statusReport.allClients[key] = isConnected ? 'connected' : 'disconnected'; if (!isConnected) { statusReport.issues.push({ clients: key, name: client.name, description: ${client.name}连接失败 }); allHealthy = false; } }); statusReport.status = allHealthy ? 'healthy' : 'unhealthy'; res.status(allHealthy ? 200 : 503).json(statusReport); }); app.listen(3000); process.on('unhandledRejection', (reason, promise) => { console.error('未处理的拒绝:', reason); }) process.on('uncaughtException', (err) => { console.error('未捕获的异常:', err); }) process.on('SIGINT', () => { console.log('程序正在关闭...'); mqttClient.end(); client.quit(); wsClient.close(); process.exit(); }); 遍历我的代码,查看哪里会发生修改const变量的情况,并且解释一下MQTT客户端发生错误: Error: read ECONNRESET,这是连接超时导致的错误吗

<template> <el-dialog v-model="visible" title="数据选择器" width="75%" top="5vh" align-center destroy-on-close > <el-input v-model="treeFilterText" placeholder="输入机构名称" clearable prefix-icon="Search" /> <el-tree ref="treeRef" :data="treeData" :props="treeProps" :filter-node-method="filterNode" node-key="value" highlight-current @node-click="handleNodeClick" /> <el-table ref="mainTableRef" :data="tableData" style="width: 100%" height="calc(60vh - 110px)" v-loading="loading" @selection-change="handleSelectionChange" > <el-table-column type="selection" width="55" /> <el-table-column prop="name" label="名称" min-width="180" /> <el-table-column prop="type" label="类型" width="120" /> <el-table-column prop="date" label="日期" width="150" /> <el-table-column prop="status" label="状态" width="100"> <template #default="{ row }"> <el-tag :type="row.status === 'active' ? 'success' : 'info'"> {{ row.status === "active" ? "启用" : "停用" }} </el-tag> </template> </el-table-column> </el-table> <el-button type="primary" @click="showSelectedTable" :disabled="selectedData.length === 0" > <el-icon><View /></el-icon> 已选择({{ selectedData.length }}) </el-button> <el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize" :page-sizes="[5, 10, 20, 50]" layout="sizes, prev, pager, next, jumper" :total="pagination.total" background @size-change="fetchTableData" @current-change="fetchTableData" /> <el-button @click="visible = false">取消</el-button> <el-button type="primary" @click="confirmSelection">确认</el-button> </el-dialog> <el-dialog v-model="selectedDialogVisible" title="已选择数据" width="70%" append-to-body destroy-on-close > <el-button type="danger" :disabled="selectedRows.length === 0" @click="batchRemoveSelected"> <el-icon><Delete /></el-icon> 批量删除 </el-button> 已选择 {{ selectedData.length }} 项数据 <el-table :data="selectedData" border @selection-change="handleSelectedTableSelection"> <el-table-column type="selection" width="55" /> <el-table-column prop="name" label="名称" min-width="180" /> <el-table-column prop="type" label="类型" width="120" /> <el-table-column prop="date" label="日期" width="150" /> <el-table-column label="操作" width="100" fixed="right"> <template #default="{ $index }"> <el-button type="danger" icon="Delete" circle @click="removeSelectedItem($index)" /> </template> </el-table-column> </el-table> <template #footer> <el-button @click="selectedDialogVisible = false">关闭</el-button> </template> </el-dialog> </template> <script lang="ts" setup> import { ref, reactive, watch, onMounted, nextTick } from "vue"; import { ElMessage, ElTree, ElTable, FilterNodeMethodFunction } from "element-plus"; import type { Node } from "element-plus/es/components/tree/src/tree.type"; import { View, Delete } from "@element-plus/icons-vue"; // 类型定义 interface TreeNode { value: number | string; label: string; children?: TreeNode[]; } interface TableData { id: number | string; name: string; type: string; date: string; status: "active" | "inactive"; } interface Pagination { currentPage: number; pageSize: number; total: number; } // 弹窗显示控制 const visible = ref(false); const selectedDialogVisible = ref(false); // 树形结构数据 const treeFilterText = ref(""); const treeRef = ref<InstanceType<typeof ElTree>>(); const treeData = ref<TreeNode[]>([ { value: 1, label: "一级节点", children: [ { value: 11, label: "二级节点1" }, { value: 12, label: "二级节点2" }, { value: 13, label: "二级节点3", children: [ { value: 131, label: "三级节点1" }, { value: 132, label: "三级节点2" }, ], }, ], }, { value: 2, label: "另一个一级节点", children: [ { value: 21, label: "二级节点A" }, { value: 22, label: "二级节点B" }, ], }, ]); const treeProps = { children: "children", label: "value", }; // 表格数据 const tableData = ref([]); const loading = ref(false); const mainTableRef = ref(); // 分页配置 const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0, }); // 所有选中ID集合,全局存储选中项的ID(使用Set避免重复) const allSelectedIds = ref<Set<string | number>>(new Set()); const selectedData = ref([]); // 所有选中数据 const currentPageSelected = ref([]); // 当前页选中数据 const selectedRows = ref([]); // 在已选表格中选择的行 const selectAllCurrentPage = ref(false); // 全选当前页 const selectAllPages = ref(false); // 全选所有页 // 树节点搜索 // 树节点过滤 watch(treeFilterText, (val) => { treeRef.value?.filter(val); }); /** * 筛选 */ function filterNode(value: string, data: any) { if (!value) { return true; } return data.label.indexOf(value) !== -1; } // 获取表格数据 const fetchTableData = async () => { try { loading.value = true; // 模拟API请求 await new Promise((resolve) => setTimeout(resolve, 500)); // 生成模拟数据 const mockData: TableData[] = []; const types = ["类型A", "类型B", "类型C", "类型D"]; const statuses: ("active" | "inactive")[] = ["active", "inactive"]; for (let i = 0; i < pagination.pageSize; i++) { mockData.push({ id: i + (pagination.currentPage - 1) * pagination.pageSize, name: 项目 ${i + 1 + (pagination.currentPage - 1) * pagination.pageSize}, type: types[Math.floor(Math.random() * types.length)], date: 2023-${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 28) + 1}, status: statuses[Math.floor(Math.random() * statuses.length)], }); } tableData.value = mockData; pagination.total = 85; // 模拟总条数 // 更新选择状态 updateMainTableSelection(); } catch (error) { ElMessage.error("数据加载失败"); } finally { loading.value = false; } }; // 表格选择变化 const handleSelectionChange = (selection: TableData[]) => { console.log("handleSelectionChange"); // 获取当前页所有ID const currentIds = tableData.value.map((row) => row.id); // 从全局集合中移除当前页所有ID currentIds.forEach((id) => { if (!allSelectedIds.value.has(id)) { allSelectedIds.value.delete(id); // 从全局集合中移除 } }); // 从全局集合中移除当前页数据 selectedData.value = selectedData.value.filter((item) => !currentIds.includes(item.id)); // 将当前页新选中的加入全局集合 currentPageSelected.value = selection; // 更新全局选中ID集合 selection.forEach((item) => allSelectedIds.value.add(item.id)); // 更新选中数据 selectedData.value = Array.from(allSelectedIds.value) .map((id) => [...currentPageSelected.value, ...selectedData.value].find((item) => item.id === id) ) .filter(Boolean) as TableData[]; // 更新全选状态 selectAllCurrentPage.value = selection.length === tableData.value.length; selectAllPages.value = allSelectedIds.value.size === pagination.total; }; // 同步表格选中状态 const syncSelection = () => { nextTick(() => { if (!mainTableRef.value) return; mainTableRef.value.clearSelection(); tableData.value.forEach((row) => { if (allSelectedIds.value.has(row.id)) { mainTableRef.value.toggleRowSelection(row, true); } }); }); }; // 已选表格中的选择变化 const handleSelectedTableSelection = (selection: TableData[]) => { selectedRows.value = selection; }; // 显示已选数据弹窗 const showSelectedTable = () => { selectedDialogVisible.value = true; }; // 删除单个已选项 const removeSelectedItem = (index: number) => { const removedItem = selectedData.value.splice(index, 1)[0]; // 从全局ID集合中移除 allSelectedIds.value.delete(removedItem.id); // 更新主表格选中状态 updateMainTableSelection(); // 如果删除的是当前页的数据,取消其在表格中的选中状态 if (tableData.value.some((item) => item.id === removedItem.id)) { nextTick(() => { const row = tableData.value.find((item) => item.id === removedItem.id); if (row && mainTableRef.value) { mainTableRef.value.toggleRowSelection(row, false); } }); } ElMessage.success("已移除选择项"); }; // 批量删除已选项 const batchRemoveSelected = () => { if (selectedRows.value.length === 0) return; // 从全局ID集合中移除选中的行 const idsToRemove = new Set(selectedRows.value.map((item) => item.id)); idsToRemove.forEach((id) => allSelectedIds.value.delete(id)); // 更新选中数据 selectedData.value = selectedData.value.filter((item) => !idsToRemove.has(item.id)); ElMessage.success(已移除 ${selectedRows.value.length} 个选项); selectedRows.value = []; // 更新主表格选中状态 updateMainTableSelection(); }; // 更新主表格选中状态 const updateMainTableSelection = () => { if (!mainTableRef.value) return; // 先清除所有选中状态 mainTableRef.value.clearSelection(); // 重新选中当前页中已选择的数据 nextTick(() => { // 重新设置当前页的选中状态 const selectedIds = allSelectedIds.value; tableData.value.forEach((row) => { if (selectedIds.has(row.id)) { // 使用 setTimeout 0 确保每次操作都在下一个事件循环中执行 setTimeout(() => { mainTableRef.value.toggleRowSelection(row, true); }, 0); } }); }); // 更新全选状态 selectAllCurrentPage.value = currentPageSelected.value.length === tableData.value.length; selectAllPages.value = allSelectedIds.value.size === pagination.total; }; // 全选/取消全选当前页 const toggleSelectCurrentPage = () => { if (selectAllCurrentPage.value) { // 全选当前页 tableData.value.forEach((row) => { allSelectedIds.value.add(row.id); }); } else { // 取消全选当前页 tableData.value.forEach((row) => { allSelectedIds.value.delete(row.id); }); } // 更新选中数据 selectedData.value = Array.from(allSelectedIds.value) .map( (id) => tableData.value.find((item) => item.id === id) || selectedData.value.find((item) => item.id === id) ) .filter(Boolean) as TableData[]; // 更新表格选中状态 updateMainTableSelection(); }; // 全选/取消全选所有页 const toggleSelectAllPages = () => { if (selectAllPages.value) { // 全选所有页 allSelectedIds.value = new Set(Array.from({ length: pagination.total }, (_, i) => i)); // 实际项目中需要从所有页获取数据 selectedData.value = [...tableData.value]; // 简化示例 } else { // 取消全选所有页 allSelectedIds.value.clear(); selectedData.value = []; } // 更新表格选中状态 updateMainTableSelection(); }; // 确认选择 const confirmSelection = () => { if (selectedData.value.length === 0) { ElMessage.warning("请至少选择一条数据"); return; } emit("confirm", selectedData.value); visible.value = false; }; // 树节点点击事件 const handleNodeClick = (node: Node) => { fetchTableData(); }; // 暴露打开方法 const open = () => { visible.value = true; // 重置选择状态 allSelectedIds.value.clear(); selectedData.value = []; selectAllCurrentPage.value = false; selectAllPages.value = false; fetchTableData(); }; // 监听已选择数据变化,更新表格选中状态 watch( selectedData, () => { selectAllPages.value = allSelectedIds.value.size === pagination.total; }, { deep: true } ); // 初始化树形结构 onMounted(() => { // 展开第一层节点 setTimeout(() => { treeData.value.forEach((node) => { treeRef.value?.store.nodesMap[node.value]?.expand(); }); }, 100); }); // 定义事件 const emit = defineEmits<{ (e: "confirm", data: TableData[]): void; }>(); defineExpose({ open }); </script> <style scoped lang="scss"> .conn-container { display: flex; height: 100%; gap: 16px; } :deep(.el-dialog) { padding: 1px; .el-dialog__header { padding: 10px; } .el-dialog__body { padding: 10px; } } .tree-panel { width: 30%; height: 100%; display: flex; flex-direction: column; padding: 16px; } .table-panel { width: 70%; display: flex; flex-direction: column; gap: 5px; } .table-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding: 5px 0; .el-button { margin-right: auto; } } .dialog-footer { display: flex; justify-content: flex-end; margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--el-border-color-light); gap: 12px; } .batch-operations { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; .selected-count { color: var(--el-text-color-secondary); font-size: 14px; } } /* 响应式调整 */ @media (max-width: 1200px) { .dialog-body { flex-direction: column; height: auto; } .tree-panel, .table-panel { width: 100%; padding: 0; } .tree-panel { border-right: none; border-bottom: 1px solid var(--el-border-color-light); padding-bottom: 20px; margin-bottom: 20px; height: 40vh; } .table-toolbar { flex-direction: column; align-items: flex-start; .selection-controls { width: 100%; justify-content: space-between; margin: 10px 0; } } } /* 美化滚动条 */ .tree-container::-webkit-scrollbar { width: 6px; height: 6px; } .tree-container::-webkit-scrollbar-thumb { background-color: var(--el-color-primary-light-5); border-radius: 3px; } .tree-container::-webkit-scrollbar-track { background: var(--el-fill-color-lighter); } </style> 优化样式,树的宽度为当前页面的100

// 工具函数 const filterChildren = (childrenMap, lastRouter = false) => { const children = []; childrenMap.forEach((el) => { el.path = lastRouter ? ${lastRouter.path}/${el.path} : el.path; if (el.children?.length && el.component === "ParentView") { children.push(...filterChildren(el.children, el)); } else { children.push(el); } }); return children; }; const loadView = (view) => { let res; for (const path in modules) { const dir = path.split("views/")[1].split(".vue")[0]; if (dir === view) { res = () => modules[path](); } } return res; }; const filterAsyncRouter = ( asyncRouterMap, lastRouter = false, type = false ) => { return asyncRouterMap.filter((route) => { if (type && route.children) { route.children = filterChildren(route.children); } if (route.component) { switch (route.component) { case "Layout": route.component = Layout; break; case "ParentView": route.component = ParentView; break; case "InnerLink": route.component = InnerLink; break; default: route.component = loadView(route.component) ? loadView(route.component) : notRouterComp; } } if (route.children?.length) { route.children = filterAsyncRouter(route.children, route, type); } else { delete route.children; delete route.redirect; } return true; }); }; const filterDynamicRoutes = (routes) => { return routes.filter((route) => { if (route.permissions) return auth.hasPermiOr(route.permissions); if (route.roles) return auth.hasRoleOr(route.roles); return true; }); }; // 操作方法 const setRoutes = (newRoutes) => { addRoutes.value = newRoutes; routes.value = [...constantRoutes, ...newRoutes]; }; const setDefaultRoutes = (newRoutes) => { defaultRoutes.value = [...constantRoutes, ...newRoutes]; }; const setTopbarRoutes = (newRoutes) => { topbarRouters.value = newRoutes; }; const setSidebarRouters = (newRoutes) => { sidebarRouters.value = newRoutes; }; // 核心方法 const generateRoutes = async (roles) => { try { const res = await getRouters(); const sdata = JSON.parse(JSON.stringify(res.data)); const rdata = JSON.parse(JSON.stringify(res.data)); const defaultData = JSON.parse(JSON.stringify(res.data)); const sidebarRoutes = filterAsyncRouter(sdata); const rewriteRoutes = filterAsyncRouter(rdata, false, true); const defaultRoutes = filterAsyncRouter(defaultData); // 处理动态路由 const asyncRoutes = filterDynamicRoutes(dynamicRoutes); asyncRoutes.forEach((route) => router.addRoute(route)); // 更新状态 setRoutes(rewriteRoutes); setSidebarRouters([...constantRoutes, ...sidebarRoutes]); setDefaultRoutes(sidebarRoutes); setTopbarRoutes(defaultRoutes); return rewriteRoutes; } catch (error) { console.error("路由生成失败:", error); return []; } }; 如果是你 你会怎么去写

// 匹配views里面所有的.vue文件(保持原样) const modules = import.meta.glob("./../../views/**/*.vue"); const usePermissionStore = defineStore("permission", () => { // 状态定义 const routes = ref([]); const addRoutes = ref([]); const defaultRoutes = ref([]); const topbarRouters = ref([]); const sidebarRouters = ref([]); // 工具函数 const filterChildren = (childrenMap, lastRouter = false) => { const children = []; childrenMap.forEach((el) => { el.path = lastRouter ? ${lastRouter.path}/${el.path} : el.path; if (el.children?.length && el.component === "ParentView") { children.push(...filterChildren(el.children, el)); } else { children.push(el); } }); return children; }; const loadView = (view) => { let res; for (const path in modules) { const dir = path.split("views/")[1].split(".vue")[0]; if (dir === view) { res = () => modules[path](); } } return res; }; const filterAsyncRouter = (asyncRouterMap,lastRouter = false,type = false) => { return asyncRouterMap.filter((route) => {if (type && route.children) {route.children = filterChildren(route.children)} if (route.component) { switch (route.component) { case "Layout": route.component = Layout; break; case "ParentView": route.component = ParentView; break; case "InnerLink": route.component = InnerLink; break; default: route.component = loadView(route.component) ? loadView(route.component) : notRouterComp; } } if (route.children?.length) {route.children = filterAsyncRouter(route.children, route, type)} else {delete route.children;delete route.redirect;} return true; }); }; const filterDynamicRoutes = (routes) => {return routes.filter((route) => {if (route.permissions) return auth.hasPermiOr(route.permissions);if (route.roles) return auth.hasRoleOr(route.roles);return true; }); }; // 操作方法 const setRoutes = (newRoutes) => {addRoutes.value = newRoutes;routes.value = [...constantRoutes, ...newRoutes]; }; const setDefaultRoutes = (newRoutes) => {defaultRoutes.value = [...constantRoutes, ...newRoutes]; }; const setTopbarRoutes = (newRoutes) => {topbarRouters.value = newRoutes; }; const setSidebarRouters = (newRoutes) => {sidebarRouters.value = newRoutes; }; // 核心方法 const generateRoutes = async (roles) => { try {const res = await getRouters();const sdata = JSON.parse(JSON.stringify(res.data));const rdata = JSON.parse(JSON.stringify(res.data));const defaultData = JSON.parse(JSON.stringify(res.data));const sidebarRoutes = filterAsyncRouter(sdata); const rewriteRoutes = filterAsyncRouter(rdata, false, true); const defaultRoutes = filterAsyncRouter(defaultData); // 处理动态路由 const asyncRoutes = filterDynamicRoutes(dynamicRoutes); asyncRoutes.forEach((route) => router.addRoute(route)); // 更新状态 setRoutes(rewriteRoutes); setSidebarRouters([...constantRoutes, ...sidebarRoutes]); setDefaultRoutes(sidebarRoutes); setTopbarRoutes(defaultRoutes); return rewriteRoutes; } catch (error) { console.error("路由生成失败:", error); return []; } };

/ 工具函数 const filterChildren = (childrenMap, lastRouter = false) => { const children = []; childrenMap.forEach((el) => { el.path = lastRouter ? l a s t R o u t e r . p a t h / lastRouter.path/{el.path} : el.path; if (el.children?.length && el.component === “ParentView”) { children.push(…filterChildren(el.children, el)); } else { children.push(el); } }); return children; }; const loadView = (view) => { let res; for (const path in modules) { const dir = path.split(“views/”)[1].split(“.vue”)[0]; if (dir === view) { res = () => modulespath; } } return res; }; const filterAsyncRouter = ( asyncRouterMap, lastRouter = false, type = false ) => { return asyncRouterMap.filter((route) => { if (type && route.children) { route.children = filterChildren(route.children); } if (route.component) { switch (route.component) { case “Layout”: route.component = Layout; break; case “ParentView”: route.component = ParentView; break; case “InnerLink”: route.component = InnerLink; break; default: route.component = loadView(route.component) ? loadView(route.component) : notRouterComp; } } if (route.children?.length) { route.children = filterAsyncRouter(route.children, route, type); } else { delete route.children; delete route.redirect; } return true; }); }; const filterDynamicRoutes = (routes) => { return routes.filter((route) => { if (route.permissions) return auth.hasPermiOr(route.permissions); if (route.roles) return auth.hasRoleOr(route.roles); return true; }); }; // 操作方法 const setRoutes = (newRoutes) => { addRoutes.value = newRoutes; routes.value = […constantRoutes, …newRoutes]; }; const setDefaultRoutes = (newRoutes) => { defaultRoutes.value = […constantRoutes, …newRoutes]; }; const setTopbarRoutes = (newRoutes) => { topbarRouters.value = newRoutes; }; const setSidebarRouters = (newRoutes) => { sidebarRouters.value = newRoutes; }; // 核心方法 const generateRoutes = async (roles) => { try { const res = await getRouters(); const sdata = JSON.parse(JSON.stringify(res.data)); const rdata = JSON.parse(JSON.stringify(res.data)); const defaultData = JSON.parse(JSON.stringify(res.data)); const sidebarRoutes = filterAsyncRouter(sdata); const rewriteRoutes = filterAsyncRouter(rdata, false, true); const defaultRoutes = filterAsyncRouter(defaultData); // 处理动态路由 const asyncRoutes = filterDynamicRoutes(dynamicRoutes); asyncRoutes.forEach((route) => router.addRoute(route)); // 更新状态 setRoutes(rewriteRoutes); setSidebarRouters([…constantRoutes, …sidebarRoutes]); setDefaultRoutes(sidebarRoutes); setTopbarRoutes(defaultRoutes); return rewriteRoutes; } catch (error) { console.error(“路由生成失败:”, error); return []; } }; 如果是你 你会怎么怎么写 别用ts

//classDiagram // class Bancai { // Integer id // Caizhi caizhi // Mupi mupi1 // Mupi mupi2 // Double houdu // Kucun kucun // } // // class Caizhi { // String name // List~Bancai~ bancai // } // // class Mupi { // String name // Boolean you // List~Bancai~ bancaisForMupi1 // List~Bancai~ bancaisForMupi2 // } // // class Chanpin { // String bianhao // List~Chanpin_zujian~ chanpin_zujian // List~Dingdan_chanpin~ dingdan_chanpin // } // // class Zujian { // String name // List~Chanpin_zujian~ chanping_zujian // } // // class Chanpin_zujian { // Bancai bancai // Double one_howmany // Double zujianshu // } // // class Dingdan { // String number // Date xiadan // Date jiaohuo // List~Dingdan_chanpin~ dingdan_chanpin // } // // class Dingdan_chanpin { // Integer shuliang // } // // class Kucun { // Integer shuliang // Dingdan reservedOrder // } // // class Jinhuo { // Integer shuliang // Date date // } // // class User { // String name // String andy // String pass // int role // } // // Bancai “1” – “1” Caizhi // Bancai “1” – “1” Mupi : mupi1 // Bancai “1” – “1” Mupi : mupi2 // Bancai “1” – “1” Kucun // Caizhi “1” – “" Bancai // Mupi “1” – "” Bancai : mupi1 // Mupi “1” – “" Bancai : mupi2 // Chanpin “1” – "” Chanpin_zujian // Chanpin “1” – “" Dingdan_chanpin // Zujian “1” – "” Chanpin_zujian // Chanpin_zujian “1” – “1” Bancai // Dingdan “1” – “*” Dingdan_chanpin // Dingdan_chanpin “1” – “1” Chanpin // Kucun “0…1” – “1” Dingdan : reservedOrder // Jinhuo “1” – “1” Dingdan_bancai // Jinhuo “1” – “1” User function resolveDataReferences(data) { // 获取 data 对象的所有顶层键 const keys = Object.keys(data); // 遍历每个顶层键(如 users, posts 等) for (const key of keys) { const entities = data[key]; // 遍历该顶层键下的每个实体(如每个 user 或 post) for (const entity of entities) { // 遍历实体的每个属性 for (const attribute in entity) { if (entity?.hasOwnProperty(attribute)) { var trpe=attribute?.replace(/\d/g, ''); // 确保属性属于当前实体 if (Array.isArray(entity[attribute])) { if(data[trpe]==null){ trpe+="s" } // 如果属性是一个数组,则将数组中的每个 ID 替换为对应的实际对象 entity[attribute] = entity[attribute].map(item => data[trpe ]?.find(updateItem => updateItem.id === item.id) || item ); } else if (typeof entity[attribute] === "object" && entity[attribute] !== null) { // 如果属性是一个对象,则将其替换为对应的实际对象 entity[attribute] = data[trpe + "s"]?.find(updateItem => updateItem.id === entity[attribute].id); } } } } } console.log(data) return data; } /** 数据管理器类,负责与后端API通信并管理数据 */ class DataManager { constructor(baseUrl) { this.baseUrl = baseUrl; this.data = { bancais: [], dingdans: [], mupis: [], chanpins: [], kucuns: [], dingdan_bancais:[], chanpin_zujians: [], zujians: [], caizhis: [], dingdan_chanpins: [], users: [], jinhuos: [] }; this.isSyncing = false; this.lastSync = null; this.callbacks = { all: [], bancais: [], dingdan: [], mupi: [], chanpin: [], kucun: [], chanpin_zujian: [], dingdan_bancai:[], zujian: [], caizhi: [], dingdan_chanpin: [], user: [], jinhuo: [] }; this.syncQueue = Promise.resolve(); } /** 获取所有数据 @returns {Promise} 是否成功 */ async fetchAll() { console.log(this) try { const response = await fetch(${this.baseUrl}/app/all); if (!response.ok) throw new Error(‘Network response was not ok’); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || ‘API error’); const resolvedData = resolveDataReferences(result.data); // 更新本地数据 Object.keys(this.data).forEach(key => { if (resolvedData[key]) { this.data[key] = resolvedData[key]; } }); this.lastSync = new Date(); // 关键改进:数据更新后触发刷新回调 this.triggerCallbacks(‘refresh’, ‘all’, this.data); return true; } catch (error) { console.error(‘Fetch error:’, error); // 触发错误回调 this.triggerCallbacks(‘fetch_error’, ‘all’, { error }); return false; } } /** 注册回调函数 @param {string} entity - 实体类型(如’bancai’)或’all’表示全局回调 @param {Function} callback - 回调函数,参数为(operation, data) */ registerCallback(entity, callback) { if (!this.callbacks[entity]) { this.callbacks[entity] = []; } this.callbacks[entity].push(callback); } /** 移除回调函数 @param {string} entity - 实体类型单数性质 @param {Function} callback - 要移除的回调函数 */ unregisterCallback(entity, callback) { if (!this.callbacks[entity]) return; const index = this.callbacks[entity].indexOf(callback); if (index !== -1) { this.callbacks[entity].splice(index, 1); } } /** 触发回调 @param {string} operation - 操作类型(‘add’, ‘update’, ‘delete’) @param {string} entity - 实体类型单数性质 @param {Object} data - 相关数据 */ triggerCallbacks(operation, entity, data) { // 触发全局回调 this.callbacks.all.forEach(cb => cb(operation, entity, data)); // 触发特定实体回调 if (this.callbacks[entity]) { this.callbacks[entity].forEach(cb => cb(operation, data)); } } /** 执行CRUD操作并触发回调 */ async crudOperation(operation, entity, data) { try { const response = await fetch(${this.baseUrl}/app/${operation}/${entity}, { method: ‘POST’, headers: {‘Content-Type’: ‘application/json’}, body: JSON.stringify(data) }); if (!response.ok) throw new Error(‘Network response was not ok’); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || ‘API error’); // 自动同步数据 this.syncData(); // 触发操作成功的回调 this.triggerCallbacks(operation, entity, data); return result; } catch (error) { console.error(‘CRUD error:’, error); // 触发操作失败的回调 this.triggerCallbacks(${operation}_error, entity, { data, error: error.message }); throw error; } } /** 执行CRUD操作 @param {string} operation - ‘add’, ‘delete’, ‘update’ @param {string} entity - 实体名称单数性质(小写) @param {Object} data - 要发送的数据 后端要求数据格式为{属性: “值”, 关联对象: {id:0}, 关联对象集: [{id:0}]} @returns {Promise} 响应结果 */ async crudOperation(operation, entity, data) { try { const response = await fetch(${this.baseUrl}/app/${operation}/${entity}, { method: ‘POST’, headers: {‘Content-Type’: ‘application/json’}, body: JSON.stringify(data) }); if (!response.ok) throw new Error('Network response was not ok'); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || 'API error'); // 自动同步数据 this.syncQueue = this.syncQueue.then(async () => { await this.syncData(); // 同步完成后触发操作回调 this.triggerCallbacks(operation, entity, data); }); return result; } catch (error) { console.error(‘CRUD error:’, error); // 触发操作失败的回调 this.triggerCallbacks(${operation}_error, entity, { data, error: error.message }); throw error; } } /** 自动同步数据(防止频繁请求) */ async syncData() { if (this.isSyncing) { this.pendingSync = true; return; } this.isSyncing = true; try { await this.fetchAll(); } catch (error) { console.error(‘Sync failed:’, error); } finally { this.isSyncing = false; // 处理等待中的同步请求 if (this.pendingSync) { this.pendingSync = false; setTimeout(() => this.syncData(), 1000); } } } /** 添加实体 @param {string} entity - 实体名称单数性质 @param {Object} data - 实体数据 */ async addEntity(entity, data) { return this.crudOperation(‘add’, entity, data); } /** 更新实体 @param {string} entity - 实体名称单数性质 @param {Object} data - 实体数据(必须包含id) */ async updateEntity(entity, data) { return this.crudOperation(‘update’, entity, data); } /** 删除实体 @param {string} entity - 实体名称单数性质 @param {number} id - 实体ID */ async deleteEntity(entity, id) { return this.crudOperation(‘delete’, entity, {id}); } /** 新增方法:手动触发数据刷新 */ async refreshData() { return this.syncQueue = this.syncQueue.then(() => this.syncData()); } } export { DataManager }; // 创建单例实例 //const dataManager = new DataManager(‘http://127.0.0.1:8080/KuCun2’); //// 初始化时获取所有数据 //dataManager.fetchAll().then(() => { // console.log(‘Initial data loaded’); //}); // 导出数据对象,外部可以直接访问 data.bancais, data.dingdans 等 //export const data = dataManager.data; //// 导出操作方法 //export const addEntity = dataManager.addEntity.bind(dataManager); //export const updateEntity = dataManager.updateEntity.bind(dataManager); //export const deleteEntity = dataManager.deleteEntity.bind(dataManager); //export const fetchAll = dataManager.fetchAll.bind(dataManager); <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>板材库存查询系统</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="../js/bootstrap-5.3.0-alpha1-dist/umd/popper.min.js"></script> <script src="../js/bootstrap-5.3.0-alpha1-dist/js/bootstrap.min.js"></script> <script src="../js/main.js"></script> </head> <body> 板材库存管理系统 查询订单、产品、板材及库存信息 订单总数 0 产品种类 0 板材库存 0 库存总量 0 高级搜索 <input type="text" class="form-control with-icon" id="orderSearch" placeholder="搜索订单号..." aria-label="订单号搜索"> <input type="text" class="form-control with-icon" id="productSearch" placeholder="搜索产品编号..." aria-label="产品编号搜索"> <input type="text" class="form-control with-icon" id="materialSearch" placeholder="搜索板材ID或材质..." aria-label="板材搜索"> <input type="text" class="form-control with-icon" id="woodSearch" placeholder="搜索木皮名称..." aria-label="木皮搜索"> <input type="number" class="form-control with-icon" id="thicknessSearch" placeholder="厚度(mm)" min="0" step="0.1"> <input type="number" class="form-control" id="minStock" placeholder="最小库存" min="0"> <input type="number" class="form-control" id="maxStock" placeholder="最大库存" min="0"> <button class="btn btn-primary" type="button" id="stockStatusBtn"> </button> 查询结果 0 条记录 实时数据更新时间: --:--:-- 订单号 产品信息 产品数量 组件 板材 单件用量 订单用量 库存数量 操作 加载中... 正在加载数据,请稍候... 没有找到匹配的记录 请尝试调整您的搜索条件 <script src="../js/test.js"></script> </body> </html> 实现页面功能,DataManager.js已经在父级页面加载过了,直接到父级调用,js和css分开文件 业务基本逻辑 一个订单有很多产品不同数量,每个产品可以再很多订单中,一个产品有很多组件,一个组件可以在很多产品中,因为每个组件因为在不同的产品中有不同的生产工艺,所以使用不同的板材和板材能生产组件数量,每个板材有不同的材质和两面木皮,木皮表面可能有油漆, 订购时可能直接购入板材,也可能按订单和产品订购板材,也用可能按订单产品组件订购板材,每次采购不标准,一个订单可能订购几次,用户有姓名 账号 密码 权限, 一个记录进货和消耗,查看的时候会查看订单下有多少板材可用

大家在看

recommend-type

公开公开公开公开-openprotocol_specification 2.7

LY-WCS-2012-01-06-01 V 1.0 公开公开公开公开 产品名称:产品名称:产品名称:产品名称: WCS 系统简介系统简介系统简介系统简介-公开版公开版公开版公开版 共共共共 13 页页页页 WCSWCSWCSWCS 系统简介系统简介系统简介系统简介 ((((客户交流用客户交流用客户交流用客户交流用)))) 文文文文 档档档档 作作作作 者:者:者:者: 王 超 日期:日期:日期:日期:2012/01/06 开发开发开发开发/测试经理:测试经理:测试经理:测试经理: 程 达 日期:日期:日期:日期:2012/01/06 项项项项 目目目目 经经经经 理:理:理:理: 程 达 日期:日期:日期:日期:2012/01/06 文文文文 档档档档 编编编编 号:号:号:号: ___________ ___ LY-WCS-2012-01-06-01______________ 上海朗因智能科技有限公司上海朗因智能科技有限公司上海朗因智能科技有限公司上海朗因智能科技有限公司 版权所有版权所有版权所有版权所有 不得复制不得复制不得复制不得复制
recommend-type

中国联通OSS系统总体框架

中国联通OSS系统总体框架。中国联通OSS系统总体框架。
recommend-type

基于 ADS9110的隔离式数据采集 (DAQ) 系统方案(待编辑)-电路方案

描述 该“可实现最大 SNR 和采样率的 18 位 2Msps 隔离式数据采集参考设计”演示了如何应对隔离式数据采集系统设计中的典型性能限制挑战: 通过将数字隔离器引入的传播延迟降至最低,使采样率达到最大 通过有效地减轻数字隔离器引入的 ADC 采样时钟抖动,使高频交流信号链性能 (SNR) 达到最大 特性 18 位、2Msps、1 通道、差分输入、隔离式数据采集 (DAQ) 系统 利用 ADS9110 的 multiSPI:trade_mark: 数字接口实现 2MSPS 采样率,同时保持低 SPI 数据速率 源同步 SPI 数据传输模式,可将隔离器传播延迟降至最低并提高采样率 可降低隔离器引入的抖动的技术,能够将 SNR 提高 12dB(100kHz Fin,2MSPS) 经测试的设计包含理论和计算、组件选择、PCB 设计和测量结果 原理图 附件文档: 方案相关器件: ISO1541:低功耗、双向 I2C 隔离器 ISO7840:高性能 5.7kVRMS 增强型四通道数字隔离器 ISO7842:高性能 5.7kVRMS 增强型四通道数字隔离器
recommend-type

自动化图书管理系统 v7.0

自动化图书馆管理系统包含了目前图书馆管理业务的每个环节,能同时管理图书和期刊,能打印条码、书标,并制作借书证,最大藏书量在300万册以上。系统采用CNMARC标准及中图法第四版分类,具有Web检索与发布功能,条码扫描,支持一卡通,支持触摸屏。系统包括系统管理、读者管理、编目、流通、统计、查询等功能。能够在一个界面下实现图书、音像、期刊的管理,设置假期、设置暂离锁(提高安全性)、暂停某些读者的借阅权、导入导出读者、交换MARC数据、升级辅助编目库等。安装本系统前请先安装SQL 2000SQL 下载地址 http://pan.baidu.com/s/145vkr安装过程如有问题可咨询: TEL 13851381727  QQ 306404635
recommend-type

MOXA UPort1110drvUSB转串口驱动

MOXA UPort1110drvUSB转串口驱动,解决没有com口的烦恼

最新推荐

recommend-type

{团队建设}网络渠道部团队规划手册.pdf

{团队建设}网络渠道部团队规划手册.pdf
recommend-type

快速付里叶变换FFTFastFourietTransformer(1).pptx

快速付里叶变换FFTFastFourietTransformer(1).pptx
recommend-type

spring-ai-model-chat-memory-cassandra-1.0.0-M8.jar中文文档.zip

1、压缩文件中包含: 中文文档、jar包下载地址、Maven依赖、Gradle依赖、源代码下载地址。 2、使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 3、特殊说明: (1)本文档为人性化翻译,精心制作,请放心使用; (2)只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; (3)不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 4、温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件。 5、本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册。
recommend-type

STM32-Led灯依次闪烁(100ms间隔)(1).doc

STM32-Led灯依次闪烁(100ms间隔)(1).doc
recommend-type

[精选]网络中心机房建设计培训教材.pptx

[精选]网络中心机房建设计培训教材.pptx
recommend-type

Wamp5: 一键配置ASP/PHP/HTML服务器工具

根据提供的文件信息,以下是关于标题、描述和文件列表中所涉及知识点的详细阐述。 ### 标题知识点 标题中提到的是"PHP集成版工具wamp5.rar",这里面包含了以下几个重要知识点: 1. **PHP**: PHP是一种广泛使用的开源服务器端脚本语言,主要用于网站开发。它可以嵌入到HTML中,从而让网页具有动态内容。PHP因其开源、跨平台、面向对象、安全性高等特点,成为最流行的网站开发语言之一。 2. **集成版工具**: 集成版工具通常指的是将多个功能组合在一起的软件包,目的是为了简化安装和配置流程。在PHP开发环境中,这样的集成工具通常包括了PHP解释器、Web服务器以及数据库管理系统等关键组件。 3. **Wamp5**: Wamp5是这类集成版工具的一种,它基于Windows操作系统。Wamp5的名称来源于它包含的主要组件的首字母缩写,即Windows、Apache、MySQL和PHP。这种工具允许开发者快速搭建本地Web开发环境,无需分别安装和配置各个组件。 4. **RAR压缩文件**: RAR是一种常见的文件压缩格式,它以较小的体积存储数据,便于传输和存储。RAR文件通常需要特定的解压缩软件进行解压缩操作。 ### 描述知识点 描述中提到了工具的一个重要功能:“可以自动配置asp/php/html等的服务器, 不用辛辛苦苦的为怎么配置服务器而烦恼”。这里面涵盖了以下知识点: 1. **自动配置**: 自动配置功能意味着该工具能够简化服务器的搭建过程,用户不需要手动进行繁琐的配置步骤,如修改配置文件、启动服务等。这是集成版工具的一项重要功能,极大地降低了初学者的技术门槛。 2. **ASP/PHP/HTML**: 这三种技术是Web开发中常用的组件。ASP (Active Server Pages) 是微软开发的服务器端脚本环境;HTML (HyperText Markup Language) 是用于创建网页的标准标记语言;PHP是服务器端脚本语言。在Wamp5这类集成环境中,可以很容易地对这些技术进行测试和开发,因为它们已经预配置在一起。 3. **服务器**: 在Web开发中,服务器是一个运行Web应用程序并响应客户端请求的软件或硬件系统。常见的服务器软件包括Apache、Nginx等。集成版工具提供了一个本地服务器环境,使得开发者可以在本地测试他们的应用程序。 ### 标签知识点 标签中仅出现了“PHP”一个关键词,这意味着该工具专注于与PHP相关的开发环境配置。 ### 压缩包子文件的文件名称列表知识点 1. **wamp.exe**: 这是Wamp5集成版工具的可执行文件,用户通过运行这个文件,即可启动Wamp5环境,开始进行PHP等相关开发。 2. **使用说明文档.txt**: 通常这样的文本文件包含了软件的安装指南、功能介绍、常见问题解答以及操作教程等内容。它是用户了解和使用软件的重要辅助资料,可以帮助用户快速上手。 ### 总结 Wamp5作为一个PHP集成版工具,为PHP开发者提供了一个便捷的本地开发环境。它通过自动化配置减少了开发者在搭建环境时的麻烦,整合了Web开发的关键组件,从而使得开发者能够专注于代码的编写,而不是环境配置。此外,它还提供了详尽的使用文档,帮助用户理解和运用这一工具,有效地提高了开发效率。对于希望快速入门PHP或需要一个简单易用的本地开发环境的开发者来说,Wamp5是一个非常好的选择。
recommend-type

【数据融合技术】:甘肃土壤类型空间分析中的专业性应用

# 摘要 数据融合技术作为一种集成多源数据信息的方法,在土壤类型空间分析中发挥着关键作用。本文介绍了数据融合技术的基本概念及其理论基础,阐述了数据预处理、同步整合及冲突解决等关键技术,并详细描述了甘肃土壤类型数据准备的流程,包括数据采集、质量评估、空间化处理及融合实践准备。通过具体案例分析,展示了数据融合在土壤类型空间分布分析、土壤质量评估及土壤保护规划中的应用。同时,文
recommend-type

sht20温湿度传感器使用什么将上拉电阻和滤波电容引出

<think>好的,我需要解决用户关于SHT20温湿度传感器的上拉电阻和滤波电容的引出方法问题。首先,我得回忆一下SHT20的基本特性和常见电路设计。SHT20是数字传感器,通常使用I2C接口,所以上拉电阻是必须的,用于确保数据线和时钟线的稳定。根据引用[2],SHT10是通过SCK和DATA线与单片机通信,而SHT30在引用[3]中使用I2C协议,需要上拉电阻。虽然用户问的是SHT20,但SHT系列通常设计类似,所以可以推断SHT20也需要类似的上拉电阻配置。通常I2C总线的上拉电阻值在4.7kΩ到10kΩ之间,但具体值可能取决于总线速度和电源电压。需要确认数据手册中的推荐值,但用户可能没有
recommend-type

Delphi仿速达财务软件导航条组件开发教程

Delphi作为一款历史悠久的集成开发环境(IDE),由Embarcadero Technologies公司开发,它使用Object Pascal语言,被广泛应用于Windows平台下的桌面应用程序开发。在Delphi中开发组件是一项核心技术,它允许开发者创建可复用的代码单元,提高开发效率和软件模块化水平。本文将详细介绍如何在Delphi环境下仿制速达财务软件中的导航条组件,这不仅涉及到组件的创建和使用,还会涉及界面设计和事件处理等技术点。 首先,需要了解Delphi组件的基本概念。在Delphi中,组件是一种特殊的对象,它们被放置在窗体(Form)上,可以响应用户操作并进行交互。组件可以是可视的,也可以是不可视的,可视组件在设计时就能在窗体上看到,如按钮、编辑框等;不可视组件则主要用于后台服务,如定时器、数据库连接等。组件的源码可以分为接口部分和实现部分,接口部分描述组件的属性和方法,实现部分包含方法的具体代码。 在开发仿速达财务软件的导航条组件时,我们需要关注以下几个方面的知识点: 1. 组件的继承体系 仿制组件首先需要确定继承体系。在Delphi中,大多数可视组件都继承自TControl或其子类,如TPanel、TButton等。导航条组件通常会继承自TPanel或者TWinControl,这取决于导航条是否需要支持子组件的放置。如果导航条只是单纯的一个显示区域,TPanel即可满足需求;如果导航条上有多个按钮或其他控件,可能需要继承自TWinControl以提供对子组件的支持。 2. 界面设计与绘制 组件的外观和交互是用户的第一印象。在Delphi中,可视组件的界面主要通过重写OnPaint事件来完成。Delphi提供了丰富的绘图工具,如Canvas对象,使用它可以绘制各种图形,如直线、矩形、椭圆等,并且可以对字体、颜色进行设置。对于导航条,可能需要绘制背景图案、分隔线条、选中状态的高亮等。 3. 事件处理 导航条组件需要响应用户的交互操作,例如鼠标点击事件。在Delphi中,可以通过重写组件的OnClick事件来响应用户的点击操作,进而实现导航条的导航功能。如果导航条上的项目较多,还可能需要考虑使用滚动条,让更多的导航项能够显示在窗体上。 4. 用户自定义属性和方法 为了使组件更加灵活和强大,开发者通常会为组件添加自定义的属性和方法。在导航条组件中,开发者可能会添加属性来定义按钮个数、按钮文本、按钮位置等;同时可能会添加方法来处理特定的事件,如自动调整按钮位置以适应不同的显示尺寸等。 5. 数据绑定和状态同步 在财务软件中,导航条往往需要与软件其他部分的状态进行同步。例如,用户当前所处的功能模块会影响导航条上相应项目的选中状态。这通常涉及到数据绑定技术,Delphi支持组件间的属性绑定,通过数据绑定可以轻松实现组件状态的同步。 6. 导航条组件的封装和发布 开发完毕后,组件需要被封装成独立的单元供其他项目使用。封装通常涉及将组件源码保存为pas文件,并在设计时能够在组件面板中找到。发布组件可能还需要编写相应的安装包和使用文档,方便其他开发者安装和使用。 7. Delphi IDE的支持 Delphi IDE提供了组件面板编辑器(Component Palette),允许开发者将开发好的组件添加到组件面板中。在组件面板编辑器中,可以自定义组件的图标和分类,使得组件在Delphi中的使用更为便捷。 通过以上的知识点梳理,可以看出Delphi仿速达导航条组件的开发涉及到的不仅仅是简单的代码编写,还涉及到用户界面设计、事件驱动编程、组件封装等多个方面。掌握这些知识点,对于一名Delphi开发者而言,是十分重要的。
recommend-type

【空间分布规律】:甘肃土壤类型与农业生产的关联性研究

# 摘要 本文对甘肃土壤类型及其在农业生产中的作用进行了系统性研究。首先概述了甘肃土壤类型的基础理论,并探讨了土壤类型与农业生产的理论联系。通过GIS技术分析,本文详细阐述了甘肃土壤的空间分布规律,并对其特征和影响因素进行了深入分析。此外,本文还研究了甘肃土壤类型对农业生产实际影响,包括不同区域土壤改良和作物种植案例,以及土壤养分、水分管理对作物生长周期和产量的具体影响。最后,提出了促进甘肃土壤与农业可持续发展的策略,包括土壤保护、退化防治对策以及土壤类型优化与农业创新的结合。本文旨在为