Jelajahi Sumber

变量列表缓存,首页和实时数据页面修改

cuiHe 4 hari lalu
induk
melakukan
0caeb812a2

+ 10 - 5
backend/app/api/v1/module_business/crane/controller.py

@@ -2,9 +2,10 @@
 
 
 from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query
 from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query
 from fastapi.responses import StreamingResponse, JSONResponse
 from fastapi.responses import StreamingResponse, JSONResponse
+from redis.asyncio import Redis
 
 
 from app.common.response import SuccessResponse, StreamResponse
 from app.common.response import SuccessResponse, StreamResponse
-from app.core.dependencies import AuthPermission
+from app.core.dependencies import AuthPermission, redis_getter
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.core.base_params import PaginationQueryParam
 from app.core.base_params import PaginationQueryParam
 from app.utils.common_util import bytes2file_response
 from app.utils.common_util import bytes2file_response
@@ -56,10 +57,11 @@ async def get_crane_list_controller(
 @BizCraneRouter.post("/create", summary="创建行车信息", description="创建行车信息")
 @BizCraneRouter.post("/create", summary="创建行车信息", description="创建行车信息")
 async def create_crane_controller(
 async def create_crane_controller(
     data: BizCraneCreateSchema,
     data: BizCraneCreateSchema,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:create"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:create"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """创建行车信息接口"""
     """创建行车信息接口"""
-    result_dict = await BizCraneService.create_crane_service(auth=auth, data=data)
+    result_dict = await BizCraneService.create_crane_service(auth=auth, data=data,redis=redis)
     log.info("创建行车信息成功")
     log.info("创建行车信息成功")
     return SuccessResponse(data=result_dict, msg="创建行车信息成功")
     return SuccessResponse(data=result_dict, msg="创建行车信息成功")
 
 
@@ -67,30 +69,33 @@ async def create_crane_controller(
 async def update_crane_controller(
 async def update_crane_controller(
     data: BizCraneUpdateSchema,
     data: BizCraneUpdateSchema,
     id: int = Path(..., description="ID"),
     id: int = Path(..., description="ID"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:update"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:update"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """修改行车信息接口"""
     """修改行车信息接口"""
-    result_dict = await BizCraneService.update_crane_service(auth=auth, id=id, data=data)
+    result_dict = await BizCraneService.update_crane_service(auth=auth, id=id, data=data,redis=redis)
     log.info("修改行车信息成功")
     log.info("修改行车信息成功")
     return SuccessResponse(data=result_dict, msg="修改行车信息成功")
     return SuccessResponse(data=result_dict, msg="修改行车信息成功")
 
 
 @BizCraneRouter.delete("/delete", summary="删除行车信息", description="删除行车信息")
 @BizCraneRouter.delete("/delete", summary="删除行车信息", description="删除行车信息")
 async def delete_crane_controller(
 async def delete_crane_controller(
     ids: list[int] = Body(..., description="ID列表"),
     ids: list[int] = Body(..., description="ID列表"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:delete"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:delete"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """删除行车信息接口"""
     """删除行车信息接口"""
-    await BizCraneService.delete_crane_service(auth=auth, ids=ids)
+    await BizCraneService.delete_crane_service(auth=auth, ids=ids,redis=redis)
     log.info(f"删除行车信息成功: {ids}")
     log.info(f"删除行车信息成功: {ids}")
     return SuccessResponse(msg="删除行车信息成功")
     return SuccessResponse(msg="删除行车信息成功")
 
 
 @BizCraneRouter.patch("/available/setting", summary="批量修改行车信息状态", description="批量修改行车信息状态")
 @BizCraneRouter.patch("/available/setting", summary="批量修改行车信息状态", description="批量修改行车信息状态")
 async def batch_set_available_crane_controller(
 async def batch_set_available_crane_controller(
     data: BatchSetAvailable,
     data: BatchSetAvailable,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:patch"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:patch"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """批量修改行车信息状态接口"""
     """批量修改行车信息状态接口"""
-    await BizCraneService.set_available_crane_service(auth=auth, data=data)
+    await BizCraneService.set_available_crane_service(auth=auth, data=data,redis=redis)
     log.info(f"批量修改行车信息状态成功: {data.ids}")
     log.info(f"批量修改行车信息状态成功: {data.ids}")
     return SuccessResponse(msg="批量修改行车信息状态成功")
     return SuccessResponse(msg="批量修改行车信息状态成功")
 
 

+ 2 - 2
backend/app/api/v1/module_business/crane/schema.py

@@ -4,7 +4,7 @@ from fastapi import Query
 from decimal import Decimal
 from decimal import Decimal
 from app.core.validator import DateTimeStr
 from app.core.validator import DateTimeStr
 from app.core.base_schema import BaseSchema, UserBySchema
 from app.core.base_schema import BaseSchema, UserBySchema
-from typing import Annotated, Optional
+from typing import Annotated
 
 
 # 定义通用的Decimal序列化类型:转为字符串(全局复用)
 # 定义通用的Decimal序列化类型:转为字符串(全局复用)
 # 若需转浮点数,把 lambda v: str(v) 改为 lambda v: float(v)
 # 若需转浮点数,把 lambda v: str(v) 改为 lambda v: float(v)
@@ -90,4 +90,4 @@ class BizCraneQueryParam:
         if created_time and len(created_time) == 2:
         if created_time and len(created_time) == 2:
             self.created_time = ("between", (created_time[0], created_time[1]))
             self.created_time = ("between", (created_time[0], created_time[1]))
         if updated_time and len(updated_time) == 2:
         if updated_time and len(updated_time) == 2:
-            self.updated_time = ("between", (updated_time[0], updated_time[1]))
+            self.updated_time = ("between", (updated_time[0], updated_time[1]))

+ 19 - 4
backend/app/api/v1/module_business/crane/service.py

@@ -1,11 +1,16 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 
 
 import io
 import io
+
+
 from fastapi import UploadFile
 from fastapi import UploadFile
 import pandas as pd
 import pandas as pd
+from redis.asyncio import Redis
 
 
+from app.common.enums import RedisInitKeyConfig
 from app.core.base_schema import BatchSetAvailable
 from app.core.base_schema import BatchSetAvailable
 from app.core.exceptions import CustomException
 from app.core.exceptions import CustomException
+from app.core.redis_crud import RedisCURD
 from app.utils.excel_util import ExcelUtil
 from app.utils.excel_util import ExcelUtil
 from app.core.logger import log
 from app.core.logger import log
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.api.v1.module_system.auth.schema import AuthSchema
@@ -56,14 +61,17 @@ class BizCraneService:
         return result
         return result
     
     
     @classmethod
     @classmethod
-    async def create_crane_service(cls, auth: AuthSchema, data: BizCraneCreateSchema) -> dict:
+    async def create_crane_service(cls, auth: AuthSchema, data: BizCraneCreateSchema,redis: Redis) -> dict:
         """创建"""
         """创建"""
         # 检查唯一性约束
         # 检查唯一性约束
         obj = await BizCraneCRUD(auth).create_crane_crud(data=data)
         obj = await BizCraneCRUD(auth).create_crane_crud(data=data)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizCraneOutSchema.model_validate(obj).model_dump()
         return BizCraneOutSchema.model_validate(obj).model_dump()
     
     
     @classmethod
     @classmethod
-    async def update_crane_service(cls, auth: AuthSchema, id: int, data: BizCraneUpdateSchema) -> dict:
+    async def update_crane_service(cls, auth: AuthSchema, id: int, data: BizCraneUpdateSchema,redis: Redis) -> dict:
         """更新"""
         """更新"""
         # 检查数据是否存在
         # 检查数据是否存在
         obj = await BizCraneCRUD(auth).get_by_id_crane_crud(id=id)
         obj = await BizCraneCRUD(auth).get_by_id_crane_crud(id=id)
@@ -73,10 +81,13 @@ class BizCraneService:
         # 检查唯一性约束
         # 检查唯一性约束
             
             
         obj = await BizCraneCRUD(auth).update_crane_crud(id=id, data=data)
         obj = await BizCraneCRUD(auth).update_crane_crud(id=id, data=data)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizCraneOutSchema.model_validate(obj).model_dump()
         return BizCraneOutSchema.model_validate(obj).model_dump()
     
     
     @classmethod
     @classmethod
-    async def delete_crane_service(cls, auth: AuthSchema, ids: list[int]) -> None:
+    async def delete_crane_service(cls, auth: AuthSchema, ids: list[int],redis: Redis) -> None:
         """删除"""
         """删除"""
         if len(ids) < 1:
         if len(ids) < 1:
             raise CustomException(msg='删除失败,删除对象不能为空')
             raise CustomException(msg='删除失败,删除对象不能为空')
@@ -85,11 +96,15 @@ class BizCraneService:
             if not obj:
             if not obj:
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
         await BizCraneCRUD(auth).delete_crane_crud(ids=ids)
         await BizCraneCRUD(auth).delete_crane_crud(ids=ids)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     
     @classmethod
     @classmethod
-    async def set_available_crane_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
+    async def set_available_crane_service(cls, auth: AuthSchema, data: BatchSetAvailable,redis: Redis) -> None:
         """批量设置状态"""
         """批量设置状态"""
         await BizCraneCRUD(auth).set_available_crane_crud(ids=data.ids, status=data.status)
         await BizCraneCRUD(auth).set_available_crane_crud(ids=data.ids, status=data.status)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     
     @classmethod
     @classmethod
     async def batch_export_crane_service(cls, obj_list: list[dict]) -> bytes:
     async def batch_export_crane_service(cls, obj_list: list[dict]) -> bytes:

+ 10 - 5
backend/app/api/v1/module_business/mec/controller.py

@@ -2,9 +2,10 @@
 
 
 from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query
 from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query
 from fastapi.responses import StreamingResponse, JSONResponse
 from fastapi.responses import StreamingResponse, JSONResponse
+from redis.asyncio import Redis
 
 
 from app.common.response import SuccessResponse, StreamResponse
 from app.common.response import SuccessResponse, StreamResponse
-from app.core.dependencies import AuthPermission
+from app.core.dependencies import AuthPermission, redis_getter
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.core.base_params import PaginationQueryParam
 from app.core.base_params import PaginationQueryParam
 from app.utils.common_util import bytes2file_response
 from app.utils.common_util import bytes2file_response
@@ -56,10 +57,11 @@ async def get_mec_list_controller(
 @BizMecRouter.post("/create", summary="创建机构信息", description="创建机构信息")
 @BizMecRouter.post("/create", summary="创建机构信息", description="创建机构信息")
 async def create_mec_controller(
 async def create_mec_controller(
     data: BizMecCreateSchema,
     data: BizMecCreateSchema,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:create"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:create"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """创建机构信息接口"""
     """创建机构信息接口"""
-    result_dict = await BizMecService.create_mec_service(auth=auth, data=data)
+    result_dict = await BizMecService.create_mec_service(auth=auth, data=data,redis=redis)
     log.info("创建机构信息成功")
     log.info("创建机构信息成功")
     return SuccessResponse(data=result_dict, msg="创建机构信息成功")
     return SuccessResponse(data=result_dict, msg="创建机构信息成功")
 
 
@@ -67,30 +69,33 @@ async def create_mec_controller(
 async def update_mec_controller(
 async def update_mec_controller(
     data: BizMecUpdateSchema,
     data: BizMecUpdateSchema,
     id: int = Path(..., description="ID"),
     id: int = Path(..., description="ID"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:update"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:update"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """修改机构信息接口"""
     """修改机构信息接口"""
-    result_dict = await BizMecService.update_mec_service(auth=auth, id=id, data=data)
+    result_dict = await BizMecService.update_mec_service(auth=auth, id=id, data=data,redis=redis)
     log.info("修改机构信息成功")
     log.info("修改机构信息成功")
     return SuccessResponse(data=result_dict, msg="修改机构信息成功")
     return SuccessResponse(data=result_dict, msg="修改机构信息成功")
 
 
 @BizMecRouter.delete("/delete", summary="删除机构信息", description="删除机构信息")
 @BizMecRouter.delete("/delete", summary="删除机构信息", description="删除机构信息")
 async def delete_mec_controller(
 async def delete_mec_controller(
     ids: list[int] = Body(..., description="ID列表"),
     ids: list[int] = Body(..., description="ID列表"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:delete"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:delete"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """删除机构信息接口"""
     """删除机构信息接口"""
-    await BizMecService.delete_mec_service(auth=auth, ids=ids)
+    await BizMecService.delete_mec_service(auth=auth, ids=ids,redis=redis)
     log.info(f"删除机构信息成功: {ids}")
     log.info(f"删除机构信息成功: {ids}")
     return SuccessResponse(msg="删除机构信息成功")
     return SuccessResponse(msg="删除机构信息成功")
 
 
 @BizMecRouter.patch("/available/setting", summary="批量修改机构信息状态", description="批量修改机构信息状态")
 @BizMecRouter.patch("/available/setting", summary="批量修改机构信息状态", description="批量修改机构信息状态")
 async def batch_set_available_mec_controller(
 async def batch_set_available_mec_controller(
     data: BatchSetAvailable,
     data: BatchSetAvailable,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:patch"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:patch"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """批量修改机构信息状态接口"""
     """批量修改机构信息状态接口"""
-    await BizMecService.set_available_mec_service(auth=auth, data=data)
+    await BizMecService.set_available_mec_service(auth=auth, data=data,redis=redis)
     log.info(f"批量修改机构信息状态成功: {data.ids}")
     log.info(f"批量修改机构信息状态成功: {data.ids}")
     return SuccessResponse(msg="批量修改机构信息状态成功")
     return SuccessResponse(msg="批量修改机构信息状态成功")
 
 

+ 3 - 3
backend/app/api/v1/module_business/mec/schema.py

@@ -14,8 +14,8 @@ class BizMecCreateSchema(BaseModel):
     mec_no: str = Field(default=..., description='机构编号 ')
     mec_no: str = Field(default=..., description='机构编号 ')
     mec_category: str = Field(default=..., description='机构分类 MecCategory')
     mec_category: str = Field(default=..., description='机构分类 MecCategory')
     mec_type: str = Field(default=..., description='机构类型 MecType')
     mec_type: str = Field(default=..., description='机构类型 MecType')
-    hoist_weight: str = Field(default=..., description='起升重量 起升机构')
-    hoist_height: str = Field(default=..., description='起升高度 起升机构')
+    hoist_weight: str | None = Field(default=None, description='起升重量 起升机构')
+    hoist_height: str | None = Field(default=None, description='起升高度 起升机构')
     hoist_speed: str | None = Field(default=None, description='起升速度 起升机构')
     hoist_speed: str | None = Field(default=None, description='起升速度 起升机构')
     rope_count: str | None = Field(default=None, description='钢线绳受力根数 起升机构')
     rope_count: str | None = Field(default=None, description='钢线绳受力根数 起升机构')
     rope_max_pull: str | None = Field(default=None, description='钢丝绳最大拉力 起升机构')
     rope_max_pull: str | None = Field(default=None, description='钢丝绳最大拉力 起升机构')
@@ -45,7 +45,7 @@ class BizMecCreateSchema(BaseModel):
     sort: int = Field(default=..., description='排序 ')
     sort: int = Field(default=..., description='排序 ')
     status: str = Field(default="1", description='是否启用(0:禁用 1:启用)')
     status: str = Field(default="1", description='是否启用(0:禁用 1:启用)')
     description: str | None = Field(default=None, max_length=255, description='备注/描述')
     description: str | None = Field(default=None, max_length=255, description='备注/描述')
-    hoist_weight_var_code: str | None = Field(default=..., description='重量关联变量')
+    hoist_weight_var_code: str | None = Field(default=None, description='重量关联变量')
     stroke_var_code: str | None = Field(default=None, description='行程高度变量')
     stroke_var_code: str | None = Field(default=None, description='行程高度变量')
     is_canvas_show: str | None = Field(default="0", description='是否首页动画展示')
     is_canvas_show: str | None = Field(default="0", description='是否首页动画展示')
     is_canvas_move: str | None = Field(default="0", description='是否启用动画')
     is_canvas_move: str | None = Field(default="0", description='是否启用动画')

+ 17 - 4
backend/app/api/v1/module_business/mec/service.py

@@ -3,9 +3,12 @@
 import io
 import io
 from fastapi import UploadFile
 from fastapi import UploadFile
 import pandas as pd
 import pandas as pd
+from redis.asyncio import Redis
 
 
+from app.common.enums import RedisInitKeyConfig
 from app.core.base_schema import BatchSetAvailable
 from app.core.base_schema import BatchSetAvailable
 from app.core.exceptions import CustomException
 from app.core.exceptions import CustomException
+from app.core.redis_crud import RedisCURD
 from app.utils.excel_util import ExcelUtil
 from app.utils.excel_util import ExcelUtil
 from app.core.logger import log
 from app.core.logger import log
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.api.v1.module_system.auth.schema import AuthSchema
@@ -64,14 +67,17 @@ class BizMecService:
         return result
         return result
     
     
     @classmethod
     @classmethod
-    async def create_mec_service(cls, auth: AuthSchema, data: BizMecCreateSchema) -> dict:
+    async def create_mec_service(cls, auth: AuthSchema, data: BizMecCreateSchema,redis: Redis) -> dict:
         """创建"""
         """创建"""
         # 检查唯一性约束
         # 检查唯一性约束
         obj = await BizMecCRUD(auth).create_mec_crud(data=data)
         obj = await BizMecCRUD(auth).create_mec_crud(data=data)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizMecOutSchema.model_validate(obj).model_dump()
         return BizMecOutSchema.model_validate(obj).model_dump()
     
     
     @classmethod
     @classmethod
-    async def update_mec_service(cls, auth: AuthSchema, id: int, data: BizMecUpdateSchema) -> dict:
+    async def update_mec_service(cls, auth: AuthSchema, id: int, data: BizMecUpdateSchema,redis: Redis) -> dict:
         """更新"""
         """更新"""
         # 检查数据是否存在
         # 检查数据是否存在
         obj = await BizMecCRUD(auth).get_by_id_mec_crud(id=id)
         obj = await BizMecCRUD(auth).get_by_id_mec_crud(id=id)
@@ -81,10 +87,13 @@ class BizMecService:
         # 检查唯一性约束
         # 检查唯一性约束
             
             
         obj = await BizMecCRUD(auth).update_mec_crud(id=id, data=data)
         obj = await BizMecCRUD(auth).update_mec_crud(id=id, data=data)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizMecOutSchema.model_validate(obj).model_dump()
         return BizMecOutSchema.model_validate(obj).model_dump()
     
     
     @classmethod
     @classmethod
-    async def delete_mec_service(cls, auth: AuthSchema, ids: list[int]) -> None:
+    async def delete_mec_service(cls, auth: AuthSchema, ids: list[int],redis: Redis) -> None:
         """删除"""
         """删除"""
         if len(ids) < 1:
         if len(ids) < 1:
             raise CustomException(msg='删除失败,删除对象不能为空')
             raise CustomException(msg='删除失败,删除对象不能为空')
@@ -93,11 +102,15 @@ class BizMecService:
             if not obj:
             if not obj:
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
         await BizMecCRUD(auth).delete_mec_crud(ids=ids)
         await BizMecCRUD(auth).delete_mec_crud(ids=ids)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     
     @classmethod
     @classmethod
-    async def set_available_mec_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
+    async def set_available_mec_service(cls, auth: AuthSchema, data: BatchSetAvailable,redis: Redis) -> None:
         """批量设置状态"""
         """批量设置状态"""
         await BizMecCRUD(auth).set_available_mec_crud(ids=data.ids, status=data.status)
         await BizMecCRUD(auth).set_available_mec_crud(ids=data.ids, status=data.status)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     
     @classmethod
     @classmethod
     async def batch_export_mec_service(cls, obj_list: list[dict]) -> bytes:
     async def batch_export_mec_service(cls, obj_list: list[dict]) -> bytes:

+ 22 - 6
backend/app/api/v1/module_business/vardict/controller.py

@@ -2,9 +2,9 @@
 
 
 from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query
 from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query
 from fastapi.responses import StreamingResponse, JSONResponse
 from fastapi.responses import StreamingResponse, JSONResponse
-
+from redis.asyncio.client import Redis
 from app.common.response import SuccessResponse, StreamResponse
 from app.common.response import SuccessResponse, StreamResponse
-from app.core.dependencies import AuthPermission
+from app.core.dependencies import AuthPermission, redis_getter
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.core.base_params import PaginationQueryParam
 from app.core.base_params import PaginationQueryParam
 from app.utils.common_util import bytes2file_response
 from app.utils.common_util import bytes2file_response
@@ -46,10 +46,11 @@ async def get_vardict_list_controller(
 @BizVarDictRouter.post("/create", summary="创建变量信息", description="创建变量信息")
 @BizVarDictRouter.post("/create", summary="创建变量信息", description="创建变量信息")
 async def create_vardict_controller(
 async def create_vardict_controller(
     data: BizVarDictCreateSchema,
     data: BizVarDictCreateSchema,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:create"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:create"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """创建变量信息接口"""
     """创建变量信息接口"""
-    result_dict = await BizVarDictService.create_vardict_service(auth=auth, data=data)
+    result_dict = await BizVarDictService.create_vardict_service(auth=auth, data=data,redis=redis)
     log.info("创建变量信息成功")
     log.info("创建变量信息成功")
     return SuccessResponse(data=result_dict, msg="创建变量信息成功")
     return SuccessResponse(data=result_dict, msg="创建变量信息成功")
 
 
@@ -57,20 +58,22 @@ async def create_vardict_controller(
 async def update_vardict_controller(
 async def update_vardict_controller(
     data: BizVarDictUpdateSchema,
     data: BizVarDictUpdateSchema,
     id: int = Path(..., description="ID"),
     id: int = Path(..., description="ID"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:update"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:update"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """修改变量信息接口"""
     """修改变量信息接口"""
-    result_dict = await BizVarDictService.update_vardict_service(auth=auth, id=id, data=data)
+    result_dict = await BizVarDictService.update_vardict_service(auth=auth, id=id, data=data,redis=redis)
     log.info("修改变量信息成功")
     log.info("修改变量信息成功")
     return SuccessResponse(data=result_dict, msg="修改变量信息成功")
     return SuccessResponse(data=result_dict, msg="修改变量信息成功")
 
 
 @BizVarDictRouter.delete("/delete", summary="删除变量信息", description="删除变量信息")
 @BizVarDictRouter.delete("/delete", summary="删除变量信息", description="删除变量信息")
 async def delete_vardict_controller(
 async def delete_vardict_controller(
     ids: list[int] = Body(..., description="ID列表"),
     ids: list[int] = Body(..., description="ID列表"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:delete"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:delete"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
     """删除变量信息接口"""
     """删除变量信息接口"""
-    await BizVarDictService.delete_vardict_service(auth=auth, ids=ids)
+    await BizVarDictService.delete_vardict_service(auth=auth, ids=ids,redis=redis)
     log.info(f"删除变量信息成功: {ids}")
     log.info(f"删除变量信息成功: {ids}")
     return SuccessResponse(msg="删除变量信息成功")
     return SuccessResponse(msg="删除变量信息成功")
 
 
@@ -123,4 +126,17 @@ async def export_vardict_template_controller() -> StreamingResponse:
         data=bytes2file_response(import_template_result),
         data=bytes2file_response(import_template_result),
         media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
         media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
         headers={'Content-Disposition': 'attachment; filename=biz_var_dict_template.xlsx'}
         headers={'Content-Disposition': 'attachment; filename=biz_var_dict_template.xlsx'}
-    )
+    )
+
+@BizVarDictRouter.get("/varDictMecGroup/{id}", summary="获取变量信息分组数据", description="获取变量信息分组数据")
+async def get_vardict_mec_group_controller(
+    id: int = Path(..., description="ID"),
+    redis: Redis = Depends(redis_getter),
+    auth: AuthSchema = Depends(AuthPermission(["module_business:crane:query"]))
+) -> JSONResponse:
+
+    result_dict = await BizVarDictService.get_vardict_group_service(
+        redis=redis, id=id,auth=auth
+    )
+    log.info(f"获取变量信息分组数据成功:{result_dict}")
+    return SuccessResponse(data=result_dict, msg="获取变量分组数据成功")

+ 7 - 0
backend/app/api/v1/module_business/vardict/schema.py

@@ -140,3 +140,10 @@ class BizVarDictQueryParam:
             self.created_time = ("between", (created_time[0], created_time[1]))
             self.created_time = ("between", (created_time[0], created_time[1]))
         if updated_time and len(updated_time) == 2:
         if updated_time and len(updated_time) == 2:
             self.updated_time = ("between", (updated_time[0], updated_time[1]))
             self.updated_time = ("between", (updated_time[0], updated_time[1]))
+
+class VarDictMecGroupSchema(BaseModel):
+    """
+    行车信息页面数据模型
+    """
+    mec_type: str = Field(default=..., description='所属机构')
+    varList_simple: list[BizVarDictOutSchema] | None = Field(default=None, description='变量数据')

+ 125 - 8
backend/app/api/v1/module_business/vardict/service.py

@@ -1,21 +1,27 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 
 
 import io
 import io
+import json
 from fastapi import UploadFile
 from fastapi import UploadFile
 import pandas as pd
 import pandas as pd
+from redis.asyncio.client import Redis
 
 
+from app.core.database import async_db_session
+from app.core.redis_crud import RedisCURD
+from app.common.enums import RedisInitKeyConfig
 from app.core.base_schema import BatchSetAvailable
 from app.core.base_schema import BatchSetAvailable
 from app.core.exceptions import CustomException
 from app.core.exceptions import CustomException
 from app.utils.excel_util import ExcelUtil
 from app.utils.excel_util import ExcelUtil
 from app.core.logger import log
 from app.core.logger import log
 from app.api.v1.module_system.auth.schema import AuthSchema
 from app.api.v1.module_system.auth.schema import AuthSchema
 from .schema import BizVarDictCreateSchema, BizVarDictUpdateSchema, BizVarDictOutSchema, BizVarDictQueryParam
 from .schema import BizVarDictCreateSchema, BizVarDictUpdateSchema, BizVarDictOutSchema, BizVarDictQueryParam
-from .crud import BizVarDictCRUD
 from ..crane.crud import BizCraneCRUD
 from ..crane.crud import BizCraneCRUD
 from ..crane.model import BizCraneModel
 from ..crane.model import BizCraneModel
 from ..gateway.crud import GatewayCRUD
 from ..gateway.crud import GatewayCRUD
 from ..gateway.model import GatewayModel
 from ..gateway.model import GatewayModel
-from ..mec.schema import BizMecOutSchema
+from ..mec.crud import BizMecCRUD
+from ..vardict.crud import BizVarDictCRUD
+from ..vardict.schema import VarDictMecGroupSchema
 
 
 
 
 class BizVarDictService:
 class BizVarDictService:
@@ -63,14 +69,17 @@ class BizVarDictService:
         return result
         return result
     
     
     @classmethod
     @classmethod
-    async def create_vardict_service(cls, auth: AuthSchema, data: BizVarDictCreateSchema) -> dict:
+    async def create_vardict_service(cls, auth: AuthSchema, data: BizVarDictCreateSchema,redis: Redis) -> dict:
         """创建"""
         """创建"""
         # 检查唯一性约束
         # 检查唯一性约束
         obj = await BizVarDictCRUD(auth).create_vardict_crud(data=data)
         obj = await BizVarDictCRUD(auth).create_vardict_crud(data=data)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizVarDictOutSchema.model_validate(obj).model_dump()
         return BizVarDictOutSchema.model_validate(obj).model_dump()
     
     
     @classmethod
     @classmethod
-    async def update_vardict_service(cls, auth: AuthSchema, id: int, data: BizVarDictUpdateSchema) -> dict:
+    async def update_vardict_service(cls, auth: AuthSchema, id: int, data: BizVarDictUpdateSchema,redis: Redis) -> dict:
         """更新"""
         """更新"""
         # 检查数据是否存在
         # 检查数据是否存在
         obj = await BizVarDictCRUD(auth).get_by_id_vardict_crud(id=id)
         obj = await BizVarDictCRUD(auth).get_by_id_vardict_crud(id=id)
@@ -80,10 +89,13 @@ class BizVarDictService:
         # 检查唯一性约束
         # 检查唯一性约束
             
             
         obj = await BizVarDictCRUD(auth).update_vardict_crud(id=id, data=data)
         obj = await BizVarDictCRUD(auth).update_vardict_crud(id=id, data=data)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizVarDictOutSchema.model_validate(obj).model_dump()
         return BizVarDictOutSchema.model_validate(obj).model_dump()
     
     
     @classmethod
     @classmethod
-    async def delete_vardict_service(cls, auth: AuthSchema, ids: list[int]) -> None:
+    async def delete_vardict_service(cls, auth: AuthSchema, ids: list[int],redis: Redis) -> None:
         """删除"""
         """删除"""
         if len(ids) < 1:
         if len(ids) < 1:
             raise CustomException(msg='删除失败,删除对象不能为空')
             raise CustomException(msg='删除失败,删除对象不能为空')
@@ -92,11 +104,15 @@ class BizVarDictService:
             if not obj:
             if not obj:
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
         await BizVarDictCRUD(auth).delete_vardict_crud(ids=ids)
         await BizVarDictCRUD(auth).delete_vardict_crud(ids=ids)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     
     @classmethod
     @classmethod
-    async def set_available_vardict_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
+    async def set_available_vardict_service(cls, auth: AuthSchema, data: BatchSetAvailable,redis: Redis) -> None:
         """批量设置状态"""
         """批量设置状态"""
         await BizVarDictCRUD(auth).set_available_vardict_crud(ids=data.ids, status=data.status)
         await BizVarDictCRUD(auth).set_available_vardict_crud(ids=data.ids, status=data.status)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     
     @classmethod
     @classmethod
     async def batch_export_vardict_service(cls, obj_list: list[dict]) -> bytes:
     async def batch_export_vardict_service(cls, obj_list: list[dict]) -> bytes:
@@ -131,7 +147,6 @@ class BizVarDictService:
             'updated_time': '更新时间',
             'updated_time': '更新时间',
             'created_id': '创建人ID',
             'created_id': '创建人ID',
             'updated_id': '更新人ID',
             'updated_id': '更新人ID',
-            'updated_id': '更新者ID',
         }
         }
 
 
         data = obj_list.copy()
         data = obj_list.copy()
@@ -308,4 +323,106 @@ class BizVarDictService:
             header_list=header_list,
             header_list=header_list,
             selector_header_list=selector_header_list,
             selector_header_list=selector_header_list,
             option_list=option_list
             option_list=option_list
-        )
+        )
+    @classmethod
+    async def get_vardict_group_service(cls,auth: AuthSchema, redis: Redis,id: int):
+        """
+        从缓存获取变量分组数据列表信息service
+
+        参数:
+        - redis (Redis): Redis客户端
+        - id (int): 行车id
+
+        返回:
+        - list[dict]: 变量分组数据列表
+        """
+        try:
+            crane = await BizCraneCRUD(auth).get_by_id_crane_crud(id)
+            redis_key = f"{RedisInitKeyConfig.VAR_DICT.key}:{crane.crane_no}"
+            obj_list_dict = await RedisCURD(redis).get(redis_key)
+
+            # 确保返回数据正确序列化
+            if obj_list_dict:
+                if isinstance(obj_list_dict, str):
+                    try:
+                        return json.loads(obj_list_dict)
+                    except json.JSONDecodeError:
+                        log.warning(f"变量分组数据反序列化失败,尝试重新初始化缓存: {crane.crane_name}")
+                elif isinstance(obj_list_dict, list):
+                    return obj_list_dict
+
+            # 缓存不存在或格式错误时重新初始化
+            await cls.init_vardict_group_service(redis)
+            obj_list_dict = await RedisCURD(redis).get(redis_key)
+            if not obj_list_dict:
+                raise CustomException(msg="变量分组数据不存在")
+
+            # 再次确保返回数据正确序列化
+            if isinstance(obj_list_dict, str):
+                try:
+                    return json.loads(obj_list_dict)
+                except json.JSONDecodeError:
+                    raise CustomException(msg="变量分组数据格式错误")
+            return obj_list_dict
+        except CustomException:
+            raise
+        except Exception as e:
+            log.error(f"获取变量分组数据缓存失败: {str(e)}")
+            raise CustomException(msg=f"获取变量分组数据失败: {str(e)}")
+    @classmethod
+    async def init_vardict_group_service(cls, redis: Redis):
+        """
+        应用初始化: 获取所有天车变量数据信息并缓存service
+
+        参数:
+        - redis (Redis): Redis客户端
+
+        返回:
+        - None
+        """
+        try:
+            async with async_db_session() as session:
+                async with session.begin():
+                    # 在初始化过程中,不需要检查数据权限
+                    auth = AuthSchema(db=session, check_data_scope=False)
+                    crane_list = await BizCraneCRUD(auth).list(search={'status':'1'},order_by=[{'order':'asc'}])
+                    if not crane_list:
+                        log.warning("未找到任何天车数据")
+                        return
+
+                    success_count = 0
+                    fail_count = 0
+                    for crane in crane_list:
+                        crane_no = crane.crane_no
+                        crane_name = crane.crane_name
+                        try:
+                            varDictMecGroupSchema: list[VarDictMecGroupSchema] = []
+                            mec_list = await BizMecCRUD(auth).list(search={'crane_no':crane_no,'status':'1'},order_by=[{'sort':'asc'}])
+                            for mec in mec_list:
+                                # 获取分组数据
+                                varDicts = await BizVarDictCRUD(auth).list(search={'crane_no':crane_no,'mec_type':mec.mec_type,'status':'1'},order_by=[{'var_sort':'asc'}])
+                                if not varDicts:
+                                    continue
+                                varDictMecGroupSchema.append(
+                                    VarDictMecGroupSchema(mec_type=mec.mec_type, varList_simple=varDicts))
+                            # 保存到Redis并设置过期时间
+                            redis_key = f"{RedisInitKeyConfig.VAR_DICT.key}:{crane_no}"
+                            var_dict_list = [item.model_dump() for item in varDictMecGroupSchema]
+                            value = json.dumps(var_dict_list, ensure_ascii=False)
+                            await RedisCURD(redis).set(
+                                key=redis_key,
+                                value=value,
+                            )
+                            success_count += 1
+                            log.info(f"✅ 变量数据缓存成功: {crane_name}")
+
+                        except Exception as e:
+                            fail_count += 1
+                            log.error(f"❌ 初始化变量数据失败 [{crane_name}]: {e}")
+
+                    log.info(f"变量数据初始化完成 - 成功: {success_count}, 失败: {fail_count}")
+
+        except Exception as e:
+            log.error(f"变量数据初始化过程发生错误: {e}")
+            # 只在严重错误时抛出异常,允许单个字典加载失败
+            raise CustomException(msg=f"变量数据初始化失败: {str(e)}")

+ 1 - 0
backend/app/common/enums.py

@@ -46,6 +46,7 @@ class RedisInitKeyConfig(Enum):
     CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'}
     CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'}
     SYSTEM_CONFIG = {'key': 'system_config', 'remark': '系统配置'}
     SYSTEM_CONFIG = {'key': 'system_config', 'remark': '系统配置'}
     SYSTEM_DICT = {'key':'system_dict','remark': '数据字典'}
     SYSTEM_DICT = {'key':'system_dict','remark': '数据字典'}
+    VAR_DICT = {'key': 'var_dict', 'remark': '变量数据'}
     
     
     @property
     @property
     def key(self) -> str:
     def key(self) -> str:

+ 4 - 0
backend/app/plugin/init_app.py

@@ -14,6 +14,7 @@ from fastapi_limiter import FastAPILimiter
 from fastapi_limiter.depends import RateLimiter
 from fastapi_limiter.depends import RateLimiter
 from math import ceil
 from math import ceil
 
 
+from app.api.v1.module_business.vardict.service import BizVarDictService
 from app.config.setting import settings
 from app.config.setting import settings
 from app.core.logger import log
 from app.core.logger import log
 from app.core.discover import router
 from app.core.discover import router
@@ -24,6 +25,7 @@ from app.scripts.initialize import InitializeData
 from app.api.v1.module_application.job.tools.ap_scheduler import SchedulerUtil
 from app.api.v1.module_application.job.tools.ap_scheduler import SchedulerUtil
 from app.api.v1.module_system.params.service import ParamsService
 from app.api.v1.module_system.params.service import ParamsService
 from app.api.v1.module_system.dict.service import DictDataService
 from app.api.v1.module_system.dict.service import DictDataService
+from app.api.v1.module_business.crane.service import BizCraneService
 
 
 
 
 @asynccontextmanager
 @asynccontextmanager
@@ -46,6 +48,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]:
         log.info("✅ Redis系统配置初始化完成")
         log.info("✅ Redis系统配置初始化完成")
         await DictDataService().init_dict_service(redis=app.state.redis)
         await DictDataService().init_dict_service(redis=app.state.redis)
         log.info("✅ Redis数据字典初始化完成")
         log.info("✅ Redis数据字典初始化完成")
+        await BizVarDictService().init_vardict_group_service(redis=app.state.redis)
+        log.info("✅ Redis天车数据初始化完成")
         await SchedulerUtil.init_system_scheduler()
         await SchedulerUtil.init_system_scheduler()
         scheduler_jobs_count = len(SchedulerUtil.get_all_jobs())
         scheduler_jobs_count = len(SchedulerUtil.get_all_jobs())
         scheduler_status = SchedulerUtil.get_job_status()
         scheduler_status = SchedulerUtil.get_job_status()

+ 8 - 0
frontend/src/api/module_business/vardict.ts

@@ -20,6 +20,14 @@ const BizVarDictAPI = {
     });
     });
   },
   },
 
 
+  // 获取变量信息
+  varDictMecGroup(id: number) {
+    return request<ApiResponse<BizVarDictTable>>({
+      url: `${API_PATH}/varDictMecGroup/${id}`,
+      method: "get",
+    });
+  },
+
   // 新增
   // 新增
   createBizVarDict(body: BizVarDictForm) {
   createBizVarDict(body: BizVarDictForm) {
     return request<ApiResponse>({
     return request<ApiResponse>({

+ 6 - 6
frontend/src/router/index.ts

@@ -64,35 +64,35 @@ export const constantRoutes: RouteRecordRaw[] = [
       {
       {
         path: '/detail',
         path: '/detail',
         name: 'Detail',
         name: 'Detail',
-        meta: { title: '起重机详情' },
+        meta: { title: '行车详情' },
         component: import("@/views/web/detail/index.vue"),
         component: import("@/views/web/detail/index.vue"),
         children: [
         children: [
           {
           {
-            path: '/detail/realtimeData/:craneId',
+            path: '/detail/realtimeData',
             name: 'RealtimeData',
             name: 'RealtimeData',
             meta: { title: '实时数据' },
             meta: { title: '实时数据' },
             component: import("@/views/web/detail/realtimeData.vue")
             component: import("@/views/web/detail/realtimeData.vue")
           },
           },
           {
           {
-            path: '/detail/historyData/:craneId',
+            path: '/detail/historyData',
             name: 'HistoryData',
             name: 'HistoryData',
             meta: { title: '历史数据' },
             meta: { title: '历史数据' },
             component: import("@/views/web/detail/historyData.vue")
             component: import("@/views/web/detail/historyData.vue")
           },
           },
           {
           {
-            path: '/detail/operationRecord/:craneId',
+            path: '/detail/operationRecord',
             name: 'OperationRecord',
             name: 'OperationRecord',
             meta: { title: '操作记录' },
             meta: { title: '操作记录' },
             component: import("@/views/web/detail/operationRecord.vue")
             component: import("@/views/web/detail/operationRecord.vue")
           },
           },
           {
           {
-            path: '/detail/realtimeAlarm/:craneId',
+            path: '/detail/realtimeAlarm',
             name: 'RealtimeAlarm',
             name: 'RealtimeAlarm',
             meta: { title: '实时报警' },
             meta: { title: '实时报警' },
             component: import("@/views/web/detail/realtimeAlarm.vue")
             component: import("@/views/web/detail/realtimeAlarm.vue")
           },
           },
           {
           {
-            path: '/detail/historyAlarm/:craneId',
+            path: '/detail/historyAlarm',
             name: 'HistoryAlarm',
             name: 'HistoryAlarm',
             meta: { title: '历史报警' },
             meta: { title: '历史报警' },
             component: import("@/views/web/detail/historyAlarm.vue")
             component: import("@/views/web/detail/historyAlarm.vue")

+ 2 - 0
frontend/src/utils/common.ts

@@ -146,3 +146,5 @@ export function isEmpty(obj: string | null | undefined) {
 export function blobValidate(data: Blob): boolean {
 export function blobValidate(data: Blob): boolean {
   return data.type !== "application/json";
   return data.type !== "application/json";
 }
 }
+
+

+ 202 - 0
frontend/src/utils/mqttUtil.ts

@@ -0,0 +1,202 @@
+// src/utils/mqttUtil.ts
+import mqtt, { MqttClient, IClientOptions  } from 'mqtt';
+
+/**
+ * MQTT消息处理回调类型
+ * @param topic 消息主题
+ * @param payload 消息内容(已解析为JSON对象,若解析失败则为原始字符串)
+ */
+export type MqttMessageCallback = (topic: string, payload: any) => void;
+
+/**
+ * MQTT工具类配置项
+ */
+export interface MqttConfig {
+  wsUrl: string; // MQTT WS连接地址
+  clientOptions?: IClientOptions ; // MQTT客户端配置(可选,如用户名、密码等)
+  defaultTopics?: string[]; // 默认订阅的主题列表(可选)
+}
+
+/**
+ * MQTT工具类(单例模式,避免重复创建连接)
+ */
+class MqttUtil {
+  // 私有属性
+  private static instance: MqttUtil; // 单例实例
+  private mqttClient: MqttClient | null = null; // MQTT客户端实例
+  private mqttConfig: MqttConfig; // MQTT配置
+  private messageCallback: MqttMessageCallback | null = null; // 全局消息回调
+
+  /**
+   * 私有构造函数(单例模式,禁止外部new)
+   * @param config MQTT配置项
+   */
+  private constructor(config: MqttConfig) {
+    this.mqttConfig = config;
+  }
+
+  /**
+   * 获取MQTT工具类单例实例
+   * @param config MQTT配置项(仅首次调用需传入,后续调用无需重复传入)
+   * @returns MqttUtil单例
+   */
+  public static getInstance(config?: MqttConfig): MqttUtil {
+    if (!MqttUtil.instance) {
+      if (!config) {
+        throw new Error('首次创建MQTT实例时,必须传入配置项!');
+      }
+      MqttUtil.instance = new MqttUtil(config);
+    }
+    return MqttUtil.instance;
+  }
+
+  /**
+   * 初始化MQTT客户端(创建连接+订阅默认主题)
+   * @param messageCallback 消息处理回调(可选,也可后续通过setMessageCallback设置)
+   */
+  public initMqttClient(messageCallback?: MqttMessageCallback): void {
+    // 避免重复连接
+    if (this.mqttClient && this.mqttClient.connected) {
+      console.log('[MQTT] 客户端已连接,无需重复初始化');
+      // 若传入新的回调,更新全局回调
+      if (messageCallback) {
+        this.messageCallback = messageCallback;
+      }
+      // 若已连接,直接订阅默认主题(补充:避免重复订阅)
+      const { defaultTopics } = this.mqttConfig;
+      if (defaultTopics && defaultTopics.length > 0) {
+        this.subscribeTopics(defaultTopics);
+      }
+      return;
+    }
+  
+    const { wsUrl, clientOptions, defaultTopics } = this.mqttConfig;
+    console.log(`[MQTT] 开始连接 Broker: ${wsUrl}`);
+  
+    // 创建MQTT客户端
+    this.mqttClient = mqtt.connect(wsUrl, clientOptions);
+    // 保存消息回调
+    if (messageCallback) {
+      this.messageCallback = messageCallback;
+    }
+  
+    // 绑定各类事件(默认主题订阅移到该方法内的connect回调)
+    this.bindClientEvents(defaultTopics); // 传入defaultTopics
+  }
+
+  /**
+   * 绑定MQTT客户端事件(连接、消息、断开、错误)
+   */
+  private bindClientEvents(defaultTopics?: string[]): void {
+    if (!this.mqttClient) return;
+  
+    // 连接成功回调
+    this.mqttClient.on('connect', () => {
+      console.log('[MQTT] 连接成功');
+      // 连接成功后,订阅默认主题
+      if (defaultTopics && defaultTopics.length > 0) {
+        this.subscribeTopics(defaultTopics);
+      }
+    });
+  
+    // 接收消息回调(统一处理,转发给全局回调)
+    this.mqttClient.on('message', (topic, payload) => {
+        let payloadStr = payload.toString();
+        let data: any = payloadStr;
+    
+        // 步骤1:还原Unicode转义字符为中文(核心处理)
+        try {
+          // 方法1:利用JSON.parse间接还原(推荐,兼容性更好)
+          payloadStr = JSON.parse(`"${payloadStr.replace(/"/g, '\\"')}"`);
+          // 步骤2:解析还原后的JSON字符串
+          data = JSON.parse(payloadStr);
+        } catch (err) {
+          // 方法2:备用方案 - 正则表达式直接替换Unicode转义
+          const unicodeReg = /\\u([0-9a-fA-F]{4})/g;
+          payloadStr = payloadStr.replace(unicodeReg, (_, hex) => {
+            return String.fromCharCode(parseInt(hex, 16));
+          });
+          try {
+            data = JSON.parse(payloadStr);
+          } catch (err2) {
+            console.warn('[MQTT] 消息非JSON格式,使用原始字符串:', err2);
+            data = payloadStr;
+          }
+        }
+    
+        // 调用全局消息回调
+        if (this.messageCallback) {
+          this.messageCallback(topic, data);
+        }
+      });
+    }
+  /**
+   * 订阅主题(支持单个/多个主题)
+   * @param topics 主题字符串或主题数组
+   */
+  public subscribeTopics(topics: string | string[]): void {
+    if (!this.mqttClient || !this.mqttClient.connected) {
+      console.error('[MQTT] 客户端未连接,无法订阅主题');
+      return;
+    }
+
+    this.mqttClient.subscribe(topics, (err) => {
+      if (err) {
+        console.error(`[MQTT] 订阅主题失败:`, err);
+        return;
+      }
+      const topicStr = Array.isArray(topics) ? topics.join('、') : topics;
+      console.log(`[MQTT] 订阅主题成功:${topicStr}`);
+    });
+  }
+
+  /**
+   * 取消订阅主题
+   * @param topics 主题字符串或主题数组
+   */
+  public unsubscribeTopics(topics: string | string[]): void {
+    if (!this.mqttClient || !this.mqttClient.connected) {
+      console.error('[MQTT] 客户端未连接,无法取消订阅');
+      return;
+    }
+
+    this.mqttClient.unsubscribe(topics, (err) => {
+      if (err) {
+        console.error(`[MQTT] 取消订阅主题失败:`, err);
+        return;
+      }
+      const topicStr = Array.isArray(topics) ? topics.join('、') : topics;
+      console.log(`[MQTT] 取消订阅主题成功:${topicStr}`);
+    });
+  }
+
+  /**
+   * 设置消息处理回调(后续可动态更新)
+   * @param callback 消息处理回调
+   */
+  public setMessageCallback(callback: MqttMessageCallback): void {
+    this.messageCallback = callback;
+  }
+
+  /**
+   * 断开MQTT连接
+   */
+  public disconnect(): void {
+    if (this.mqttClient && this.mqttClient.connected) {
+      this.mqttClient.end();
+      this.mqttClient = null;
+      console.log('[MQTT] 主动断开连接');
+    }
+  }
+
+  /**
+   * 判断MQTT客户端是否已连接
+   * @returns 连接状态(true=已连接,false=未连接)
+   */
+  public isConnected(): boolean {
+    return !!this.mqttClient && this.mqttClient.connected;
+  }
+}
+
+// 导出单例实例(默认先不初始化,需在项目入口/首次使用时初始化)
+export default MqttUtil;

+ 2 - 2
frontend/src/views/module_business/gateway/index.vue

@@ -587,7 +587,7 @@ const tableColumns = ref([
 
 
 // 导出列(不含选择/序号/操作)
 // 导出列(不含选择/序号/操作)
 const exportColumns = [
 const exportColumns = [
-  { prop: 'crane_no', label: '行车编号' },
+  { prop: 'crane_name', label: '行车' },
   { prop: 'gateway_name', label: '网关名称' },
   { prop: 'gateway_name', label: '网关名称' },
   { prop: 'gateway_type', label: '网关类型' },
   { prop: 'gateway_type', label: '网关类型' },
   { prop: 'gateway_ipaddress', label: '网关IP地址 ' },
   { prop: 'gateway_ipaddress', label: '网关IP地址 ' },
@@ -599,7 +599,7 @@ const exportColumns = [
   { prop: 'serial_data_bits', label: '数据位 5678' },
   { prop: 'serial_data_bits', label: '数据位 5678' },
   { prop: 'serial_stop_bits', label: '停止位 ' },
   { prop: 'serial_stop_bits', label: '停止位 ' },
   { prop: 'serial_parity', label: '检验位 ' },
   { prop: 'serial_parity', label: '检验位 ' },
-  { prop: 'status', label: '是否启用' },
+  { prop: 'status', label: '是否启用(0 否 1 是)' },
   { prop: 'description', label: '备注/描述' },
   { prop: 'description', label: '备注/描述' },
   { prop: 'created_time', label: '创建时间' },
   { prop: 'created_time', label: '创建时间' },
   { prop: 'updated_time', label: '更新时间' },
   { prop: 'updated_time', label: '更新时间' },

+ 6 - 7
frontend/src/views/module_business/mec/index.vue

@@ -598,7 +598,7 @@ const tableColumns = ref([
   { prop: "selection", label: "选择框", show: true },
   { prop: "selection", label: "选择框", show: true },
   { prop: "index", label: "序号", show: true },
   { prop: "index", label: "序号", show: true },
   { prop: 'crane_name', label: '行车', show: true },
   { prop: 'crane_name', label: '行车', show: true },
-  { prop: 'crane_no', label: '起重机编号', show: true },
+  { prop: 'crane_no', label: '行车编号', show: true },
   { prop: 'mec_no', label: '机构编号', show: true },
   { prop: 'mec_no', label: '机构编号', show: true },
   { prop: 'mec_category', label: '机构分类', show: true },
   { prop: 'mec_category', label: '机构分类', show: true },
   { prop: 'mec_type', label: '机构类型', show: true },
   { prop: 'mec_type', label: '机构类型', show: true },
@@ -618,21 +618,21 @@ const tableColumns = ref([
 
 
 // 导出列(不含选择/序号/操作)
 // 导出列(不含选择/序号/操作)
 const exportColumns = [
 const exportColumns = [
-  { prop: 'crane_no', label: '起重机编号' },
+  { prop: 'crane_name', label: '行车' },
   { prop: 'mec_no', label: '机构编号' },
   { prop: 'mec_no', label: '机构编号' },
   { prop: 'mec_category', label: '机构分类' },
   { prop: 'mec_category', label: '机构分类' },
   { prop: 'mec_type', label: '机构类型' },
   { prop: 'mec_type', label: '机构类型' },
   { prop: 'hoist_weight', label: '起升重量' },
   { prop: 'hoist_weight', label: '起升重量' },
   { prop: 'hoist_height', label: '起升高度' },
   { prop: 'hoist_height', label: '起升高度' },
   { prop: 'sort', label: '排序' },
   { prop: 'sort', label: '排序' },
-  { prop: 'status', label: '是否启用' },
+  { prop: 'status', label: '是否启用(0 禁用 1 启用)' },
   { prop: 'description', label: '备注/描述' },
   { prop: 'description', label: '备注/描述' },
   { prop: 'created_time', label: '创建时间' },
   { prop: 'created_time', label: '创建时间' },
   { prop: 'updated_time', label: '更新时间' },
   { prop: 'updated_time', label: '更新时间' },
   { prop: 'hoist_weight_var_code', label: '重量关联变量' },
   { prop: 'hoist_weight_var_code', label: '重量关联变量' },
   { prop: 'stroke_var_code', label: '行程高度变量' },
   { prop: 'stroke_var_code', label: '行程高度变量' },
-  { prop: 'is_canvas_show', label: '是否首页动画展示' },
-  { prop: 'is_canvas_move', label: '是否启用动画' },
+  { prop: 'is_canvas_show', label: '是否首页动画展示(0 禁用 1 启用)' },
+  { prop: 'is_canvas_move', label: '是否启用动画(0 禁用 1 启用)' },
 ]
 ]
 
 
 // 导入/导出配置
 // 导入/导出配置
@@ -730,7 +730,7 @@ const rules = reactive({
     { required: false, message: '请输入编号', trigger: 'blur' },
     { required: false, message: '请输入编号', trigger: 'blur' },
   ],
   ],
   crane_no: [
   crane_no: [
-    { required: false, message: '请输入起重机编号', trigger: 'blur' },
+    { required: false, message: '请输入行车编号', trigger: 'blur' },
   ],
   ],
   mec_no: [
   mec_no: [
     { required: false, message: '请输入机构编号', trigger: 'blur' },
     { required: false, message: '请输入机构编号', trigger: 'blur' },
@@ -1012,7 +1012,6 @@ const handleUpload = async (formData: FormData) => {
 onMounted(async () => {
 onMounted(async () => {
   // 预加载字典数据
   // 预加载字典数据
   if (dictTypes.length > 0) {
   if (dictTypes.length > 0) {
-    console.log(dictStore.getDictArray('mec_category'));
     await dictStore.getDict(dictTypes)
     await dictStore.getDict(dictTypes)
   }
   }
   loadingData();
   loadingData();

+ 1 - 1
frontend/src/views/module_business/vardict/index.vue

@@ -886,7 +886,7 @@ const tableColumns = ref([
 
 
 // 导出列(不含选择/序号/操作)
 // 导出列(不含选择/序号/操作)
 const exportColumns = [
 const exportColumns = [
-  { prop: 'crane_no', label: 'crane_no' },
+  { prop: 'crane_name', label: '行车' },
   { prop: 'var_code', label: '变量code' },
   { prop: 'var_code', label: '变量code' },
   { prop: 'var_name', label: '变量名称' },
   { prop: 'var_name', label: '变量名称' },
   { prop: 'mec_type', label: '所属机构' },
   { prop: 'mec_type', label: '所属机构' },

+ 8 - 10
frontend/src/views/web/detail/index.vue

@@ -1,13 +1,13 @@
 <template>
 <template>
     <div class="menu-content">
     <div class="menu-content">
       <el-menu ellipsis :default-active="activeIndex" mode="horizontal" :router="true">
       <el-menu ellipsis :default-active="activeIndex" mode="horizontal" :router="true">
-        <el-menu-item :index="`/crane/realtimeData/${CraneId}`">实时数据</el-menu-item>
-        <el-menu-item :index="`/crane/historyData/${CraneId}`">历史数据</el-menu-item>
-        <el-menu-item :index="`/crane/operationRecord/${CraneId}`">操作记录</el-menu-item>
-        <el-menu-item :index="`/crane/realtimeAlarm/${CraneId}`">实时报警</el-menu-item>
-        <el-menu-item :index="`/crane/historyAlarm/${CraneId}`">历史报警</el-menu-item>
-        <el-menu-item :index="`/crane/realtimeCurve/${CraneId}`">实时曲线</el-menu-item>
-        <el-menu-item :index="`/crane/historyCurve/${CraneId}`">历史曲线</el-menu-item>
+        <el-menu-item :index="`/detail/realtimeData`">实时数据</el-menu-item>
+        <el-menu-item :index="`/detail/historyData`">历史数据</el-menu-item>
+        <el-menu-item :index="`/detail/operationRecord`">操作记录</el-menu-item>
+        <el-menu-item :index="`/detail/realtimeAlarm`">实时报警</el-menu-item>
+        <el-menu-item :index="`/detail/historyAlarm`">历史报警</el-menu-item>
+        <el-menu-item :index="`/detail/realtimeCurve`">实时曲线</el-menu-item>
+        <el-menu-item :index="`/detail/historyCurve`">历史曲线</el-menu-item>
       </el-menu>
       </el-menu>
     </div>
     </div>
     <router-view class="view-content" />
     <router-view class="view-content" />
@@ -17,9 +17,7 @@
   import { ref, onMounted } from 'vue'
   import { ref, onMounted } from 'vue'
   
   
   const router = useRouter()
   const router = useRouter()
-  const craneInfo = JSON.parse(localStorage.getItem('craneInfo') || '{}')
-  const CraneId = craneInfo.id
-  const activeIndex = ref(`/detail/realtimeData/${CraneId}`)
+  const activeIndex = ref(`/detail/realtimeData`)
   
   
   onMounted(() => {
   onMounted(() => {
     router.push(activeIndex.value)
     router.push(activeIndex.value)

+ 119 - 56
frontend/src/views/web/detail/realtimeData.vue

@@ -2,16 +2,18 @@
   <div class="realtimeData-content">
   <div class="realtimeData-content">
 
 
     <div v-loading="loading" class="mec-content">
     <div v-loading="loading" class="mec-content">
-      <div class="mec-item" v-for="(item, index) in deviceStateData" :key="index">
+      <div class="mec-item" v-for="(item, index) in varDictMecGroupData" :key="index">
         <div class="mec-title">
         <div class="mec-title">
-          {{ item.GroupName }}
+          {{ (item.MecType 
+                  ? (dictStore.getDictLabel("mec_type", item.MecType) as any)
+                  : undefined
+                )?.dict_label || detailFormData.MecType }}
         </div>
         </div>
         <div class="mec-content-item">
         <div class="mec-content-item">
           <div class="number-type-content" v-for="(temp, index1) in item.numberTypeList" :key="index1">
           <div class="number-type-content" v-for="(temp, index1) in item.numberTypeList" :key="index1">
             <span>{{ temp.VarName }}</span>
             <span>{{ temp.VarName }}</span>
             <div>
             <div>
               <span style="font-size: 22px;">{{ temp.Value }}</span>
               <span style="font-size: 22px;">{{ temp.Value }}</span>
-              <span style="font-size: 22px;">{{ temp.UnitValue }}</span>
             </div>
             </div>
           </div>
           </div>
           <div style="margin-top: 30px;">
           <div style="margin-top: 30px;">
@@ -26,20 +28,27 @@
   </div>
   </div>
 
 
 </template>
 </template>
-<script setup>
+<script setup lang="ts">
 import BizVarDictAPI, { } from '@/api/module_business/vardict'
 import BizVarDictAPI, { } from '@/api/module_business/vardict'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
+import { useDictStore } from "@/store";
+import mqtt, { MqttClient } from 'mqtt';
 //import MqttService from '@/utils/mqttService';
 //import MqttService from '@/utils/mqttService';
 //import { getCraneMecTree } from '@/api/crane';
 //import { getCraneMecTree } from '@/api/crane';
 //import { mecDataFormat, gearCalculation } from '@/utils/hooks'
 //import { mecDataFormat, gearCalculation } from '@/utils/hooks'
 
 
 const route = useRoute()
 const route = useRoute()
-const deviceStateData = ref([])
+const receiveData = inject<(data: { craneName: string; isShowHomeButton: boolean }) => void>('receiveData');
+const craneInfo = JSON.parse(localStorage.getItem('craneInfo') || '{}')
+const varDictMecGroupData = ref([])
 const loading = ref(true);
 const loading = ref(true);
+const MQTT_WS_URL = import.meta.env.VITE_APP_WS_ENDPOINT || 'ws://127.0.0.1:9001';
+const VAR_TOPIC = 'gc/var/' + craneInfo.crane_no;
+let mqttClient: MqttClient | null = null;
 const getCraneMecTreeData = async () => {
 const getCraneMecTreeData = async () => {
-  const response = await BizVarDictAPI.varDictMecTree(route.params.craneId);
-  console.log(response.data.data)
-  deviceStateData.value = mecDataFormat(data.data)
+  const response = await BizVarDictAPI.varDictMecGroup(craneInfo.id);
+  varDictMecGroupData.value = mecDataFormat(response.data.data)
+  console.log(varDictMecGroupData.value)
   //localStorage.setItem('varDict', JSON.stringify(deviceStateData.value))
   //localStorage.setItem('varDict', JSON.stringify(deviceStateData.value))
 }
 }
 
 
@@ -50,8 +59,8 @@ const mecDataFormat = (mecData) => {
       const numberTypelist = []
       const numberTypelist = []
       const boolTypeList = []
       const boolTypeList = []
       const gearList = []
       const gearList = []
-      item.vardict_list.forEach((simpleItem) => {
-        if (simpleItem.DataType === 1) {
+      item.varList_simple.forEach((simpleItem) => {
+        if (simpleItem.data_type === 1) {
             boolTypeList.push({
             boolTypeList.push({
               VarName: simpleItem.var_name,
               VarName: simpleItem.var_name,
               Value: true,
               Value: true,
@@ -59,31 +68,24 @@ const mecDataFormat = (mecData) => {
               SwitchType: simpleItem.switch_type
               SwitchType: simpleItem.switch_type
             })
             })
           } else {
           } else {
-            if (simpleItem.VarCategory === 11) {
-              simpleItem.UnitValue = '挡'
-            }
             numberTypelist.push({
             numberTypelist.push({
-              VarName: simpleItem.VarName,
+              VarName: simpleItem.var_name,
               Value: 0,
               Value: 0,
-              UnitValue: simpleItem.UnitValue,
-              VarCategory: simpleItem.VarCategory,
-              VarCode: simpleItem.VarCode
+              VarCategory: simpleItem.var_category,
+              VarCode: simpleItem.var_code
             })
             })
           }
           }
-        if (simpleItem.VarCategory != 1) {
-          
-        } else {
+        if (simpleItem.var_category == 1) {
           gearList.push({
           gearList.push({
-            VarName: simpleItem.VarName,
+            VarName: simpleItem.var_name,
             Value: false,
             Value: false,
-            VarCode: simpleItem.VarCode,
-            VarCategory: simpleItem.VarCategory
+            VarCode: simpleItem.var_code,
+            VarCategory: simpleItem.var_category
           })
           })
         }
         }
       })
       })
       resultData.push({
       resultData.push({
-        GroupName: item.GroupName,
-        MecType: item.MecType,
+        MecType: item.mec_type,
         numberTypeList: numberTypelist,
         numberTypeList: numberTypelist,
         boolTypeList: boolTypeList,
         boolTypeList: boolTypeList,
         gearList: gearList
         gearList: gearList
@@ -93,6 +95,12 @@ const mecDataFormat = (mecData) => {
   return reactive(resultData);
   return reactive(resultData);
 }
 }
 
 
+// 字典仓库与需要加载的字典类型
+const dictStore = useDictStore()
+const dictTypes: any = [
+  'mec_type'
+]
+
 const getData = () => {
 const getData = () => {
   getCraneMecTreeData()
   getCraneMecTreeData()
 }
 }
@@ -108,42 +116,97 @@ const getBgColor = (bool, type) => {
       return bool ? 'pilot-lamp-bg-green' : 'pilot-lamp-bg-red';
       return bool ? 'pilot-lamp-bg-green' : 'pilot-lamp-bg-red';
   }
   }
 }
 }
-//const mqttService = new MqttService();
-const topics = ['gc/var/' + route.params.craneNo];
 
 
-onMounted(() => {
+//初始化 MQTT 连接并订阅主题
+const initMqttClient = () => {
+  // 避免重复连接
+  if (mqttClient && mqttClient.connected) {
+    console.log('[MQTT] 客户端已连接,无需重复初始化');
+    return;
+  }
+  console.log(`[MQTT] 开始连接 Broker: ${MQTT_WS_URL}`);
+
+  // 创建 MQTT 客户端
+  mqttClient = mqtt.connect(MQTT_WS_URL);
+
+  // 连接成功回调
+  mqttClient.on('connect', () => {
+    console.log('[MQTT] 连接成功');
+
+    // 同时订阅两个主题
+    mqttClient?.subscribe([VAR_TOPIC], (err) => {
+      if (err) {
+        console.error('[MQTT] 订阅主题失败:', err);
+        return;
+      }
+      console.log(`[MQTT] 订阅主题成功:${VAR_TOPIC}`);
+    });
+  });
+
+  // 接收消息回调(统一处理所有主题消息)
+  mqttClient.on('message', (topic, payload) => {
+    console.log(`[MQTT] 收到主题 ${topic} 的消息:`, payload.toString());
+    try {
+      const message = JSON.parse(payload.toString());
+      //挡位计算
+      message = gearCalculation(message, varDictMecGroupData.value)
+      message.data.forEach(msgItem => {
+        deviceStateData.value.forEach(item => {
+          item.numberTypeList.forEach(numberItem => {
+            if (msgItem.var_code === numberItem.VarCode) {
+              numberItem.Value = msgItem.value
+              return;
+            }
+          })
+          item.boolTypeList.forEach(boolItem => {
+            if (msgItem.var_code === boolItem.VarCode) {
+              boolItem.Value = msgItem.value
+              return;
+            }
+          })
+        })
+      })
+      loading.value = false
+    } catch (err) {
+      console.error('[MQTT] 解析消息失败:', err);
+    }
+  });
+
+  // 连接断开回调
+  mqttClient.on('close', () => {
+    console.log('[MQTT] 连接已断开');
+  });
+
+  // 连接错误回调
+  mqttClient.on('error', (err) => {
+    console.error('[MQTT] 连接错误:', err);
+  });
+};
+
+onMounted(async () => {
+  if (dictTypes.length > 0) {
+    await dictStore.getDict(dictTypes)
+  }
+  if (receiveData) {
+    receiveData({ craneName: craneInfo.crane_name ?? '', isShowHomeButton: true });
+  }
   getData()
   getData()
-  // mqttService.connect();
-  // topics.forEach(topic => {
-  //   mqttService.subscribe(topic, (message) => {
-  //     //挡位计算
-  //     message = gearCalculation(message, deviceStateData.value)
-  //     message.data.forEach(msgItem => {
-  //       deviceStateData.value.forEach(item => {
-  //         item.numberTypeList.forEach(numberItem => {
-  //           if (msgItem.var_code === numberItem.VarCode) {
-  //             numberItem.Value = msgItem.value
-  //             return;
-  //           }
-  //         })
-  //         item.boolTypeList.forEach(boolItem => {
-  //           if (msgItem.var_code === boolItem.VarCode) {
-  //             boolItem.Value = msgItem.value
-  //             return;
-  //           }
-  //         })
-  //       })
-  //     })
-  //     loading.value = false
-  //   });
-  // });
+  mqttService.connect();
+  topics.forEach(topic => {
+    mqttService.subscribe(topic, (message) => {
+
+    });
+  });
 });
 });
 
 
 onUnmounted(() => {
 onUnmounted(() => {
-  // topics.forEach(topic => {
-  //   mqttService.unsubscribe(topic);
-  // });
-  // mqttService.disconnect();
+  // 页面销毁时主动断开 MQTT 连接
+  if (mqttClient) {
+    mqttClient.end(true, () => {
+      console.log('[MQTT] 主动断开连接');
+      mqttClient = null;
+    });
+  }
 });
 });
 
 
 </script>
 </script>

+ 32 - 84
frontend/src/views/web/overview/index.vue

@@ -54,7 +54,7 @@ import BizCraneAPI, { BizCranePageQuery, BizCraneTable } from '@/api/module_busi
 import emptybgUrl from '@/assets/images/empty-bg.png';
 import emptybgUrl from '@/assets/images/empty-bg.png';
 import { onMounted, onUnmounted, ref, reactive, inject } from 'vue';
 import { onMounted, onUnmounted, ref, reactive, inject } from 'vue';
 import { useRouter } from 'vue-router';
 import { useRouter } from 'vue-router';
-import mqtt, { MqttClient } from 'mqtt';
+import MqttUtil, { MqttMessageCallback } from '@/utils/mqttUtil';
 
 
 interface alertData {
 interface alertData {
   switch_type?: string;
   switch_type?: string;
@@ -78,13 +78,12 @@ const tab_loading = ref(true)
 const alertData = ref<alertData[]>([])
 const alertData = ref<alertData[]>([])
 const craneData = ref<BizCraneTable[]>([]);
 const craneData = ref<BizCraneTable[]>([]);
 
 
-// ======================== MQTT 配置 ========================
-const MQTT_WS_URL = import.meta.env.VITE_APP_WS_ENDPOINT || 'ws://127.0.0.1:9001';
-const ALERT_TOPIC = 'gc/alert'; // 报警消息主题
-const ONLINE_STATUS_TOPIC = 'gc/crane_status'; // 在线状态消息主题
-
-// MQTT 客户端实例 & 连接状态
-let mqttClient: MqttClient | null = null;
+const mqttConfig = {
+  wsUrl: import.meta.env.VITE_APP_WS_ENDPOINT || 'ws://127.0.0.1:9001',
+  clientOptions: {},
+  defaultTopics: ['gc/alert', 'gc/crane_status']
+}
+const mqttUtil = MqttUtil.getInstance(mqttConfig);
 
 
 // 表格配置
 // 表格配置
 const tableConfig = ref([
 const tableConfig = ref([
@@ -153,21 +152,21 @@ const getCraneListData = async () => {
 // 颜色样式处理
 // 颜色样式处理
 const getColor = (type: string) => {
 const getColor = (type: string) => {
   switch (type) {
   switch (type) {
-    case '2':
+    case 2:
       return 'content-item-yellow';
       return 'content-item-yellow';
-    case '3':
+    case 3:
       return 'content-item-orange';
       return 'content-item-orange';
-    case '4':
+    case 4:
       return 'content-item-red';
       return 'content-item-red';
   }
   }
 }
 }
 const getColorSpan = (type: string) => {
 const getColorSpan = (type: string) => {
   switch (type) {
   switch (type) {
-    case '2':
+    case 2:
       return 'content-item-span-yellow';
       return 'content-item-span-yellow';
-    case '3':
+    case 3:
       return 'content-item-span-orange';
       return 'content-item-span-orange';
-    case '4':
+    case 4:
       return 'content-item-span-red';
       return 'content-item-span-red';
   }
   }
 }
 }
@@ -176,87 +175,36 @@ const getColorSpan = (type: string) => {
 const getData = () => {
 const getData = () => {
   getCraneListData()
   getCraneListData()
 }
 }
-//初始化 MQTT 连接并订阅主题
-const initMqttClient = () => {
-  // 避免重复连接
-  if (mqttClient && mqttClient.connected) {
-    console.log('[MQTT] 客户端已连接,无需重复初始化');
-    return;
-  }
-  console.log(`[MQTT] 开始连接 Broker: ${MQTT_WS_URL}`);
-
-  // 创建 MQTT 客户端
-  mqttClient = mqtt.connect(MQTT_WS_URL);
-
-  // 连接成功回调
-  mqttClient.on('connect', () => {
-    console.log('[MQTT] 连接成功');
-
-    // 同时订阅两个主题
-    mqttClient?.subscribe([ALERT_TOPIC, ONLINE_STATUS_TOPIC], (err) => {
-      if (err) {
-        console.error('[MQTT] 订阅主题失败:', err);
-        return;
-      }
-      console.log(`[MQTT] 订阅主题成功:${ALERT_TOPIC}、${ONLINE_STATUS_TOPIC}`);
-    });
-  });
-
-  // 接收消息回调(统一处理所有主题消息)
-  mqttClient.on('message', (topic, payload) => {
-    console.log(`[MQTT] 收到主题 ${topic} 的消息:`, payload.toString());
-    try {
-      const data = JSON.parse(payload.toString());
-      // 根据主题区分消息类型,复用原有消息处理逻辑
-      if (topic === ALERT_TOPIC) {
-        alarm_loading.value = false;
-        alertData.value = data || [];
-      } else if (topic === ONLINE_STATUS_TOPIC) {
-        if (Array.isArray(data)) {
-          data.forEach((item: any) => {
-            craneData.value.forEach((craneItem) => {
-              if (craneItem.crane_no === item.crane_no) {
-                craneItem.online_status = item.is_online ? '在线' : '离线';
-              }
-            });
-          });
-        }
-      }
-    } catch (err) {
-      console.error('[MQTT] 解析消息失败:', err);
-    }
-  });
 
 
-  // 连接断开回调
-  mqttClient.on('close', () => {
-    console.log('[MQTT] 连接已断开');
-  });
+const handleMqttMessage: MqttMessageCallback = (topic, payload) => {
+  if (topic === 'gc/alert') {
+    alarm_loading.value = false;
+    alertData.value = payload.data || [];
+  } else if (topic === 'gc/crane_status') {
+    if (Array.isArray(payload)) {
+      payload.forEach((item: any) => {
+        craneData.value.forEach((craneItem) => {
+          if (craneItem.crane_no === item.crane_no) {
+            craneItem.online_status = item.is_online ? '在线' : '离线';
+          }
+        });
+      });
+    }
+  }
+}
 
 
-  // 连接错误回调
-  mqttClient.on('error', (err) => {
-    console.error('[MQTT] 连接错误:', err);
-  });
-};
 
 
-// ======================== 生命周期 ========================
 onMounted(async () => {
 onMounted(async () => {
-  // 初始化业务数据
   getData();
   getData();
-  // 初始化 MQTT 连接
-  initMqttClient();
+  mqttUtil.initMqttClient(handleMqttMessage);
   if (receiveData) {
   if (receiveData) {
     receiveData({ craneName: '', isShowHomeButton: false });
     receiveData({ craneName: '', isShowHomeButton: false });
   }
   }
 });
 });
 
 
 onUnmounted(() => {
 onUnmounted(() => {
-  // 页面销毁时主动断开 MQTT 连接
-  if (mqttClient) {
-    mqttClient.end(true, () => {
-      console.log('[MQTT] 主动断开连接');
-      mqttClient = null;
-    });
-  }
+  mqttUtil.unsubscribeTopics(['gc/alert','gc/crane_status']);
+  mqttUtil.disconnect();
 });
 });
 </script>
 </script>