lvkun996 vor 2 Jahren
Ursprung
Commit
cca0754387
3 geänderte Dateien mit 687 neuen und 679 gelöschten Zeilen
  1. 110 110
      src/hooks/effect.ts
  2. 8 0
      src/pages/Iot/tps/device.vue
  3. 569 569
      src/pages/cvs/video/device.vue

+ 110 - 110
src/hooks/effect.ts

@@ -1,110 +1,110 @@
-import { Emitter } from '@/enum/emitter'
-import mitt from 'mitt'
-import { setBaseUrl } from '@/service/request'
-import { useRoute } from 'vue-router'
-import { ref, onUnmounted, onMounted } from 'vue'
-
-const emitter = mitt()
-
-export const useEmitter = () => {
-  const _on = (key: keyof typeof Emitter, cb: () => void) => emitter.on(key, cb)
-  const _emit = (key: keyof typeof Emitter) => emitter.emit(key)
-  const _off = (key: keyof typeof Emitter, cb: () => void) => emitter.off(key, cb)
-
-  return {
-    on: _on,
-    emit: _emit,
-    off: _off
-  }
-}
-
-export const useScheduler = (callback: () => void, delay: number) => {
-  const timer = ref<any>()
-
-  function start () {
-    timer.value = setInterval(callback, delay)
-  }
-
-  function stop () {
-    clearInterval(timer.value)
-  }
-
-  onUnmounted(() => {
-    stop()
-  })
-
-  return {
-    start,
-    stop
-  }
-}
-
-export const useSchedulerOnce = (callback: () => void, delay: number) => {
-  const timeId = ref()
-  timeId.value = setTimeout(() => {
-    callback()
-    clearTimeout(timeId.value)
-  }, delay)
-
-  onUnmounted(() => {
-    clearTimeout(timeId.value)
-  })
-}
-
-export const usePort = (title: string) => {
-  if (title === '物联网') {
-    setBaseUrl('/iot')
-  } else if (title === '视联网') {
-    setBaseUrl('/cvss')
-  } else if (title === '用户群组') {
-    setBaseUrl('/user')
-  } else if (title === '数据中台') {
-    setBaseUrl('/datacenter')
-  }
-}
-
-/**
- * `useDeviceResolution` 函数是一个 TypeScript 函数,允许您跟踪设备的屏幕宽度和高度,并在屏幕大小调整时执行回调函数。
- * @param {Function} cb - 参数“cb”是一个回调函数,每当设备分辨率发生变化时就会调用该函数。
- * @returns 函数“useDeviceResolution”返回一个具有两个属性的对象:“screenWidth”和“screenHeight”。
- */
-export const useDeviceResolution = (cb: Function) => {
-  const screenWidth = ref(window.innerWidth)
-  const screenHeight = ref(window.innerHeight)
-
-  const handleResize = () => {
-    screenWidth.value = window.innerWidth
-    screenHeight.value = window.innerHeight
-    cb()
-  }
-
-  onMounted(() => {
-    window.addEventListener('resize', handleResize)
-  })
-
-  onUnmounted(() => {
-    window.removeEventListener('resize', handleResize)
-  })
-
-  return {
-    screenWidth,
-    screenHeight
-  }
-}
-
-/**
- *  @description 获取当前模块信息
- *  @example
- *  const module = useModule()
- *  console.log(module.name)
- *  console.log(module.path)
- */
-export const useModule = () => {
-  const route = useRoute()
-  return {
-    name: route.matched[0].name,
-    path: route.matched[0].path
-  }
-}
-
-export const useIsMicro = () => !!window.__POWERED_BY_QIANKUN__
+import { Emitter } from '@/enum/emitter'
+import mitt from 'mitt'
+import { setBaseUrl } from '@/service/request'
+import { useRoute } from 'vue-router'
+import { ref, onUnmounted, onMounted } from 'vue'
+
+const emitter = mitt()
+
+export const useEmitter = () => {
+  const _on = (key: keyof typeof Emitter, cb: () => void) => emitter.on(key, cb)
+  const _emit = (key: keyof typeof Emitter) => emitter.emit(key)
+  const _off = (key: keyof typeof Emitter, cb: () => void) => emitter.off(key, cb)
+
+  return {
+    on: _on,
+    emit: _emit,
+    off: _off
+  }
+}
+
+export const useScheduler = (callback: () => void, delay: number) => {
+  const timer = ref<any>()
+
+  function start () {
+    timer.value = setInterval(callback, delay)
+  }
+
+  function stop () {
+    clearInterval(timer.value)
+  }
+
+  onUnmounted(() => {
+    stop()
+  })
+
+  return {
+    start,
+    stop
+  }
+}
+
+export const useSchedulerOnce = (callback: () => void, delay: number) => {
+  const timeId = ref()
+  timeId.value = setTimeout(() => {
+    callback()
+    clearTimeout(timeId.value)
+  }, delay)
+
+  onUnmounted(() => {
+    clearTimeout(timeId.value)
+  })
+}
+
+export const usePort = (title: string) => {
+  if (title === '物联网') {
+    setBaseUrl('http://localhost:10800/iot')
+  } else if (title === '视联网') {
+    setBaseUrl('http://localhost:10800/cvss')
+  } else if (title === '用户群组') {
+    setBaseUrl('http://localhost:10800/user')
+  } else if (title === '数据中台') {
+    setBaseUrl('http://localhost:10800/datacenter')
+  }
+}
+
+/**
+ * `useDeviceResolution` 函数是一个 TypeScript 函数,允许您跟踪设备的屏幕宽度和高度,并在屏幕大小调整时执行回调函数。
+ * @param {Function} cb - 参数“cb”是一个回调函数,每当设备分辨率发生变化时就会调用该函数。
+ * @returns 函数“useDeviceResolution”返回一个具有两个属性的对象:“screenWidth”和“screenHeight”。
+ */
+export const useDeviceResolution = (cb: Function) => {
+  const screenWidth = ref(window.innerWidth)
+  const screenHeight = ref(window.innerHeight)
+
+  const handleResize = () => {
+    screenWidth.value = window.innerWidth
+    screenHeight.value = window.innerHeight
+    cb()
+  }
+
+  onMounted(() => {
+    window.addEventListener('resize', handleResize)
+  })
+
+  onUnmounted(() => {
+    window.removeEventListener('resize', handleResize)
+  })
+
+  return {
+    screenWidth,
+    screenHeight
+  }
+}
+
+/**
+ *  @description 获取当前模块信息
+ *  @example
+ *  const module = useModule()
+ *  console.log(module.name)
+ *  console.log(module.path)
+ */
+export const useModule = () => {
+  const route = useRoute()
+  return {
+    name: route.matched[0].name,
+    path: route.matched[0].path
+  }
+}
+
+export const useIsMicro = () => !!window.__POWERED_BY_QIANKUN__

+ 8 - 0
src/pages/Iot/tps/device.vue

@@ -196,7 +196,15 @@ const { resetFields, validate, validateInfos } = useForm(deviceState, {
 const opraState = ref<'add' | 'update'>('add')
 
 const ok = () => {
+  validate().then(async () => {
+    opraState.value === 'add' ? await TpsController.addDevice(deviceState) : await TpsController.updateDevice(deviceState)
+    search()
+    closeModal()
+  })
+}
 
+const search = () => {
+  tableProDom.value.reload()
 }
 
 const openModal = (type: 'add' | 'update', record: TPS.Device) => {

+ 569 - 569
src/pages/cvs/video/device.vue

@@ -1,569 +1,569 @@
-<template>
-<a-card>
-  <template #title >
-    <a-row>
-      <a-col style="font-size: 20px;margin-right: 10px;" >设备管理</a-col>
-      <a-col >
-        <a-select
-          :value="deviceState.spaceId"
-          style="width: 170px;"
-          @change="onChangeSpace"
-          placeholder="请选择空间"
-          allowClear
-        >
-          <a-select-option
-            :key="index"
-            :value="item.spaceId"
-            v-for="(item, index) in state.spaceList"
-          >
-            {{item.spaceName}}
-          </a-select-option>
-        </a-select>
-    </a-col>
-    </a-row>
-  </template>
-  <table-pro
-    ref="tableProDom"
-    :service="SpaceController.devicePage"
-    :serviceParams="{deviceName, spaceId: deviceState.spaceId}"
-    :columns="columns"
-    @add="openModal"
-  >
-    <template #search >
-      <a-space><InputTsx placeholder="请输入设备名称进行搜索" v-model:value="deviceName" /> <a-button type="primary" @click="search">搜索</a-button> </a-space>
-    </template>
-
-    <template #render="{column, record}" >
-
-      <template v-if="column.key === 'status'" >
-        <div style="display: flex;align-items: center;" >
-          <div :style="{width: '8px',height: '8px',borderRadius: '50%', backgroundColor:SpaceController.statusMap.get(record.status)?.color, marginRight: '5px' }" ></div>
-          <div>{{SpaceController.deviceStatusMap.get(record.status)?.label}}</div>
-        </div>
-      </template>
-
-      <template v-if="column.key === 'deviceId'">
-        <a @click="baseInfo(record)">{{record.deviceId}}</a>
-      </template>
-
-      <template v-if="column.key === 'type'">
-        <a-tag>{{SpaceController.type.find((item)=>{
-          return item.key == record.type
-        })?.label}}</a-tag>
-      </template>
-
-      <template v-if="column.key === 'action'" >
-        <a-space>
-          <a @click="baseInfo(record)">基本信息</a>
-          <a @click="recordParty(record)">录像回放</a>
-          <a @click="thumbParty(record)">截图查看</a>
-          <a @click="arAnalysis(record)">AI分析</a>
-        </a-space>
-      </template>
-    </template>
-
-  </table-pro>
-</a-card>
-
-<RealView
-  :open="state.activeVisible"
-  @cancel="closeRealView"
-  :tab-key="activeTabKey"
-  :tabs-list="deviceTabs"
-  @tab-chang="key => onTabChange(key)"
->
-    <!--  个人基础信息 -->
-    <div v-if="activeTabKey === 'base'" >
-      <a-card title="基本信息" >
-        <template #extra >
-          <a-button type="primary" @click="ok" >提交修改</a-button>
-        </template>
-        <a-form  style="width: 100%;" :labelCol="{span: 3}" :wrapperCol="{span: 14}" >
-      <a-form-item label="设备名称" v-bind="validateInfos.deviceName"  >
-        <InputTsx allowClear placeholder="请输入设备名称" v-model:value="deviceState.deviceName" />
-      </a-form-item>
-      <a-form-item  label="app" v-bind="validateInfos.app"  >
-        <InputTsx allowClear placeholder="请输入英文、数字组成的app" v-model:value="deviceState.app" />
-      </a-form-item>
-      <a-form-item  label="stream" v-bind="validateInfos.stream"  >
-        <InputTsx allowClear placeholder="请输入英文、数字组成的stream" v-model:value="deviceState.stream" />
-      </a-form-item>
-      <a-form-item  label="空间名称" v-bind="validateInfos.stream"  >
-        <a-select
-          :value="spaceId"
-          style="width: 170px;"
-          @change="onChangeSpace"
-          placeholder="请选择空间"
-        >
-          <a-select-option
-            :key="index"
-            :value="item.spaceId"
-            v-for="(item, index) in state.spaceList"
-          >
-            {{item.spaceName}}
-          </a-select-option>
-        </a-select>
-      </a-form-item>
-      <a-form-item  label="设备类型"  >
-        <a-radio-group v-model:value="deviceState.type" button-style="solid">
-          <a-radio-button
-            v-for="item in SpaceController.type"
-            :key="item.key"
-            :value="item.value"
-          >
-            {{item.label}}
-          </a-radio-button>
-        </a-radio-group>
-      </a-form-item>
-      <a-form-item  label="设备地址"  >
-        <InputTsx allowClear placeholder="请输入设备地址" v-model:value="deviceState.gisName" />
-      </a-form-item>
-      <a-form-item  label="拉流地址" v-if="['PULL_RTMP', 'PULL_RTSP'].includes(deviceState.type)" >
-        <InputTsx v-model:value="deviceState.pullPath" placeholder="请输入拉流地址" />
-      </a-form-item>
-      <span v-if="deviceState.type === 'GB28181'">
-        <a-form-item  label="国标Id"  >
-          <InputTsx v-model:value="deviceState.gbConfig.gbId" placeholder="请输入拉流地址" />
-        </a-form-item>
-        <a-form-item  label="用户名"  >
-          <InputTsx v-model:value="deviceState.gbConfig.username" placeholder="请输入用户名"/>
-        </a-form-item>
-        <a-form-item  label="用户密码"  >
-          <InputTsx v-model:value="deviceState.gbConfig.password" placeholder="请输入用户密码" />
-        </a-form-item>
-        <a-form-item  label="国标平台"  >
-          <a-radio-group v-model:value="deviceState.gbConfig.platform" button-style="solid">
-          <a-radio-button value="IPC">IPC</a-radio-button>
-          <a-radio-button value="NVR">NVR</a-radio-button>
-          </a-radio-group>
-        </a-form-item>
-      </span>
-      <a-form-item  label="设备描述"  >
-        <a-textarea v-model:value="deviceState.description" placeholder="请输入设备描述" :rows="4" />
-      </a-form-item>
-        </a-form>
-      </a-card>
-      <a-card v-if="operator === 'preview'" title="推播流地址" style="margin-top: 20px;" >
-        <!-- 非国标设备 -->
-        <a-row :gutter="[8, 18]" >
-          <a-col :span="24">
-            <a-row>
-              <a-col :span="4">
-                请选择转码模版:
-              </a-col>
-              <a-col>
-                  <a-space>
-                    <a-select style="width: 170px;" >
-                      <a-select-option key="2" >原始流</a-select-option>
-                    </a-select>
-                    <a-button>获取最新地址</a-button>
-                  </a-space>
-              </a-col>
-            </a-row>
-          </a-col>
-          <!-- 推流地址 -->
-          <a-col :span="24">
-            <a-row>
-              <a-col :span="2" >推流地址</a-col>
-              <a-col>
-                <div v-for="item in gbState.pushList" :key="item" >{{item}}</div>
-              </a-col>
-            </a-row>
-          </a-col>
-          <!-- 播放地址 -->
-          <a-col :span="24">
-            <a-row>
-              <a-col :span="2" >播放地址</a-col>
-              <a-col >
-                <div v-for="item in gbState.pullList" :key="item" >{{item}}</div>
-              </a-col>
-            </a-row>
-          </a-col>
-        </a-row>
-      </a-card>
-    </div>
-      <a-card style="background-color: #fff;width: 100%;" v-if="activeTabKey !== 'base'" >
-        <a-space >
-          <a-range-picker v-model:value="deviceActionParams.times" />
-          <a-select style="width: 170px;"  v-if="activeTabKey === 'record'" v-model:value="deviceActionParams.recordFormat" >
-            <a-select-option v-for="item in SpaceController.recordFormat" :key="item" :value="item" >
-              {{item}}
-            </a-select-option>
-          </a-select>
-          <a-select style="width: 170px;"  v-if="activeTabKey === 'ai'" v-model:value="deviceActionParams.aiId" >
-            <a-select-option v-for="item in state.aiList" :key="item.aiId" :value="item.aiId" >
-              {{item.aiName}}
-            </a-select-option>
-          </a-select>
-          <a-button type="primary" @click="searchDevice">搜索</a-button>
-        </a-space>
-      </a-card>
-
-      <!-- 视频 图片区域 -->
-      <a-spin :spinning="state.loading" v-if="activeTabKey !== 'base'">
-          <a-row :gutter="[8, 8]" style="margin-top: 20px;background-color: #fff;padding: 18px;padding-bottom: 38px;" >
-            <a-col :span="4" v-for="item in state.deviceMediaList" :key="item" >
-              <div class="media-card">
-                  <video
-                  v-if="activeTabKey === 'record'"
-                  controls
-                    :src="item.defaultUrl"
-                  ></video>
-                <div v-else-if="activeTabKey === 'thumb'" >
-                  <a-image
-                    width="100%"
-                    height="150px"
-                    style="object-fit: cover;display: block;"
-                    :src="item.defaultUrl"
-                  />
-                </div>
-                <div v-else-if="activeTabKey === 'ai'" style="height: 150px;" >
-                  <a-image width="100%" height="150px"  style="object-fit: cover;display: block;" :src="item.defaultUrl"></a-image>
-                </div>
-                <div class="craete-time">{{dayjs(item.ts).format('YYYY/MM/DD HH:mm:ss')}}</div>
-              </div>
-            </a-col>
-          </a-row>
-      </a-spin>
-
-</RealView>
-
-</template>
-<script lang='ts' setup >
-import { InputTsx } from '@/components/MicroComponents/index'
-import { reactive, onMounted, nextTick, ref, watch } from 'vue'
-import { Form } from 'ant-design-vue'
-import { OperatorController, SpaceController } from '@/controller'
-import { useRoute } from 'vue-router'
-import dayjs from 'dayjs'
-import { RealView } from '@/components/RealView/index'
-
-const columns = [
-  {
-    title: '设备ID',
-    dataIndex: 'deviceId',
-    key: 'deviceId'
-  },
-  {
-    title: '设备名称',
-    dataIndex: 'deviceName',
-    key: 'deviceName'
-  },
-  {
-    title: '空间类型',
-    dataIndex: 'type',
-    key: 'type'
-  },
-  {
-    title: '描述',
-    dataIndex: 'description',
-    key: 'description'
-  },
-  {
-    title: '状态',
-    dataIndex: 'status',
-    key: 'status'
-  },
-  {
-    title: '操作',
-    dataIndex: 'action',
-    key: 'action'
-  }
-]
-
-const _spaceId = useRoute().query.spaceId
-
-const useForm = Form.useForm
-
-const spaceId = ref(null)
-
-const deviceName = ref(null)
-
-const tableProDom = ref()
-
-const activeTabKey = ref<'base' | 'record' | 'thumb' | 'ai'>('base')
-
-const operator = ref<'add' | 'preview'>('add')
-
-const deviceTabs = [
-  { key: 'base', tab: '基础信息' },
-  { key: 'record', tab: '录像回放' },
-  { key: 'thumb', tab: '截图查看' },
-  { key: 'ai', tab: 'AI分析' }
-]
-
-const recordColumn = [
-  {
-    title: '录制格式',
-    dataIndex: 'extra',
-    key: 'extra'
-  },
-  {
-    title: '录制时间',
-    dataIndex: 'ts',
-    key: 'ts'
-  },
-  {
-    title: '可播放地址',
-    dataIndex: 'defaultUrl',
-    key: 'defaultUrl'
-  }
-]
-
-const thumbColumn = [
-  {
-    title: '截图地址',
-    dataIndex: 'defaultUrl',
-    key: 'defaultUrl'
-  },
-  {
-    title: '截图时间',
-    dataIndex: 'ts',
-    key: 'ts'
-  }
-]
-
-const aiColumn = [
-  {
-    title: '算子模型id',
-    dataIndex: 'extra',
-    key: 'extra'
-  },
-  {
-    title: '结果地址',
-    dataIndex: 'defaultUrl',
-    key: 'defaultUrl'
-  },
-  {
-    title: '时间',
-    dataIndex: 'ts',
-    key: 'ts'
-  }
-]
-
-const deviceActionMap = new Map([
-  ['record', { get: SpaceController.deviceRecordById, column: recordColumn }],
-  ['thumb', { get: SpaceController.deviceThumbById, column: thumbColumn }],
-  ['ai', { get: SpaceController.deviceAiRetById, column: aiColumn }]
-])
-
-const state = reactive<{
-  visible: boolean,
-  activeVisible: boolean,
-  spaceList: CVS.space[]
-  deviceMediaList: any[],
-  aiList: CVS.Operator[]
-  loading: boolean
-}>({
-  visible: false,
-  activeVisible: false,
-  spaceList: [],
-  deviceMediaList: [],
-  aiList: [],
-  loading: false
-})
-
-const gbState = reactive<{
-  channelList: string[]
-  pullList: string[],
-  gbPullList: string[]
-  pushList: string[]
-}>({
-  channelList: [],
-  pullList: [],
-  gbPullList: [],
-  pushList: []
-})
-
-const deviceActionParams = reactive({
-  times: [],
-  startTs: '',
-  endTs: '',
-  recordFormat: '',
-  deviceId: '',
-  page: 1,
-  pageSize: 10,
-  aiId: null
-})
-
-const deviceState = reactive({
-  deviceId: '',
-  spaceId: null,
-  deviceName: '',
-  app: '',
-  stream: '',
-  description: '',
-  deviceStreamId: '',
-  gisLongitude: '',
-  gisLatitude: '',
-  gisName: '',
-  type: 'RTMP',
-  pullPath: '',
-  gbConfig: {
-    gbId: '',
-    username: '',
-    password: '',
-    platform: 'IPC'
-  }
-})
-
-watch(
-  () => activeTabKey.value,
-  () => {
-    activeTabKey.value !== 'base' && getMedia()
-  }
-)
-
-watch(
-  () => deviceActionParams.times,
-  () => {
-    deviceActionParams.startTs = dayjs(deviceActionParams.times[0]).unix() as unknown as string
-    deviceActionParams.endTs = dayjs(deviceActionParams.times[0]).unix() as unknown as string
-  }
-)
-
-watch(
-  () => deviceState.deviceId,
-  () => {
-    if (deviceState.gbConfig && deviceState.gbConfig.gbId) {
-      getDeviceChannel()
-      getGbPull()
-    } else {
-      getNotGbPush()
-      getNotGbPull()
-    }
-  }
-)
-
-const { resetFields, validate, validateInfos } = useForm(deviceState, {
-  deviceName: [{ required: true, message: '请填写设备名称' }],
-  app: [{ required: true, message: '请填写app名称' }],
-  stream: [{ required: true, message: '请填写stream名称' }]
-})
-
-const searchDevice = () => getMedia()
-
-const onTabChange = (key: 'record' | 'thumb' | 'ai') => activeTabKey.value = key
-
-const getMedia = async () => {
-  state.loading = true
-  const { data, sum } = await deviceActionMap.get(activeTabKey.value)?.get({ ...deviceActionParams, deviceId: deviceState.deviceId } as any)
-  state.loading = false
-  state.deviceMediaList = data
-  console.log(state.deviceMediaList)
-}
-
-const ok = () => {
-  validate().then(async () => {
-    deviceState.deviceStreamId = deviceState.app + '/' + deviceState.stream
-    await SpaceController.addDevice(deviceState as unknown as CVS.device)
-    closeModal()
-    tableProDom.value.reload()
-  }).catch(() => {})
-}
-
-// 获取设备通道
-const getDeviceChannel = async () => {
-  gbState.channelList = (await SpaceController.deviceChannel(deviceState.deviceId))!
-}
-
-const getGbPull = async () => {
-  gbState.gbPullList = (await SpaceController.gbPull(deviceState.deviceId))!
-}
-
-const getNotGbPush = async () => {
-  gbState.pushList = (await SpaceController.notGBPush(deviceState.deviceId))!
-}
-
-const getNotGbPull = async () => {
-  gbState.pullList = await SpaceController.notGbPull(deviceState.deviceId)
-}
-
-const baseInfo = (record: CVS.device) => {
-  activeTabKey.value = 'base'
-  nextTick(() => {
-    state.activeVisible = true
-    console.log(record)
-    resetFields({ ...record })
-  })
-}
-
-const recordParty = async (record: CVS.device) => {
-  state.activeVisible = true
-  resetFields({ ...record })
-  deviceActionParams.deviceId = record.deviceId as unknown as string
-  activeTabKey.value = 'record'
-  getMedia()
-}
-
-const thumbParty = (record: CVS.device) => {
-  state.activeVisible = true
-  resetFields({ ...record })
-  deviceActionParams.deviceId = record.deviceId as unknown as string
-  activeTabKey.value = 'thumb'
-  getMedia()
-}
-
-const arAnalysis = (record: CVS.device) => {
-  state.activeVisible = true
-  resetFields({ ...record })
-  deviceActionParams.deviceId = record.deviceId as unknown as string
-  activeTabKey.value = 'ai'
-  getMedia()
-}
-
-const onChangeSpace = (spaceId: string) => {
-  deviceState.spaceId = spaceId as any
-  deviceState.type = spaceId ? state.spaceList.find(item => item.spaceId === spaceId)!.type : 'RTSP'
-  nextTick(() => {
-    tableProDom.value.reload({ page: 1 })
-  })
-}
-
-const closeModal = () => state.visible = false
-
-const openModal = () => {
-  resetFields({})
-  operator.value = 'add'
-  state.activeVisible = true
-}
-
-const closeRealView = () => {
-  state.activeVisible = false
-  operator.value = 'preview'
-}
-
-const getSpaceList = async () => {
-  state.spaceList = await SpaceController.list()
-  deviceState.spaceId = _spaceId as any
-}
-
-const getAiList = async () => {
-  state.aiList = await OperatorController.list()
-}
-
-const search = () => tableProDom.value.reload()
-
-onMounted(async () => {
-  getAiList()
-  await getSpaceList()
-  if (_spaceId) deviceState.spaceId = Number(_spaceId)
-})
-</script>
-<style lang='less' scoped >
-
-.media-card {
-  width: 100%;
-  height: 150px !important;
-  background-color: #000;
-  video {
-    width: 100%;
-    height: 150px !important;
-  }
-  .craete-time {
-    margin-top: 5px;
-  }
-}
-</style>
-
-  getAiList()
+<template>
+<a-card>
+  <template #title >
+    <a-row>
+      <a-col style="font-size: 20px;margin-right: 10px;" >设备管理</a-col>
+      <a-col >
+        <a-select
+          :value="deviceState.spaceId"
+          style="width: 170px;"
+          @change="onChangeSpace"
+          placeholder="请选择空间"
+          allowClear
+        >
+          <a-select-option
+            :key="index"
+            :value="item.spaceId"
+            v-for="(item, index) in state.spaceList"
+          >
+            {{item.spaceName}}
+          </a-select-option>
+        </a-select>
+    </a-col>
+    </a-row>
+  </template>
+  <table-pro
+    ref="tableProDom"
+    :service="SpaceController.devicePage"
+    :serviceParams="{deviceName, spaceId: deviceState.spaceId}"
+    :columns="columns"
+    @add="openModal"
+  >
+    <template #search >
+      <a-space><InputTsx placeholder="请输入设备名称进行搜索" v-model:value="deviceName" /> <a-button type="primary" @click="search">搜索</a-button> </a-space>
+    </template>
+
+    <template #render="{column, record}" >
+
+      <template v-if="column.key === 'status'" >
+        <div style="display: flex;align-items: center;" >
+          <div :style="{width: '8px',height: '8px',borderRadius: '50%', backgroundColor:SpaceController.statusMap.get(record.status)?.color, marginRight: '5px' }" ></div>
+          <div>{{SpaceController.deviceStatusMap.get(record.status)?.label}}</div>
+        </div>
+      </template>
+
+      <template v-if="column.key === 'deviceId'">
+        <a @click="baseInfo(record)">{{record.deviceId}}</a>
+      </template>
+
+      <template v-if="column.key === 'type'">
+        <a-tag>{{SpaceController.type.find((item)=>{
+          return item.key == record.type
+        })?.label}}</a-tag>
+      </template>
+
+      <template v-if="column.key === 'action'" >
+        <a-space>
+          <a @click="baseInfo(record)">基本信息</a>
+          <a @click="recordParty(record)">录像回放</a>
+          <a @click="thumbParty(record)">截图查看</a>
+          <a @click="arAnalysis(record)">AI分析</a>
+        </a-space>
+      </template>
+    </template>
+
+  </table-pro>
+</a-card>
+
+<RealView
+  :open="state.activeVisible"
+  @cancel="closeRealView"
+  :tab-key="activeTabKey"
+  :tabs-list="deviceTabs"
+  @tab-chang="key => onTabChange(key)"
+>
+    <!--  个人基础信息 -->
+    <div v-if="activeTabKey === 'base'" >
+      <a-card title="基本信息" >
+        <template #extra >
+          <a-button type="primary" @click="ok" >提交修改</a-button>
+        </template>
+        <a-form  style="width: 100%;" :labelCol="{span: 3}" :wrapperCol="{span: 14}" >
+      <a-form-item label="设备名称" v-bind="validateInfos.deviceName"  >
+        <InputTsx allowClear placeholder="请输入设备名称" v-model:value="deviceState.deviceName" />
+      </a-form-item>
+      <a-form-item  label="app" v-bind="validateInfos.app"  >
+        <InputTsx allowClear placeholder="请输入英文、数字组成的app" v-model:value="deviceState.app" />
+      </a-form-item>
+      <a-form-item  label="stream" v-bind="validateInfos.stream"  >
+        <InputTsx allowClear placeholder="请输入英文、数字组成的stream" v-model:value="deviceState.stream" />
+      </a-form-item>
+      <a-form-item  label="空间名称" v-bind="validateInfos.stream"  >
+        <a-select
+          :value="spaceId"
+          style="width: 170px;"
+          @change="onChangeSpace"
+          placeholder="请选择空间"
+        >
+          <a-select-option
+            :key="index"
+            :value="item.spaceId"
+            v-for="(item, index) in state.spaceList"
+          >
+            {{item.spaceName}}
+          </a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item  label="设备类型"  >
+        <a-radio-group v-model:value="deviceState.type" button-style="solid">
+          <a-radio-button
+            v-for="item in SpaceController.type"
+            :key="item.key"
+            :value="item.value"
+          >
+            {{item.label}}
+          </a-radio-button>
+        </a-radio-group>
+      </a-form-item>
+      <a-form-item  label="设备地址"  >
+        <InputTsx allowClear placeholder="请输入设备地址" v-model:value="deviceState.gisName" />
+      </a-form-item>
+      <a-form-item  label="拉流地址" v-if="['PULL_RTMP', 'PULL_RTSP'].includes(deviceState.type)" >
+        <InputTsx v-model:value="deviceState.pullPath" placeholder="请输入拉流地址" />
+      </a-form-item>
+      <span v-if="deviceState.type === 'GB28181'">
+        <a-form-item  label="国标Id"  >
+          <InputTsx v-model:value="deviceState.gbConfig.gbId" placeholder="请输入拉流地址" />
+        </a-form-item>
+        <a-form-item  label="用户名"  >
+          <InputTsx v-model:value="deviceState.gbConfig.username" placeholder="请输入用户名"/>
+        </a-form-item>
+        <a-form-item  label="用户密码"  >
+          <InputTsx v-model:value="deviceState.gbConfig.password" placeholder="请输入用户密码" />
+        </a-form-item>
+        <a-form-item  label="国标平台"  >
+          <a-radio-group v-model:value="deviceState.gbConfig.platform" button-style="solid">
+          <a-radio-button value="IPC">IPC</a-radio-button>
+          <a-radio-button value="NVR">NVR</a-radio-button>
+          </a-radio-group>
+        </a-form-item>
+      </span>
+      <a-form-item  label="设备描述"  >
+        <a-textarea v-model:value="deviceState.description" placeholder="请输入设备描述" :rows="4" />
+      </a-form-item>
+        </a-form>
+      </a-card>
+      <a-card v-if="operator === 'preview'" title="推播流地址" style="margin-top: 20px;" >
+        <!-- 非国标设备 -->
+        <a-row :gutter="[8, 18]" >
+          <a-col :span="24">
+            <a-row>
+              <a-col :span="4">
+                请选择转码模版:
+              </a-col>
+              <a-col>
+                  <a-space>
+                    <a-select style="width: 170px;" >
+                      <a-select-option key="2" >原始流</a-select-option>
+                    </a-select>
+                    <a-button>获取最新地址</a-button>
+                  </a-space>
+              </a-col>
+            </a-row>
+          </a-col>
+          <!-- 推流地址 -->
+          <a-col :span="24">
+            <a-row>
+              <a-col :span="2" >推流地址</a-col>
+              <a-col>
+                <div v-for="item in gbState.pushList" :key="item" >{{item}}</div>
+              </a-col>
+            </a-row>
+          </a-col>
+          <!-- 播放地址 -->
+          <a-col :span="24">
+            <a-row>
+              <a-col :span="2" >播放地址</a-col>
+              <a-col >
+                <div v-for="item in gbState.pullList" :key="item" >{{item}}</div>
+              </a-col>
+            </a-row>
+          </a-col>
+        </a-row>
+      </a-card>
+    </div>
+      <a-card style="background-color: #fff;width: 100%;" v-if="activeTabKey !== 'base'" >
+        <a-space >
+          <a-range-picker v-model:value="deviceActionParams.times" />
+          <a-select style="width: 170px;"  v-if="activeTabKey === 'record'" v-model:value="deviceActionParams.recordFormat" >
+            <a-select-option v-for="item in SpaceController.recordFormat" :key="item" :value="item" >
+              {{item}}
+            </a-select-option>
+          </a-select>
+          <a-select style="width: 170px;"  v-if="activeTabKey === 'ai'" v-model:value="deviceActionParams.aiId" >
+            <a-select-option v-for="item in state.aiList" :key="item.aiId" :value="item.aiId" >
+              {{item.aiName}}
+            </a-select-option>
+          </a-select>
+          <a-button type="primary" @click="searchDevice">搜索</a-button>
+        </a-space>
+      </a-card>
+
+      <!-- 视频 图片区域 -->
+      <a-spin :spinning="state.loading" v-if="activeTabKey !== 'base'">
+          <a-row :gutter="[8, 8]" style="margin-top: 20px;background-color: #fff;padding: 18px;padding-bottom: 38px;" >
+            <a-col :span="4" v-for="item in state.deviceMediaList" :key="item" >
+              <div class="media-card">
+                  <video
+                  v-if="activeTabKey === 'record'"
+                  controls
+                    :src="item.defaultUrl"
+                  ></video>
+                <div v-else-if="activeTabKey === 'thumb'" >
+                  <a-image
+                    width="100%"
+                    height="150px"
+                    style="object-fit: cover;display: block;"
+                    :src="item.defaultUrl"
+                  />
+                </div>
+                <div v-else-if="activeTabKey === 'ai'" style="height: 150px;" >
+                  <a-image width="100%" height="150px"  style="object-fit: cover;display: block;" :src="item.defaultUrl"></a-image>
+                </div>
+                <div class="craete-time">{{dayjs(item.ts).format('YYYY/MM/DD HH:mm:ss')}}</div>
+              </div>
+            </a-col>
+          </a-row>
+      </a-spin>
+
+</RealView>
+
+</template>
+<script lang='ts' setup >
+import { InputTsx } from '@/components/MicroComponents/index'
+import { reactive, onMounted, nextTick, ref, watch } from 'vue'
+import { Form } from 'ant-design-vue'
+import { OperatorController, SpaceController } from '@/controller'
+import { useRoute } from 'vue-router'
+import dayjs from 'dayjs'
+import { RealView } from '@/components/RealView/index'
+
+const columns = [
+  {
+    title: '设备ID',
+    dataIndex: 'deviceId',
+    key: 'deviceId'
+  },
+  {
+    title: '设备名称',
+    dataIndex: 'deviceName',
+    key: 'deviceName'
+  },
+  {
+    title: '空间类型',
+    dataIndex: 'type',
+    key: 'type'
+  },
+  {
+    title: '描述',
+    dataIndex: 'description',
+    key: 'description'
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    key: 'status'
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    key: 'action'
+  }
+]
+
+const _spaceId = useRoute().query.spaceId
+
+const useForm = Form.useForm
+
+const spaceId = ref(null)
+
+const deviceName = ref(null)
+
+const tableProDom = ref()
+
+const activeTabKey = ref<'base' | 'record' | 'thumb' | 'ai'>('base')
+
+const operator = ref<'add' | 'preview'>('add')
+
+const deviceTabs = [
+  { key: 'base', tab: '基础信息' },
+  { key: 'record', tab: '录像回放' },
+  { key: 'thumb', tab: '截图查看' },
+  { key: 'ai', tab: 'AI分析' }
+]
+
+const recordColumn = [
+  {
+    title: '录制格式',
+    dataIndex: 'extra',
+    key: 'extra'
+  },
+  {
+    title: '录制时间',
+    dataIndex: 'ts',
+    key: 'ts'
+  },
+  {
+    title: '可播放地址',
+    dataIndex: 'defaultUrl',
+    key: 'defaultUrl'
+  }
+]
+
+const thumbColumn = [
+  {
+    title: '截图地址',
+    dataIndex: 'defaultUrl',
+    key: 'defaultUrl'
+  },
+  {
+    title: '截图时间',
+    dataIndex: 'ts',
+    key: 'ts'
+  }
+]
+
+const aiColumn = [
+  {
+    title: '算子模型id',
+    dataIndex: 'extra',
+    key: 'extra'
+  },
+  {
+    title: '结果地址',
+    dataIndex: 'defaultUrl',
+    key: 'defaultUrl'
+  },
+  {
+    title: '时间',
+    dataIndex: 'ts',
+    key: 'ts'
+  }
+]
+
+const deviceActionMap = new Map([
+  ['record', { get: SpaceController.deviceRecordById, column: recordColumn }],
+  ['thumb', { get: SpaceController.deviceThumbById, column: thumbColumn }],
+  ['ai', { get: SpaceController.deviceAiRetById, column: aiColumn }]
+])
+
+const state = reactive<{
+  visible: boolean,
+  activeVisible: boolean,
+  spaceList: CVS.space[]
+  deviceMediaList: any[],
+  aiList: CVS.Operator[]
+  loading: boolean
+}>({
+  visible: false,
+  activeVisible: false,
+  spaceList: [],
+  deviceMediaList: [],
+  aiList: [],
+  loading: false
+})
+
+const gbState = reactive<{
+  channelList: string[]
+  pullList: string[],
+  gbPullList: string[]
+  pushList: string[]
+}>({
+  channelList: [],
+  pullList: [],
+  gbPullList: [],
+  pushList: []
+})
+
+const deviceActionParams = reactive({
+  times: [],
+  startTs: '',
+  endTs: '',
+  recordFormat: '',
+  deviceId: '',
+  page: 1,
+  pageSize: 10,
+  aiId: null
+})
+
+const deviceState = reactive({
+  deviceId: '',
+  spaceId: null,
+  deviceName: '',
+  app: '',
+  stream: '',
+  description: '',
+  deviceStreamId: '',
+  gisLongitude: '',
+  gisLatitude: '',
+  gisName: '',
+  type: 'RTMP',
+  pullPath: '',
+  gbConfig: {
+    gbId: '',
+    username: '',
+    password: '',
+    platform: 'IPC'
+  }
+})
+
+watch(
+  () => activeTabKey.value,
+  () => {
+    activeTabKey.value !== 'base' && getMedia()
+  }
+)
+
+watch(
+  () => deviceActionParams.times,
+  () => {
+    deviceActionParams.startTs = dayjs(deviceActionParams.times[0]).unix() as unknown as string
+    deviceActionParams.endTs = dayjs(deviceActionParams.times[0]).unix() as unknown as string
+  }
+)
+
+watch(
+  () => deviceState.deviceId,
+  () => {
+    if (deviceState.gbConfig && deviceState.gbConfig.gbId) {
+      getDeviceChannel()
+      getGbPull()
+    } else {
+      getNotGbPush()
+      getNotGbPull()
+    }
+  }
+)
+
+const { resetFields, validate, validateInfos } = useForm(deviceState, {
+  deviceName: [{ required: true, message: '请填写设备名称' }],
+  app: [{ required: true, message: '请填写app名称' }],
+  stream: [{ required: true, message: '请填写stream名称' }]
+})
+
+const searchDevice = () => getMedia()
+
+const onTabChange = (key: 'record' | 'thumb' | 'ai') => activeTabKey.value = key
+
+const getMedia = async () => {
+  state.loading = true
+  const { data, sum } = await deviceActionMap.get(activeTabKey.value)?.get({ ...deviceActionParams, deviceId: deviceState.deviceId } as any)
+  state.loading = false
+  state.deviceMediaList = data
+  console.log(state.deviceMediaList)
+}
+
+const ok = () => {
+  validate().then(async () => {
+    deviceState.deviceStreamId = deviceState.app + '/' + deviceState.stream
+    await SpaceController.addDevice(deviceState as unknown as CVS.device)
+    closeModal()
+    tableProDom.value.reload()
+  }).catch(() => {})
+}
+
+// 获取设备通道
+const getDeviceChannel = async () => {
+  gbState.channelList = (await SpaceController.deviceChannel(deviceState.deviceId))!
+}
+
+const getGbPull = async () => {
+  gbState.gbPullList = (await SpaceController.gbPull(deviceState.deviceId))!
+}
+
+const getNotGbPush = async () => {
+  gbState.pushList = (await SpaceController.notGBPush(deviceState.deviceId))!
+}
+
+const getNotGbPull = async () => {
+  gbState.pullList = await SpaceController.notGbPull(deviceState.deviceId)
+}
+
+const baseInfo = (record: CVS.device) => {
+  activeTabKey.value = 'base'
+  nextTick(() => {
+    state.activeVisible = true
+    console.log(record)
+    resetFields({ ...record })
+  })
+}
+
+const recordParty = async (record: CVS.device) => {
+  state.activeVisible = true
+  resetFields({ ...record })
+  deviceActionParams.deviceId = record.deviceId as unknown as string
+  activeTabKey.value = 'record'
+  getMedia()
+}
+
+const thumbParty = (record: CVS.device) => {
+  state.activeVisible = true
+  resetFields({ ...record })
+  deviceActionParams.deviceId = record.deviceId as unknown as string
+  activeTabKey.value = 'thumb'
+  getMedia()
+}
+
+const arAnalysis = (record: CVS.device) => {
+  state.activeVisible = true
+  resetFields({ ...record })
+  deviceActionParams.deviceId = record.deviceId as unknown as string
+  activeTabKey.value = 'ai'
+  getMedia()
+}
+
+const onChangeSpace = (spaceId: string) => {
+  deviceState.spaceId = spaceId as any
+  deviceState.type = spaceId ? state.spaceList.find(item => item.spaceId === spaceId)!.type : 'RTSP'
+  nextTick(() => {
+    tableProDom.value.reload({ page: 1 })
+  })
+}
+
+const closeModal = () => state.visible = false
+
+const openModal = () => {
+  resetFields({})
+  operator.value = 'add'
+  state.activeVisible = true
+}
+
+const closeRealView = () => {
+  state.activeVisible = false
+  operator.value = 'preview'
+}
+
+const getSpaceList = async () => {
+  state.spaceList = await SpaceController.list()
+  deviceState.spaceId = _spaceId as any
+}
+
+const getAiList = async () => {
+  state.aiList = await OperatorController.list()
+}
+
+const search = () => tableProDom.value.reload()
+
+onMounted(async () => {
+  getAiList()
+  await getSpaceList()
+  if (_spaceId) deviceState.spaceId = Number(_spaceId)
+})
+</script>
+<style lang='less' scoped >
+
+.media-card {
+  width: 100%;
+  height: 150px !important;
+  background-color: #000;
+  video {
+    width: 100%;
+    height: 150px !important;
+  }
+  .craete-time {
+    margin-top: 5px;
+  }
+}
+</style>
+
+  getAiList()