Browse Source

feat: 站点监控新增告警显示功能

Jerry Liang 1 year ago
parent
commit
2926705dfb

+ 43 - 0
service/controllers/sitesMonitorControllers.go

@@ -129,6 +129,28 @@ type SitePowerFlowResponse struct {
 	Result SitePowerFlow `json:"result"`
 }
 
+type SiteAlarmList struct {
+	Id              uint   `json:"id"`
+	GatewayId       string `json:"gatewayId"`
+	AlarmEqSn       string `json:"alarmEqSn"`
+	AlarmCode       string `json:"alarmCode"`
+	CommonAlarmCode string `json:"commonAlarmCode"`
+	Level           int    `json:"level"`
+	Time            string `json:"time"`
+	LogName         string `json:"logName"`
+	CommonLogName   string `json:"commonLogName"`
+	LogNameEn       string `json:"logNameEn"`
+	CommonLogNameEn string `json:"commonLogNameEn"`
+}
+
+type SiteAlarmListResponse struct {
+	Code    int             `json:"code"`
+	Message string          `json:"message"`
+	Result  []SiteAlarmList `json:"result"`
+	Success bool            `json:"success"`
+	Total   uint            `json:"total"`
+}
+
 // 从数据库获取站点监控配置列表
 func GetSiteMonitorConfig() ([]SitesMonitorConfig, error) {
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -354,3 +376,24 @@ func QuerySitePowerFlow(site string, day time.Time) (SitePowerFlowResponse, erro
 
 	return sitePowerFlowRes, nil
 }
+
+// 从fwh_pro获取站点告警列表
+func QuerySiteAlarmList(site string) (SiteAlarmListResponse, error) {
+	resByte, err := fwh_pro.QuerySiteAlarmList(site)
+	if err != nil {
+		return SiteAlarmListResponse{}, err
+	}
+	var siteAlarmListRes SiteAlarmListResponse
+	// 反序列化(unmarshal)
+	jsonErr := json.Unmarshal(resByte, &siteAlarmListRes)
+	if jsonErr != nil {
+		return SiteAlarmListResponse{}, jsonErr
+	}
+
+	if siteAlarmListRes.Code == 401 {
+		// TODO 尝试重新登录
+		return SiteAlarmListResponse{}, errors.New("certification expired")
+	}
+
+	return siteAlarmListRes, nil
+}

+ 47 - 0
service/ext/api/fwh_pro/fwh_pro.go

@@ -162,6 +162,7 @@ func ClearToken() {
 	loginInfo.Token = ""
 }
 
+// 查询站点基本信息
 func QuerySiteInfoList(sn []string, pageSize uint, pageNum uint) ([]byte, error) {
 	snList := strings.Join(sn, ",")
 
@@ -210,6 +211,7 @@ func QuerySiteInfoList(sn []string, pageSize uint, pageNum uint) ([]byte, error)
 	return body, nil
 }
 
+// 查询站点工作模式信息
 func QuerySiteModeInfo(sn string) ([]byte, error) {
 	token := getLoginToken()
 
@@ -256,6 +258,7 @@ func QuerySiteModeInfo(sn string) ([]byte, error) {
 	return body, nil
 }
 
+// 查询站点功率流信息
 func QuerySitePowerFlow(sn string, day time.Time) ([]byte, error) {
 	baseURL := "https://energy.franklinwh.com/api-energy/power/getFhpPowerByDayOld"
 
@@ -298,3 +301,47 @@ func QuerySitePowerFlow(sn string, day time.Time) ([]byte, error) {
 	// fmt.Printf("Status: %s\nBody: %s\n", resp.Status, body)
 	return body, nil
 }
+
+// 查询站点告警信息
+func QuerySiteAlarmList(sn string) ([]byte, error) {
+	baseURL := "https://energy.franklinwh.com/hes-gateway/manage/getEquipmentAlarm"
+
+	token := getLoginToken()
+
+	// 构造 query 参数
+	params := url.Values{}
+	params.Add("gatewayId", sn)
+	params.Add("land", "ZH_CN")
+
+	// 拼接到 URL
+	finalURL := baseURL + "?" + params.Encode()
+
+	// 创建请求
+	req, err := http.NewRequest(
+		http.MethodGet,
+		finalURL,
+		nil)
+	if err != nil {
+		fmt.Println(err.Error())
+		return nil, err
+	}
+
+	// 设置 Content-Type 和 Authorization
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Logintoken", token)
+
+	// fmt.Println(req.Header)
+
+	// 发送请求
+	client := &http.Client{Timeout: 5 * time.Second}
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println(err.Error())
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	body, _ := io.ReadAll(resp.Body)
+	// fmt.Printf("Status: %s\nBody: %s\n", resp.Status, body)
+	return body, nil
+}

+ 13 - 0
service/routes/sitesMonitorRoutes.go

@@ -8,6 +8,7 @@
  * /sitesMonitor/site/{site}/info - GET 获取单个站点信息
  * /sitesMonitor/site/{site}/modeInfo - GET 获取站点工作模式信息
  * /sitesMonitor/site/{site}/powerFlow/{day} - GET 获取单个站点功率流
+ * /sitesMonitor/site/{site}/alarmList - GET 获取单个站点告警列表
  */
 package routes
 
@@ -181,4 +182,16 @@ func SitesMonitorSetup(api fiber.Router) {
 		}
 		return JSONResponse(c, 0, "success", sitePowerFlow)
 	})
+
+	// 获取单个站点告警列表
+	api.Get("/sitesMonitor/site/:site/alarmList", func(c *fiber.Ctx) error {
+		site := c.Params("site")
+
+		siteAlarmList, err := controllers.QuerySiteAlarmList(site)
+		if err != nil {
+			fmt.Println("发生错误:", err)
+			return JSONResponseNoData(c, 500, "fwh_pro query failed.")
+		}
+		return JSONResponse(c, 0, "success", siteAlarmList)
+	})
 }

+ 20 - 0
src/service/sitesMonitor/index.ts

@@ -7,6 +7,7 @@
 // /sitesMonitor/site/{site}/info - GET 获取单个站点信息
 // /sitesMonitor/site/{site}/modeInfo - GET 获取站点工作模式信息
 // /sitesMonitor/site/{site}/powerFlow/{day} - GET 获取单个站点功率流
+// /sitesMonitor/site/{site}/alarmList - GET 获取单个站点告警列表
 
 import http from '@/service/serverReq'
 import type {
@@ -15,6 +16,7 @@ import type {
   SiteInfoResponse,
   SiteModeInfoResponse,
   SitePowerFlowResponse,
+  SiteAlarmListResponse,
 } from './interface'
 import { TaskQueue } from '@/service/taskQueue'
 
@@ -27,6 +29,7 @@ const API_List = {
   siteInfo: '/sitesMonitor/site/{site}/info',
   siteModeInfo: '/sitesMonitor/site/{site}/modeInfo',
   sitePowerFlow: '/sitesMonitor/site/{site}/powerFlow/{day}',
+  siteAlarmList: '/sitesMonitor/site/{site}/alarmList',
 }
 
 // GET 监控管理表
@@ -118,14 +121,31 @@ export const getSitePowerFlow = async (sn: string, day: string) => {
   }
 }
 
+const siteAlarmQueue = new TaskQueue(1) // 队列最大执行数为1
+
+// GET 获取单个站点功率流
+export const getSiteAlarmList = async (sn: string) => {
+  const url = API_List.siteAlarmList.replace('{site}', encodeURIComponent(sn))
+  const res = await http.get<SiteAlarmListResponse>(url, {
+    queue: siteAlarmQueue,
+    key: `${sn}`,
+  })
+  console.log(res)
+  if (res.code === 0 || res.code === 200) {
+    return res.result
+  }
+}
+
 export const clearSitesMonitorQueue = () => {
   siteInfoQueue.clear()
   siteModeInfoQueue.clear()
   sitePowerQueue.clear()
+  siteAlarmQueue.clear()
 }
 
 export const clearSitesMonitorQueueCache = () => {
   siteInfoQueue.clearCache()
   siteModeInfoQueue.clearCache()
   sitePowerQueue.clearCache()
+  siteAlarmQueue.clearCache()
 }

+ 14 - 1
src/service/sitesMonitor/interface.ts

@@ -1,4 +1,9 @@
-import type { SiteInfo, SiteModeInfo, SitePowerFlow } from '@/views/sites-monitor/interface'
+import type {
+  SiteInfo,
+  SiteModeInfo,
+  SitePowerFlow,
+  SiteAlarmList,
+} from '@/views/sites-monitor/interface'
 
 export interface PageConfig {
   enable: boolean
@@ -46,3 +51,11 @@ export interface SitePowerFlowResponse {
   msg: string
   result: SitePowerFlow
 }
+
+export interface SiteAlarmListResponse {
+  code: number
+  message: string
+  result: SiteAlarmList[]
+  success: boolean
+  total: number
+}

+ 80 - 0
src/stores/modules/data/siteAlarmList.ts

@@ -0,0 +1,80 @@
+import { defineSetupStore } from '@/stores/public/template'
+import { isExpired } from '@/stores/public'
+import type { SiteAlarmList } from '@/views/sites-monitor/interface'
+import { getSiteAlarmList } from '@/service/sitesMonitor'
+
+const STORES_KEY = 'SiteAlarmListStore'
+const CURRENT_VERSION = '0.0.0'
+
+export interface SiteAlarmListData {
+  updateTime: number // 最后更新时间戳
+  value: SiteAlarmList[]
+}
+
+export interface SiteAlarmListDataSet {
+  [sn: string]: SiteAlarmListData
+}
+
+export const useSiteAlarmListDataStore = defineSetupStore(STORES_KEY, CURRENT_VERSION, () => {
+  // 站点告警数据
+  let siteAlarmListDate: SiteAlarmListDataSet = {}
+
+  // 获取某个站点的告警列表
+  function siteAlarmListGet(sn: string): SiteAlarmList[] | undefined {
+    if (sn in siteAlarmListDate) {
+      // 判断数据是否过期
+      // 1天过期
+      if (isExpired(siteAlarmListDate[sn].updateTime, 24 * 60 * 60)) {
+        return
+      }
+      return siteAlarmListDate[sn].value
+    }
+  }
+
+  // 查询站点基本信息
+  async function querySiteAlarmList(sn: string) {
+    const siteAlarmList = await getSiteAlarmList(sn)
+    if (siteAlarmList === undefined) {
+      console.error('❌ SiteAlarmList query failed.')
+      return
+    }
+
+    if (sn in siteAlarmListDate) {
+      siteAlarmListDate[sn].updateTime = Date.now()
+      siteAlarmListDate[sn].value = [...siteAlarmList]
+    } else {
+      siteAlarmListDate[sn] = {
+        updateTime: Date.now(),
+        value: [...siteAlarmList],
+      }
+    }
+
+    return siteAlarmList
+  }
+
+  // 清空临时数据
+  function clearSitesData(siteList: string[]) {
+    // 清空temp数据
+    siteList.forEach((sn) => {
+      if (siteAlarmListDate[sn] !== null) {
+        delete siteAlarmListDate[sn]
+      }
+    })
+  }
+
+  return {
+    // normal state
+    siteAlarmListDate,
+
+    // reactive state - ref()
+
+    // reactive getters - computed()
+
+    // normal getters - function()
+    siteAlarmListGet,
+
+    // actions - function()
+    querySiteAlarmList,
+    clearSitesData,
+  }
+})

+ 3 - 0
src/types/components.d.ts

@@ -20,11 +20,14 @@ declare module 'vue' {
     NDropdown: typeof import('naive-ui')['NDropdown']
     Noise: typeof import('./../components/Noise.vue')['default']
     NPerformantEllipsis: typeof import('naive-ui')['NPerformantEllipsis']
+    NPopover: typeof import('naive-ui')['NPopover']
     NSelect: typeof import('naive-ui')['NSelect']
     NSpace: typeof import('naive-ui')['NSpace']
     NSpin: typeof import('naive-ui')['NSpin']
     NTag: typeof import('naive-ui')['NTag']
     NThing: typeof import('naive-ui')['NThing']
+    NTimeline: typeof import('naive-ui')['NTimeline']
+    NTimelineItem: typeof import('naive-ui')['NTimelineItem']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
   }

+ 46 - 1
src/views/sites-monitor/component/siteItem.vue

@@ -3,6 +3,22 @@
     <template #header>
       <div class="inline">
         <span class="font-bold">{{ props.sn }}</span>
+        <NPopover placement="right-start" trigger="click">
+          <template #trigger>
+            <NButton v-show="showAlarmIcon"
+              class="i-ph:warning-circle-fill text-red-500 hover:text-red-600 transition-colors duration-200"
+              size="small" text />
+          </template>
+          <NTimeline :icon-size="20">
+            <n-timeline-item v-for="alarmItem in aLarmList" :key="alarmItem.alarmCode" color="grey"
+              :content="alarmItem.logName">
+              <template #icon>
+                <i v-if="alarmItem.level === 2" class="i-ph:warning-circle-fill text-red-500" />
+                <i v-else class="i-ph:warning-fill text-yellow-500" />
+              </template>
+            </n-timeline-item>
+          </NTimeline>
+        </NPopover>
         <NDivider vertical v-show="aPowerEnable" />
         <span v-show="aPowerEnable">
           <NPerformantEllipsis class="text-gray-500">
@@ -38,7 +54,8 @@ import { useSitesMonitorConfigStore } from '@/stores/modules/views/sitesMonitorC
 import { useSiteInfoDataStore } from '@/stores/modules/data/siteInfoData';
 import { useSiteModeDataStore } from '@/stores/modules/data/siteModeData';
 import { useSitePowerFlowDataStore } from '@/stores/modules/data/sitePowerFlowData';
-import type { SiteInfo } from '../interface';
+import type { SiteAlarmList, SiteInfo } from '../interface';
+import { useSiteAlarmListDataStore } from '@/stores/modules/data/siteAlarmList';
 
 const props = defineProps<{
   monitorName: string
@@ -51,6 +68,7 @@ const powerFlowChartRef = useTemplateRef<InstanceType<typeof PowerFlowChart>>('p
 const sitesMonitorStateStore = useSitesMonitorStateStore()
 const siteInfoDataStore = useSiteInfoDataStore()
 const siteModeDataStore = useSiteModeDataStore()
+const siteAlarmListStore = useSiteAlarmListDataStore()
 const sitePowerFlowDataStore = useSitePowerFlowDataStore()
 const sitesMonitorConfigStore = useSitesMonitorConfigStore()
 
@@ -62,6 +80,14 @@ const versionConfig = sitesMonitorConfigStore.getVersionConfigState(props.monito
 const tariffsConfig = sitesMonitorConfigStore.getTariffsConfigState(props.monitorName)
 const othersConfig = sitesMonitorConfigStore.getOthersConfigState(props.monitorName)
 
+const showAlarmIcon = computed<boolean>(() => {
+  if (alarmConfig.value.enable) {
+    return aLarmList.value.length > 0
+  }
+  return false
+})
+const aLarmList = ref<SiteAlarmList[]>([])
+
 const aPowerEnable = computed(() => {
   return aPowerConfig.value.enable
 })
@@ -150,6 +176,16 @@ const loadingSiteInfoData = async (sn: string): Promise<SiteInfo | undefined> =>
   return siteInfo
 }
 
+// 加载设备告警信息
+const loadingSiteAlarmList = async (sn: string): Promise<SiteAlarmList[] | undefined> => {
+  if (sn === "") return
+  let siteAlarmList = siteAlarmListStore.siteAlarmListGet(sn)
+  if (siteAlarmList === undefined) {
+    siteAlarmList = await siteAlarmListStore.querySiteAlarmList(sn)
+  }
+  return siteAlarmList
+}
+
 const switchItemLoading = <T extends Ref<{ enable: boolean }>>(config: T, loadingFn: (enable: boolean) => void) => {
   loadingFn(config.value.enable)
 
@@ -210,6 +246,15 @@ onMounted(() => {
         }
       }
     })
+
+    switchItemLoading(alarmConfig, async (enable: boolean) => {
+      if (enable) {
+        const siteAlarmList = await loadingSiteAlarmList(props.sn)
+        if (siteAlarmList !== undefined) {
+          aLarmList.value = [...siteAlarmList]
+        }
+      }
+    })
   }, 1000) // 延时 1 秒
 })
 </script>

+ 3 - 0
src/views/sites-monitor/index.vue

@@ -14,6 +14,7 @@ import { NBackTop, NDatePicker, NFormItem, NVirtualList, NTooltip, NSwitch } fro
 import type { SelectOption, DropdownOption, DropdownGroupOption, DropdownDividerOption, DropdownRenderOption } from 'naive-ui'
 import type { DropdownMixedOption, RenderOption } from 'naive-ui/es/dropdown/src/interface'
 import type { ApiMonitorInfo } from '@/service/sitesMonitor/interface'
+import { useSiteAlarmListDataStore } from '@/stores/modules/data/siteAlarmList';
 
 defineOptions({
   name: 'SitesMonitor',
@@ -24,6 +25,7 @@ const sitesMonitorStateStore = useSitesMonitorStateStore()
 const sitesMonitorConfigStore = useSitesMonitorConfigStore()
 const siteInfoDataStore = useSiteInfoDataStore()
 const siteModeDataStore = useSiteModeDataStore()
+const siteAlarmListStore = useSiteAlarmListDataStore()
 
 // 获取 name
 const monitorName = route.params.name as string
@@ -234,6 +236,7 @@ onUnmounted(() => {
   // 渲染前情况stores缓存
   siteInfoDataStore.clearSitesData(monitorSiteList.value)
   siteModeDataStore.clearSitesData(monitorSiteList.value)
+  siteAlarmListStore.clearSitesData(monitorSiteList.value)
 })
 
 </script>

+ 14 - 0
src/views/sites-monitor/interface.ts

@@ -51,3 +51,17 @@ export interface SitePowerFlow {
   runStatusArray: number[]
   kwhTotalArray: number[]
 }
+
+export interface SiteAlarmList {
+  id: number
+  gatewayId: string
+  alarmEqSn: string
+  alarmCode: string
+  commonAlarmCode: string
+  level: number
+  time: string
+  logName: string
+  commonLogName: string
+  logNameEn: string
+  commonLogNameEn: string
+}