瀏覽代碼

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

cuiHe 4 天之前
父節點
當前提交
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.responses import StreamingResponse, JSONResponse
+from redis.asyncio import Redis
 
 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.core.base_params import PaginationQueryParam
 from app.utils.common_util import bytes2file_response
@@ -56,10 +57,11 @@ async def get_crane_list_controller(
 @BizCraneRouter.post("/create", summary="创建行车信息", description="创建行车信息")
 async def create_crane_controller(
     data: BizCraneCreateSchema,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:create"]))
 ) -> 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("创建行车信息成功")
     return SuccessResponse(data=result_dict, msg="创建行车信息成功")
 
@@ -67,30 +69,33 @@ async def create_crane_controller(
 async def update_crane_controller(
     data: BizCraneUpdateSchema,
     id: int = Path(..., description="ID"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:update"]))
 ) -> 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("修改行车信息成功")
     return SuccessResponse(data=result_dict, msg="修改行车信息成功")
 
 @BizCraneRouter.delete("/delete", summary="删除行车信息", description="删除行车信息")
 async def delete_crane_controller(
     ids: list[int] = Body(..., description="ID列表"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:delete"]))
 ) -> 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}")
     return SuccessResponse(msg="删除行车信息成功")
 
 @BizCraneRouter.patch("/available/setting", summary="批量修改行车信息状态", description="批量修改行车信息状态")
 async def batch_set_available_crane_controller(
     data: BatchSetAvailable,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:patch"]))
 ) -> 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}")
     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 app.core.validator import DateTimeStr
 from app.core.base_schema import BaseSchema, UserBySchema
-from typing import Annotated, Optional
+from typing import Annotated
 
 # 定义通用的Decimal序列化类型:转为字符串(全局复用)
 # 若需转浮点数,把 lambda v: str(v) 改为 lambda v: float(v)
@@ -90,4 +90,4 @@ class BizCraneQueryParam:
         if created_time and len(created_time) == 2:
             self.created_time = ("between", (created_time[0], created_time[1]))
         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 -*-
 
 import io
+
+
 from fastapi import UploadFile
 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.exceptions import CustomException
+from app.core.redis_crud import RedisCURD
 from app.utils.excel_util import ExcelUtil
 from app.core.logger import log
 from app.api.v1.module_system.auth.schema import AuthSchema
@@ -56,14 +61,17 @@ class BizCraneService:
         return result
     
     @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)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizCraneOutSchema.model_validate(obj).model_dump()
     
     @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)
@@ -73,10 +81,13 @@ class BizCraneService:
         # 检查唯一性约束
             
         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()
     
     @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:
             raise CustomException(msg='删除失败,删除对象不能为空')
@@ -85,11 +96,15 @@ class BizCraneService:
             if not obj:
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
         await BizCraneCRUD(auth).delete_crane_crud(ids=ids)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     @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 RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     @classmethod
     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.responses import StreamingResponse, JSONResponse
+from redis.asyncio import Redis
 
 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.core.base_params import PaginationQueryParam
 from app.utils.common_util import bytes2file_response
@@ -56,10 +57,11 @@ async def get_mec_list_controller(
 @BizMecRouter.post("/create", summary="创建机构信息", description="创建机构信息")
 async def create_mec_controller(
     data: BizMecCreateSchema,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:create"]))
 ) -> 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("创建机构信息成功")
     return SuccessResponse(data=result_dict, msg="创建机构信息成功")
 
@@ -67,30 +69,33 @@ async def create_mec_controller(
 async def update_mec_controller(
     data: BizMecUpdateSchema,
     id: int = Path(..., description="ID"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:update"]))
 ) -> 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("修改机构信息成功")
     return SuccessResponse(data=result_dict, msg="修改机构信息成功")
 
 @BizMecRouter.delete("/delete", summary="删除机构信息", description="删除机构信息")
 async def delete_mec_controller(
     ids: list[int] = Body(..., description="ID列表"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:delete"]))
 ) -> 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}")
     return SuccessResponse(msg="删除机构信息成功")
 
 @BizMecRouter.patch("/available/setting", summary="批量修改机构信息状态", description="批量修改机构信息状态")
 async def batch_set_available_mec_controller(
     data: BatchSetAvailable,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:mec:patch"]))
 ) -> 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}")
     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_category: str = Field(default=..., description='机构分类 MecCategory')
     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='起升速度 起升机构')
     rope_count: 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='排序 ')
     status: str = Field(default="1", description='是否启用(0:禁用 1:启用)')
     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='行程高度变量')
     is_canvas_show: 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
 from fastapi import UploadFile
 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.exceptions import CustomException
+from app.core.redis_crud import RedisCURD
 from app.utils.excel_util import ExcelUtil
 from app.core.logger import log
 from app.api.v1.module_system.auth.schema import AuthSchema
@@ -64,14 +67,17 @@ class BizMecService:
         return result
     
     @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)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizMecOutSchema.model_validate(obj).model_dump()
     
     @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)
@@ -81,10 +87,13 @@ class BizMecService:
         # 检查唯一性约束
             
         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()
     
     @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:
             raise CustomException(msg='删除失败,删除对象不能为空')
@@ -93,11 +102,15 @@ class BizMecService:
             if not obj:
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
         await BizMecCRUD(auth).delete_mec_crud(ids=ids)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     @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 RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     @classmethod
     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.responses import StreamingResponse, JSONResponse
-
+from redis.asyncio.client import Redis
 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.core.base_params import PaginationQueryParam
 from app.utils.common_util import bytes2file_response
@@ -46,10 +46,11 @@ async def get_vardict_list_controller(
 @BizVarDictRouter.post("/create", summary="创建变量信息", description="创建变量信息")
 async def create_vardict_controller(
     data: BizVarDictCreateSchema,
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:create"]))
 ) -> 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("创建变量信息成功")
     return SuccessResponse(data=result_dict, msg="创建变量信息成功")
 
@@ -57,20 +58,22 @@ async def create_vardict_controller(
 async def update_vardict_controller(
     data: BizVarDictUpdateSchema,
     id: int = Path(..., description="ID"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:update"]))
 ) -> 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("修改变量信息成功")
     return SuccessResponse(data=result_dict, msg="修改变量信息成功")
 
 @BizVarDictRouter.delete("/delete", summary="删除变量信息", description="删除变量信息")
 async def delete_vardict_controller(
     ids: list[int] = Body(..., description="ID列表"),
+    redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:delete"]))
 ) -> 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}")
     return SuccessResponse(msg="删除变量信息成功")
 
@@ -123,4 +126,17 @@ async def export_vardict_template_controller() -> StreamingResponse:
         data=bytes2file_response(import_template_result),
         media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
         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]))
         if updated_time and len(updated_time) == 2:
             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 -*-
 
 import io
+import json
 from fastapi import UploadFile
 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.exceptions import CustomException
 from app.utils.excel_util import ExcelUtil
 from app.core.logger import log
 from app.api.v1.module_system.auth.schema import AuthSchema
 from .schema import BizVarDictCreateSchema, BizVarDictUpdateSchema, BizVarDictOutSchema, BizVarDictQueryParam
-from .crud import BizVarDictCRUD
 from ..crane.crud import BizCraneCRUD
 from ..crane.model import BizCraneModel
 from ..gateway.crud import GatewayCRUD
 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:
@@ -63,14 +69,17 @@ class BizVarDictService:
         return result
     
     @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)
+        if obj:
+            # 更新缓存中数据
+            await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
         return BizVarDictOutSchema.model_validate(obj).model_dump()
     
     @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)
@@ -80,10 +89,13 @@ class BizVarDictService:
         # 检查唯一性约束
             
         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()
     
     @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:
             raise CustomException(msg='删除失败,删除对象不能为空')
@@ -92,11 +104,15 @@ class BizVarDictService:
             if not obj:
                 raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
         await BizVarDictCRUD(auth).delete_vardict_crud(ids=ids)
+        # 更新缓存中数据
+        await RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     @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 RedisCURD(redis).clear(f"{RedisInitKeyConfig.VAR_DICT.key}:*")
     
     @classmethod
     async def batch_export_vardict_service(cls, obj_list: list[dict]) -> bytes:
@@ -131,7 +147,6 @@ class BizVarDictService:
             'updated_time': '更新时间',
             'created_id': '创建人ID',
             'updated_id': '更新人ID',
-            'updated_id': '更新者ID',
         }
 
         data = obj_list.copy()
@@ -308,4 +323,106 @@ class BizVarDictService:
             header_list=header_list,
             selector_header_list=selector_header_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': '图片验证码'}
     SYSTEM_CONFIG = {'key': 'system_config', 'remark': '系统配置'}
     SYSTEM_DICT = {'key':'system_dict','remark': '数据字典'}
+    VAR_DICT = {'key': 'var_dict', 'remark': '变量数据'}
     
     @property
     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 math import ceil
 
+from app.api.v1.module_business.vardict.service import BizVarDictService
 from app.config.setting import settings
 from app.core.logger import log
 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_system.params.service import ParamsService
 from app.api.v1.module_system.dict.service import DictDataService
+from app.api.v1.module_business.crane.service import BizCraneService
 
 
 @asynccontextmanager
@@ -46,6 +48,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]:
         log.info("✅ Redis系统配置初始化完成")
         await DictDataService().init_dict_service(redis=app.state.redis)
         log.info("✅ Redis数据字典初始化完成")
+        await BizVarDictService().init_vardict_group_service(redis=app.state.redis)
+        log.info("✅ Redis天车数据初始化完成")
         await SchedulerUtil.init_system_scheduler()
         scheduler_jobs_count = len(SchedulerUtil.get_all_jobs())
         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) {
     return request<ApiResponse>({

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

@@ -64,35 +64,35 @@ export const constantRoutes: RouteRecordRaw[] = [
       {
         path: '/detail',
         name: 'Detail',
-        meta: { title: '起重机详情' },
+        meta: { title: '行车详情' },
         component: import("@/views/web/detail/index.vue"),
         children: [
           {
-            path: '/detail/realtimeData/:craneId',
+            path: '/detail/realtimeData',
             name: 'RealtimeData',
             meta: { title: '实时数据' },
             component: import("@/views/web/detail/realtimeData.vue")
           },
           {
-            path: '/detail/historyData/:craneId',
+            path: '/detail/historyData',
             name: 'HistoryData',
             meta: { title: '历史数据' },
             component: import("@/views/web/detail/historyData.vue")
           },
           {
-            path: '/detail/operationRecord/:craneId',
+            path: '/detail/operationRecord',
             name: 'OperationRecord',
             meta: { title: '操作记录' },
             component: import("@/views/web/detail/operationRecord.vue")
           },
           {
-            path: '/detail/realtimeAlarm/:craneId',
+            path: '/detail/realtimeAlarm',
             name: 'RealtimeAlarm',
             meta: { title: '实时报警' },
             component: import("@/views/web/detail/realtimeAlarm.vue")
           },
           {
-            path: '/detail/historyAlarm/:craneId',
+            path: '/detail/historyAlarm',
             name: 'HistoryAlarm',
             meta: { title: '历史报警' },
             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 {
   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 = [
-  { prop: 'crane_no', label: '行车编号' },
+  { prop: 'crane_name', label: '行车' },
   { prop: 'gateway_name', label: '网关名称' },
   { prop: 'gateway_type', label: '网关类型' },
   { prop: 'gateway_ipaddress', label: '网关IP地址 ' },
@@ -599,7 +599,7 @@ const exportColumns = [
   { prop: 'serial_data_bits', label: '数据位 5678' },
   { prop: 'serial_stop_bits', label: '停止位 ' },
   { prop: 'serial_parity', label: '检验位 ' },
-  { prop: 'status', label: '是否启用' },
+  { prop: 'status', label: '是否启用(0 否 1 是)' },
   { prop: 'description', label: '备注/描述' },
   { prop: 'created_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: "index", 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_category', label: '机构分类', show: true },
   { prop: 'mec_type', label: '机构类型', show: true },
@@ -618,21 +618,21 @@ const tableColumns = ref([
 
 // 导出列(不含选择/序号/操作)
 const exportColumns = [
-  { prop: 'crane_no', label: '起重机编号' },
+  { prop: 'crane_name', label: '行车' },
   { prop: 'mec_no', label: '机构编号' },
   { prop: 'mec_category', label: '机构分类' },
   { prop: 'mec_type', label: '机构类型' },
   { prop: 'hoist_weight', label: '起升重量' },
   { prop: 'hoist_height', label: '起升高度' },
   { prop: 'sort', label: '排序' },
-  { prop: 'status', label: '是否启用' },
+  { prop: 'status', label: '是否启用(0 禁用 1 启用)' },
   { prop: 'description', label: '备注/描述' },
   { prop: 'created_time', label: '创建时间' },
   { prop: 'updated_time', label: '更新时间' },
   { prop: 'hoist_weight_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' },
   ],
   crane_no: [
-    { required: false, message: '请输入起重机编号', trigger: 'blur' },
+    { required: false, message: '请输入行车编号', trigger: 'blur' },
   ],
   mec_no: [
     { required: false, message: '请输入机构编号', trigger: 'blur' },
@@ -1012,7 +1012,6 @@ const handleUpload = async (formData: FormData) => {
 onMounted(async () => {
   // 预加载字典数据
   if (dictTypes.length > 0) {
-    console.log(dictStore.getDictArray('mec_category'));
     await dictStore.getDict(dictTypes)
   }
   loadingData();

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

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

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

@@ -1,13 +1,13 @@
 <template>
     <div class="menu-content">
       <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>
     </div>
     <router-view class="view-content" />
@@ -17,9 +17,7 @@
   import { ref, onMounted } from 'vue'
   
   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(() => {
     router.push(activeIndex.value)

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

@@ -2,16 +2,18 @@
   <div class="realtimeData-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">
-          {{ item.GroupName }}
+          {{ (item.MecType 
+                  ? (dictStore.getDictLabel("mec_type", item.MecType) as any)
+                  : undefined
+                )?.dict_label || detailFormData.MecType }}
         </div>
         <div class="mec-content-item">
           <div class="number-type-content" v-for="(temp, index1) in item.numberTypeList" :key="index1">
             <span>{{ temp.VarName }}</span>
             <div>
               <span style="font-size: 22px;">{{ temp.Value }}</span>
-              <span style="font-size: 22px;">{{ temp.UnitValue }}</span>
             </div>
           </div>
           <div style="margin-top: 30px;">
@@ -26,20 +28,27 @@
   </div>
 
 </template>
-<script setup>
+<script setup lang="ts">
 import BizVarDictAPI, { } from '@/api/module_business/vardict'
 import { useRoute } from 'vue-router'
+import { useDictStore } from "@/store";
+import mqtt, { MqttClient } from 'mqtt';
 //import MqttService from '@/utils/mqttService';
 //import { getCraneMecTree } from '@/api/crane';
 //import { mecDataFormat, gearCalculation } from '@/utils/hooks'
 
 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 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 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))
 }
 
@@ -50,8 +59,8 @@ const mecDataFormat = (mecData) => {
       const numberTypelist = []
       const boolTypeList = []
       const gearList = []
-      item.vardict_list.forEach((simpleItem) => {
-        if (simpleItem.DataType === 1) {
+      item.varList_simple.forEach((simpleItem) => {
+        if (simpleItem.data_type === 1) {
             boolTypeList.push({
               VarName: simpleItem.var_name,
               Value: true,
@@ -59,31 +68,24 @@ const mecDataFormat = (mecData) => {
               SwitchType: simpleItem.switch_type
             })
           } else {
-            if (simpleItem.VarCategory === 11) {
-              simpleItem.UnitValue = '挡'
-            }
             numberTypelist.push({
-              VarName: simpleItem.VarName,
+              VarName: simpleItem.var_name,
               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({
-            VarName: simpleItem.VarName,
+            VarName: simpleItem.var_name,
             Value: false,
-            VarCode: simpleItem.VarCode,
-            VarCategory: simpleItem.VarCategory
+            VarCode: simpleItem.var_code,
+            VarCategory: simpleItem.var_category
           })
         }
       })
       resultData.push({
-        GroupName: item.GroupName,
-        MecType: item.MecType,
+        MecType: item.mec_type,
         numberTypeList: numberTypelist,
         boolTypeList: boolTypeList,
         gearList: gearList
@@ -93,6 +95,12 @@ const mecDataFormat = (mecData) => {
   return reactive(resultData);
 }
 
+// 字典仓库与需要加载的字典类型
+const dictStore = useDictStore()
+const dictTypes: any = [
+  'mec_type'
+]
+
 const getData = () => {
   getCraneMecTreeData()
 }
@@ -108,42 +116,97 @@ const getBgColor = (bool, type) => {
       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()
-  // 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(() => {
-  // topics.forEach(topic => {
-  //   mqttService.unsubscribe(topic);
-  // });
-  // mqttService.disconnect();
+  // 页面销毁时主动断开 MQTT 连接
+  if (mqttClient) {
+    mqttClient.end(true, () => {
+      console.log('[MQTT] 主动断开连接');
+      mqttClient = null;
+    });
+  }
 });
 
 </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 { onMounted, onUnmounted, ref, reactive, inject } from 'vue';
 import { useRouter } from 'vue-router';
-import mqtt, { MqttClient } from 'mqtt';
+import MqttUtil, { MqttMessageCallback } from '@/utils/mqttUtil';
 
 interface alertData {
   switch_type?: string;
@@ -78,13 +78,12 @@ const tab_loading = ref(true)
 const alertData = ref<alertData[]>([])
 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([
@@ -153,21 +152,21 @@ const getCraneListData = async () => {
 // 颜色样式处理
 const getColor = (type: string) => {
   switch (type) {
-    case '2':
+    case 2:
       return 'content-item-yellow';
-    case '3':
+    case 3:
       return 'content-item-orange';
-    case '4':
+    case 4:
       return 'content-item-red';
   }
 }
 const getColorSpan = (type: string) => {
   switch (type) {
-    case '2':
+    case 2:
       return 'content-item-span-yellow';
-    case '3':
+    case 3:
       return 'content-item-span-orange';
-    case '4':
+    case 4:
       return 'content-item-span-red';
   }
 }
@@ -176,87 +175,36 @@ const getColorSpan = (type: string) => {
 const getData = () => {
   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 () => {
-  // 初始化业务数据
   getData();
-  // 初始化 MQTT 连接
-  initMqttClient();
+  mqttUtil.initMqttClient(handleMqttMessage);
   if (receiveData) {
     receiveData({ craneName: '', isShowHomeButton: false });
   }
 });
 
 onUnmounted(() => {
-  // 页面销毁时主动断开 MQTT 连接
-  if (mqttClient) {
-    mqttClient.end(true, () => {
-      console.log('[MQTT] 主动断开连接');
-      mqttClient = null;
-    });
-  }
+  mqttUtil.unsubscribeTopics(['gc/alert','gc/crane_status']);
+  mqttUtil.disconnect();
 });
 </script>