Forráskód Böngészése

feat✨: 添加ai,告警弹窗

gitboyzcf 2 hete
szülő
commit
fdcc179224

+ 1 - 1
.env.development

@@ -4,7 +4,7 @@
 VITE_APP_TITLE = 全景布控球
 
 # 网络请求
-VITE_APP_API_BASEURL = https://192.168.211.44
+VITE_APP_API_BASEURL = https://192.168.211.58:8082
 
 # 项目localStorage存储前缀
 VITE_APP_PREFIX = pbc

+ 16 - 0
src/api/modules/ly.js

@@ -379,4 +379,20 @@ export default {
       params,
     });
   },
+  // ai告警类型
+  API_AI_TYPE_GET(data = {}) {
+    return request({
+      url: `/KunPengYun/AlgorithmList`,
+      method: 'get',
+      data
+    })
+  },
+  // 获取AI免密登陆Url
+  API_FREE_LOGIN_URL(data = {}) {
+    return request({
+      url: `/KunPengYun/FreeLoginURL`,
+      method: 'get',
+      data
+    })
+  },
 };

+ 102 - 0
src/components/PubMessage.vue

@@ -0,0 +1,102 @@
+<template>
+  <div
+    ref="modalRef"
+    class="absolute left-4 z-50 bg-#0d317866 transition-all duration-300 rounded-lg"
+    :style="{ top }"
+    v-if="visible"
+    v-on:[systemStore.event]="itemClickFn"
+  >
+    <div
+      v-if="isClose"
+      class="close absolute top-4 right-4 color-white text-xl"
+      v-on:[systemStore.event]="() => (bindVisible = false)"
+    >
+      <svg
+        xmlns="http://www.w3.org/2000/svg"
+        width="1.2em"
+        height="1.2em"
+        viewBox="0 0 24 24"
+      >
+        <path
+          fill="none"
+          stroke="currentColor"
+          stroke-linecap="round"
+          stroke-linejoin="round"
+          stroke-width="2.5"
+          d="m7 7l10 10M7 17L17 7"
+        />
+      </svg>
+    </div>
+    <div class="bg-#0d317866 color-white rounded-lg shadow-lg p-6">
+      <h2 class="text-2xl font-semibold mb-4">{{ title }}</h2>
+      <div>{{ content }}</div>
+      <!-- <div>{{ message }}</div>
+      <div>{{ appMessage }}</div> -->
+      <slot v-bind="{ type: '默认插槽' }"></slot>
+      <slot name="footer" v-bind="{ type: '具名插槽' }"></slot>
+    </div>
+  </div>
+</template>
+<script setup>
+import { useSystemStore } from "@/stores/modules/system";
+import { inject, onMounted } from "vue";
+import { useVModel } from "@vueuse/core";
+
+const systemStore = useSystemStore();
+const props = defineProps({
+  visible: Boolean,
+  title: String,
+  content: String,
+  index: Number,
+  itemMY: Number,
+  itemClick: Function,
+  isClose: {
+    type: Boolean,
+    default: true,
+  },
+});
+
+const modalRef = ref(null);
+const emit = defineEmits(["update:visible", "loadList"]);
+const bindVisible = useVModel(props, "visible", emit);
+// const message = inject("message");
+// const appMessage = inject("appMessage");
+const top = computed(
+  () =>
+    modalRef.value?.offsetHeight * props.index +
+    props.itemMY * props.index +
+    "px"
+);
+
+const itemClickFn = () => {
+  if (!props.itemClick) return;
+  console.log(modalRef.value);
+  
+  // modalRef.value.className += "animate__animated animate__rubberBand";
+  // modalRef.value.addEventListener("animationend", () => {
+  //   // do something
+  // });
+  props.itemClick();
+};
+
+const handleOk = () => {
+  // 假装请求业务接口...
+  bindVisible.value = false;
+  emit("loadList");
+};
+
+onMounted(() => {
+  setTimeout(() => {
+    bindVisible.value = true;
+  }, 5000);
+});
+
+defineExpose({
+  // getInfo() {
+  //   return {
+  //     message,
+  //     appMessage,
+  //   };
+  // },
+});
+</script>

+ 13 - 0
src/hooks/useAlarm.js

@@ -0,0 +1,13 @@
+import { provide, h } from "vue";
+import useShowModal from "@/hooks/useShowModal";
+
+const showModal = useShowModal();
+export async function useAlarm(title, content, slots, args) {
+  const Modal = await showModal({
+    modalComponent: () => import("@/components/PubMessage.vue"),
+    title: title || "弹窗标题",
+    content: content,
+    slots,
+    ...args
+  });
+}

+ 129 - 0
src/hooks/useShowModal.js

@@ -0,0 +1,129 @@
+import {
+  defineAsyncComponent,
+  getCurrentInstance,
+  h,
+  nextTick,
+  render,
+  createVNode,
+  ref,
+  watch,
+  Transition
+} from 'vue'
+
+const getAppendToElement = (appendTo) => {
+  let appendToEL = document.body
+  if (appendTo) {
+    if (typeof appendTo === 'string') {
+      appendToEL = document.querySelector(appendTo)
+    }
+    if (appendTo instanceof HTMLElement) {
+      appendToEL = appendTo
+    }
+    if (!(appendToEL instanceof HTMLElement)) {
+      appendToEL = document.body
+    }
+  }
+  return appendToEL
+}
+
+function getProvides(instance) {
+  let provides = (instance && instance.provides) || {}
+  if (instance && instance.parent) {
+    provides = { ...provides, ...getProvides(instance.parent) }
+  }
+  return provides
+}
+
+
+export default function useShowModal() {
+  let index = 0
+  const itemMY = 15
+  const vNodes = []
+  const currentInstance = getCurrentInstance()
+  const provides = getProvides(currentInstance)
+
+  function showModal(options) {
+    const container = document.createElement('div')
+    const isAsync = typeof options.modalComponent === 'function'
+    const innerRef = ref()
+
+    const modalComponent = isAsync
+      ? defineAsyncComponent(options.modalComponent)
+      : options.modalComponent
+
+    const props = {}
+    for (const key in options) {
+      if (!['modalComponent', 'appendTo', 'slots'].includes(key)) props[key] = options[key]
+    }
+
+    container.style.position = 'absolute'
+    container.style.top = '30px'
+    container.style.width = '100%'
+    const vNode = createVNode({
+      setup() {
+        const instance = getCurrentInstance()
+        if (instance) {
+          instance.provides = { ...instance.provides, ...provides }
+        }
+      },
+      render: () =>
+        h(Transition, {
+          enterActiveClass: 'animate__animated animate__fadeInLeft animate__faster',
+          leaveActiveClass: 'animate__animated animate__fadeOutLeftBig animate__faster',
+        }, () => h(
+          modalComponent,
+          {
+            visible: true,
+            ...props,
+            ref: innerRef,
+            index,
+            itemMY,
+            'onUpdate:visible': () => {
+              nextTick(() => {
+                close()
+              })
+            }
+          },
+          options.slots
+        ))
+
+    })
+
+    render(vNode, container)
+    getAppendToElement(options.appendTo).appendChild(container)
+
+    function close() {
+      vNode.el.className += ' animate__animated animate__fadeOutLeftBig animate__faster';
+      vNode.el.addEventListener('animationend', () => {
+        render(null, container)
+        container.parentNode && container.parentNode.removeChild(container)
+      });
+      index--
+      vNodes.splice(vNodes.indexOf(vNode), 1)
+      for (let i = 0; i < vNodes.length; i++) {
+        vNodes[i].el.style.top = `${vNodes[i].el.offsetHeight * i + itemMY * i}px`
+      }
+    }
+    index++
+    vNodes.push(vNode)
+
+    if (!isAsync) {
+      return innerRef.value
+    } else {
+      return new Promise((resolve) => {
+        watch(
+          innerRef,
+          () => {
+            resolve(innerRef.value)
+          },
+          {
+            once: true
+          }
+        )
+      })
+    }
+  }
+
+
+  return showModal
+}

+ 103 - 0
src/stores/modules/system.js

@@ -1,6 +1,9 @@
 import { piniaStore } from "@/stores";
+import { h } from "vue";
 import { isWap } from "@/utils";
 import storage from "@/utils/storage";
+import Msg from "@/views/Home/components/msg.vue"
+import { useAlarm } from "@/hooks/useAlarm";
 
 const {
   API_VIEW_BC_LIST_GET,
@@ -29,6 +32,8 @@ export const useSystemStore = defineStore("systemStore", {
     BallCameraId: "",
     BallCameraInfo: {}, // 球机信息
     cilpInfo: {}, // 裁剪信息
+    aiList: [], // ai报警信息列表
+    typeObj: null, // ai告警类型
     points: [
       {
         Id: 1,
@@ -139,6 +144,104 @@ export const useSystemStore = defineStore("systemStore", {
       //   points: points.map((v) => ({ ...v, upd: false })),
       // });
     },
+
+    async connectAlarmWS() {
+      const { API_AI_TYPE_GET, API_FREE_LOGIN_URL } = useRequest()
+      const res = await API_AI_TYPE_GET()
+      this.typeObj = res
+      const resUrl = await API_FREE_LOGIN_URL()
+      // 模拟数据
+
+      // setTimeout(() => {
+      //   // const data = {
+      //   //   AlarmTime: 1709274691,
+      //   //   CfgId: 95,
+      //   //   Type: 'human',
+      //   //   SerialNumber: '5cc8e284-55c9-4916-b493-20a222d3386f',
+      //   //   AlarmId: 1516,
+      //   //   Res: {
+      //   //     '16535ccf-ab52-4553-a494-f168f5144bd2%7C9%7C1546372969796': [
+      //   //       { x: 525, y: 1775, w: 1615, h: 2067 }
+      //   //     ],
+      //   //     'd29e7346-8d8d-44ff-918b-ca0928687f75|0|1709628059861': [
+      //   //       { x: 525, y: 1775, w: 1615, h: 2067 }
+      //   //     ]
+      //   //   }
+      //   // }
+      //   const data = {
+      //     "AlertType": "28",
+      //     "Archived": 0,
+      //     "ArchivedTime": "0000-00-00 00:00:00",
+      //     "ArchivedUserPhone": "",
+      //     "AssociateResult": null,
+      //     "AssociateType": null,
+      //     "CameraId": 24,
+      //     "CreatedAt": "2025-08-06 18:01:19",
+      //     "DataID": "4dd2a7d672ac11f091380242ac120007",
+      //     "GUID": "003a3374-7b1f-4d2f-a4ec-2c94602d5603",
+      //     "HandleStatus": 0,
+      //     "HandleStatusTime": "0000-00-00 00:00:00",
+      //     "HandleStatusUserPhone": "",
+      //     "Id": 94276,
+      //     "JsonFile": "resultJson/20250806/20250806180119_433968_28_24_result.json?serverUUID=3f16a3b113234807b81f6f8d0f268232",
+      //     "OriginPicture": "originPicture/20250806/20250806180119_433968_28_24_src.jpg?serverUUID=3f16a3b113234807b81f6f8d0f268232",
+      //     "Other": "",
+      //     "Picture": "picture/20250806/20250806180119_433968_28_24_alarm.jpg",
+      //     "Remark": "",
+      //     "RemarkTime": "0000-00-00 00:00:00",
+      //     "RemarkUserPhone": "",
+      //     "StandardPicture": "",
+      //     "TaskGroupId": null,
+      //     "TaskId": 9,
+      //     "Thumbnail": "picthumbnail/20250806/20250806180119_433968_28_24_src.jpg?serverUUID=3f16a3b113234807b81f6f8d0f268232",
+      //     "UserId": 1,
+      //     "Video": null
+      //   }
+      //   useAlarm("警告⚠️", "", { default: () => h(Msg, { ...data, AlertType: '123123' }) }, {
+      //     itemClick: () => {
+      //       location.href = resUrl
+      //     }
+      //   });
+      //   this.aiList.unshift({
+      //     ...data,
+      //     state: 0,
+      //   })
+      //   if (this.aiList.length >= 30) {
+      //     this.aiList.splice(20, 10)
+      //   }
+      // }, 1000)
+
+      let websocket
+      let that = this
+        ; (() => {
+          websocket = new WebSocket(`wss://${this.ipF}/Ai/Alarm`)
+          websocket.onopen = function (e) {
+            console.log('/Ai/Alarm 连接已经建立')
+          }
+          websocket.onmessage = function (e) {
+            const data = JSON.parse(e.data)
+            that.aiList.unshift({
+              ...data,
+              state: 0
+            })
+            console.log(that.aiList)
+            if (that.aiList.length >= 100) {
+              that.aiList.splice(50, 10)
+            }
+            useAlarm("警告⚠️", "", { default: () => h(Msg, { ...data, AlertType: that.typeObj[data.AlertType] }) }, {
+              itemClick: () => {
+                location.href = resUrl
+              }
+            });
+          }
+          websocket.onerror = function (e) {
+            console.log('Ai/Alarm 发生错误')
+          }
+          websocket.onclose = function (e) {
+            console.log('Ai/Alarm 连接断开')
+          }
+        })()
+    },
   },
 });
 

+ 2 - 1
src/views/Home/Index.vue

@@ -29,7 +29,7 @@ const systemStore = useOutsideSystemStore();
 const activeComponent = shallowRef(Home);
 
 systemStore.getBsllCameraId();
-
+systemStore.connectAlarmWS()
 
 const to = (comN) => {
   !isWap() && hidPlugin();
@@ -47,4 +47,5 @@ const to = (comN) => {
       break;
   }
 };
+
 </script>

+ 17 - 0
src/views/Home/components/msg.vue

@@ -0,0 +1,17 @@
+<template>
+  <div class="text-2xl">
+    <span class="color-red font-bold">【{{ AlertType }}】</span> <span>{{ CreatedAt }}</span>
+  </div>
+</template>
+
+<script setup>
+defineProps({
+  AlertType: String,
+  CreatedAt: String,
+  // 其他 props
+})
+
+defineOptions({
+  inheritAttrs: false
+})
+</script>