Casper Dai hace 1 año
padre
commit
58cb0cedcb

+ 1 - 0
.env

@@ -55,6 +55,7 @@ VUE_APP_MQTT_PASSWORD = 'inspur-frontend'
 # camera
 # {protocol}://{gateway | host}{proxy}
 VUE_APP_CAMERA_PROXY = '/prod-api/websocket'
+VUE_APP_CAMERA_EVS_PROXY = '/prod-api/dahuaEVSWebsocket'
 VUE_APP_CAMERA_RECORD_PROXY = '/prod-api/playBackWebSocket'
 
 # user default password

+ 10 - 1
src/api/external.js

@@ -357,7 +357,16 @@ export function deleteCamera (id) {
 
 // 录像
 // 查询SD卡的录像
-export function getSDRecords ({ identifier, ...params }) {
+export function getSDRecords ({ identifier, throughEvs, ...params }) {
+  if (throughEvs === 1) {
+    return request({
+      url: `/dahua/evs/${identifier}/queryRecord`,
+      method: 'GET',
+      params: {
+        ...params
+      }
+    })
+  }
   return request({
     url: `/camera/${identifier}/queryRecord`,
     method: 'GET',

+ 7 - 1
src/components/service/external/camera/CameraDetail/index.vue

@@ -75,6 +75,7 @@
         />
       </div>
       <people-counting
+        v-if="isTraffic"
         class="c-sibling-item--v far c-camera-detail__traffic has-padding"
         :camera="camera"
       />
@@ -92,6 +93,7 @@ import {
 } from '@/api/camera'
 import {
   GATEWAY_CAMERA,
+  GATEWAY_CAMERA_EVS,
   Camera
 } from '@/constant'
 import playerMixin from '../../player'
@@ -150,7 +152,11 @@ export default {
       isOnline(this.camera.identifier, { custom: true }).then(
         ({ data }) => {
           if (data) {
-            this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+            if (this.camera.throughEvs) {
+              this.playUrl(`${GATEWAY_CAMERA_EVS}/realplay/${this.camera.identifier}`)
+            } else {
+              this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+            }
           } else {
             this.loading = false
             this.$message({

+ 12 - 3
src/components/service/external/camera/CameraPlayer/index.vue

@@ -32,7 +32,9 @@
       v-if="controls"
       class="l-flex--row c-footer"
     >
-      <div class="l-flex__auto c-sibling-item u-ellipsis">{{ camera.remark }}</div>
+      <div class="l-flex__auto c-sibling-item u-ellipsis">
+        {{ camera.remark }}
+      </div>
       <slot />
       <div
         v-if="online && settingsShow"
@@ -85,7 +87,10 @@
 
 <script>
 import { mapGetters } from 'vuex'
-import { GATEWAY_CAMERA } from '@/constant'
+import {
+  GATEWAY_CAMERA,
+  GATEWAY_CAMERA_EVS
+} from '@/constant'
 import {
   isOnline,
   getVideoInfo,
@@ -166,7 +171,11 @@ export default {
           this.online = data
           if (data) {
             this.$nextTick(() => {
-              this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+              if (this.camera.throughEvs) {
+                this.playUrl(`${GATEWAY_CAMERA_EVS}/realplay/${this.camera.identifier}`)
+              } else {
+                this.playUrl(`${GATEWAY_CAMERA}/${this.camera.identifier}?authorization=${this.token}`)
+              }
             })
           } else {
             this.hideSettingsMenu()

+ 2 - 0
src/constant.js

@@ -7,6 +7,8 @@ export const GATEWAY_WS = `${protocol === 'https:' ? 'wss:' : 'ws:'}//${host}`
 
 export const GATEWAY_CAMERA = `${GATEWAY_WS}${process.env.VUE_APP_CAMERA_PROXY}`
 
+export const GATEWAY_CAMERA_EVS = `${GATEWAY_WS}${process.env.VUE_APP_CAMERA_EVS_PROXY}`
+
 export const GATEWAY_CAMERA_RECORD = `${GATEWAY_WS}${process.env.VUE_APP_CAMERA_RECORD_PROXY}`
 
 export const ONE_DAY = 3600 * 24 * 1000

+ 20 - 3
src/views/device/detail/components/DeviceExternal/components/Record/components/PlayAxisDialog/components/PlayAxis.vue

@@ -51,7 +51,10 @@
         />
       </div>
       <div class="c-table__filters o-time-axis__selecttime">
-        <div class="o-time-axis__download">
+        <div
+          v-if="!camera.throughEvs"
+          class="o-time-axis__download"
+        >
           <div
             class="o-button"
             @click="onStartDownload"
@@ -94,7 +97,10 @@
 
 <script>
 import { mapGetters } from 'vuex'
-import { GATEWAY_CAMERA_RECORD } from '@/constant'
+import {
+  GATEWAY_CAMERA_RECORD,
+  GATEWAY_CAMERA_EVS
+} from '@/constant'
 import { parseTime } from '@/utils'
 import { isOnline } from '@/api/camera'
 import { downloadRecord } from '@/api/external'
@@ -158,6 +164,13 @@ export default {
     this.createPlayer()
     this.disanableWidth = (60 - new Date().getMinutes()) / 60 * 100
     this.end = `${(Number(parseTime(new Date(), '{h}:00').split(':')[0]) + 1).toString().padStart(2, '0')}:00`
+    this.endTimer = setInterval(() => {
+      this.end = `${(Number(parseTime(new Date(), '{h}:00').split(':')[0]) + 1).toString().padStart(2, '0')}:00`
+    }, 60000)
+  },
+  destroyed () {
+    clearInterval(this.endTimer)
+    clearInterval(this.$playTimer)
   },
   methods: {
     onStartDownload () {
@@ -241,7 +254,11 @@ export default {
           this.online = data
           if (data) {
             this.$nextTick(() => {
-              this.playUrl(`${GATEWAY_CAMERA_RECORD}/${this.camera.identifier}/${startTime.replace(/-|:| /g, '')}/${endTime.replace(/-|:| /g, '')}`)
+              if (this.camera.throughEvs) {
+                this.playUrl(`${GATEWAY_CAMERA_EVS}/playBack/${this.camera.identifier}/${startTime.replace(/-|:| /g, '')}/${endTime.replace(/-|:| /g, '')}`)
+              } else {
+                this.playUrl(`${GATEWAY_CAMERA_RECORD}/${this.camera.identifier}/${startTime.replace(/-|:| /g, '')}/${endTime.replace(/-|:| /g, '')}`)
+              }
               // this.playUrl(`${GATEWAY_CAMERA_RECORD}/${this.camera.identifier}/20230307150000/20230307150010`)
               // this.onWebsocket(`${GATEWAY_CAMERA_RECORD}/${this.camera.identifier}/20230307150000/20230307150010`)
             })

+ 34 - 21
src/views/device/detail/components/DeviceExternal/components/Record/index.vue

@@ -25,7 +25,9 @@
         >
           <img :src="index === active ? require('@/assets/icon_camera_white.svg') : require('@/assets/icon_camera.svg')">
           <div class="l-flex__fill">
-            <div class="c-camera__name">{{ camera.identifier }}</div>
+            <div class="c-camera__name">
+              {{ camera.identifier }}
+            </div>
             <div
               class="c-camera__bound"
               :style="{color: index === active ? '#fff':'#4293FE'}"
@@ -41,7 +43,9 @@
           <div
             class="c-camera__tip"
             :style="{ backgroundColor: camera.onlineStatus === 1 ? '#04A681' : '#E51414' }"
-          >{{ camera.onlineStatus === 1 ? '在线' : '离线' }}</div>
+          >
+            {{ camera.onlineStatus === 1 ? '在线' : '离线' }}
+          </div>
         </div>
       </div>
     </div>
@@ -53,6 +57,7 @@
       >
         <grid-table-item v-slot="item">
           <record-player
+            v-if="!throughEvs"
             :key="item.id"
             :record="item"
             @del="onDeleteRecord"
@@ -69,7 +74,9 @@
     >
       <template #header>
         <div class="l-flex--row u-color--info">
-          展示的为结束时间往前&nbsp;<span class="u-color--blue u-font-size--lg">6</span>&nbsp;小时内的录像
+          展示的为结束时间往前&nbsp;<span class="u-color--blue u-font-size--lg">
+            6
+          </span>&nbsp;小时内的录像
         </div>
       </template>
     </table-dialog>
@@ -109,20 +116,32 @@ export default {
   data () {
     return {
       identifier: '',
+      throughEvs: '',
       searchName: '',
       active: 0,
-      recordSchema: {
+      checked: false,
+      cameraObj: {}
+    }
+  },
+  computed: {
+    recordSchema () {
+      return {
         autoRefresh: true,
-        pageSize: 6,
+        pageSize: this.throughEvs ? 999 : 6,
         list: this.getRecords,
-        buttons: [
-          { type: 'add', label: '新增录像', on: this.onViewSD },
-          { type: '', label: '实时回放', on: this.onPlayBack }
-        ]
-      },
-      checked: false,
-      // SD卡数据
-      sdRecordSchema: {
+        buttons: this.throughEvs
+          ? [
+            { type: '', label: '实时回放', on: this.onPlayBack }
+          ]
+          : [
+            { type: 'add', label: '新增录像', on: this.onViewSD },
+            { type: '', label: '实时回放', on: this.onPlayBack }
+          ]
+      }
+    },
+    // SD卡数据
+    sdRecordSchema () {
+      return {
         nonPagination: true,
         list: this.getSDRecords,
         filters: [
@@ -143,14 +162,6 @@ export default {
             { label: '下载', on: this.onStartDownload }
           ] }
         ]
-      },
-      cameraObj: {}
-    }
-  },
-  computed: {
-    pickerOptions () {
-      return {
-        disabledDate: this.isDisableDate
       }
     }
   },
@@ -159,6 +170,7 @@ export default {
       this.cameraObj = this.cameras[this.active]
       this.cameraId = this.cameras[this.active].id
       this.identifier = this.cameras[this.active].identifier
+      this.throughEvs = this.cameras[this.active].throughEvs
       this.$refs.table?.pageTo(1)
     }
   },
@@ -231,6 +243,7 @@ export default {
       startTime.setHours(startTime.getHours() - 1)
       return getSDRecords({
         identifier: this.identifier,
+        throughEvs: this.throughEvs,
         startTime: parseTime(startTime, '{y}-{m}-{d} {h}:{i}:{s}'),
         endTime: parseTime(time, '{y}-{m}-{d} {h}:{i}:{s}')
       })

+ 104 - 19
src/views/device/detail/components/DeviceExternal/components/Traffic.vue

@@ -1,21 +1,58 @@
 <template>
-  <div class="l-flex--col">
-    <grid-table
-      ref="table"
-      :schema="schema"
-      size="large"
-    >
-      <grid-table-item v-slot="item">
-        <camera-player
-          v-if="isActivated"
-          :key="item.identifier"
-          :camera="item"
-          autoplay
-          controls
-          @fullscreen="onFullScreen(item)"
-        />
-      </grid-table-item>
-    </grid-table>
+  <div class="l-flex--col o-tabs">
+    <template v-if="isThroughEvs">
+      <el-tabs
+        v-model="active"
+        type="card"
+        class="l-flex l-flex--col l-flex__auto"
+      >
+        <el-tab-pane
+          label="ʵʱ»­Ãæ"
+          name="real"
+        >
+          <grid-table
+            ref="table"
+            :schema="schema"
+            size="large"
+          >
+            <grid-table-item v-slot="item">
+              <camera-player
+                v-if="isActivated"
+                :key="item.identifier"
+                :camera="item"
+                autoplay
+                controls
+                @fullscreen="onFullScreen(item)"
+              />
+            </grid-table-item>
+          </grid-table>
+        </el-tab-pane>
+        <el-tab-pane
+          label="¼Ïñ»Ø·Å"
+          name="record"
+        >
+          <camera-record :cameras="cameras" />
+        </el-tab-pane>
+      </el-tabs>
+    </template>
+    <template v-else>
+      <grid-table
+        ref="table"
+        :schema="schema"
+        size="large"
+      >
+        <grid-table-item v-slot="item">
+          <camera-player
+            v-if="isActivated"
+            :key="item.identifier"
+            :camera="item"
+            autoplay
+            controls
+            @fullscreen="onFullScreen(item)"
+          />
+        </grid-table-item>
+      </grid-table>
+    </template>
     <camera-dialog
       ref="cameraDialog"
       @open="onOpen"
@@ -27,9 +64,13 @@
 <script>
 import { ThirdPartyDevice } from '@/constant'
 import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
+import CameraRecord from './Record'
 
 export default {
   name: 'DeviceTrafficCamera',
+  components: {
+    CameraRecord
+  },
   props: {
     device: {
       type: Object,
@@ -42,13 +83,24 @@ export default {
         nonPagination: true,
         list: this.getThirdPartyDevicesByThirdPartyDevice
       },
-      isActivated: true
+      isActivated: true,
+      active: 'real',
+      cameras: [],
+      isThroughEvs: false
     }
   },
   methods: {
     getThirdPartyDevicesByThirdPartyDevice () {
       return getThirdPartyDevicesByThirdPartyDevice(this.device.id, [ThirdPartyDevice.TRAFFIC_CAMERA]).then(({ data }) => {
-        return { data: data.filter(({ instance }) => instance).map(({ instance }) => instance) }
+        data = data.filter(({ instance }) => instance).map(({ instance }) => instance)
+        this.cameras = data
+        for (let i = 0; i < data.length; i++) {
+          if (data[i].throughEvs === 1) {
+            this.isThroughEvs = true
+            break
+          }
+        }
+        return { data }
       })
     },
     onFullScreen (camera) {
@@ -63,3 +115,36 @@ export default {
   }
 }
 </script>
+<style lang="scss">
+.o-tabs {
+  .el-tabs--card > .el-tabs__header {
+    border: none;
+  }
+  .el-tabs--card > .el-tabs__header .el-tabs__nav {
+    background-color: #f4f7fb;
+    border: none;
+    padding: 4px;
+    border-radius: 5px;
+  }
+  .el-tabs__item.is-active {
+    color: #1c5cb0;
+    background-color: #fff;
+  }
+  .el-tabs--card > .el-tabs__header .el-tabs__item {
+    font-weight: bold;
+    color: #8e929c;
+    border: none;
+    height: 24px;
+    line-height: 24px;
+  }
+  .el-tabs__content {
+    flex: 1 1 auto;
+    display: flex;
+  }
+  .el-tab-pane {
+    flex: 1 1 auto;
+    display: flex;
+    min-height: 400px;
+  }
+}
+</style>

+ 10 - 1
src/views/external/camera/index.vue

@@ -62,6 +62,11 @@ export default {
           { prop: 'manufacturerName', label: '厂商' },
           { prop: 'model', label: '型号' },
           { prop: 'identifier', label: '唯一标识' },
+          { label: '视频服务器', type: 'tag', render: ({ throughEvs }) => throughEvs
+            ? { type: 'success', label: '是' }
+            : { type: 'danger', label: '否' } },
+          { prop: 'evsChannel', label: '服务器通道号' },
+          { prop: 'evsLocation', label: '服务器编号' },
           { prop: 'remark', label: '备注', render: (data, h) => h('edit-input', {
             props: {
               value: `${data.remark}`
@@ -75,15 +80,19 @@ export default {
             ? { type: 'success', label: '已使用' }
             : { type: 'primary', label: '未使用' } },
           { type: 'invoke', render: [
+            { label: '编辑', on: this.onEdit },
             { label: '查看', allow: ({ onlineStatus }) => onlineStatus, on: this.onView },
             { label: '所属网点', allow: ({ bound }) => bound, on: this.onViewMesh },
             { label: '删除', allow: ({ bound }) => !bound, on: this.onDel }
-          ], width: 180 }
+          ], width: 220 }
         ]
       }
     }
   },
   methods: {
+    onEdit (asset) {
+      this.$refs.cameraConfigDialog.show(this.type, asset)
+    },
     getManufacturersByType () {
       return getManufacturersByType(CameraToThirdPartyMap[this.type])
     },

+ 105 - 34
src/views/external/components/CameraConfigDialog.vue

@@ -1,20 +1,25 @@
 <template>
   <confirm-dialog
     ref="dialog"
-    title="新增摄像头"
+    :title="dialogTitle"
     @confirm="onConfirm"
   >
     <div class="c-grid-form u-align-self--center">
       <span class="c-grid-form__label u-required">
         厂商
       </span>
-      <schema-select
-        ref="manufacturer"
+      <el-select
         v-model="item.manufacturerKey"
         class="u-width"
         placeholder="请选择厂商"
-        :schema="manufacturerSelectSchema"
-      />
+      >
+        <el-option
+          v-for="(option, index) in manufacturerOptions"
+          :key="index"
+          :value="option.manufacturerKey"
+          :label="option.manufacturerName"
+        />
+      </el-select>
       <span class="c-grid-form__label u-required">
         型号
       </span>
@@ -33,17 +38,51 @@
         maxlength="30"
         clearable
       />
+      <template v-if="!item.id">
+        <span class="c-grid-form__label u-required">
+          账号
+        </span>
+        <el-input v-model.trim="item.username" />
+        <span class="c-grid-form__label u-required">
+          密码
+        </span>
+        <el-input
+          v-model.trim="item.password"
+          class="u-password"
+        />
+      </template>
       <span class="c-grid-form__label u-required">
-        账号
-      </span>
-      <el-input v-model.trim="item.username" />
-      <span class="c-grid-form__label u-required">
-        密码
+        通过视频服务器
       </span>
-      <el-input
-        v-model.trim="item.password"
-        class="u-password"
-      />
+      <el-select
+        v-model="item.throughEvs"
+        placeholder="请选择"
+      >
+        <el-option
+          label="是"
+          value="1"
+        />
+        <el-option
+          label="否"
+          value="0"
+        />
+      </el-select>
+      <template v-if="item.throughEvs == '1'">
+        <span class="c-grid-form__label u-required">
+          服务器通道号
+        </span>
+        <el-input-number
+          v-model="item.evsChannel"
+          controls-position="right"
+        />
+        <span class="c-grid-form__label u-required">
+          服务器编号
+        </span>
+        <el-input-number
+          v-model="item.evsLocation"
+          controls-position="right"
+        />
+      </template>
       <span class="c-grid-form__label">
         备注
       </span>
@@ -69,32 +108,56 @@ import {
 } from '@/constant'
 import {
   getManufacturersByType,
-  addCamera
+  addCamera,
+  updateCamera
 } from '@/api/external'
 
 export default {
   name: 'CameraConfigDialog',
   data () {
     return {
-      manufacturerSelectSchema: {
-        remote: this.getManufacturersByType,
-        value: 'manufacturerKey',
-        label: 'manufacturerName'
-      },
-      item: {}
+      item: {},
+      manufacturerOptions: []
+    }
+  },
+  computed: {
+    dialogTitle () {
+      return this.item?.id ? '编辑摄像头' : ''
     }
   },
   methods: {
-    show (type) {
-      this.item = {
-        cameraType: type,
-        identifier: '',
-        manufacturerKey: '',
-        model: '',
-        remark: ThirdPartyDeviceInfo[CameraToThirdPartyMap[type]],
-        username: '',
-        password: ''
+    async show (type, options) {
+      const result = await getManufacturersByType(CameraToThirdPartyMap[type])
+      this.manufacturerOptions = result.data
+      if (options) {
+        this.item = {
+          id: options.id,
+          cameraType: type,
+          identifier: options.identifier,
+          manufacturerKey: options.manufacturerKey,
+          model: options.model,
+          remark: ThirdPartyDeviceInfo[CameraToThirdPartyMap[type]],
+          username: options.username,
+          password: options.password,
+          throughEvs: String(options.throughEvs || 0),
+          evsChannel: options.evsChannel,
+          evsLocation: options.evsLocation
+        }
+      } else {
+        this.item = {
+          cameraType: type,
+          identifier: '',
+          manufacturerKey: '',
+          model: '',
+          remark: ThirdPartyDeviceInfo[CameraToThirdPartyMap[type]],
+          username: '',
+          password: '',
+          throughEvs: '',
+          evsChannel: 0,
+          evsLocation: 0
+        }
       }
+
       this.$refs.dialog.show()
     },
     getManufacturersByType () {
@@ -129,23 +192,31 @@ export default {
         })
         return
       }
-      if (!this.item.username) {
+      if (!this.item.username && !this.item.id) {
         this.$message({
           type: 'warning',
           message: '请填写账号'
         })
         return
       }
-      if (!this.item.password) {
+      if (!this.item.password && !this.item.id) {
         this.$message({
           type: 'warning',
           message: '请填写密码'
         })
         return
       }
+      if (!this.item.throughEvs) {
+        this.$message({
+          type: 'warning',
+          message: '请选择是否通过视频服务器'
+        })
+        return
+      }
       const key = this.item.manufacturerKey
-      addCamera({
-        manufacturerName: this.$refs.manufacturer.getOptions().find(({ value }) => value === key).label,
+      const isAdd = this.item.id ? updateCamera : addCamera
+      isAdd({
+        manufacturerName: this.manufacturerOptions.find(({ manufacturerKey }) => manufacturerKey === key).manufacturerName,
         camProvider: 0,
         ...this.item
       }).then(() => {