Explorar o código

Merge branch 'video_warn' of shuzhihua/pms-iot-vue into master

yanghao hai 10 horas
pai
achega
4b3c5396a3

+ 1 - 1
.env.local

@@ -4,7 +4,7 @@ NODE_ENV=development
 VITE_DEV=true
 
 # 请求路径  http://192.168.188.149:48080  https://iot.deepoil.cc  http://172.26.0.3:48080
-VITE_BASE_URL='https://iot.deepoil.cc'
+VITE_BASE_URL='http://192.168.188.79:48080'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server

+ 15 - 1
src/views/pms/maintain/index.vue

@@ -37,6 +37,19 @@
               class="!w-200px"
             />
           </el-form-item>
+          <el-form-item label="执行时间" label-width="70px" prop="executeDate">
+            <el-date-picker
+              size="small"
+              v-model="queryParams.executeDate"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              type="daterange"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              class="!w-220px"
+              :clearable="true"
+              :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+            />
+          </el-form-item>
           <el-form-item :label="t('maintain.status')" label-width="40px" prop="status">
             <el-select
               v-model="queryParams.status"
@@ -364,7 +377,8 @@ const queryParams = reactive({
   deviceName: undefined,
   processInstanceId: undefined,
   auditStatus: undefined,
-  deptId: undefined
+  deptId: undefined,
+  executeDate: []
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中

+ 31 - 15
src/views/pms/video_center/warn/index.vue

@@ -32,7 +32,7 @@
             </el-form-item>
           </el-col>
           <el-col :span="6">
-            <el-form-item label="告警类型" prop="warnType">
+            <el-form-item label="告警类型" prop="eventType">
               <el-select
                 v-model="filterForm.eventType"
                 placeholder="请选择告警类型"
@@ -76,8 +76,8 @@
           fixed="left"
         />
 
-        <el-table-column prop="channelName" label="通道名称" align="center" width="100" />
-        <el-table-column prop="businessChannel" label="通道ID" align="center" width="100" />
+        <el-table-column prop="channelName" label="通道名称" align="center" />
+        <el-table-column prop="businessChannel" label="通道ID" align="center" />
 
         <el-table-column label="告警时间" align="center" width="160">
           <template #default="{ row }">
@@ -85,7 +85,7 @@
           </template>
         </el-table-column>
 
-        <el-table-column prop="activePostCount" label="已告警次数" align="center" width="120" />
+        <el-table-column prop="activePostCount" label="已告警次数" align="center" />
 
         <el-table-column prop="warnType" label="告警类型" align="center" width="120">
           <template #default="{ row }">
@@ -93,25 +93,32 @@
           </template>
         </el-table-column>
 
-        <el-table-column prop="status" label="事件状态" align="center" width="100">
+        <el-table-column label="事件状态" align="center">
           <template #default="{ row }">
             <dict-tag :type="DICT_TYPE.EVENT_STATE" :value="row.eventState" />
           </template>
         </el-table-column>
 
-        <el-table-column label="AI算法" prop="mpName" align="center" width="100" />
+        <el-table-column label="AI算法" prop="mpName" align="center" />
 
         <el-table-column
           prop="eventDescription"
           label="告警描述"
           align="center"
-          min-width="180"
+          width="160"
           show-overflow-tooltip
         />
 
         <el-table-column label="图片" align="center" width="150">
           <template #default="{ row }">
-            <el-link v-if="row.imageUrl" type="primary" @click="openDetail(row.id)">查看</el-link>
+            <el-button
+              link
+              :loading="exportLoading(row.id)"
+              v-if="row.imageUrl || row.url"
+              type="primary"
+              @click="openDetail(row)"
+              >查看</el-button
+            >
             <span v-else>无图片</span>
           </template>
         </el-table-column>
@@ -171,6 +178,15 @@ const pagination = reactive({
   total: 0
 })
 
+let currentid = ref('')
+const exportLoading = (id) => {
+  if (id === currentid.value) {
+    return true
+  } else {
+    return false
+  }
+}
+
 // 初始化表格数据
 const initTableData = async () => {
   // 这里可以调用API获取数据
@@ -227,19 +243,19 @@ const handleCurrentChange = (page) => {
 
 let currentImageUrl = ref('')
 let dialogTableVisible = ref(false)
-const openDetail = async (id) => {
-  const res = await getAlarmPic(id)
-  console.log(res)
+const openDetail = async (row) => {
+  currentid.value = row.id
+  exportLoading()
 
-  // // 2. 将流转换为 Blob
-  // const blob = await res.blob()
+  const res = await getAlarmPic(row.id)
 
   // // 3. 创建 Blob URL
   const blobUrl = URL.createObjectURL(res)
   currentImageUrl.value = blobUrl
 
-  console.log(blobUrl)
-
+  exportLoading.value = false
+  currentid.value = ''
+  exportLoading()
   dialogTableVisible.value = true
 }
 

+ 177 - 92
src/views/system/tree/index.vue

@@ -1,6 +1,6 @@
 <template>
   <ContentWrap>
-    <div style="display: flex; justify-content: space-between; align-items: center;">
+    <div style="display: flex; justify-content: space-between; align-items: center">
       <el-breadcrumb separator=">" class="breadcrumb-container">
         <el-breadcrumb-item
           v-for="(item, index) in breadcrumbs"
@@ -12,21 +12,28 @@
           {{ item.name }}
         </el-breadcrumb-item>
       </el-breadcrumb>
-      <el-input v-model="queryParams.allName" :placeholder="'在'+breadcrumbs[breadcrumbs.length - 1].name+'下搜索'" style="width: 250px;height: 30px" @input="searchFolderAndFile"/>
+      <el-input
+        v-model="queryParams.allName"
+        :placeholder="'在' + breadcrumbs[breadcrumbs.length - 1].name + '下搜索'"
+        style="width: 250px; height: 30px"
+        @input="searchFolderAndFile"
+      />
     </div>
   </ContentWrap>
   <div class="container-tree" ref="container">
-  <el-row >
-    <div class="left-tree" :style="{ width: leftWidth + 'px' }">
-      <ContentWrapNoBottom >
-        <PmsTree @node-click="handleFileNodeClick" @success="successList" :currentId="queryParams.classId" :deviceId="id" />
-      </ContentWrapNoBottom>
-    </div>
-<!--    </el-col>-->
-      <div
-        class="divider-tree"
-        @mousedown="startDrag"
-      ></div>
+    <el-row>
+      <div class="left-tree" :style="{ width: leftWidth + 'px' }">
+        <ContentWrapNoBottom>
+          <PmsTree
+            @node-click="handleFileNodeClick"
+            @success="successList"
+            :currentId="queryParams.classId"
+            :deviceId="id"
+          />
+        </ContentWrapNoBottom>
+      </div>
+      <!--    </el-col>-->
+      <div class="divider-tree" @mousedown="startDrag"></div>
       <div class="right-tree" :style="{ width: rightWidth + 'px' }">
         <ContentWrap>
           <el-form
@@ -36,7 +43,7 @@
             :inline="true"
             label-width="68px"
           >
-            <el-form-item :label="t('file.name') " prop="filename">
+            <el-form-item :label="t('file.name')" prop="filename">
               <el-input
                 v-model="queryParams.filename"
                 :placeholder="t('file.nameHolder')"
@@ -45,7 +52,7 @@
                 class="!w-240px"
               />
             </el-form-item>
-            <el-form-item v-show="false" :label="t('file.fileType') " prop="fileType">
+            <el-form-item v-show="false" :label="t('file.fileType')" prop="fileType">
               <el-select
                 v-model="queryParams.fileType"
                 :placeholder="t('file.fileTypeHolder')"
@@ -61,55 +68,135 @@
               </el-select>
             </el-form-item>
             <el-form-item>
-              <el-button @click="handleQuery"><Icon icon="ep:search" />
-                {{  t('file.search')}}</el-button>
-              <el-button @click="resetQuery"><Icon icon="ep:refresh" />{{  t('file.reset')}}</el-button>
-              <el-button type="primary" :loading="uploadLoading"  @click="openForm('create')">
-                <Icon icon="ep:plus" /> {{  t('file.upload')}}
+              <el-button @click="handleQuery"
+                ><Icon icon="ep:search" /> {{ t('file.search') }}</el-button
+              >
+              <el-button @click="resetQuery"
+                ><Icon icon="ep:refresh" />{{ t('file.reset') }}</el-button
+              >
+              <el-button type="primary" :loading="uploadLoading" @click="openForm('create')">
+                <Icon icon="ep:plus" /> {{ t('file.upload') }}
               </el-button>
             </el-form-item>
           </el-form>
         </ContentWrap>
         <ContentWrap>
-          <el-table v-loading="formLoading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-dblclick="inContent" class="custom-table">
-            <el-table-column :label="t('file.name') " align="left" prop="filename" min-width="300">
+          <el-table
+            v-loading="formLoading"
+            :data="list"
+            :stripe="true"
+            :show-overflow-tooltip="true"
+            @row-dblclick="inContent"
+            class="custom-table"
+          >
+            <el-table-column :label="t('file.name')" align="left" prop="filename" min-width="300">
               <template #default="scope">
-                <div style="display: flex; align-items: center; gap: 5px;">
-                <Icon v-if="scope.row.fileType==='content'" icon="fa:folder-open"  color="orange"/>
-                  <Icon v-else-if="scope.row.fileType==='device'" icon="fa:folder-open"  color="blue"/>
-                <Icon v-else-if="scope.row.fileType==='pic'||scope.row.fileClassify==='jpg'||scope.row.fileClassify==='png'||scope.row.fileClassify==='JPG'" icon="ep:picture-filled"  color="#2183D1"/>
-                <Icon v-else-if="scope.row.fileType==='file'&&(scope.row.fileClassify==='pdf'||scope.row.fileClassify==='PDF')" icon="fa-solid:file-pdf"  color="#E20012"/>
-                <Icon v-else-if="scope.row.fileType==='file'&&(scope.row.fileClassify==='doc'||scope.row.fileClassify==='docx')" icon="fa:file-word-o"  color="blue"/>
-                <Icon v-else-if="scope.row.fileType==='file'&&(scope.row.fileClassify==='xls'||scope.row.fileClassify==='xlsx')" icon="fa-solid:file-excel"  color="#107C41"/>
-                <Icon v-else-if="scope.row.fileType==='file'&&(scope.row.fileClassify==='txt')" icon="fa:file-text-o" />
-                <Icon v-else-if="scope.row.fileType==='file'&&(scope.row.fileClassify==='ppt'||scope.row.fileClassify==='pptx')" icon="fa-solid:file-powerpoint" color="#C43E1C" />
-                <Icon v-else icon="fa-solid:file-alt" />
-                  {{scope.row.filename}}
+                <div style="display: flex; align-items: center; gap: 5px">
+                  <Icon
+                    v-if="scope.row.fileType === 'content'"
+                    icon="fa:folder-open"
+                    color="orange"
+                  />
+                  <Icon
+                    v-else-if="scope.row.fileType === 'device'"
+                    icon="fa:folder-open"
+                    color="blue"
+                  />
+                  <Icon
+                    v-else-if="
+                      scope.row.fileType === 'pic' ||
+                      scope.row.fileClassify === 'jpg' ||
+                      scope.row.fileClassify === 'png' ||
+                      scope.row.fileClassify === 'JPG'
+                    "
+                    icon="ep:picture-filled"
+                    color="#2183D1"
+                  />
+                  <Icon
+                    v-else-if="
+                      scope.row.fileType === 'file' &&
+                      (scope.row.fileClassify === 'pdf' || scope.row.fileClassify === 'PDF')
+                    "
+                    icon="fa-solid:file-pdf"
+                    color="#E20012"
+                  />
+                  <Icon
+                    v-else-if="
+                      scope.row.fileType === 'file' &&
+                      (scope.row.fileClassify === 'doc' || scope.row.fileClassify === 'docx')
+                    "
+                    icon="fa:file-word-o"
+                    color="blue"
+                  />
+                  <Icon
+                    v-else-if="
+                      scope.row.fileType === 'file' &&
+                      (scope.row.fileClassify === 'xls' || scope.row.fileClassify === 'xlsx')
+                    "
+                    icon="fa-solid:file-excel"
+                    color="#107C41"
+                  />
+                  <Icon
+                    v-else-if="scope.row.fileType === 'file' && scope.row.fileClassify === 'txt'"
+                    icon="fa:file-text-o"
+                  />
+                  <Icon
+                    v-else-if="scope.row.fileType === 'file' && scope.row.fileClassify === 'mp4'"
+                    icon="fa:play-circle-o"
+                    color="#009fff"
+                  />
+                  <Icon
+                    v-else-if="
+                      scope.row.fileType === 'file' &&
+                      (scope.row.fileClassify === 'ppt' || scope.row.fileClassify === 'pptx')
+                    "
+                    icon="fa-solid:file-powerpoint"
+                    color="#C43E1C"
+                  />
+                  <Icon v-else icon="fa-solid:file-alt" />
+                  {{ scope.row.filename }}
                 </div>
               </template>
-
             </el-table-column>
-            <el-table-column :label="t('file.fileType') " align="center" prop="fileType" >
+            <el-table-column :label="t('file.fileType')" align="center" prop="fileType">
               <template #default="scope">
                 <dict-tag :type="DICT_TYPE.PMS_FILE_TYPE" :value="scope.row.fileType" />
               </template>
             </el-table-column>
-            <el-table-column :label="t('file.fileSize') " align="center" prop="fileSize" />
-<!--            <el-table-column :label="t('file.preview') " align="center" prop="filePath" >-->
-<!--              <template #default="scope">-->
-<!--                <el-button v-if="scope.row.fileType!=='content'" link type="primary" @click="openWeb(scope.row.filePath)"> <Icon size="19" icon="ep:view"  /> </el-button>-->
-<!--              </template>-->
-<!--            </el-table-column>-->
-            <el-table-column :label="t('file.dept') " align="center" prop="deptName" />
-            <el-table-column :label="t('file.device') " align="center" prop="deviceCode" min-width="220"/> />
-            <el-table-column :label="t('file.operation') " align="center" width="160">
+            <el-table-column :label="t('file.fileSize')" align="center" prop="fileSize" />
+            <!--            <el-table-column :label="t('file.preview') " align="center" prop="filePath" >-->
+            <!--              <template #default="scope">-->
+            <!--                <el-button v-if="scope.row.fileType!=='content'" link type="primary" @click="openWeb(scope.row.filePath)"> <Icon size="19" icon="ep:view"  /> </el-button>-->
+            <!--              </template>-->
+            <!--            </el-table-column>-->
+            <el-table-column :label="t('file.dept')" align="center" prop="deptName" />
+            <el-table-column
+              :label="t('file.device')"
+              align="center"
+              prop="deviceCode"
+              min-width="220"
+            />
+            />
+            <el-table-column :label="t('file.operation')" align="center" width="160">
               <template #default="scope">
                 <div class="flex items-center justify-center">
-                  <el-button type="primary" v-if="scope.row.fileType!=='content'" link  @click="handleDownload( scope.row.filePath)" v-hasPermi="['rq:iot-info:download']">
-                    <Icon icon="ep:download" />{{t('file.dow')}}
+                  <el-button
+                    type="primary"
+                    v-if="scope.row.fileType !== 'content'"
+                    link
+                    @click="handleDownload(scope.row.filePath)"
+                    v-hasPermi="['rq:iot-info:download']"
+                  >
+                    <Icon icon="ep:download" />{{ t('file.dow') }}
                   </el-button>
-                  <el-button type="danger" v-if="scope.row.fileType!=='content'" link  @click="deleteInfo( scope.row.id)" v-hasPermi="['rq:iot-info:download']">
-                    <Icon icon="ep:delete" />{{t('file.delete')}}
+                  <el-button
+                    type="danger"
+                    v-if="scope.row.fileType !== 'content'"
+                    link
+                    @click="deleteInfo(scope.row.id)"
+                    v-hasPermi="['rq:iot-info:download']"
+                  >
+                    <Icon icon="ep:delete" />{{ t('file.delete') }}
                   </el-button>
                 </div>
               </template>
@@ -117,13 +204,13 @@
           </el-table>
         </ContentWrap>
       </div>
-  </el-row>
+    </el-row>
   </div>
   <IotInfoFormTree
     ref="formRef"
     @success="getList"
     :deviceId="deviceId"
-    :nodeId = "nodeId"
+    :nodeId="nodeId"
     :classId="clickNodeId"
   />
 </template>
@@ -136,30 +223,31 @@ import { IotInfoVO } from '@/api/pms/iotinfo'
 import { ref, onMounted, onUnmounted } from 'vue'
 
 import PmsTree from '@/views/system/tree/PmsTree.vue'
-import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
-import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
-import {IotInfoClassifyApi} from "@/api/pms/info";
-import {IotTreeApi} from "@/api/system/tree";
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import { IotInfoClassifyApi } from '@/api/pms/info'
+import { IotTreeApi } from '@/api/system/tree'
 defineOptions({ name: 'IotTree' })
 
 const container = ref(null)
 const leftWidth = ref(350) // 初始左侧宽度
 const rightWidth = ref(window.innerWidth * 0.8)
 let isDragging = false
-const uploadLoading = ref(false);
+const uploadLoading = ref(false)
 
-
-const searchFolderAndFile = async () =>{
+const searchFolderAndFile = async () => {
   debugger
   formLoading.value = true
   const data = await IotInfoApi.IotInfoApi.getAllChildContentFile(queryParams)
   debugger
   list.value = data
   formLoading.value = false
-  queryParams.filename = "";
+  queryParams.filename = ''
 }
 const openWeb = (url) => {
-  window.open('http://1.94.244.160:8012/onlinePreview?url='+encodeURIComponent(Base64.encode(url)));
+  window.open(
+    'http://1.94.244.160:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(url))
+  )
 }
 const handleView = (row) => {
   openForm('detail', row.id)
@@ -173,17 +261,15 @@ const topNodeId = ref('')
 const successList = async (id) => {
   queryParams.classId = id
   topNodeId.value = id
-  const rootItem = breadcrumbs.value.find(item => item.type === 'root');
+  const rootItem = breadcrumbs.value.find((item) => item.type === 'root')
   if (rootItem) {
-    rootItem.id = id;
+    rootItem.id = id
   }
 
   await getList()
   // queryParams.classId = ''
 }
 
-
-
 const onDrag = (e) => {
   if (!isDragging) return
 
@@ -202,8 +288,6 @@ const stopDrag = () => {
   document.removeEventListener('mouseup', stopDrag)
 }
 
-
-
 const queryFormRef = ref() // 搜索的表单
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
@@ -222,7 +306,7 @@ const queryParams = reactive({
   deviceId: null,
   classId: null,
   deptId: undefined,
-  allName: null,
+  allName: null
 })
 // SPU 表单数据
 const formData = ref({
@@ -253,12 +337,12 @@ const handleDownload = async (url) => {
     console.error('下载失败:', error)
   }
 }
-const deleteInfo= async (id) =>{
+const deleteInfo = async (id) => {
   await message.delConfirm()
-  await IotInfoApi.IotInfoApi.deleteIotInfo(id).then(res=>{
+  await IotInfoApi.IotInfoApi.deleteIotInfo(id).then((res) => {
     if (res) {
       message.success('文件删除成功')
-       getList();
+      getList()
     } else {
       message.error('文件删除失败')
     }
@@ -294,9 +378,9 @@ const openForm = async (type: string, id?: number) => {
   formRef.value.open(type, id)
   await new Promise((resolve) => {
     setTimeout(() => {
-      resolve(); // 操作完成后resolve
-    }, 2000); // 模拟2秒耗时
-  });
+      resolve() // 操作完成后resolve
+    }, 2000) // 模拟2秒耗时
+  })
   uploadLoading.value = false
 }
 const deviceId = ref('')
@@ -305,13 +389,13 @@ const nodeId = ref('')
 const classType = ref('')
 
 const breadcrumbs = ref([
-  { id: null, name: '科瑞石油技术',type:'root' } // 根节点
+  { id: null, name: '科瑞石油技术', type: 'root' } // 根节点
 ])
 
 // 共享的面包屑更新逻辑
 const updateBreadcrumbs = async (node) => {
   // 查找当前节点是否已在面包屑中
-  const currentIndex = breadcrumbs.value.findIndex(item => item.id === node.id)
+  const currentIndex = breadcrumbs.value.findIndex((item) => item.id === node.id)
 
   if (currentIndex > -1) {
     // 如果已存在则截断后面的节点
@@ -322,15 +406,18 @@ const updateBreadcrumbs = async (node) => {
   }
 
   // 更新表格数据
-  queryParams.classId = node.id;
+  queryParams.classId = node.id
   const data = await IotInfoApi.IotInfoApi.getChildContentFile(queryParams)
   list.value = data
 }
 
 // 表格行点击事件
 const inContent = async (row) => {
-  if (row.fileType!='content') {
-    window.open('http://doc.deepoil.cc:8012/onlinePreview?url='+encodeURIComponent(Base64.encode(row.filePath)));
+  if (row.fileType != 'content') {
+    window.open(
+      'http://doc.deepoil.cc:8012/onlinePreview?url=' +
+        encodeURIComponent(Base64.encode(row.filePath))
+    )
     return
   }
   queryParams.filename = ''
@@ -338,7 +425,7 @@ const inContent = async (row) => {
   // 调用共享方法更新面包屑和表格
   await updateBreadcrumbs(row)
   // 可以添加其他表格行点击需要的逻辑
-  queryParams.classId = row.id;
+  queryParams.classId = row.id
   nodeId.value = row.id
   const data = await IotInfoApi.IotInfoApi.getChildContentFile(queryParams)
   formLoading.value = false
@@ -348,33 +435,33 @@ const inContent = async (row) => {
 // 文件树节点点击事件
 const handleFileNodeClick = async (row) => {
   queryParams.filename = ''
-  const parentItems = await IotTreeApi.getParentIds(row.id);
-  breadcrumbs.value = [];
-  parentItems.forEach(item => {
-    breadcrumbs.value.push({ id: item.id, name: item.name,type:'root' } );
+  const parentItems = await IotTreeApi.getParentIds(row.id)
+  breadcrumbs.value = []
+  parentItems.forEach((item) => {
+    breadcrumbs.value.push({ id: item.id, name: item.name, type: 'root' })
   })
 
   queryParams.classId = row.id
-  if (row.type=='device') {
+  if (row.type == 'device') {
     id.value = row.originId
   }
 
   classType.value = row.type
-  if (row.type==='device') {
+  if (row.type === 'device') {
     deviceId.value = row.originId
     const queryParam = {
       deviceId: row.originId,
       pageNo: 1,
-      pagesize: 10,
+      pagesize: 10
     }
     const data = await IotInfoClassifyApi.getIotInfoClassifyPage(queryParam)
-    if (data){
-      const target = data.filter((item)=> item.parentId===0)
+    if (data) {
+      const target = data.filter((item) => item.parentId === 0)
       clickNodeId.value = target[0].id
     }
-  } else if (row.type === 'file'){
+  } else if (row.type === 'file') {
     clickNodeId.value = row.originId
-  } else if (row.type==='dept') {
+  } else if (row.type === 'dept') {
     // message.error("请选择设备及文件节点")
     // return
   }
@@ -395,7 +482,7 @@ const handleBreadcrumbClick = async (index) => {
 
   // 获取对应节点的数据
   let targetId = targetBreadcrumbs[index].id
-  if (!targetId){
+  if (!targetId) {
     targetId = topNodeId.value
   }
   queryParams.classId = targetId
@@ -407,7 +494,6 @@ const handleBreadcrumbClick = async (index) => {
   formLoading.value = false
 }
 
-
 // const handleFileNodeClick = async (row) => {
 //   queryParams.classId = row.id
 //   classType.value = row.type
@@ -518,7 +604,6 @@ onMounted(async () => {
   font-size: 12px;
 }
 
-
 .custom-table {
   cursor: pointer;
   --el-table-row-hover-bg-color: #f5f7fa; /* 优化悬停背景色 */