Преглед изворни кода

实时数据和实时报警页面和接口开发

cuiHe пре 2 месеци
родитељ
комит
adcfa9a53d

+ 34 - 1
backend/app/api/v1/module_business/crane/controller.py

@@ -1,10 +1,11 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
-
+import httpx
 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 redis.asyncio import Redis
 
 
 from app.common.response import SuccessResponse, StreamResponse
 from app.common.response import SuccessResponse, StreamResponse
+from app.config.setting import settings
 from app.core.dependencies import AuthPermission, redis_getter
 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
@@ -54,6 +55,38 @@ async def get_crane_list_controller(
     log.info("查询行车信息列表成功")
     log.info("查询行车信息列表成功")
     return SuccessResponse(data=result_dict, msg="查询行车信息列表成功")
     return SuccessResponse(data=result_dict, msg="查询行车信息列表成功")
 
 
+@BizCraneRouter.get("/list_status", summary="查询行车信息列表", description="查询行车信息列表")
+async def get_crane_list_status_controller(
+    page: PaginationQueryParam = Depends(),
+    search: BizCraneQueryParam = Depends(),
+    auth: AuthSchema = Depends(AuthPermission(["module_business:crane:query"]))
+) -> JSONResponse:
+    """查询行车信息列表接口(数据库分页)"""
+    result_dict = await BizCraneService.page_crane_service(
+        auth=auth,
+        page_no=page.page_no if page.page_no is not None else 1,
+        page_size=page.page_size if page.page_size is not None else 10,
+        search=search,
+        order_by=page.order_by
+    )
+    #请求采集接口获取状态信息
+    async with httpx.AsyncClient() as client:
+        response = await client.get(
+            url=settings.COLLECT_DATA_FULL,
+            params={},
+            timeout=2
+        )
+        if response.status_code == 200:
+            json_data = response.json()
+            if json_data['code'] == 200 and json_data['data']:
+                for item in result_dict['items']:
+                    crane_no = item['crane_no']
+                    crane_data = json_data.get('data').get(crane_no)
+                    if crane_data:
+                        item['work_status'] = crane_data.get('data').get('status').get('status')
+    log.info("查询行车信息列表成功")
+    return SuccessResponse(data=result_dict, msg="查询行车信息列表成功")
+
 @BizCraneRouter.post("/create", summary="创建行车信息", description="创建行车信息")
 @BizCraneRouter.post("/create", summary="创建行车信息", description="创建行车信息")
 async def create_crane_controller(
 async def create_crane_controller(
     data: BizCraneCreateSchema,
     data: BizCraneCreateSchema,

+ 60 - 4
backend/app/api/v1/module_business/vardict/controller.py

@@ -1,9 +1,10 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
-
+import httpx
 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 redis.asyncio.client import Redis
 from app.common.response import SuccessResponse, StreamResponse
 from app.common.response import SuccessResponse, StreamResponse
+from app.config.setting import settings
 from app.core.dependencies import AuthPermission, redis_getter
 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
@@ -42,6 +43,38 @@ async def get_vardict_list_controller(
     )
     )
     log.info("查询变量信息列表成功")
     log.info("查询变量信息列表成功")
     return SuccessResponse(data=result_dict, msg="查询变量信息列表成功")
     return SuccessResponse(data=result_dict, msg="查询变量信息列表成功")
+@BizVarDictRouter.get("/list_alarms", summary="查询变量信息列表", description="查询变量信息列表")
+async def get_vardict_list_alarms_controller(
+    page: PaginationQueryParam = Depends(),
+    search: BizVarDictQueryParam = Depends(),
+    auth: AuthSchema = Depends(AuthPermission(["module_business:vardict:query"]))
+) -> JSONResponse:
+    """查询变量信息列表接口(数据库分页)"""
+    result_dict = await BizVarDictService.page_vardict_service(
+        auth=auth,
+        page_no=page.page_no if page.page_no is not None else 1,
+        page_size=page.page_size if page.page_size is not None else 10,
+        search=search,
+        order_by=[{'mec_type':'asc'}]
+    )
+    #请求采集接口获取状态信息
+    async with httpx.AsyncClient() as client:
+        response = await client.get(
+            url=settings.COLLECT_DATA_FULL,
+            params={},
+            timeout=2
+        )
+        if response.status_code == 200:
+            json_data = response.json()
+            if json_data['code'] == 200 and json_data['data']:
+                for item in result_dict['items']:
+                    item['value'] = False
+                    crane_no = item['crane_no']
+                    alarm = json_data.get('data').get(crane_no).get('data').get('alarm').get(item['var_code'])
+                    if alarm:
+                        item['value'] = alarm.get('value')
+    log.info("查询变量信息列表成功")
+    return SuccessResponse(data=result_dict, msg="查询变量信息列表成功")
 
 
 @BizVarDictRouter.post("/create", summary="创建变量信息", description="创建变量信息")
 @BizVarDictRouter.post("/create", summary="创建变量信息", description="创建变量信息")
 async def create_vardict_controller(
 async def create_vardict_controller(
@@ -128,15 +161,38 @@ async def export_vardict_template_controller() -> StreamingResponse:
         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="获取变量信息分组数据")
+@BizVarDictRouter.get("/varDictMecGroup/{crane_no}", summary="获取变量信息分组数据", description="获取变量信息分组数据")
 async def get_vardict_mec_group_controller(
 async def get_vardict_mec_group_controller(
-    id: int = Path(..., description="ID"),
+    crane_no: str = Path(..., description="crane_no"),
     redis: Redis = Depends(redis_getter),
     redis: Redis = Depends(redis_getter),
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:query"]))
     auth: AuthSchema = Depends(AuthPermission(["module_business:crane:query"]))
 ) -> JSONResponse:
 ) -> JSONResponse:
 
 
     result_dict = await BizVarDictService.get_vardict_group_service(
     result_dict = await BizVarDictService.get_vardict_group_service(
-        redis=redis, id=id,auth=auth
+        redis=redis, crane_no=crane_no,auth=auth
     )
     )
+    if not result_dict:
+        log.info(f"获取变量信息分组数据成功:{result_dict}")
+        return SuccessResponse(data=result_dict, msg="获取变量分组数据成功")
+    #请求采集接口获取状态信息
+    async with httpx.AsyncClient() as client:
+        response = await client.get(
+            url=settings.COLLECT_DATA_FULL,
+            params={},
+            timeout=2
+        )
+        if response.status_code == 200:
+            json_data = response.json()
+            if json_data['code'] == 200 and json_data['data']:
+                json_analog = json_data.get('data').get(crane_no).get('data').get('analog')
+                json_digital = json_data.get('data').get(crane_no).get('data').get('digital')
+                for var_dict in result_dict:
+                    for key,inner_dict in var_dict.items():
+                        if key != 'mec_type' and key != 'alarm_varList' and key != 'varList_simple':
+                            for item in inner_dict:
+                                if key == 'digital_varList':
+                                    item['value'] = json_digital.get(item.get('var_code')).get('value')
+                                else:
+                                    item['value'] = json_analog.get(item.get('var_code')).get('value')
     log.info(f"获取变量信息分组数据成功:{result_dict}")
     log.info(f"获取变量信息分组数据成功:{result_dict}")
     return SuccessResponse(data=result_dict, msg="获取变量分组数据成功")
     return SuccessResponse(data=result_dict, msg="获取变量分组数据成功")

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

@@ -82,7 +82,9 @@ class BizVarDictQueryParam:
         updated_id: int | None = Query(None, description="更新人ID"),
         updated_id: int | None = Query(None, description="更新人ID"),
         created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
         created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
         updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
         updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]),
-        
+        # 新增:场景区分参数,默认 False(代表后台内部请求),True 代表前端接口请求
+        is_api_request: bool | None = Query(False, description="是否为前端接口请求(用于切换 switch_type 查询方式)")
+
     ) -> None:
     ) -> None:
         
         
         # 精确查询字段
         # 精确查询字段
@@ -95,8 +97,10 @@ class BizVarDictQueryParam:
         self.mec_type = mec_type
         self.mec_type = mec_type
         # 精确查询字段
         # 精确查询字段
         self.data_type = data_type
         self.data_type = data_type
-        # 精确查询字段
-        self.switch_type = switch_type
+        if is_api_request:
+            self.switch_type = (">=", switch_type)
+        else:
+            self.switch_type = switch_type
         # 模糊查询字段
         # 模糊查询字段
         self.addr = ("like", addr)
         self.addr = ("like", addr)
         # 精确查询字段
         # 精确查询字段
@@ -146,4 +150,7 @@ class VarDictMecGroupSchema(BaseModel):
     行车信息页面数据模型
     行车信息页面数据模型
     """
     """
     mec_type: str = Field(default=..., description='所属机构')
     mec_type: str = Field(default=..., description='所属机构')
+    alarm_varList: list[BizVarDictOutSchema] | None = Field(default=None, description='报警变量数据')
+    digital_varList: list[BizVarDictOutSchema] | None = Field(default=None, description='开关量变量数据')
+    analog_varList: list[BizVarDictOutSchema] | None = Field(default=None, description='模拟量变量数据')
     varList_simple: list[BizVarDictOutSchema] | None = Field(default=None, description='变量数据')
     varList_simple: list[BizVarDictOutSchema] | None = Field(default=None, description='变量数据')

+ 14 - 6
backend/app/api/v1/module_business/vardict/service.py

@@ -325,7 +325,7 @@ class BizVarDictService:
             option_list=option_list
             option_list=option_list
         )
         )
     @classmethod
     @classmethod
-    async def get_vardict_group_service(cls,auth: AuthSchema, redis: Redis,id: int):
+    async def get_vardict_group_service(cls,auth: AuthSchema, redis: Redis,crane_no: str):
         """
         """
         从缓存获取变量分组数据列表信息service
         从缓存获取变量分组数据列表信息service
 
 
@@ -337,8 +337,7 @@ class BizVarDictService:
         - list[dict]: 变量分组数据列表
         - list[dict]: 变量分组数据列表
         """
         """
         try:
         try:
-            crane = await BizCraneCRUD(auth).get_by_id_crane_crud(id)
-            redis_key = f"{RedisInitKeyConfig.VAR_DICT.key}:{crane.crane_no}"
+            redis_key = f"{RedisInitKeyConfig.VAR_DICT.key}:{crane_no}"
             obj_list_dict = await RedisCURD(redis).get(redis_key)
             obj_list_dict = await RedisCURD(redis).get(redis_key)
 
 
             # 确保返回数据正确序列化
             # 确保返回数据正确序列化
@@ -347,7 +346,7 @@ class BizVarDictService:
                     try:
                     try:
                         return json.loads(obj_list_dict)
                         return json.loads(obj_list_dict)
                     except json.JSONDecodeError:
                     except json.JSONDecodeError:
-                        log.warning(f"变量分组数据反序列化失败,尝试重新初始化缓存: {crane.crane_name}")
+                        log.warning(f"变量分组数据反序列化失败,尝试重新初始化缓存: {'行车:'+crane_no}")
                 elif isinstance(obj_list_dict, list):
                 elif isinstance(obj_list_dict, list):
                     return obj_list_dict
                     return obj_list_dict
 
 
@@ -400,11 +399,20 @@ class BizVarDictService:
                             mec_list = await BizMecCRUD(auth).list(search={'crane_no':crane_no,'status':'1'},order_by=[{'sort':'asc'}])
                             mec_list = await BizMecCRUD(auth).list(search={'crane_no':crane_no,'status':'1'},order_by=[{'sort':'asc'}])
                             for mec in mec_list:
                             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'}])
+                                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:
                                 if not varDicts:
                                     continue
                                     continue
+                                alarmVarList = await BizVarDictCRUD(auth).list(search={'crane_no': crane_no,'mec_type':mec.mec_type, 'switch_type': ('>=','2'), 'status': '1'},order_by=[{'var_sort': 'asc'}])
+                                digitalVarList = await BizVarDictCRUD(auth).list(search={'crane_no':crane_no,'mec_type':mec.mec_type,'data_type':'1','status':'1'},order_by=[{'var_sort':'asc'}])
+                                analogVarList = await BizVarDictCRUD(auth).list(search={'crane_no': crane_no,'mec_type':mec.mec_type, 'data_type': ('!=', '1'), 'status': '1'},order_by=[{'var_sort': 'asc'}])
                                 varDictMecGroupSchema.append(
                                 varDictMecGroupSchema.append(
-                                    VarDictMecGroupSchema(mec_type=mec.mec_type, varList_simple=varDicts))
+                                    VarDictMecGroupSchema(mec_type=mec.mec_type,
+                                                          varList_simple=varDicts,
+                                                          digital_varList=digitalVarList,
+                                                          analog_varList=analogVarList,
+                                                          alarm_varList=alarmVarList))
                             # 保存到Redis并设置过期时间
                             # 保存到Redis并设置过期时间
                             redis_key = f"{RedisInitKeyConfig.VAR_DICT.key}:{crane_no}"
                             redis_key = f"{RedisInitKeyConfig.VAR_DICT.key}:{crane_no}"
                             var_dict_list = [item.model_dump() for item in varDictMecGroupSchema]
                             var_dict_list = [item.model_dump() for item in varDictMecGroupSchema]

+ 5 - 0
backend/app/config/setting.py

@@ -162,6 +162,11 @@ class Settings(BaseSettings):
     OPENAI_API_KEY: str = ''
     OPENAI_API_KEY: str = ''
     OPENAI_MODEL: str = ''
     OPENAI_MODEL: str = ''
 
 
+    # ================================================= #
+    # ******************* 数据采集配置 ****************** #
+    # ================================================= #
+    COLLECT_DATA_FULL: str = ''
+
     # ================================================= #
     # ================================================= #
     # ******************* 请求限制配置 ****************** #
     # ******************* 请求限制配置 ****************** #
     # ================================================= #
     # ================================================= #

+ 1 - 1
backend/app/plugin/init_app.py

@@ -131,7 +131,7 @@ def register_routers(app: FastAPI) -> None:
     返回:
     返回:
     - None
     - None
     """
     """
-    app.include_router(router=router, dependencies=[Depends(RateLimiter(times=5, seconds=10))])
+    app.include_router(router=router, dependencies=[Depends(RateLimiter(times=10, seconds=10))])
 
 
 def register_files(app: FastAPI) -> None:
 def register_files(app: FastAPI) -> None:
     """
     """

+ 1 - 1
backend/app/utils/console.py

@@ -27,7 +27,7 @@ def create_service_panel(
 
 
     # 核心服务信息
     # 核心服务信息
     service_info = Text()
     service_info = Text()
-    service_info.append(f"服务名称 {settings.TITLE} • 优雅 • 简洁 • 高效", style="bold magenta")
+    service_info.append(f"服务名称 {settings.TITLE}", style="bold magenta")
     service_info.append(f"\n当前版本 v{settings.VERSION}" , style="bold green")
     service_info.append(f"\n当前版本 v{settings.VERSION}" , style="bold green")
     service_info.append(f"\n服务地址 {url}", style="bold blue")
     service_info.append(f"\n服务地址 {url}", style="bold blue")
     service_info.append(f"\n运行环境 {settings.ENVIRONMENT.value if hasattr(settings.ENVIRONMENT, 'value') else settings.ENVIRONMENT}", style="bold red")
     service_info.append(f"\n运行环境 {settings.ENVIRONMENT.value if hasattr(settings.ENVIRONMENT, 'value') else settings.ENVIRONMENT}", style="bold red")

+ 9 - 7
backend/banner.txt

@@ -1,8 +1,10 @@
 
 
-    ___               _                   _                __                _            
-  .' ..]             / |_                (_)              |  ]              (_)           
- _| |_  ,--.   .--. `| |-',--.  _ .--.   __   ,--.    .--.| |  _ .--..--.   __   _ .--.   
-'-| |-'`'_\ : ( (`\] | | `'_\ :[ '/'`\ \[  | `'_\ : / /'`\' | [ `.-. .-. | [  | [ `.-. |  
-  | |  // | |, `'.'. | |,// | |,| \__/ | | | // | |,| \__/  |  | | | | | |  | |  | | | |  
- [___] \'-;__/[\__) )\__/\'-;__/| ;.__/ [___]\'-;__/ '.__.;__][___||__||__][___][___||__] 
-                               [__|                                                       
+ ,--.-,,-,--,       ,----.   .-._                          ,---.         .=-.-.
+/==/  /|=|  |    ,-.--` , \ /==/ \  .-._    _,..---._    .--.'  \       /==/_ /
+|==|_ ||=|, |   |==|-  _.-` |==|, \/ /, / /==/,   -  \   \==\-/\ \     |==|, |
+|==| ,|/=| _|   |==|   `.-. |==|-  \|  |  |==|   _   _\  /==/-|_\ |    |==|  |
+|==|- `-' _ |  /==/_ ,    / |==| ,  | -|  |==|  .=.   |  \==\,   - \   |==|- |
+|==|  _     |  |==|    .-'  |==| -   _ |  |==|,|   | -|  /==/ -   ,|   |==| ,|
+|==|   .-. ,\  |==|_  ,`-._ |==|  /\ , |  |==|  '='   / /==/-  /\ - \  |==|- |
+/==/, //=/  |  /==/ ,     / /==/, | |- |  |==|-,   _`/  \==\ _.\=\.-'  /==/. /
+`--`-' `-`--`  `--`-----``  `--`./  `--`  `-.`.____.'    `--`          `--`-`

+ 4 - 1
backend/env/.env.dev

@@ -25,7 +25,7 @@ DEMO_ENABLE = False
 DATABASE_TYPE = "mysql"  # mysql、postgres、[qlite、dm这俩种不支持代码生成]
 DATABASE_TYPE = "mysql"  # mysql、postgres、[qlite、dm这俩种不支持代码生成]
 
 
 # 数据库配置
 # 数据库配置
-DATABASE_HOST = "localhost"
+DATABASE_HOST = "192.168.0.247"
 DATABASE_PORT = 3306  # MySQL:3306 PostgreSQL:5432
 DATABASE_PORT = 3306  # MySQL:3306 PostgreSQL:5432
 DATABASE_USER = "root" # mysql:root, postgresql:tao
 DATABASE_USER = "root" # mysql:root, postgresql:tao
 DATABASE_PASSWORD = "!23Qwe"
 DATABASE_PASSWORD = "!23Qwe"
@@ -46,3 +46,6 @@ LOGGER_LEVEL = 'DEBUG'   # 日志级别
 OPENAI_BASE_URL = https://dashscope.aliyuncs.com/compatible-mode/v1
 OPENAI_BASE_URL = https://dashscope.aliyuncs.com/compatible-mode/v1
 OPENAI_API_KEY = sk-e688534f2d984e7fa2eb46add409422f
 OPENAI_API_KEY = sk-e688534f2d984e7fa2eb46add409422f
 OPENAI_MODEL = qwen-plus
 OPENAI_MODEL = qwen-plus
+
+# 数据采集服务地址
+COLLECT_DATA_FULL = http://192.168.0.247:5000/api/data/full

+ 3 - 0
backend/env/.env.prod

@@ -46,3 +46,6 @@ LOGGER_LEVEL = 'INFO'
 OPENAI_BASE_URL = https://dashscope.aliyuncs.com/compatible-mode/v1
 OPENAI_BASE_URL = https://dashscope.aliyuncs.com/compatible-mode/v1
 OPENAI_API_KEY = sk-e688534f2d984e7fa2eb46add409422f
 OPENAI_API_KEY = sk-e688534f2d984e7fa2eb46add409422f
 OPENAI_MODEL = qwen-plus
 OPENAI_MODEL = qwen-plus
+
+# 数据采集服务地址
+COLLECT_DATA_FULL = http://192.168.0.247:5000/api/data/full

BIN
backend/static/image/favicon.png


+ 1 - 5
frontend/index.html

@@ -4,11 +4,7 @@
     <meta charset="UTF-8" />
     <meta charset="UTF-8" />
     <link rel="icon" href="/logo.png" />
     <link rel="icon" href="/logo.png" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <meta name="description" content="Vue3 + Vite + TypeScript + Element-Plus 的后台管理模板 " />
-    <meta
-      name="keywords"
-      content="vue,element-plus,typescript,vue-element-admin,vue3-element-admin"
-    />
+    <meta name="description" content="恒达行车地面站管理系统" />
     <title>%VITE_APP_TITLE%</title>
     <title>%VITE_APP_TITLE%</title>
   </head>
   </head>
 
 

+ 10 - 1
frontend/src/api/module_business/crane.ts

@@ -12,6 +12,15 @@ const BizCraneAPI = {
     });
     });
   },
   },
 
 
+    // 列表查询
+  listBizCraneStatus(query: BizCranePageQuery) {
+    return request<ApiResponse<PageResult<BizCraneTable[]>>>({
+      url: `${API_PATH}/list_status`,
+      method: "get",
+      params: query,
+    });
+  },
+
   // 详情查询
   // 详情查询
   detailBizCrane(id: number) {
   detailBizCrane(id: number) {
     return request<ApiResponse<BizCraneTable>>({
     return request<ApiResponse<BizCraneTable>>({
@@ -120,7 +129,7 @@ export interface BizCraneTable extends BaseType{
   work_span?: string;
   work_span?: string;
   work_height?: string;
   work_height?: string;
   ip_address?: string;
   ip_address?: string;
-  online_status?: string;
+  work_status?: string;
   order?: string;
   order?: string;
   created_id?: string;
   created_id?: string;
   updated_id?: string;
   updated_id?: string;

+ 28 - 3
frontend/src/api/module_business/vardict.ts

@@ -1,4 +1,5 @@
 import request from "@/utils/request";
 import request from "@/utils/request";
+import { A } from "vue-router/dist/router-CWoNjPRp.mjs";
 
 
 const API_PATH = "/business/vardict";
 const API_PATH = "/business/vardict";
 
 
@@ -12,6 +13,14 @@ const BizVarDictAPI = {
     });
     });
   },
   },
 
 
+  listBizVarDictAlarms(query: BizVarDictPageQuery) {
+    return request<ApiResponse<PageResult<BizVarDictTable[]>>>({
+      url: `${API_PATH}/list_alarms`,
+      method: "get",
+      params: query,
+    });
+  },
+
   // 详情查询
   // 详情查询
   detailBizVarDict(id: number) {
   detailBizVarDict(id: number) {
     return request<ApiResponse<BizVarDictTable>>({
     return request<ApiResponse<BizVarDictTable>>({
@@ -21,9 +30,9 @@ const BizVarDictAPI = {
   },
   },
 
 
   // 获取变量信息
   // 获取变量信息
-  varDictMecGroup(id: number) {
-    return request<ApiResponse<BizVarDictTable>>({
-      url: `${API_PATH}/varDictMecGroup/${id}`,
+  varDictMecGroup(crane_no: string) {
+    return request<ApiResponse<MecDataItem[]>>({
+      url: `${API_PATH}/varDictMecGroup/${crane_no}`,
       method: "get",
       method: "get",
     });
     });
   },
   },
@@ -119,6 +128,7 @@ export interface BizVarDictPageQuery extends PageQuery {
   updated_id?: number;
   updated_id?: number;
   created_time?: string[];
   created_time?: string[];
   updated_time?: string[];
   updated_time?: string[];
+  is_api_request?: string;
 }
 }
 
 
 // 列表展示项
 // 列表展示项
@@ -151,6 +161,7 @@ export interface BizVarDictTable extends BaseType{
   updated_id?: string;
   updated_id?: string;
   created_by?: creatorType;
   created_by?: creatorType;
   updated_by?: updatorType;
   updated_by?: updatorType;
+  value?:string;
 }
 }
 
 
 // 新增/修改/详情表单参数
 // 新增/修改/详情表单参数
@@ -180,3 +191,17 @@ export interface BizVarDictForm extends BaseFormType{
   is_upload?: string;
   is_upload?: string;
   diagnosis_id?: string;
   diagnosis_id?: string;
 }
 }
+
+export interface VarDictMecGroupData{
+  mec_type?: string;
+  bool_type_list?:BizVarDictTable[]
+  gear_list?:BizVarDictTable[]
+  number_type_list?:BizVarDictTable[]
+}
+
+export interface MecDataItem {
+  mec_type: string;
+  varList_simple: BizVarDictTable[];
+  digital_varList: BizVarDictTable[];
+  analog_varList: BizVarDictTable[];
+}

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

@@ -147,4 +147,72 @@ export function blobValidate(data: Blob): boolean {
   return data.type !== "application/json";
   return data.type !== "application/json";
 }
 }
 
 
+export const gearCalculation = (varData: any[]) => {
+  varData.forEach((item) => {
+    //剔除掉重点显示和配电总览
+    if (item.mec_type != '0' && item.mec_type != '6') {
+      let virtualizationCode = '';
+      const gears: number[] = [];
+      let number = 0;
+      item.bool_type_list.forEach((boolItem: any) => { 
+        if(boolItem.var_category == '1'){
+          gears.push(boolItem.value ? 1 : 0)
+        }
+      })
+      if (gears.length == 0) {
+        return;
+      }
+      //有零位剔除
+      if (gears.length == 6) {
+        gears.shift();
+      }
+
+      virtualizationCode = 'G00'+item.mec_type
+      let str ='零挡';
+
+      item.number_type_list.forEach((numberItem: any) => { 
+        if(numberItem.var_code === virtualizationCode){
+          let direction = gears[0] + '' + gears[1];
+          if(direction == '00'){
+            numberItem.value = str;
+          }else{
+            number += 1;
+            gears.forEach((gearItem, index) => {
+              if (index > 1 && gearItem == 1) {
+                number += 1;
+              }
+            })
+            if (direction == '01') {
+              number = -number
+            }
+            let positiveDirectionStr = '';
+            let oppositeDirectionStr = '';
+            switch (item.mec_type) {
+              case '1':
+              case '2':
+              case '7':
+              case '8':
+                positiveDirectionStr = '上升'
+                oppositeDirectionStr = '下降'
+                break;
+              case '3':
+                positiveDirectionStr = '左行'
+                oppositeDirectionStr = '右行'
+                break;
+              case '4':
+              case '5':
+                positiveDirectionStr = '前行'
+                oppositeDirectionStr = '后行'
+                break;
+            }
+            str = number > 0 ? positiveDirectionStr + number : oppositeDirectionStr + Math.abs(number);
+            str += '挡';
+            numberItem.value = str;
+          }
+        }
+      })
+    }
+  })
+  return reactive(varData);
+}
 
 

+ 111 - 134
frontend/src/utils/mqttUtil.ts

@@ -1,202 +1,179 @@
 // src/utils/mqttUtil.ts
 // src/utils/mqttUtil.ts
-import mqtt, { MqttClient, IClientOptions  } from 'mqtt';
+import mqtt, { MqttClient, IClientOptions } from 'mqtt';
 
 
 /**
 /**
  * MQTT消息处理回调类型
  * MQTT消息处理回调类型
- * @param topic 消息主题
- * @param payload 消息内容(已解析为JSON对象,若解析失败则为原始字符串)
  */
  */
 export type MqttMessageCallback = (topic: string, payload: any) => void;
 export type MqttMessageCallback = (topic: string, payload: any) => void;
 
 
 /**
 /**
- * MQTT工具类配置项
+ * 单个MQTT实例配置项
  */
  */
-export interface MqttConfig {
-  wsUrl: string; // MQTT WS连接地址
-  clientOptions?: IClientOptions ; // MQTT客户端配置(可选,如用户名、密码等)
-  defaultTopics?: string[]; // 默认订阅的主题列表(可选)
+export interface MqttInstanceConfig {
+  wsUrl: string; // 当前组件的MQTT WS地址
+  clientOptions?: IClientOptions; // 客户端配置(用户名、密码等)
+  topics: string[]; // 当前组件要订阅的主题列表
 }
 }
 
 
 /**
 /**
- * MQTT工具类(单例模式,避免重复创建连接
+ * MQTT工具类(非单例,每个组件可独立实例化
  */
  */
 class MqttUtil {
 class MqttUtil {
-  // 私有属性
-  private static instance: MqttUtil; // 单例实例
-  private mqttClient: MqttClient | null = null; // MQTT客户端实例
-  private mqttConfig: MqttConfig; // MQTT配置
-  private messageCallback: MqttMessageCallback | null = null; // 全局消息回调
+  // 实例私有属性(每个实例独立拥有)
+  private mqttClient: MqttClient | null = null;
+  private config: MqttInstanceConfig;
+  private messageCallback: MqttMessageCallback | null = null;
+  private isConnected: boolean = false; // 当前实例连接状态
 
 
   /**
   /**
-   * 私有构造函数(单例模式,禁止外部new
-   * @param config MQTT配置项
+   * 构造函数(每个组件new独立实例
+   * @param config 当前实例的配置项
    */
    */
-  private constructor(config: MqttConfig) {
-    this.mqttConfig = config;
+  constructor(config: MqttInstanceConfig) {
+    this.config = config;
   }
   }
 
 
   /**
   /**
-   * 获取MQTT工具类单例实例
-   * @param config MQTT配置项(仅首次调用需传入,后续调用无需重复传入)
-   * @returns MqttUtil单例
+   * 初始化当前组件的MQTT连接(创建连接+订阅主题)
+   * @param callback 当前组件的消息回调
    */
    */
-  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);
-      }
+  public initConnect(callback: MqttMessageCallback): void {
+    // 避免重复初始化
+    if (this.isConnected && this.mqttClient) {
+      console.log(`[MQTT] 当前组件连接已建立,无需重复初始化`);
+      this.messageCallback = callback;
       return;
       return;
     }
     }
-  
-    const { wsUrl, clientOptions, defaultTopics } = this.mqttConfig;
-    console.log(`[MQTT] 开始连接 Broker: ${wsUrl}`);
-  
-    // 创建MQTT客户端
+
+    const { wsUrl, clientOptions, topics } = this.config;
+    console.log(`[MQTT] 组件独立连接 Broker: ${wsUrl}`);
+
+    // 创建独立的MQTT客户端
     this.mqttClient = mqtt.connect(wsUrl, clientOptions);
     this.mqttClient = mqtt.connect(wsUrl, clientOptions);
-    // 保存消息回调
-    if (messageCallback) {
-      this.messageCallback = messageCallback;
-    }
-  
-    // 绑定各类事件(默认主题订阅移到该方法内的connect回调)
-    this.bindClientEvents(defaultTopics); // 传入defaultTopics
+    this.messageCallback = callback;
+
+    // 绑定事件
+    this.bindEvents(topics);
   }
   }
 
 
   /**
   /**
-   * 绑定MQTT客户端事件(连接、消息、断开、错误)
+   * 绑定当前实例的MQTT事件
+   * @param topics 要订阅的主题
    */
    */
-  private bindClientEvents(defaultTopics?: string[]): void {
+  private bindEvents(topics: string[]): void {
     if (!this.mqttClient) return;
     if (!this.mqttClient) return;
-  
-    // 连接成功回调
+
+    // 连接成功
     this.mqttClient.on('connect', () => {
     this.mqttClient.on('connect', () => {
-      console.log('[MQTT] 连接成功');
-      // 连接成功后,订阅默认主题
-      if (defaultTopics && defaultTopics.length > 0) {
-        this.subscribeTopics(defaultTopics);
-      }
+      this.isConnected = true;
+      console.log(`[MQTT] 组件独立连接成功`);
+      // 连接成功后订阅当前组件的主题
+      this.subscribeTopics(topics);
     });
     });
-  
-    // 接收消息回调(统一处理,转发给全局回调)
+
+    // 接收消息(组件专属回调)
     this.mqttClient.on('message', (topic, payload) => {
     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);
-        }
-      });
-    }
+      let data: any = payload.toString();
+      // 组件内单独处理中文转义(按需开启)
+      try {
+        // 还原Unicode中文 + 解析JSON
+        const unicodeReg = /\\u([0-9a-fA-F]{4})/g;
+        const payloadStr = payload.toString().replace(unicodeReg, (_, hex) => {
+          return String.fromCharCode(parseInt(hex, 16));
+        });
+        data = JSON.parse(payloadStr);
+      } catch (err) {
+        console.warn('[MQTT] 消息非JSON格式,使用原始字符串:', err);
+      }
+      // 执行当前组件的回调
+      if (this.messageCallback) {
+        this.messageCallback(topic, data);
+      }
+    });
+
+    // 连接错误
+    this.mqttClient.on('error', (err) => {
+      this.isConnected = false;
+      console.error(`[MQTT] 组件独立连接错误:`, err);
+    });
+
+    // 连接断开
+    this.mqttClient.on('close', () => {
+      this.isConnected = false;
+      console.log(`[MQTT] 组件独立连接已断开`);
+    });
+  }
+
   /**
   /**
-   * 订阅主题(支持单个/多个主题)
-   * @param topics 主题字符串或主题数组
+   * 订阅主题(当前实例专属
+   * @param topics 主题列表/单个主题
    */
    */
   public subscribeTopics(topics: string | string[]): void {
   public subscribeTopics(topics: string | string[]): void {
-    if (!this.mqttClient || !this.mqttClient.connected) {
-      console.error('[MQTT] 客户端未连接,无法订阅主题');
+    if (!this.mqttClient || !this.isConnected) {
+      console.error(`[MQTT] 组件连接未建立,无法订阅主题`);
       return;
       return;
     }
     }
 
 
-    this.mqttClient.subscribe(topics, (err) => {
+    const topicList = Array.isArray(topics) ? topics : [topics];
+    this.mqttClient.subscribe(topicList, (err) => {
       if (err) {
       if (err) {
-        console.error(`[MQTT] 订阅主题失败:`, err);
+        console.error(`[MQTT] 组件订阅主题失败:`, err);
         return;
         return;
       }
       }
-      const topicStr = Array.isArray(topics) ? topics.join('、') : topics;
-      console.log(`[MQTT] 订阅主题成功:${topicStr}`);
+      console.log(`[MQTT] 组件订阅主题成功:${topicList.join('、')}`);
     });
     });
   }
   }
 
 
   /**
   /**
-   * 取消订阅主题
-   * @param topics 主题字符串或主题数组
+   * 取消订阅主题(当前实例专属)
+   * @param topics 主题列表/单个主题
    */
    */
   public unsubscribeTopics(topics: string | string[]): void {
   public unsubscribeTopics(topics: string | string[]): void {
-    if (!this.mqttClient || !this.mqttClient.connected) {
-      console.error('[MQTT] 客户端未连接,无法取消订阅');
+    if (!this.mqttClient || !this.isConnected) {
+      console.error(`[MQTT] 组件连接未建立,无法取消订阅`);
       return;
       return;
     }
     }
 
 
-    this.mqttClient.unsubscribe(topics, (err) => {
+    const topicList = Array.isArray(topics) ? topics : [topics];
+    this.mqttClient.unsubscribe(topicList, (err) => {
       if (err) {
       if (err) {
-        console.error(`[MQTT] 取消订阅主题失败:`, err);
+        console.error(`[MQTT] 组件取消订阅主题失败:`, err);
         return;
         return;
       }
       }
-      const topicStr = Array.isArray(topics) ? topics.join('、') : topics;
-      console.log(`[MQTT] 取消订阅主题成功:${topicStr}`);
+      console.log(`[MQTT] 组件取消订阅主题成功:${topicList.join('、')}`);
     });
     });
   }
   }
 
 
   /**
   /**
-   * 设置消息处理回调(后续可动态更新)
-   * @param callback 消息处理回调
+   * 释放当前实例所有资源(断开连接+清空回调+置空实例)
    */
    */
-  public setMessageCallback(callback: MqttMessageCallback): void {
-    this.messageCallback = callback;
-  }
-
-  /**
-   * 断开MQTT连接
-   */
-  public disconnect(): void {
-    if (this.mqttClient && this.mqttClient.connected) {
-      this.mqttClient.end();
+  public releaseResources(): void {
+    // 1. 仅当连接有效时,执行断开操作(无需手动取消订阅)
+    if (this.mqttClient) {
+      try {
+        // end(false):优雅断开(先发送完未发送的消息,再断开),默认false
+        // 无需手动取消订阅,Broker会自动清理该客户端的所有订阅
+        this.mqttClient.end(false, () => {
+          console.log(`[MQTT] 组件MQTT连接优雅断开`);
+        });
+      } catch (err) {
+        console.warn(`[MQTT] 组件MQTT连接断开时出现异常:`, err);
+      }
       this.mqttClient = null;
       this.mqttClient = null;
-      console.log('[MQTT] 主动断开连接');
     }
     }
+  
+    // 2. 清空回调和连接状态(无论连接是否有效,都要重置)
+    this.messageCallback = null;
+    this.isConnected = false;
+    console.log(`[MQTT] 组件MQTT资源已全部释放`);
   }
   }
 
 
   /**
   /**
-   * 判断MQTT客户端是否已连接
-   * @returns 连接状态(true=已连接,false=未连接)
+   * 获取当前实例连接状态
    */
    */
-  public isConnected(): boolean {
-    return !!this.mqttClient && this.mqttClient.connected;
+  public getConnectStatus(): boolean {
+    return this.isConnected;
   }
   }
 }
 }
 
 
-// 导出单例实例(默认先不初始化,需在项目入口/首次使用时初始化)
 export default MqttUtil;
 export default MqttUtil;

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

@@ -456,10 +456,10 @@
               />
               />
             </el-select>
             </el-select>
           </el-form-item>
           </el-form-item>
-          <el-form-item label="PLC型号 " prop="plc_model" :required="true" v-if="formData.gateway_type === 1">
+          <el-form-item label="PLC型号 " prop="plc_model" :required="true" v-if="formData.gateway_type == '1'">
             <el-input v-model="formData.plc_model" placeholder="请输入PLC型号 " />
             <el-input v-model="formData.plc_model" placeholder="请输入PLC型号 " />
           </el-form-item>
           </el-form-item>
-          <el-form-item label="端口号" prop="serial_port_name" :required="true" v-if="formData.gateway_type === 1">
+          <el-form-item label="端口号" prop="serial_port_name" :required="true" v-if="formData.gateway_type == '1'">
             <el-input v-model="formData.serial_port_name" placeholder="请输入端口号" />
             <el-input v-model="formData.serial_port_name" placeholder="请输入端口号" />
           </el-form-item>
           </el-form-item>
           <el-form-item label="波特率 " prop="serial_baud_rate" :required="false">
           <el-form-item label="波特率 " prop="serial_baud_rate" :required="false">
@@ -867,7 +867,7 @@ async function handleOpenDialog(type: "create" | "update" | "detail", id?: numbe
     formData.id = undefined;
     formData.id = undefined;
     formData.crane_no = undefined;
     formData.crane_no = undefined;
     formData.gateway_name = undefined;
     formData.gateway_name = undefined;
-    formData.gateway_type = 1;
+    formData.gateway_type = undefined;
     formData.gateway_ipaddress = undefined;
     formData.gateway_ipaddress = undefined;
     formData.gateway_port = undefined;
     formData.gateway_port = undefined;
     formData.plc_brand = undefined;
     formData.plc_brand = undefined;

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

@@ -326,7 +326,7 @@
           min-width="100">
           min-width="100">
           <template #default="scope">
           <template #default="scope">
             {{
             {{
-                (scope.row.switch_type
+                (scope.row.switch_type || scope.row.switch_type === 0
                   ? (dictStore.getDictLabel("switch_type", scope.row.switch_type) as any)
                   ? (dictStore.getDictLabel("switch_type", scope.row.switch_type) as any)
                   : undefined
                   : undefined
                 )?.dict_label || scope.row.switch_type
                 )?.dict_label || scope.row.switch_type
@@ -1006,14 +1006,14 @@ const formData = reactive<BizVarDictForm>({
   var_category: undefined,
   var_category: undefined,
   translate: undefined,
   translate: undefined,
   device_no: undefined,
   device_no: undefined,
-  is_reverse: 0,
-  is_top_show: 0,
-  is_save: 1,
-  is_calibration: 0,
-  is_overview_top_show: 0,
-  is_home_page_show: 0,
-  is_diagnose: 0,
-  is_upload: 0,
+  is_reverse: '0',
+  is_top_show: '0',
+  is_save: "1",
+  is_calibration: '0',
+  is_overview_top_show: '0',
+  is_home_page_show: '0',
+  is_diagnose: '0',
+  is_upload: '0',
   diagnosis_id: undefined,
   diagnosis_id: undefined,
   status: '1',
   status: '1',
   description: undefined,
   description: undefined,
@@ -1191,14 +1191,14 @@ const initialFormData: BizVarDictForm = {
   var_category: undefined,
   var_category: undefined,
   translate: undefined,
   translate: undefined,
   device_no: undefined,
   device_no: undefined,
-  is_reverse: 0,
-  is_top_show: 0,
-  is_save: 1,
-  is_calibration: 0,
-  is_overview_top_show: 0,
-  is_home_page_show: 0,
-  is_diagnose: 0,
-  is_upload: 0,
+  is_reverse: '0',
+  is_top_show: '0',
+  is_save: '1',
+  is_calibration: '0',
+  is_overview_top_show: '0',
+  is_home_page_show: '0',
+  is_diagnose: '0',
+  is_upload: '0',
   diagnosis_id: undefined,
   diagnosis_id: undefined,
   status: '1',
   status: '1',
   description: undefined,
   description: undefined,
@@ -1254,14 +1254,14 @@ async function handleOpenDialog(type: "create" | "update" | "detail", id?: numbe
     formData.var_category = undefined;
     formData.var_category = undefined;
     formData.translate = undefined;
     formData.translate = undefined;
     formData.device_no = undefined;
     formData.device_no = undefined;
-    formData.is_reverse = 0;
-    formData.is_top_show = 0;
-    formData.is_save = 1;
-    formData.is_calibration = 0;
-    formData.is_overview_top_show = 0;
-    formData.is_home_page_show = 0;
-    formData.is_diagnose = 0;
-    formData.is_upload = 0;
+    formData.is_reverse = '0';
+    formData.is_top_show = '0';
+    formData.is_save = '1';
+    formData.is_calibration = '0';
+    formData.is_overview_top_show = '0';
+    formData.is_home_page_show = '0';
+    formData.is_diagnose = '0';
+    formData.is_upload = '0';
     formData.diagnosis_id = undefined;
     formData.diagnosis_id = undefined;
     formData.status = '1';
     formData.status = '1';
     formData.description = undefined;
     formData.description = undefined;

+ 108 - 54
frontend/src/views/web/detail/realtimeAlarm.vue

@@ -1,66 +1,120 @@
 <template>
 <template>
-  <!-- <div>
+  <div>
     <div class="el-table-content">
     <div class="el-table-content">
-      <pro-table :height="tabHeight" :loading="tab_loading" :data="allData" :config="tableConfig">
+      <pro-table :height="tabHeight" :loading="tab_loading" :data="filteredData" :config="tableConfig">
         <template #default="{ row }">
         <template #default="{ row }">
-          <div :class="getColor(row.switch_type)">
-            {{ row.switch_type }}
+          <div :class="getColor(row.switch_type.toString())">
+            {{ (row.switch_type 
+                  ? (dictStore.getDictLabel("switch_type", row.switch_type) as any)
+                  : undefined
+                )?.dict_label || row.switch_type }}
           </div>
           </div>
         </template>
         </template>
       </pro-table>
       </pro-table>
     </div>
     </div>
-  </div> -->
+  </div>
 </template>
 </template>
 
 
-<script setup>
-// import { ref } from 'vue';
-// import MqttService from '@/utils/mqttService';
-
-// const route = useRoute();
-// const tabHeight = ref('calc(100vh - 70px - 5px - 50px - 10px - 44px)')
-// const mqttService = new MqttService();
-// const topics = ['gc/alert'];
-// const tab_loading = ref(true)
-// const tableConfig = ref([{
-//   prop: 'msg',
-//   label: '报警内容'
-// },
-// {
-//   prop: 'switch_type',
-//   label: '报警级别',
-//   slot: 'true'
-// }])
-// const allData = ref([]);
-// const getData = async () => {
-//   mqttService.connect();
-//   topics.forEach(topic => {
-//     mqttService.subscribe(topic, (message) => {
-//       allData.value = [];
-//       message = JSON.parse(message.toString());
-//       message.data.forEach(item => {
-//         if (item.crane_no === route.params.craneNo) {
-//           item.switch_type = item.switch_type === 2 ? '预警' : item.switch_type === 3 ? '报警' : '故障';
-//           allData.value.push(item);
-//         }
-//       })
-//       tab_loading.value = false
-//     });
-//   });
-// }
-// onMounted(async () => {
-//   getData();
-// })
-
-// const getColor = (type) => {
-//   switch (type) {
-//     case '报警':
-//       return 'pilot-lamp-bg-yellow';
-//     case '预警':
-//       return 'pilot-lamp-bg-orange';
-//     case '故障':
-//       return 'pilot-lamp-bg-red';
-//   }
-// }
+<script setup lang="ts">
+import { ref } from 'vue';
+import { useDictStore } from "@/store";
+import BizVarDictAPI, { BizVarDictTable,BizVarDictPageQuery,MecDataItem } from '@/api/module_business/vardict'
+import MqttUtil, { MqttMessageCallback } from '@/utils/mqttUtil';
+
+const tabHeight = ref('calc(100vh - 70px - 5px - 50px - 10px - 44px)')
+const craneInfo = JSON.parse(localStorage.getItem('craneInfo') || '{}')
+const tab_loading = ref(true)
+const tableConfig = ref([{
+  prop: 'var_name',
+  label: '报警内容'
+},
+{
+  prop: 'switch_type',
+  label: '报警级别',
+  slot: 'true'
+}])
+
+const dictStore = useDictStore()
+const dictTypes: any = [
+  'switch_type'
+]
+
+const mqttConfig = {
+  wsUrl: import.meta.env.VITE_APP_WS_ENDPOINT || 'ws://127.0.0.1:9001',
+  topics: ['cdc/'+craneInfo.crane_no+'/alarm/#']
+};
+const mqttUtil = new MqttUtil(mqttConfig);
+
+const queryFormVarDictData = reactive<BizVarDictPageQuery>({
+  page_no: 1,
+  page_size: 100,
+  crane_no: craneInfo.crane_no,
+  var_code: undefined,
+  var_name: undefined,
+  mec_type: undefined,
+  switch_type: '2',
+  gateway_id: undefined,
+  var_group: undefined,
+  var_category: undefined,
+  is_top_show: undefined,
+  is_save: undefined,
+  is_overview_top_show: undefined,
+  is_home_page_show: undefined,
+  status: undefined,
+  created_time: undefined,
+  updated_time: undefined,
+  created_id: undefined,
+  updated_id: undefined,
+  is_api_request: 'True',
+});
+const allData = ref<BizVarDictTable[]>([]);
+
+const filteredData = computed(() => {
+  return allData.value.filter(item => item.value);
+});
+const getData = async () => {
+  const response = await BizVarDictAPI.listBizVarDictAlarms(queryFormVarDictData);
+  allData.value = response.data.data.items
+  tab_loading.value = false
+}
+
+const handleMqttMessage: MqttMessageCallback = (topic, payload) => {
+  let topic_levels = topic.split('/')
+  let crane_no = topic_levels[1]
+  let suffix = topic_levels[2]
+  if (suffix === 'alarm') {
+    allData.value.forEach((item) => {
+      if (item.var_code === payload.var_code) {
+        item.value = payload.value
+      }
+    });
+  }
+}
+
+
+
+const getColor = (type:string) => {
+  switch (type) {
+    case '2':
+      return 'pilot-lamp-bg-yellow';
+    case '3':
+      return 'pilot-lamp-bg-orange';
+    case '4':
+      return 'pilot-lamp-bg-red';
+  }
+}
+
+onMounted(async () => {
+  if (dictTypes.length > 0) {
+    await dictStore.getDict(dictTypes)
+  }
+  getData();
+  mqttUtil.initConnect(handleMqttMessage);
+})
+
+onUnmounted(() => {
+  mqttUtil.releaseResources();
+});
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>

+ 133 - 135
frontend/src/views/web/detail/realtimeData.vue

@@ -4,91 +4,94 @@
     <div v-loading="loading" class="mec-content">
     <div v-loading="loading" class="mec-content">
       <div class="mec-item" v-for="(item, index) in varDictMecGroupData" :key="index">
       <div class="mec-item" v-for="(item, index) in varDictMecGroupData" :key="index">
         <div class="mec-title">
         <div class="mec-title">
-          {{ (item.MecType 
-                  ? (dictStore.getDictLabel("mec_type", item.MecType) as any)
+          {{ (item.mec_type 
+                  ? (dictStore.getDictLabel("mec_type", item.mec_type) as any)
                   : undefined
                   : undefined
-                )?.dict_label || detailFormData.MecType }}
+                )?.dict_label || item.mec_type }}
         </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">
-            <span>{{ temp.VarName }}</span>
+          <div class="number-type-content" v-for="(temp, index1) in item.number_type_list" :key="index1">
+            <span>{{ temp.var_name }}</span>
             <div>
             <div>
-              <span style="font-size: 22px;">{{ temp.Value }}</span>
+              <span style="font-size: 22px;">{{ temp.value }}</span>
             </div>
             </div>
           </div>
           </div>
           <div style="margin-top: 30px;">
           <div style="margin-top: 30px;">
-            <div class="bool-type-content" v-for="(temp, index1) in item.boolTypeList" :key="index1">
-              <span>{{ temp.VarName }}</span>
-              <span :class="getBgColor(temp.Value, temp.SwitchType)"></span>
+            <div class="bool-type-content" v-for="(temp, index1) in item.bool_type_list" :key="index1">
+              <span>{{ temp.var_name }}</span>
+              <span :class="getBgColor(temp.value, temp.switch_type)"></span>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
+      <div v-if="varDictMecGroupData.length === 0" class="el-table-empty">
+        <el-image :src="emptybg"></el-image>
+        <span>当前无数据</span>
+      </div>
     </div>
     </div>
   </div>
   </div>
 
 
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
-import BizVarDictAPI, { } from '@/api/module_business/vardict'
+import BizVarDictAPI, { BizVarDictTable,VarDictMecGroupData,MecDataItem } from '@/api/module_business/vardict'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
 import { useDictStore } from "@/store";
 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'
+import emptybgUrl from '@/assets/images/empty-bg.png';
+import MqttUtil, { MqttMessageCallback } from '@/utils/mqttUtil';
+import { reactive, onMounted, onUnmounted, inject } from 'vue';
+import { gearCalculation } from '@/utils/common';
+import { S } from 'vue-router/dist/router-CWoNjPRp.mjs';
 
 
 const route = useRoute()
 const route = useRoute()
 const receiveData = inject<(data: { craneName: string; isShowHomeButton: boolean }) => void>('receiveData');
 const receiveData = inject<(data: { craneName: string; isShowHomeButton: boolean }) => void>('receiveData');
 const craneInfo = JSON.parse(localStorage.getItem('craneInfo') || '{}')
 const craneInfo = JSON.parse(localStorage.getItem('craneInfo') || '{}')
-const varDictMecGroupData = ref([])
+const varDictMecGroupData = ref<VarDictMecGroupData[]>([])
 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 emptybg = ref(emptybgUrl)
+
+const mqttConfig = {
+  wsUrl: import.meta.env.VITE_APP_WS_ENDPOINT || 'ws://127.0.0.1:9001',
+  topics: ['cdc/'+craneInfo.crane_no+'/analog/batch/#','cdc/'+craneInfo.crane_no+'/digital/#']
+};
+
+const mqttUtil = new MqttUtil(mqttConfig);
+
+
 const getCraneMecTreeData = async () => {
 const getCraneMecTreeData = async () => {
-  const response = await BizVarDictAPI.varDictMecGroup(craneInfo.id);
+  const response = await BizVarDictAPI.varDictMecGroup(craneInfo.crane_no);
   varDictMecGroupData.value = mecDataFormat(response.data.data)
   varDictMecGroupData.value = mecDataFormat(response.data.data)
-  console.log(varDictMecGroupData.value)
+  //挡位计算
+  gearCalculation(varDictMecGroupData.value)
+  loading.value = false;
   //localStorage.setItem('varDict', JSON.stringify(deviceStateData.value))
   //localStorage.setItem('varDict', JSON.stringify(deviceStateData.value))
 }
 }
 
 
-const mecDataFormat = (mecData) => {
-  const resultData = []
+const mecDataFormat = (mecData: MecDataItem[]) => {
+  const resultData: VarDictMecGroupData[] = []
   if (mecData) {
   if (mecData) {
     mecData.forEach((item) => {
     mecData.forEach((item) => {
-      const numberTypelist = []
-      const boolTypeList = []
-      const gearList = []
-      item.varList_simple.forEach((simpleItem) => {
-        if (simpleItem.data_type === 1) {
-            boolTypeList.push({
-              VarName: simpleItem.var_name,
-              Value: true,
-              VarCode: simpleItem.var_code,
-              SwitchType: simpleItem.switch_type
-            })
-          } else {
-            numberTypelist.push({
-              VarName: simpleItem.var_name,
-              Value: 0,
-              VarCategory: simpleItem.var_category,
-              VarCode: simpleItem.var_code
-            })
-          }
-        if (simpleItem.var_category == 1) {
-          gearList.push({
-            VarName: simpleItem.var_name,
-            Value: false,
-            VarCode: simpleItem.var_code,
-            VarCategory: simpleItem.var_category
-          })
+      let numberTypelist: BizVarDictTable[] = []
+      let boolTypeList: BizVarDictTable[] = []
+      let gearList: BizVarDictTable[] = []
+      numberTypelist = [...item.analog_varList]
+      boolTypeList = [...item.digital_varList]
+      if(item.mec_type != '0' && item.mec_type != '6'){
+        numberTypelist.push({
+          var_code: 'G00'+item.mec_type,
+          var_name: '挡位',
+          value: '0'
+        })
+      }
+      boolTypeList.forEach((boolItem) => {
+        if (boolItem.var_category == "1") {
+          gearList.push(boolItem) 
         }
         }
       })
       })
       resultData.push({
       resultData.push({
-        MecType: item.mec_type,
-        numberTypeList: numberTypelist,
-        boolTypeList: boolTypeList,
-        gearList: gearList
+        mec_type: item.mec_type,
+        bool_type_list: boolTypeList,
+        gear_list: gearList,
+        number_type_list: numberTypelist
       })
       })
     })
     })
   }
   }
@@ -104,84 +107,60 @@ const dictTypes: any = [
 const getData = () => {
 const getData = () => {
   getCraneMecTreeData()
   getCraneMecTreeData()
 }
 }
-const getBgColor = (bool, type) => {
-  switch (type) {
-    case 1:
-      return bool ? 'pilot-lamp-bg-green' : 'pilot-lamp-bg-grey';
-    case 2:
-      return bool ? 'pilot-lamp-bg-green' : 'pilot-lamp-bg-yellow';
-    case 3:
-      return bool ? 'pilot-lamp-bg-green' : 'pilot-lamp-bg-orange';
-    case 4:
-      return bool ? 'pilot-lamp-bg-green' : 'pilot-lamp-bg-red';
-  }
-}
+const getBgColor = (bool: string | undefined, type: string | undefined) => {
+  
+  const validType = type || '0';
 
 
-//初始化 MQTT 连接并订阅主题
-const initMqttClient = () => {
-  // 避免重复连接
-  if (mqttClient && mqttClient.connected) {
-    console.log('[MQTT] 客户端已连接,无需重复初始化');
-    return;
-  }
-  console.log(`[MQTT] 开始连接 Broker: ${MQTT_WS_URL}`);
+  const falseColorMap: Record<string, string> = {
+    '0': 'pilot-lamp-bg-grey',
+    '1': 'pilot-lamp-bg-grey',
+    '2': 'pilot-lamp-bg-green',
+    '3': 'pilot-lamp-bg-green',
+    '4': 'pilot-lamp-bg-green'
+  };
 
 
-  // 创建 MQTT 客户端
-  mqttClient = mqtt.connect(MQTT_WS_URL);
+  const trueColorMap: Record<string, string> = {
+    '0': 'pilot-lamp-bg-green',
+    '1': 'pilot-lamp-bg-green',
+    '2': 'pilot-lamp-bg-yellow',
+    '3': 'pilot-lamp-bg-orange',
+    '4': 'pilot-lamp-bg-red'
+  };
 
 
-  // 连接成功回调
-  mqttClient.on('connect', () => {
-    console.log('[MQTT] 连接成功');
+  const class_name = bool 
+    ? trueColorMap[validType]
+    : falseColorMap[validType] || 'pilot-lamp-bg-grey';
 
 
-    // 同时订阅两个主题
-    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;
-            }
-          })
+  return class_name;
+};
+
+const handleMqttMessage: MqttMessageCallback = (topic, payload) => {
+  let topic_levels = topic.split('/')
+  let crane_no = topic_levels[1]
+  let type = topic_levels[2]
+  let var_code = topic_levels[3]
+  varDictMecGroupData.value.forEach(item => {
+    if(type == 'analog' && item.number_type_list && Array(item.number_type_list)){
+      item.number_type_list.forEach(numberItem => {
+        payload.data.forEach((payloadItem: any) => {
+          if (payloadItem.var_code === numberItem.var_code) {
+            numberItem.value = payloadItem.value
+          }
         })
         })
       })
       })
-      loading.value = false
-    } catch (err) {
-      console.error('[MQTT] 解析消息失败:', err);
+    }else if(type == 'digital' && item.bool_type_list && Array(item.bool_type_list)){
+      item.bool_type_list.forEach(boolItem => {
+        if (var_code === boolItem.var_code) {
+          boolItem.value = payload.value
+          return;
+        }
+      })
     }
     }
-  });
-
-  // 连接断开回调
-  mqttClient.on('close', () => {
-    console.log('[MQTT] 连接已断开');
-  });
+  })
+  //挡位计算
+  gearCalculation(varDictMecGroupData.value)
+}
 
 
-  // 连接错误回调
-  mqttClient.on('error', (err) => {
-    console.error('[MQTT] 连接错误:', err);
-  });
-};
 
 
 onMounted(async () => {
 onMounted(async () => {
   if (dictTypes.length > 0) {
   if (dictTypes.length > 0) {
@@ -191,22 +170,11 @@ onMounted(async () => {
     receiveData({ craneName: craneInfo.crane_name ?? '', isShowHomeButton: true });
     receiveData({ craneName: craneInfo.crane_name ?? '', isShowHomeButton: true });
   }
   }
   getData()
   getData()
-  mqttService.connect();
-  topics.forEach(topic => {
-    mqttService.subscribe(topic, (message) => {
-
-    });
-  });
+  mqttUtil.initConnect(handleMqttMessage);
 });
 });
 
 
 onUnmounted(() => {
 onUnmounted(() => {
-  // 页面销毁时主动断开 MQTT 连接
-  if (mqttClient) {
-    mqttClient.end(true, () => {
-      console.log('[MQTT] 主动断开连接');
-      mqttClient = null;
-    });
-  }
+  mqttUtil.releaseResources();
 });
 });
 
 
 </script>
 </script>
@@ -315,4 +283,34 @@ onUnmounted(() => {
   background: #f39902;
   background: #f39902;
   border-radius: 50%;
   border-radius: 50%;
 }
 }
+
+.el-table-empty {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  color: #8ECAFF;
+
+  img {
+    width: 200px;
+    height: auto;
+    margin-bottom: 20px;
+  }
+}
+
+::-webkit-scrollbar {
+  width: 5px;
+  height: 5px;
+}
+
+::-webkit-scrollbar-track {
+  border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 7px;
+  background-color: #798DAE;
+}
 </style>
 </style>

+ 93 - 44
frontend/src/views/web/overview/index.vue

@@ -7,16 +7,16 @@
         </div>
         </div>
         <div class="overview-left-content">
         <div class="overview-left-content">
           <ul v-loading="alarm_loading">
           <ul v-loading="alarm_loading">
-            <li v-for="(item, index) in alertData" :key="index">
-              <div class="content-item" :class="getColor(item.switch_type??'')">
+            <li v-for="(item, index) in varDictData" :key="index">
+              <div v-if="item.value" class="content-item" :class="getColor(item.switch_type?.toString() ?? '')">
                 <IconAlarm />
                 <IconAlarm />
                 <span style="width:120px; margin-left: 15px;">{{ item.crane_name }}</span>
                 <span style="width:120px; margin-left: 15px;">{{ item.crane_name }}</span>
-                <span style="width: 2px;height: 60%;margin: 0px 30px;" :class="getColorSpan(item.switch_type??'')" />
-                <span>{{ item.msg }}</span>
+                <span style="width: 2px;height: 60%;margin: 0px 30px;" :class="getColorSpan(item.switch_type?.toString() ?? '')" />
+                <span>{{ item.var_name }}</span>
               </div>
               </div>
             </li>
             </li>
           </ul>
           </ul>
-          <div v-if="!alarm_loading && alertData.length === 0" class="el-table-empty">
+          <div v-if="!alarm_loading && !hasActiveAlarms" class="el-table-empty">
             <el-image :src="emptybg"></el-image>
             <el-image :src="emptybg"></el-image>
             <span>当前无数据</span>
             <span>当前无数据</span>
           </div>
           </div>
@@ -37,9 +37,12 @@
                 </div>
                 </div>
               </div>
               </div>
               <div v-if="label === '在线状态'">
               <div v-if="label === '在线状态'">
-                <el-tag effect="dark" v-if="row.online_status != '在线' && row.online_status != '离线'" type="warning">连接中</el-tag>
-                <el-tag effect="dark" v-if="row.online_status === '在线'" type="success">在线</el-tag>
-                <el-tag effect="dark" v-if="row.online_status === '离线'" type="danger">离线</el-tag>
+                <el-tag effect="dark" v-if="!row.work_status && row.work_status != '0'" type="warning">连接中</el-tag>
+                <el-tag effect="dark" v-if="row.work_status == '0'" type="danger">离线</el-tag>
+                <el-tag effect="dark" v-if="row.work_status == '1'" type="success">运行</el-tag>
+                <el-tag effect="dark" v-if="row.work_status == '2'" type="info">停止</el-tag>
+                <el-tag effect="dark" v-if="row.work_status == '3'" type="success">工作</el-tag>
+                <el-tag effect="dark" v-if="row.work_status == '4'" type="primary">空闲</el-tag>
               </div>
               </div>
             </template>
             </template>
           </pro-table>
           </pro-table>
@@ -51,17 +54,12 @@
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import BizCraneAPI, { BizCranePageQuery, BizCraneTable } from '@/api/module_business/crane'
 import BizCraneAPI, { BizCranePageQuery, BizCraneTable } from '@/api/module_business/crane'
+import BizVarDictAPI, { BizVarDictPageQuery, BizVarDictTable } from '@/api/module_business/vardict'
 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 MqttUtil, { MqttMessageCallback } from '@/utils/mqttUtil';
 import MqttUtil, { MqttMessageCallback } from '@/utils/mqttUtil';
 
 
-interface alertData {
-  switch_type?: string;
-  crane_name?: string;
-  msg?: string;
-}
-
 // 路由 & 全局注入
 // 路由 & 全局注入
 const router = useRouter()
 const router = useRouter()
 const receiveData = inject<(data: { craneName: string; isShowHomeButton: boolean }) => void>('receiveData');
 const receiveData = inject<(data: { craneName: string; isShowHomeButton: boolean }) => void>('receiveData');
@@ -75,15 +73,18 @@ const alarm_loading = ref(true);
 const tab_loading = ref(true)
 const tab_loading = ref(true)
 
 
 // 业务数据
 // 业务数据
-const alertData = ref<alertData[]>([])
 const craneData = ref<BizCraneTable[]>([]);
 const craneData = ref<BizCraneTable[]>([]);
+const varDictData = ref<BizVarDictTable[]>([])
+
+const hasActiveAlarms = computed(() => {
+  return varDictData.value.some(item => item.value);
+});
 
 
 const mqttConfig = {
 const mqttConfig = {
   wsUrl: import.meta.env.VITE_APP_WS_ENDPOINT || 'ws://127.0.0.1:9001',
   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);
+  topics: ['cdc/+/alarm/#', 'cdc/+/status']
+};
+const mqttUtil = new MqttUtil(mqttConfig);
 
 
 // 表格配置
 // 表格配置
 const tableConfig = ref([
 const tableConfig = ref([
@@ -114,8 +115,7 @@ const tableConfig = ref([
   }
   }
 ])
 ])
 
 
-// 分页查询参数
-const queryFormData = reactive<BizCranePageQuery>({
+const queryFormCraneData = reactive<BizCranePageQuery>({
   page_no: 1,
   page_no: 1,
   page_size: 100,
   page_size: 100,
   crane_name: undefined,
   crane_name: undefined,
@@ -129,6 +129,29 @@ const queryFormData = reactive<BizCranePageQuery>({
   updated_id: undefined,
   updated_id: undefined,
 });
 });
 
 
+const queryFormVarDictData = reactive<BizVarDictPageQuery>({
+  page_no: 1,
+  page_size: 100,
+  crane_no: undefined,
+  var_code: undefined,
+  var_name: undefined,
+  mec_type: undefined,
+  switch_type: '2',
+  gateway_id: undefined,
+  var_group: undefined,
+  var_category: undefined,
+  is_top_show: undefined,
+  is_save: undefined,
+  is_overview_top_show: undefined,
+  is_home_page_show: undefined,
+  status: undefined,
+  created_time: undefined,
+  updated_time: undefined,
+  created_id: undefined,
+  updated_id: undefined,
+  is_api_request: 'True',
+});
+
 // 操作按钮点击事件
 // 操作按钮点击事件
 const handleClick = (item: BizCraneTable) => {
 const handleClick = (item: BizCraneTable) => {
   if (receiveData) {
   if (receiveData) {
@@ -142,31 +165,41 @@ const handleClick = (item: BizCraneTable) => {
 const getCraneListData = async () => {
 const getCraneListData = async () => {
   try {
   try {
     tab_loading.value = true
     tab_loading.value = true
-    const response = await BizCraneAPI.listBizCrane(queryFormData);
+    const response = await BizCraneAPI.listBizCraneStatus(queryFormCraneData);
     craneData.value = response.data.data.items;
     craneData.value = response.data.data.items;
   } finally {
   } finally {
     tab_loading.value = false
     tab_loading.value = false
   }
   }
 }
 }
 
 
+const getVarDictData = async () => {
+  try {
+    alarm_loading.value = true;
+    const response = await BizVarDictAPI.listBizVarDictAlarms(queryFormVarDictData);
+    varDictData.value = response.data.data.items;
+  } finally {
+    alarm_loading.value = false
+  }
+}
+
 // 颜色样式处理
 // 颜色样式处理
 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';
   }
   }
 }
 }
@@ -174,37 +207,40 @@ const getColorSpan = (type: string) => {
 // 初始化业务数据
 // 初始化业务数据
 const getData = () => {
 const getData = () => {
   getCraneListData()
   getCraneListData()
+  getVarDictData()
 }
 }
 
 
 const handleMqttMessage: MqttMessageCallback = (topic, payload) => {
 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 ? '在线' : '离线';
-          }
-        });
-      });
-    }
+  let topic_levels = topic.split('/')
+  let crane_no = topic_levels[1]
+  let suffix = topic_levels[2]
+  if (suffix === 'alarm') {
+    varDictData.value.forEach((item) => {
+      if (item.crane_no === crane_no && item.var_code === payload.var_code) {
+        item.value = payload.value
+      }
+    });
+  } else if (suffix === 'status') {
+    craneData.value.forEach((craneItem) => {
+      if (craneItem.crane_no === crane_no) {
+        craneItem.work_status = payload.status;
+      }
+    });
   }
   }
 }
 }
 
 
 
 
 onMounted(async () => {
 onMounted(async () => {
   getData();
   getData();
-  mqttUtil.initMqttClient(handleMqttMessage);
+  mqttUtil.initConnect(handleMqttMessage);
+  
   if (receiveData) {
   if (receiveData) {
     receiveData({ craneName: '', isShowHomeButton: false });
     receiveData({ craneName: '', isShowHomeButton: false });
   }
   }
 });
 });
 
 
 onUnmounted(() => {
 onUnmounted(() => {
-  mqttUtil.unsubscribeTopics(['gc/alert','gc/crane_status']);
-  mqttUtil.disconnect();
+  mqttUtil.releaseResources();
 });
 });
 </script>
 </script>
 
 
@@ -351,4 +387,17 @@ onUnmounted(() => {
     margin-bottom: 20px;
     margin-bottom: 20px;
   }
   }
 }
 }
+::-webkit-scrollbar {
+  width: 5px;
+  height: 5px;
+}
+
+::-webkit-scrollbar-track {
+  border-radius: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 7px;
+  background-color: #798DAE;
+}
 </style>
 </style>