From aed8e13b3cbca5c83fbd0d8a57a3e0d9e6e8c561 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期五, 15 十一月 2024 16:49:11 +0800
Subject: [PATCH] 1、大华和海康摄像头图像采集功能开发

---
 src/views/data/video/nvr/index.vue          |  187 ++++++------
 /dev/null                                   |   28 --
 src/views/Login/components/LoginForm.vue    |   32 +-
 src/views/data/video/nvr/NvrCamera.vue      |  174 ++++++-----
 src/views/data/video/camera/index.vue       |  197 +++++++------
 types/env.d.ts                              |    1 
 src/utils/hostMap.ts                        |    2 
 src/views/bpm/model/index.vue               |   17 -
 src/api/data/video/image/index.ts           |   50 +++
 src/views/Home/Index.vue                    |   29 +
 src/utils/is.ts                             |    3 
 src/views/data/video/camera/CameraImage.vue |  114 ++++++++
 12 files changed, 498 insertions(+), 336 deletions(-)

diff --git a/src/api/data/video/image/index.ts b/src/api/data/video/image/index.ts
new file mode 100644
index 0000000..d2d5f7a
--- /dev/null
+++ b/src/api/data/video/image/index.ts
@@ -0,0 +1,50 @@
+import request from '@/config/axios'
+
+export interface ImageVO {
+  id: undefined,
+  cameraId: string,
+  imagePath: string,
+  imageUrl: string,
+  createDate: undefined,
+}
+
+// 查询列表
+export const getImagePage = (params: PageParam) => {
+  return request.get({url: '/data/video/image/page', params})
+}
+
+// 获得
+export const getImage = (id: number) => {
+  return request.get({url: '/data/video/image/get?id=' + id})
+}
+
+// 查询应用列表
+export const getImageList = () => {
+  return request.get({url: '/data/video/image/list'})
+}
+
+// 新增
+export const createImage = (data: ImageVO) => {
+  return request.post({url: '/data/video/image/create', data})
+}
+
+// 修改
+export const updateImage = (data: ImageVO) => {
+  return request.put({url: '/data/video/image/update', data})
+}
+
+// 删除
+export const deleteImage = (id: number) => {
+  return request.delete({url: '/data/video/image/delete?id=' + id})
+}
+
+// 导出
+export const exportImage = (params: ImageVO) => {
+  return request.download({url: '/data/video/image/export-excel', params})
+}
+
+//预览摄像头截图
+export const getPreviewUrl = (url: string) => {
+  const host = import.meta.env.VITE_VIDEO_CAMERA_DOMAIN ? import.meta.env.VITE_VIDEO_CAMERA_DOMAIN : window.location.host
+  return `http://${host}:8899` + url
+}
diff --git a/src/utils/hostMap.ts b/src/utils/hostMap.ts
index 81df37d..c2904af 100644
--- a/src/utils/hostMap.ts
+++ b/src/utils/hostMap.ts
@@ -1,6 +1,6 @@
 const map = {
   "//localhost:7200/": "//wujie-micro.github.io/demo-vue2/",
-  "//localhost:90/": "//localhost:90/",
+  "//localhost:9000/": "//localhost:9000/",
   "//localhost:8000/": "//wujie-micro.github.io/demo-main-vue/",
 };
 
diff --git a/src/utils/is.ts b/src/utils/is.ts
index eec86a9..39812b6 100644
--- a/src/utils/is.ts
+++ b/src/utils/is.ts
@@ -98,8 +98,9 @@
 export const isClient = !isServer
 
 export const isUrl = (path: string): boolean => {
+  // fix:修复hash路由无法跳转的问题
   const reg =
-    /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
+    /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%#\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
   return reg.test(path)
 }
 
diff --git a/src/views/Home/Index.vue b/src/views/Home/Index.vue
index aa64a85..822f800 100644
--- a/src/views/Home/Index.vue
+++ b/src/views/Home/Index.vue
@@ -5,9 +5,9 @@
   <el-skeleton :loading="loading" animated>
     <div id="app" v-for="(item, index) in appList" :key="`dynamics-${index}`">
       <div class="card" @click="gotoApp(item)">
-        <img :src="item.icon" style="width: 100px; height: 100px" />
+        <img :src="item.icon" style="width: 100px; height: 100px"/>
         <div>
-          {{item.appName}}
+          {{ item.appName }}
         </div>
       </div>
     </div>
@@ -19,12 +19,11 @@
 import * as AppApi from '@/api/system/app'
 import {Apps} from "@/views/Home/types";
 import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
-import * as authUtil from "@/utils/auth";
 
 
-defineOptions({ name: 'Home' })
+defineOptions({name: 'Home'})
 
-const { wsCache } = useCache()
+const {wsCache} = useCache()
 
 const loading = ref(true)
 
@@ -35,7 +34,7 @@
   appList = Object.assign(appList, data)
 }
 
-const getAppMenuList = async (id) => {
+const getAppMenuList = async (id, appCode) => {
   const data = await AppApi.getAppMenuList(id)
   let userInfo = wsCache.get(CACHE_KEY.USER)
   userInfo.menus = data
@@ -55,32 +54,38 @@
 
 // 进入应用
 const gotoApp = async (item) => {
+  let path = window.location.pathname
+  let appName = path.split("/")[0]
+  console.log(appName)
   let id = item.id
   let type = item.type
-  if(type === 0) {
-    getAppMenuList(id)
+  let appCode = item.appCode
+  if (type === 0) {
+    await getAppMenuList(id, appCode)
   } else {
     const data = await AppApi.getAppMenuList(id)
     let userInfo = wsCache.get(CACHE_KEY.USER)
     userInfo.menus = data
     wsCache.set(CACHE_KEY.USER, userInfo)
     wsCache.set(CACHE_KEY.ROLE_ROUTERS, data)
-    // await OAuth2Login(formData.value)
-    // window.open(item.appDomain + '/login?appid=' + item.id + "&username=" + authUtil.getLoginForm().username, '_blank')
     window.open(item.appDomain + '/index', '_blank')
+    // window.open('/plat/shasteel', '_blank')
+    // window.location.href = '/plat/shasteel'
+    // window.location.href = `/plat/shasteel?key=energy&url=http://localhost:9000&energy=/energy/demo`
   }
 }
 
 </script>
 
 <style lang="scss" scoped>
-#app{
+#app {
   width: 300px;
   height: 200px;
   display: inline-block;
   background: transparent;
 }
-.card{
+
+.card {
   border: thin dashed gainsboro;
   width: 150px;
   height: 120px;
diff --git a/src/views/Login/components/LoginForm.vue b/src/views/Login/components/LoginForm.vue
index 22a80b2..bc3a32a 100644
--- a/src/views/Login/components/LoginForm.vue
+++ b/src/views/Login/components/LoginForm.vue
@@ -12,7 +12,7 @@
     <el-row style="margin-right: -10px; margin-left: -10px">
       <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
         <el-form-item>
-          <LoginFormTitle style="width: 100%" />
+          <LoginFormTitle style="width: 100%"/>
         </el-form-item>
       </el-col>
       <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
@@ -86,27 +86,27 @@
   </el-form>
 </template>
 <script lang="ts" setup>
-import { ElLoading } from 'element-plus'
+import {ElLoading} from 'element-plus'
 import LoginFormTitle from './LoginFormTitle.vue'
-import type { RouteLocationNormalizedLoaded } from 'vue-router'
+import type {RouteLocationNormalizedLoaded} from 'vue-router'
 
-import { useIcon } from '@/hooks/web/useIcon'
+import {useIcon} from '@/hooks/web/useIcon'
 
 import * as authUtil from '@/utils/auth'
-import { usePermissionStore } from '@/store/modules/permission'
+import {usePermissionStore} from '@/store/modules/permission'
 import * as LoginApi from '@/api/login'
-import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
+import {LoginStateEnum, useFormValid, useLoginState} from './useLogin'
 
-defineOptions({ name: 'LoginForm' })
+defineOptions({name: 'LoginForm'})
 
-const { t } = useI18n()
-const iconHouse = useIcon({ icon: 'ep:house' })
-const iconAvatar = useIcon({ icon: 'ep:avatar' })
-const iconLock = useIcon({ icon: 'ep:lock' })
+const {t} = useI18n()
+const iconHouse = useIcon({icon: 'ep:house'})
+const iconAvatar = useIcon({icon: 'ep:avatar'})
+const iconLock = useIcon({icon: 'ep:lock'})
 const formLogin = ref()
-const { validForm } = useFormValid(formLogin)
-const { getLoginState } = useLoginState()
-const { currentRoute, push } = useRouter()
+const {validForm} = useFormValid(formLogin)
+const {getLoginState} = useLoginState()
+const {currentRoute, push} = useRouter()
 const permissionStore = usePermissionStore()
 const redirect = ref<string>('')
 const loginLoading = ref(false)
@@ -199,14 +199,14 @@
       authUtil.removeLoginForm()
     }
     authUtil.setToken(res)
-    if (!redirect.value) {
+    if (!redirect.value || redirect.value == "/") {
       redirect.value = '/index'
     }
     // 判断是否为SSO登录
     if (redirect.value.indexOf('sso') !== -1) {
       window.location.href = window.location.href.replace('/login?redirect=', '')
     } else {
-      push({ path: redirect.value || permissionStore.addRouters[0].path })
+      push({path: redirect.value || permissionStore.addRouters[0].path})
     }
   } finally {
     loginLoading.value = false
diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue
index f8b0b75..a20ea4e 100644
--- a/src/views/bpm/model/index.vue
+++ b/src/views/bpm/model/index.vue
@@ -163,14 +163,6 @@
           <el-button
             link
             type="primary"
-            @click="handleSimpleDesign(scope.row.id)"
-            v-hasPermi="['bpm:model:update']"
-          >
-            仿钉钉设计流程
-          </el-button>
-          <el-button
-            link
-            type="primary"
             @click="handleDeploy(scope.row)"
             v-hasPermi="['bpm:model:deploy']"
           >
@@ -328,15 +320,6 @@
 const handleDesign = (row) => {
   push({
     name: 'BpmModelEditor',
-    query: {
-      modelId: row.id
-    }
-  })
-}
-
-const handleSimpleDesign = (row) => {
-  push({
-    name: 'SimpleWorkflowDesignEditor',
     query: {
       modelId: row.id
     }
diff --git a/src/views/bpm/simpleWorkflow/index.vue b/src/views/bpm/simpleWorkflow/index.vue
deleted file mode 100644
index 144615e..0000000
--- a/src/views/bpm/simpleWorkflow/index.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-<template>
-  <div>
-    <section class="dingflow-design">
-      <div class="box-scale">
-        <nodeWrap v-model:nodeConfig="nodeConfig" />
-        <div class="end-node">
-          <div class="end-node-circle"></div>
-          <div class="end-node-text">流程结束</div>
-        </div>
-      </div>
-    </section>
-  </div>
-</template>
-<script lang="ts" setup>
-import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
-defineOptions({ name: 'SimpleWorkflowDesignEditor' })
-let nodeConfig = ref({
-  nodeName: '发起人',
-  type: 0,
-  id: 'root',
-  formPerms: {},
-  nodeUserList: [],
-  childNode: {}
-})
-</script>
-<style>
-@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
-</style>
\ No newline at end of file
diff --git a/src/views/data/video/camera/CameraImage.vue b/src/views/data/video/camera/CameraImage.vue
new file mode 100644
index 0000000..335983b
--- /dev/null
+++ b/src/views/data/video/camera/CameraImage.vue
@@ -0,0 +1,114 @@
+<template>
+  <el-dialog v-model="dialogVisible" :title="dialogTitle" :close="handleClose"
+             style="width: 50%; margin-top: 20px; overflow: auto; z-index: 1">
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column
+        label="截图时间"
+        align="center"
+        prop="createDate"
+        :formatter="dateFormatter"
+        width="200"
+      />
+      <el-table-column label="图片路径" align="center" prop="imagePath" width="500"
+                       :show-overflow-tooltip="true"/>
+      <el-table-column label="图片预览" align="center" prop="imageUrl">
+        <template #default="scope">
+          <el-image style="height: 50px; z-index: 1"
+                    :src="getPreviewUrl(scope.row.imageUrl)"
+                    :preview-src-list="getPreviewSrcList(scope.row.imageUrl)" fit="cover"
+                    preview-teleported/>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" min-width="60" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="danger"
+            style="z-index: 1"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['video:camera:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="open"
+    />
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import * as ImageApi from '@/api/data/video/image'
+import {getPreviewUrl} from "@/api/data/video/image";
+import {dateFormatter} from "@/utils/formatTime";
+
+defineOptions({name: 'CameraImage'})
+
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+const dialogTitle = ref('截图列表')
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  id: undefined,
+  brand: undefined,
+  code: undefined,
+  device: undefined,
+  cameraId: '',
+  imagePath: undefined,
+  imageUrl: undefined,
+  createDate: undefined,
+})
+const cameraId = ref('')
+const dialogVisible = ref(false)
+
+/** 查询列表 */
+const open = async (camera_id: string) => {
+  dialogVisible.value = true
+  cameraId.value = camera_id
+  queryParams.cameraId = camera_id
+  loading.value = true
+  try {
+    const data = await ImageApi.getImagePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+const handleClose = async () => {
+  dialogVisible.value = false
+}
+
+const getPreviewSrcList = (imageUrl: string) => {
+  let previewSrcList: string[] = []
+  previewSrcList.push(getPreviewUrl(imageUrl))
+  return previewSrcList
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ImageApi.deleteImage(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await open(cameraId.value)
+  } catch {
+  }
+}
+</script>
diff --git a/src/views/data/video/camera/index.vue b/src/views/data/video/camera/index.vue
index d0fbbe6..add49c5 100644
--- a/src/views/data/video/camera/index.vue
+++ b/src/views/data/video/camera/index.vue
@@ -61,11 +61,11 @@
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
-          <Icon icon="ep:search" class="mr-5px" />
+          <Icon icon="ep:search" class="mr-5px"/>
           搜索
         </el-button>
         <el-button @click="resetQuery">
-          <Icon icon="ep:refresh" class="mr-5px" />
+          <Icon icon="ep:refresh" class="mr-5px"/>
           重置
         </el-button>
         <el-button
@@ -74,7 +74,7 @@
           @click="openForm('create')"
           v-hasPermi="['video:camera:save']"
         >
-          <Icon icon="ep:plus" class="mr-5px" />
+          <Icon icon="ep:plus" class="mr-5px"/>
           新增
         </el-button>
         <el-button
@@ -84,7 +84,7 @@
           :loading="exportLoading"
           v-hasPermi="['video:camera:export']"
         >
-          <Icon icon="ep:download" class="mr-5px" />
+          <Icon icon="ep:download" class="mr-5px"/>
           导出
         </el-button>
       </el-form-item>
@@ -96,20 +96,21 @@
     <el-table v-loading="loading" :data="list">
       <el-table-column label="品牌" align="center" prop="brand" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand" />
+          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand"/>
         </template>
       </el-table-column>
       <el-table-column label="设备类型" align="center" prop="device" width="200"/>
       <el-table-column label="编码" align="center" prop="code" width="200"/>
-      <el-table-column label="IP" align="center" prop="ip" />
-      <el-table-column label="端口" align="center" prop="port" width="100"/>
+      <el-table-column label="IP" align="center" prop="ip"/>
+      <el-table-column label="端口" align="center" prop="port" width="80"/>
+      <el-table-column label="通道" align="center" prop="channel" width="80"/>
       <el-table-column label="用户名" align="center" prop="username" width="100"/>
       <el-table-column label="状态" prop="status" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status" />
+          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status"/>
         </template>
       </el-table-column>
-      <el-table-column label="位置" align="center" prop="location" />
+      <el-table-column label="位置" align="center" prop="location"/>
       <el-table-column label="备注" align="center" prop="remark" width="150"/>
       <el-table-column label="操作" align="center" min-width="110" fixed="right">
         <template #default="scope">
@@ -129,6 +130,14 @@
           >
             删除
           </el-button>
+          <el-button
+            link
+            type="success"
+            @click="imageHandle(scope.row.id)"
+            v-hasPermi="['video:image:query']"
+          >
+            查看截图
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -142,99 +151,109 @@
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
-  <CameraForm ref="formRef" @success="getList" />
+  <CameraForm ref="formRef" @success="getList"/>
 
+  <CameraImage ref="imageFormRef"/>
 
 </template>
 <script lang="ts" setup>
 import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
-  import download from '@/utils/download'
-  import * as CameraApi from '@/api/data/video/camera'
-  import CameraForm from './CameraForm.vue'
+import download from '@/utils/download'
+import * as CameraApi from '@/api/data/video/camera'
+import CameraForm from './CameraForm.vue'
+import CameraImage from './CameraImage.vue'
 
-  defineOptions({name: 'Camera'})
 
-  const message = useMessage() // 消息弹窗
-  const {t} = useI18n() // 国际化
+defineOptions({name: 'Camera'})
 
-  const loading = ref(true) // 列表的加载中
-  const total = ref(0) // 列表的总页数
-  const list = ref([]) // 列表的数据
-  const queryParams = reactive({
-    pageNo: 1,
-    pageSize: 10,
-    type: 1,
-    brand: undefined,
-    ip: undefined,
-    code: undefined,
-    device: undefined,
-    location: undefined,
-    status: undefined
-  })
-  const queryFormRef = ref() // 搜索的表单
-  const exportLoading = ref(false) // 导出的加载中
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
 
-  /** 查询列表 */
-  const getList = async () => {
-    loading.value = true
-    try {
-      const data = await CameraApi.getCameraPage(queryParams)
-      list.value = data.list
-      total.value = data.total
-    } finally {
-      loading.value = false
-    }
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  type: 1,
+  brand: undefined,
+  ip: undefined,
+  code: undefined,
+  device: undefined,
+  location: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await CameraApi.getCameraPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
+}
 
-  /** 搜索按钮操作 */
-  const handleQuery = () => {
-    queryParams.pageNo = 1
-    getList()
-  }
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
 
-  /** 重置按钮操作 */
-  const resetQuery = () => {
-    queryFormRef.value.resetFields()
-    handleQuery()
-  }
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.brand = undefined
+  handleQuery()
+}
 
-  /** 添加/修改操作 */
-  const formRef = ref()
-  const openForm = (type: string, id?: number) => {
-    formRef.value.open(type, id)
-  }
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
 
-  /** 删除按钮操作 */
-  const handleDelete = async (id: number) => {
-    try {
-      // 删除的二次确认
-      await message.delConfirm()
-      // 发起删除
-      await CameraApi.deleteCamera(id)
-      message.success(t('common.delSuccess'))
-      // 刷新列表
-      await getList()
-    } catch {
-    }
-  }
-
-  /** 导出按钮操作 */
-  const handleExport = async () => {
-    try {
-      // 导出的二次确认
-      await message.exportConfirm()
-      // 发起导出
-      exportLoading.value = true
-      const data = await CameraApi.exportCamera(queryParams)
-      download.excel(data, '录像机列表.xls')
-    } catch {
-    } finally {
-      exportLoading.value = false
-    }
-  }
-
-  /** 初始化 **/
-  onMounted(async () => {
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await CameraApi.deleteCamera(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
     await getList()
-  })
+  } catch {
+  }
+}
+
+/** 查看截图 */
+const imageFormRef = ref()
+const imageHandle = (id: string) => {
+  imageFormRef.value.open(id)
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await CameraApi.exportCamera(queryParams)
+    download.excel(data, '录像机列表.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
 </script>
diff --git a/src/views/data/video/nvr/NvrCamera.vue b/src/views/data/video/nvr/NvrCamera.vue
index 0ff49b3..212ff57 100644
--- a/src/views/data/video/nvr/NvrCamera.vue
+++ b/src/views/data/video/nvr/NvrCamera.vue
@@ -6,95 +6,104 @@
     @close="handleClose"
     size="60%">
     <div class="mod-dev__camera" style="padding: 10px;">
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label="监控区域" prop="code">
-        <el-input
-          v-model="queryParams.location"
-          placeholder="请输入监控区域"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery">
-          <Icon icon="ep:search" class="mr-5px" />
-          搜索
-        </el-button>
-        <el-button @click="resetQuery">
-          <Icon icon="ep:refresh" class="mr-5px" />
-          重置
-        </el-button>
-        <el-button
-          type="primary"
-          plain
-          @click="openForm('create')"
-        >
-          <Icon icon="ep:plus" class="mr-5px" />
-          新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['dev:camera:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" />
-          导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-
-  <!-- 列表 -->
-    <el-table v-loading="loading" :data="list">
-      <el-table-column label="编码" align="center" prop="code" width="80" />
-      <el-table-column label="抓图方式" align="center" prop="captureType" width="80">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.CAPTURE_TYPE" :value="scope.row.captureType" />
-        </template>
-      </el-table-column>
-      <el-table-column label="通道" align="center" prop="channel" width="80"  />
-      <el-table-column label="监控区域" align="center" prop="location" />
-      <el-table-column label="备注" align="center" prop="remark" width="200" />
-      <el-table-column label="操作" align="center" min-width="110" fixed="right">
-        <template #default="scope">
+      <el-form
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+      >
+        <el-form-item label="监控区域" prop="code">
+          <el-input
+            v-model="queryParams.location"
+            placeholder="请输入监控区域"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleQuery">
+            <Icon icon="ep:search" class="mr-5px"/>
+            搜索
+          </el-button>
+          <el-button @click="resetQuery">
+            <Icon icon="ep:refresh" class="mr-5px"/>
+            重置
+          </el-button>
           <el-button
-            link
             type="primary"
-            @click="openForm('update', scope.row.id)"
+            plain
+            @click="openForm('create')"
           >
-            编辑
+            <Icon icon="ep:plus" class="mr-5px"/>
+            新增
           </el-button>
           <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['dev:camera:export']"
           >
-            删除
+            <Icon icon="ep:download" class="mr-5px"/>
+            导出
           </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
+        </el-form-item>
+      </el-form>
+
+      <!-- 列表 -->
+      <el-table v-loading="loading" :data="list">
+        <el-table-column label="编码" align="center" prop="code" width="80"/>
+        <el-table-column label="抓图方式" align="center" prop="captureType" width="80">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.CAPTURE_TYPE" :value="scope.row.captureType"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="通道" align="center" prop="channel" width="80"/>
+        <el-table-column label="监控区域" align="center" prop="location"/>
+        <el-table-column label="备注" align="center" prop="remark" width="200"/>
+        <el-table-column label="操作" align="center" min-width="110" fixed="right">
+          <template #default="scope">
+            <el-button
+              link
+              type="primary"
+              @click="openForm('update', scope.row.id)"
+            >
+              编辑
+            </el-button>
+            <el-button
+              link
+              type="danger"
+              @click="handleDelete(scope.row.id)"
+            >
+              删除
+            </el-button>
+            <el-button
+              link
+              type="success"
+              @click="imageHandle(scope.row.id)"
+              v-hasPermi="['video:image:query']"
+            >
+              查看截图
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
     </div>
   </el-drawer>
 
   <!-- 表单弹窗:添加/修改 -->
-  <NvrCameraForm ref="formRef" @success="getList" />
+  <NvrCameraForm ref="formRef" @success="getList"/>
 
+  <CameraImage ref="imageFormRef"/>
 
 </template>
 <script lang="ts" setup>
@@ -102,6 +111,7 @@
 import * as CameraApi from '@/api/data/video/camera'
 import NvrCameraForm from './NvrCameraForm.vue'
 import {DICT_TYPE} from "@/utils/dict";
+import CameraImage from "../camera/CameraImage.vue";
 
 defineOptions({name: 'NvrCamera'})
 
@@ -134,7 +144,7 @@
   await getList()
 }
 
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
 
 /** 查询列表 */
 const getList = async () => {
@@ -148,6 +158,12 @@
   }
 }
 
+/** 查看截图 */
+const imageFormRef = ref()
+const imageHandle = (id: string) => {
+  imageFormRef.value.open(id)
+}
+
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
@@ -156,7 +172,7 @@
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryParams.location = undefined
   handleQuery()
 }
 
diff --git a/src/views/data/video/nvr/index.vue b/src/views/data/video/nvr/index.vue
index 61d0163..b780a0e 100644
--- a/src/views/data/video/nvr/index.vue
+++ b/src/views/data/video/nvr/index.vue
@@ -52,11 +52,11 @@
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
-          <Icon icon="ep:search" class="mr-5px" />
+          <Icon icon="ep:search" class="mr-5px"/>
           搜索
         </el-button>
         <el-button @click="resetQuery">
-          <Icon icon="ep:refresh" class="mr-5px" />
+          <Icon icon="ep:refresh" class="mr-5px"/>
           重置
         </el-button>
         <el-button
@@ -65,7 +65,7 @@
           @click="openForm('create')"
           v-hasPermi="['video:nvr:save']"
         >
-          <Icon icon="ep:plus" class="mr-5px" />
+          <Icon icon="ep:plus" class="mr-5px"/>
           新增
         </el-button>
         <el-button
@@ -75,7 +75,7 @@
           :loading="exportLoading"
           v-hasPermi="['video:nvr:export']"
         >
-          <Icon icon="ep:download" class="mr-5px" />
+          <Icon icon="ep:download" class="mr-5px"/>
           导出
         </el-button>
       </el-form-item>
@@ -87,20 +87,19 @@
     <el-table v-loading="loading" :data="list">
       <el-table-column label="品牌" align="center" prop="brand" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand" />
+          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand"/>
         </template>
       </el-table-column>
       <el-table-column label="编码" align="center" prop="code" width="100"/>
       <el-table-column label="名称" align="center" prop="name"/>
-      <el-table-column label="IP" align="center" prop="ip" />
+      <el-table-column label="IP" align="center" prop="ip"/>
       <el-table-column label="端口" align="center" prop="port" width="100"/>
       <el-table-column label="用户名" align="center" prop="username" width="100"/>
       <el-table-column label="状态" prop="status" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status" />
+          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status"/>
         </template>
       </el-table-column>
-      <el-table-column label="位置" align="center" prop="position" />
       <el-table-column label="备注" align="center" prop="remark" width="150"/>
       <el-table-column label="操作" align="center" min-width="110" fixed="right">
         <template #default="scope">
@@ -120,7 +119,8 @@
           >
             删除
           </el-button>
-          <el-button link type="success" size="small" @click="cameraHandle(scope.row.id)">摄像头</el-button>
+          <el-button link type="success" size="small" @click="cameraHandle(scope.row.id)">摄像头
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -134,7 +134,7 @@
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
-  <NvrForm ref="formRef" @success="getList" />
+  <NvrForm ref="formRef" @success="getList"/>
 
   <!-- 弹窗, 摄像头 -->
   <NvrCamera ref="videoCameraRef"/>
@@ -142,99 +142,100 @@
 </template>
 <script lang="ts" setup>
 import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
-  import download from '@/utils/download'
-  import * as NvrApi from '@/api/data/video/nvr'
-  import NvrForm from './NvrForm.vue'
-  import NvrCamera from './NvrCamera.vue'
+import download from '@/utils/download'
+import * as NvrApi from '@/api/data/video/nvr'
+import NvrForm from './NvrForm.vue'
+import NvrCamera from './NvrCamera.vue'
 
-  defineOptions({name: 'Nvr'})
+defineOptions({name: 'Nvr'})
 
-  const message = useMessage() // 消息弹窗
-  const {t} = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
 
-  const loading = ref(true) // 列表的加载中
-  const total = ref(0) // 列表的总页数
-  const list = ref([]) // 列表的数据
-  const queryParams = reactive({
-    pageNo: 1,
-    pageSize: 10,
-    brand: undefined,
-    ip: undefined,
-    code: undefined,
-    name: undefined,
-    status: undefined
-  })
-  const queryFormRef = ref() // 搜索的表单
-  const exportLoading = ref(false) // 导出的加载中
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  brand: undefined,
+  ip: undefined,
+  code: undefined,
+  name: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
 
-  const videoCameraRef = ref()
+const videoCameraRef = ref()
 
-  /** 查询列表 */
-  const getList = async () => {
-    loading.value = true
-    try {
-      const data = await NvrApi.getNvrPage(queryParams)
-      list.value = data.list
-      total.value = data.total
-    } finally {
-      loading.value = false
-    }
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await NvrApi.getNvrPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
+}
 
-  const cameraHandle = (id: string) => {
-    // devCameraVisible.value = true
-    videoCameraRef.value.open(id)
-  }
+const cameraHandle = (id: string) => {
+  // devCameraVisible.value = true
+  videoCameraRef.value.open(id)
+}
 
-  /** 搜索按钮操作 */
-  const handleQuery = () => {
-    queryParams.pageNo = 1
-    getList()
-  }
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
 
-  /** 重置按钮操作 */
-  const resetQuery = () => {
-    queryFormRef.value.resetFields()
-    handleQuery()
-  }
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.brand = undefined
+  handleQuery()
+}
 
-  /** 添加/修改操作 */
-  const formRef = ref()
-  const openForm = (type: string, id?: number) => {
-    formRef.value.open(type, id)
-  }
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
 
-  /** 删除按钮操作 */
-  const handleDelete = async (id: number) => {
-    try {
-      // 删除的二次确认
-      await message.delConfirm()
-      // 发起删除
-      await NvrApi.deleteNvr(id)
-      message.success(t('common.delSuccess'))
-      // 刷新列表
-      await getList()
-    } catch {
-    }
-  }
-
-  /** 导出按钮操作 */
-  const handleExport = async () => {
-    try {
-      // 导出的二次确认
-      await message.exportConfirm()
-      // 发起导出
-      exportLoading.value = true
-      const data = await NvrApi.exportNvr(queryParams)
-      download.excel(data, '录像机列表.xls')
-    } catch {
-    } finally {
-      exportLoading.value = false
-    }
-  }
-
-  /** 初始化 **/
-  onMounted(async () => {
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await NvrApi.deleteNvr(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
     await getList()
-  })
+  } catch {
+  }
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await NvrApi.exportNvr(queryParams)
+    download.excel(data, '录像机列表.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
 </script>
diff --git a/types/env.d.ts b/types/env.d.ts
index 40ce343..82b4cd7 100644
--- a/types/env.d.ts
+++ b/types/env.d.ts
@@ -25,6 +25,7 @@
   readonly VITE_UPLOAD_URL: string
   readonly VITE_API_URL: string
   readonly VITE_BASE_PATH: string
+  readonly VITE_VIDEO_CAMERA_DOMAIN: string
   readonly VITE_DROP_DEBUGGER: string
   readonly VITE_DROP_CONSOLE: string
   readonly VITE_SOURCEMAP: string

--
Gitblit v1.9.3