houzhongjian
2024-11-15 aed8e13b3cbca5c83fbd0d8a57a3e0d9e6e8c561
1、大华和海康摄像头图像采集功能开发
已修改9个文件
已删除1个文件
已添加2个文件
834 ■■■■■ 文件已修改
src/api/data/video/image/index.ts 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/hostMap.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/is.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Home/Index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Login/components/LoginForm.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/model/index.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/simpleWorkflow/index.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/video/camera/CameraImage.vue 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/video/camera/index.vue 197 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/video/nvr/NvrCamera.vue 174 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/video/nvr/index.vue 187 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
types/env.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
}
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/",
};
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)
}
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;
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
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
    }
src/views/bpm/simpleWorkflow/index.vue
文件已删除
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>
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>
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()
}
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>
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