浏览代码

master: Added 告警列表功能添加

gitboyzcf 5 月之前
父节点
当前提交
bca2a99ba1

+ 1 - 0
package.json

@@ -48,6 +48,7 @@
     "query-string": "^8.0.3",
     "sass": "^1.79.3",
     "sortablejs": "^1.15.0",
+    "viewerjs": "^1.11.7",
     "vue": "^3.2.40",
     "vue-echarts": "^6.2.3",
     "vue-i18n": "^9.2.2",

+ 7 - 0
pnpm-lock.yaml

@@ -64,6 +64,9 @@ dependencies:
   sortablejs:
     specifier: ^1.15.0
     version: 1.15.0
+  viewerjs:
+    specifier: ^1.11.7
+    version: 1.11.7
   vue:
     specifier: ^3.2.40
     version: 3.2.47
@@ -9393,6 +9396,10 @@ packages:
       vfile-message: 1.1.1
     dev: true
 
+  /viewerjs@1.11.7:
+    resolution: {integrity: sha512-0JuVqOmL5v1jmEAlG5EBDR3XquxY8DWFQbFMprOXgaBB0F7Q/X9xWdEaQc59D8xzwkdUgXEMSSknTpriq95igg==}
+    dev: false
+
   /vite-plugin-compression@0.5.1(vite@3.2.5):
     resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==}
     peerDependencies:

+ 17 - 0
src/api/system.ts

@@ -15,4 +15,21 @@ export function getKey(): Promise<any> {
 export function getCropFullInfo(headers = {}): Promise<any> {
   return axios.get('/rpc/rabbitMQ/VideoShow/CropFullInfo', { headers });
 }
+export function getPubApi(path: string, headers = {}): Promise<any> {
+  return axios.get(`/rpc/rabbitMQ/${path}`, { headers });
+}
+export function getAlarmLog(params = {}, headers = {}): Promise<any> {
+  return axios.get('/rpc/rabbitMQ/KunPengYun/AlarmLog', { params, headers });
+}
+export function getAlgorithmList(params = {}, headers = {}): Promise<any> {
+  return axios.get('/rpc/rabbitMQ/KunPengYun/AlgorithmList', {
+    params,
+    headers,
+  });
+}
+export function getAlarmImgFiles(path: string, headers = {}): Promise<any> {
+  return axios.get(`/rpc/rabbitMQ/KunPengYun/Files/${path}`, {
+    headers,
+  });
+}
 export default {};

+ 17 - 0
src/assets/style/global.scss

@@ -135,3 +135,20 @@ body,
 .arco-alert-warning {
   background-color: var(--color-warning-light-4);
 }
+.arco-input-wrapper,
+.arco-picker,
+.arco-select-view-single,
+.arco-radio-group-button {
+  background-color: var(--color-neutral-2);
+}
+.arco-modal-body {
+  height: 100%;
+}
+.arco-radio-button.arco-radio-checked,
+.arco-radio-button:hover {
+  background-color: var(--color-bg-3);
+}
+
+.arco-divider-horizontal {
+  border-bottom: 1px solid var(--color-neutral-2);
+}

+ 12 - 12
src/store/modules/user/index.ts

@@ -1,7 +1,6 @@
 import { defineStore } from 'pinia';
-// import { LoginData } from '@/api/user';
-// import { setToken, clearToken } from '@/utils/auth';
-import { clearToken } from '@/utils/auth';
+import { LoginData } from '@/api/user';
+import { setToken, clearToken } from '@/utils/auth';
 import { removeRouteListener } from '@/utils/route-listener';
 import { UserState } from './types';
 import useAppStore from '../app';
@@ -59,15 +58,16 @@ const useUserStore = defineStore('user', {
 
     // Login
     // async login(loginForm: LoginData) {
-    //   try {
-    //     // const res = await userLogin(loginForm);
-    //     // setToken(res.data.token);
-    //     setToken('token');
-    //   } catch (err) {
-    //     clearToken();
-    //     throw err;
-    //   }
-    // },
+    async login() {
+      try {
+        // const res = await userLogin(loginForm);
+        // setToken(res.data.token);
+        setToken('token');
+      } catch (err) {
+        clearToken();
+        throw err;
+      }
+    },
     logoutCallBack() {
       const appStore = useAppStore();
       this.resetInfo();

+ 9 - 9
src/utils/Intercom.ts

@@ -149,15 +149,15 @@ export const IntercomFn = (
     $msg?.warning('websocket 连接断开');
     disabled.value = false;
   });
-  getDevice()
-    .then(() => {
-      // 获取摄像头成功
-    })
-    .catch((err) => {
-      $msg?.error('获取摄像头失败');
-      // eslint-disable-next-line no-console
-      console.error(err);
-    });
 };
+getDevice()
+  .then(() => {
+    // 获取摄像头成功
+  })
+  .catch((err) => {
+    $msg?.error('获取摄像头失败');
+    // eslint-disable-next-line no-console
+    console.error(err);
+  });
 
 export default null;

+ 24 - 0
src/views/preview-list/children/preview-info.vue

@@ -261,6 +261,18 @@
                     </template>
                     <template #title>
                       <span>{{ t('previewList.cz') }}</span>
+                      <a-tooltip :content="$t('previewList.ckqb')">
+                        <a-button
+                          type="text"
+                          size="mini"
+                          style="position: absolute; right: 40px"
+                          @click="modalRef.open()"
+                        >
+                          <template #icon>
+                            <Icon icon="gg:view-list" width="22" height="22" />
+                          </template>
+                        </a-button>
+                      </a-tooltip>
                       <a-tooltip :content="$t('previewList.tip1')">
                         <a-button
                           type="text"
@@ -295,6 +307,14 @@
         </div>
       </a-space>
     </a-card>
+    <PubModal
+      ref="modalRef"
+      title="报警列表"
+      :fullscreen="true"
+      :closable="true"
+    >
+      <AlarmAll :guid="data.id" />
+    </PubModal>
   </div>
 </template>
 
@@ -309,6 +329,9 @@
   import dayjs from 'dayjs';
   import { Message } from '@arco-design/web-vue';
   import Speech from '../components/speech.vue';
+  import AlarmAll from '../components/alarm-all.vue';
+  import PubModal from '../components/pub-modal.vue';
+
   // import useWheel from '@/assets/js/useWheel';
 
   const { t } = useI18n();
@@ -318,6 +341,7 @@
   }
   const app = useAppStore();
 
+  const modalRef = ref();
   const workerObj: any = ref({ qj: null });
   const cropFullInfo = ref<{
     H: number;

+ 311 - 2
src/views/preview-list/components/alarm-all.vue

@@ -1,12 +1,321 @@
 <template>
-  <div class="alarm-all"> 测试 </div>
+  <div class="alarm-all">
+    <a-grid :cols="4" :col-gap="20" :row-gap="16" class="grid-demo-grid">
+      <a-grid-item class="demo-item">
+        <label>{{ $t('previewList.bjlx') }}</label>
+        <a-select
+          v-model="paramObj.AlertType"
+          allow-clear
+          :style="{ width: '320px' }"
+          :placeholder="$t('previewList.qxz')"
+        >
+          <a-option
+            v-for="item in typeAlarmOptions"
+            :key="item.value"
+            :value="item.value"
+            >{{ item.label }}</a-option
+          >
+        </a-select>
+      </a-grid-item>
+      <a-grid-item class="demo-item" :span="2">
+        <label>{{ $t('previewList.bjsj') }}</label>
+        <a-range-picker
+          show-time
+          :time-picker-props="{ defaultValue: ['00:00:00', '00:00:00'] }"
+          format="YYYY-MM-DD HH:mm:ss"
+          @ok="onOk"
+        />
+      </a-grid-item>
+      <a-grid-item class="demo-item">
+        <a-radio-group
+          type="button"
+          :model-value="radioActive"
+          @change="(v) => (radioActive = v)"
+        >
+          <a-radio value="bg">{{ $t('previewList.bg') }}</a-radio>
+          <a-radio value="gg">{{ $t('previewList.gg') }}</a-radio>
+        </a-radio-group>
+        <a-button type="primary" size="small" @click="searchBtn">{{
+          $t('previewList.cx')
+        }}</a-button>
+      </a-grid-item>
+    </a-grid>
+    <template v-if="radioActive === 'bg'">
+      <a-table
+        :data="alarmData"
+        :scroll="{ y: '100%' }"
+        :pagination="false"
+        :bordered="{ cell: true }"
+        :loading="loading"
+        style="height: calc(100% - 114px)"
+      >
+        <template #columns>
+          <a-table-column
+            :title="$t('previewList.bjlx')"
+            :ellipsis="true"
+            :tooltip="true"
+            data-index="type"
+          >
+          </a-table-column>
+          <a-table-column
+            :title="$t('previewList.bjsj')"
+            data-index="timeN"
+            :width="180"
+          ></a-table-column>
+          <a-table-column
+            :title="$t('previewList.status')"
+            data-index="status"
+            :width="150"
+            :body-cell-style="
+              (record) => ({
+                background: record.status ? '' : '#7e2d2d',
+              })
+            "
+          >
+            <template #cell="{ record }">
+              <span>{{
+                record.status ? $t('previewList.ycl') : $t('previewList.wcl')
+              }}</span>
+            </template>
+          </a-table-column>
+          <a-table-column
+            :title="$t('previewList.dqstatus')"
+            data-index="isRead"
+            :width="150"
+            :body-cell-style="
+              (record) => ({
+                background: record.isRead ? '' : '#2d4f7e',
+              })
+            "
+          >
+            <template #cell="{ record }">
+              <span>{{
+                record.isRead ? $t('previewList.yd') : $t('previewList.wd')
+              }}</span>
+            </template>
+          </a-table-column>
+          <a-table-column :title="$t('previewList.cz')" :width="150">
+            <template #cell="{ record }">
+              <a-button type="text" size="mini" @click="showView(record)">{{
+                $t('previewList.ck')
+              }}</a-button>
+            </template>
+          </a-table-column>
+        </template>
+      </a-table>
+    </template>
+    <template v-else>
+      <a-scrollbar>
+        <ul class="img-list">
+          <li
+            v-for="(item, i) in alarmImgData"
+            :key="i"
+            class="img-list-item"
+            @click="showView(item)"
+          >
+            <img :src="item.tImg" />
+          </li>
+        </ul>
+      </a-scrollbar>
+    </template>
+    <div class="pagination">
+      <a-pagination
+        :total="total"
+        show-total
+        show-jumper
+        show-page-size
+        @change="pageChange"
+        @page-size-change="pageSizeChange"
+      />
+    </div>
+    <a-drawer
+      class="show-view-drawer"
+      unmount-on-close
+      :width="340"
+      :visible="tpckVisible"
+      :footer="false"
+      :mask-closable="false"
+      :mask="false"
+      @ok="handleOk"
+      @cancel="handleCancel"
+    >
+      <template #title>{{ $t('previewList.tpck') }}</template>
+      <ViewAlarm :data="viewData" />
+    </a-drawer>
+  </div>
 </template>
 
 <script setup lang="ts">
-  import { ref } from 'vue';
+  import dayjs from 'dayjs';
+  import { useAppStore } from '@/store';
+  import { ref, reactive, onMounted } from 'vue';
+  import {
+    getAlarmImgFiles,
+    getAlarmLog,
+    getAlgorithmList,
+  } from '@/api/system';
+  import ViewAlarm from './view-alarm.vue';
+
+  const app = useAppStore();
+  const props = defineProps<{
+    guid?: string;
+  }>();
+  const radioActive = ref<any>('bg');
+  const paramObj = reactive<{
+    PageNum: number;
+    PageSize: number;
+    AlertType: string;
+    StartDate: string | number;
+    EndDate: string | number;
+  }>({
+    PageNum: 1,
+    PageSize: 10,
+    AlertType: '',
+    StartDate: '',
+    EndDate: '',
+  });
+  const loading = ref(false);
+  const total = ref(0);
+  const typeAlarmOptions = ref<any[]>([]);
+  const viewData = ref({});
+
+  const alarmData = ref<{ [key: string]: number | string | boolean }[]>([
+    // {
+    //   type: '行人闯入',
+    //   timeN: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+    //   status: 0,
+    //   isRead: false,
+    // },
+  ]);
+  const alarmImgData = ref<any[]>([
+    // {
+    //   url: 'https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
+    //   time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+    // },
+  ]);
+
+  const getList = async () => {
+    loading.value = true;
+    alarmImgData.value = [];
+    try {
+      const alarmLog = await getAlarmLog(paramObj, { GUID: props.guid });
+      total.value = alarmLog.data.TotalCount;
+      const jsonPromises = alarmLog.data.AlarmList.map((v: any) =>
+        getAlarmImgFiles(v.JsonFile, { GUID: props.guid })
+      );
+      Promise.all(jsonPromises)
+        .then((results) => {
+          alarmData.value = alarmLog.data.AlarmList.map(
+            (item: any, i: number) => {
+              return {
+                ...item,
+                type: item.Name,
+                timeN: item.CreatedAt,
+                status: 0,
+                isRead: false,
+                img: `${app.ip}/rpc/rabbitMQ/KunPengYun/Files/${item.OriginPicture}&GUID=${props.guid}`,
+                tImg: `${app.ip}/rpc/rabbitMQ/KunPengYun/Files/${item.Thumbnail}&GUID=${props.guid}`,
+                imgJson: results[i],
+              };
+            }
+          );
+          alarmImgData.value = alarmData.value;
+          loading.value = false;
+        })
+        .catch(() => {
+          loading.value = false;
+        });
+    } catch (e) {
+      loading.value = false;
+    }
+  };
+  const onOk = (value: any[], date: any[], dateString: any[]) => {
+    paramObj.StartDate = new Date(dateString[0]).getTime() / 1000;
+    paramObj.EndDate = new Date(dateString[1]).getTime() / 1000;
+  };
+  const pageChange = (current: number) => {
+    paramObj.PageNum = current;
+    getList();
+  };
+  const pageSizeChange = (pageSize: number) => {
+    paramObj.PageSize = pageSize;
+    getList();
+  };
+
+  const tpckVisible = ref(false);
+
+  const handleOk = () => {
+    tpckVisible.value = false;
+  };
+  const handleCancel = () => {
+    tpckVisible.value = false;
+  };
+  const showView = (record: any) => {
+    viewData.value = record;
+    tpckVisible.value = true;
+  };
+
+  const searchBtn = () => {
+    getList();
+  };
+  onMounted(async () => {
+    const algorithmList = await getAlgorithmList({}, { GUID: props.guid });
+    // eslint-disable-next-line no-restricted-syntax, guard-for-in
+    for (const key in algorithmList.data) {
+      typeAlarmOptions.value.push({
+        label: algorithmList.data[key],
+        value: key,
+      });
+    }
+    getList();
+  });
 </script>
 
 <style lang="less" scoped>
   .alarm-all {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    gap: 15px;
+    .pagination {
+      display: flex;
+      justify-content: flex-end;
+    }
+    .demo-item {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      & > label {
+        min-width: 65px;
+      }
+      &:last-child {
+        justify-content: flex-end;
+      }
+    }
+    .arco-scrollbar {
+      height: calc(100% - 114px);
+      :deep(.arco-scrollbar-container) {
+        height: 100%;
+        overflow-y: auto;
+      }
+    }
+    .img-list {
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+      grid-template-rows: repeat(auto-fill, 135px);
+      gap: 15px;
+      .img-list-item {
+        width: 100%;
+        height: 135px;
+        overflow: hidden;
+        background-color: #333;
+        cursor: pointer;
+        img {
+          height: 100%;
+          width: 100%;
+          object-fit: cover;
+        }
+      }
+    }
   }
 </style>

+ 171 - 0
src/views/preview-list/components/view-alarm.vue

@@ -0,0 +1,171 @@
+<template>
+  <div class="view-alarm">
+    <a-row class="grid-demo" style="margin-bottom: 16px">
+      <a-col flex="100px">
+        <div>{{ $t('previewList.bjlx') }}</div>
+      </a-col>
+      <a-col flex="auto">
+        <div>{{ data.Name }}</div>
+      </a-col>
+    </a-row>
+    <a-row class="grid-demo" style="margin-bottom: 16px">
+      <a-col flex="100px">
+        <div>{{ $t('previewList.bjsj') }}</div>
+      </a-col>
+      <a-col flex="auto">
+        <div>{{ data.timeN }}</div>
+      </a-col>
+    </a-row>
+    <a-divider />
+    <img
+      id="image"
+      class="view-pic"
+      width="200"
+      :src="data.tImg"
+      alt="Picture"
+    />
+    <div id="image-preview">
+      <div v-if="loading" class="va-loading">
+        <a-spin dot />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, onUnmounted, ref } from 'vue';
+  import Viewer from 'viewerjs';
+  import 'viewerjs/dist/viewer.css';
+
+  interface DataProps {
+    [key: string]: any;
+  }
+
+  const props = defineProps<{
+    data: DataProps;
+  }>();
+  const viewer = ref<Viewer | null>(null);
+  const loading = ref(true);
+
+  let url: string;
+  const init = () => {
+    const canvas = document.createElement('canvas');
+    const { imgJson, img } = props.data;
+    canvas.width = imgJson.imageWidth;
+    canvas.height = imgJson.imageHeight;
+
+    const ctx: CanvasRenderingContext2D | null = canvas.getContext('2d');
+    const imgObj = new Image(imgJson.imageWidth, imgJson.imageHeight);
+    imgObj.setAttribute('crossOrigin', 'anonymous');
+    imgObj.src = img;
+    imgObj.onload = () => {
+      if (ctx) {
+        ctx.drawImage(imgObj, 0, 0);
+        imgJson.resultData.objectList.forEach((item: any) => {
+          // 绘制矩形
+          ctx.strokeStyle = 'red';
+          ctx.lineWidth = 2;
+          ctx.strokeRect(
+            item.rect.x,
+            item.rect.y,
+            item.rect.width,
+            item.rect.height
+          );
+
+          // 绘制标题
+          ctx.fillStyle = 'red';
+          ctx.fillRect(
+            item.rect.x - 2,
+            item.rect.y - 20,
+            item.rect.width + 4,
+            20
+          );
+
+          // 在矩形框内绘制矩形文字
+          ctx.fillStyle = 'white';
+          ctx.font = '14px Arial';
+          ctx.fillText(props.data.Name, item.rect.x + 6, item.rect.y - 4);
+        });
+      }
+
+      canvas.toBlob((blob: any) => {
+        // 创建一个指向该 Blob 的 URL
+        url = URL.createObjectURL(blob);
+
+        viewer.value = new Viewer(
+          document.getElementById('image') as HTMLImageElement,
+          {
+            inline: false,
+            navbar: false,
+            backdrop: false,
+            button: false,
+            title: false,
+            toolbar: {
+              zoomIn: 4,
+              zoomOut: 4,
+              oneToOne: 4,
+              reset: 4,
+              prev: 0,
+              play: {
+                show: 4,
+                size: 'large',
+              },
+              next: 0,
+              rotateLeft: 4,
+              rotateRight: 4,
+              flipHorizontal: 4,
+              flipVertical: 4,
+            },
+            className: 'viewer-box',
+            container: '#image-preview',
+            url: () => url,
+            viewed() {
+              const viewImg: any = document.querySelector('.viewer-canvas>img');
+              viewer.value?.scale(0.8);
+              viewer.value?.moveTo(
+                (window.innerWidth - 340) / 2 - viewImg.offsetWidth / 2,
+                window.innerHeight / 2 - viewImg.offsetHeight / 2
+              );
+              loading.value = false;
+            },
+          }
+        );
+        viewer.value?.show(true);
+      }, 'image/jpeg');
+    };
+  };
+  onMounted(async () => {
+    init();
+  });
+  onUnmounted(() => {
+    viewer.value?.destroy();
+    URL.revokeObjectURL(url);
+  });
+</script>
+
+<style lang="less">
+  .viewer-box {
+    width: 100%;
+    position: absolute;
+  }
+  #image-preview {
+    position: fixed;
+    left: 0;
+    top: 0;
+    width: calc(100% - 340px);
+    height: 100%;
+  }
+  .viewer-canvas {
+    background-color: #000000a1;
+  }
+  .view-alarm {
+    .va-loading {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      background-color: #000000a1;
+    }
+  }
+</style>

+ 16 - 7
src/views/preview-list/index.vue

@@ -14,7 +14,11 @@
               <a-col :span="4">
                 <a-input-search
                   v-model="searchKey"
-                  style="margin-bottom: 8px; max-width: 100%"
+                  style="
+                    margin-bottom: 8px;
+                    max-width: 100%;
+                    background-color: var(--color-fill-2);
+                  "
                 />
                 <a-alert
                   v-if="originTreeData.some((v) => v.disabled)"
@@ -275,7 +279,7 @@
                       :scroll="{ y: 100 }"
                       :pagination="false"
                       :bordered="{ cell: true }"
-                      :loading="alarmLoading"
+                      :loading="false"
                       size="small"
                       style="height: 100%"
                     >
@@ -351,18 +355,18 @@
                           </template>
                           <template #title>
                             <span>{{ t('previewList.cz') }}</span>
-                            <a-tooltip :content="$t('previewList.tip1')">
+                            <a-tooltip :content="$t('previewList.ckqb')">
                               <a-button
                                 type="text"
                                 size="mini"
-                                style="position: absolute; right: 8px"
+                                style="position: absolute; right: 40px"
                                 @click="modalRef.open()"
                               >
                                 <template #icon>
                                   <Icon
                                     icon="gg:view-list"
-                                    width="20"
-                                    height="20"
+                                    width="22"
+                                    height="22"
                                   />
                                 </template>
                               </a-button>
@@ -411,7 +415,12 @@
         <PreviewInfo :data="comData" @handle-back="handleBack" />
       </template>
     </Transition>
-    <PubModal ref="modalRef" :fullscreen="true">
+    <PubModal
+      ref="modalRef"
+      title="报警列表"
+      :fullscreen="true"
+      :closable="true"
+    >
       <AlarmAll />
     </PubModal>
   </div>

+ 7 - 0
src/views/preview-list/locale/en-US.ts

@@ -18,7 +18,9 @@ export default {
   'previewList.ycl': 'Already processed',
   'previewList.wcl': 'Not processed',
   'previewList.cz': 'Operation',
+  'previewList.cx': 'Query',
   'previewList.ck': 'View',
+  'previewList.ckqb': 'View all',
   'previewList.wxh': 'No signal',
   'previewList.tip1': 'Mark as read',
   'previewList.yd': 'Already viewed',
@@ -32,4 +34,9 @@ export default {
   'previewList.tip2': 'AI alarm connection failed, please check the device',
   'previewList.tip3':
     'Disabling indicates that there is an issue with the device',
+  'previewList.qsr': 'Please enter',
+  'previewList.qxz': 'Please select',
+  'previewList.tpck': 'View pictures',
+  'previewList.bg': 'Table',
+  'previewList.gg': 'Grid',
 };

+ 7 - 0
src/views/preview-list/locale/zh-CN.ts

@@ -19,6 +19,8 @@ export default {
   'previewList.wcl': '未处理',
   'previewList.cz': '操作',
   'previewList.ck': '查看',
+  'previewList.cx': '查询',
+  'previewList.ckqb': '查看全部',
   'previewList.wxh': '无信号',
   'previewList.tip1': '标记为已读',
   'previewList.yd': '已读',
@@ -31,4 +33,9 @@ export default {
   'previewList.msqh': '模式切换',
   'previewList.tip2': 'AI 报警连接失败,请检查设备',
   'previewList.tip3': '禁用说明设备有存在问题',
+  'previewList.qsr': '请输入',
+  'previewList.qxz': '请选择',
+  'previewList.tpck': '图片查看',
+  'previewList.bg': '表格',
+  'previewList.gg': '宫格',
 };