Explorar el Código

refactor: adjust routes

Casper Dai hace 3 años
padre
commit
9005a6532e

+ 53 - 38
src/router/index.js

@@ -48,7 +48,7 @@ export const asyncRoutes = [
         name: 'dashboard',
         path: 'dashboard',
         component: () => import('@/views/dashboard/index'),
-        meta: { title: '首页', icon: 'el-icon-s-home' }
+        meta: { title: '首页', icon: 'home' }
       },
       {
         name: 'profile',
@@ -63,7 +63,7 @@ export const asyncRoutes = [
   {
     path: '/cm',
     component: Layout,
-    meta: { title: '智能信发', icon: 'el-icon-s-promotion' },
+    meta: { title: '智能信发', icon: 'cm' },
     children: [
       {
         name: 'media',
@@ -126,7 +126,7 @@ export const asyncRoutes = [
   {
     path: '/dm',
     component: Layout,
-    meta: { title: '大屏设备', icon: 'el-icon-monitor' },
+    meta: { title: '大屏设备', icon: 'dm' },
     children: [
       {
         name: 'schedule-timeline',
@@ -134,26 +134,69 @@ export const asyncRoutes = [
         component: () => import('@/views/schedule/timeline/index'),
         meta: { title: '排期预览' }
       },
+      {
+        path: 'device',
+        component: Solo,
+        meta: { title: '设备管理' },
+        children: [
+          {
+            name: 'device-list',
+            path: '',
+            component: () => import('@/views/device/index'),
+            access: [Access.MANAGE_DEVICES, Access.MANAGE_DEVICE],
+            meta: { cache: 'DeviceList' }
+          },
+          {
+            name: 'device-detail',
+            path: ':id',
+            component: () => import('@/views/device/detail/index'),
+            meta: { title: '设备详情', activeMenu: '/m/device', cache: 'DeviceList' },
+            hidden: true
+          }
+        ]
+      },
+      {
+        name: 'group',
+        path: 'group',
+        component: () => import('@/views/device/group/index'),
+        access: Access.MANAGE_GROUPS,
+        meta: { title: '分组管理' }
+      },
       {
         dev: true,
         name: 'remote',
         path: 'remote',
         component: () => import('@/views/device/remote/index'),
         meta: { title: '设备操控' }
+      }
+    ]
+  },
+  {
+    path: '/em',
+    component: Layout,
+    meta: { title: '设备录入', icon: 'em' },
+    children: [
+      {
+        dev: true,
+        path: 'transmitter',
+        name: 'transmitter',
+        access: Access.MANAGE_DEVICES,
+        component: () => import('@/views/external/transmitter/index'),
+        meta: { title: '发送控制设备' }
       },
       {
-        name: 'camera',
         path: 'camera',
+        name: 'camera',
         access: Access.VIEW_CAMERAS,
-        component: () => import('@/views/device/camera/index'),
-        meta: { title: '视频监控' }
+        component: () => import('@/views/external/camera/index'),
+        meta: { title: '摄像头' }
       }
     ]
   },
   {
-    path: '/m',
+    path: '/pm',
     component: Layout,
-    meta: { title: '大屏管理', icon: 'el-icon-monitor' },
+    meta: { title: '平台管理', icon: 'pm' },
     children: [
       {
         name: 'category',
@@ -168,34 +211,6 @@ export const asyncRoutes = [
         component: () => import('@/views/device/product/index'),
         access: Access.MANAGE_PRODUCTS,
         meta: { title: '产品管理' }
-      },
-      {
-        path: 'device',
-        component: Solo,
-        meta: { title: '设备管理' },
-        children: [
-          {
-            name: 'device-list',
-            path: '',
-            component: () => import('@/views/device/index'),
-            access: [Access.MANAGE_DEVICES, Access.MANAGE_DEVICE],
-            meta: { cache: 'DeviceList' }
-          },
-          {
-            name: 'device-detail',
-            path: ':id',
-            component: () => import('@/views/device/detail/index'),
-            meta: { title: '设备详情', activeMenu: '/m/device', cache: 'DeviceList' },
-            hidden: true
-          }
-        ]
-      },
-      {
-        name: 'group',
-        path: 'group',
-        component: () => import('@/views/device/group/index'),
-        access: Access.MANAGE_GROUPS,
-        meta: { title: '分组管理' }
       }
     ]
   },
@@ -203,7 +218,7 @@ export const asyncRoutes = [
     path: '/l',
     component: Layout,
     access: Access.VIEW_LOGS,
-    meta: { icon: 'el-icon-edit-outline' },
+    meta: { icon: 'logger' },
     children: [
       {
         path: '',
@@ -231,7 +246,7 @@ export const asyncRoutes = [
     path: '/u',
     component: Layout,
     access: Access.MANAGE_UPGRADE,
-    meta: { title: '升级管理', icon: 'el-icon-upload' },
+    meta: { title: '升级管理', icon: 'upgrade' },
     children: [
       {
         path: 'apk',

+ 0 - 612
src/views/device/camera/index.vue

@@ -1,612 +0,0 @@
-<template>
-  <wrapper
-    fill
-    margin
-    background
-  >
-    <el-tabs
-      v-model="activeName"
-      v-loading="videoOption.loading"
-      class="c-tabs video"
-      @tab-click="handleClick"
-    >
-      <el-tab-pane
-        label="人流量监测"
-        name="first"
-      >
-        <div class="has-padding">
-          <div class="l-flex--row c-table__header">
-            <div class="l-flex__auto c-sibling-item">
-              <button
-                v-if="canEdit"
-                class="o-button"
-                @click="addbtn"
-              >
-                <i class="o-button__icon el-icon-circle-plus-outline" />新增
-              </button>
-            </div>
-            <search-input
-              v-model.trim="searchname"
-              class="l-flex__none c-sibling-item"
-              placeholder="搜索"
-              @search="search"
-            />
-            <button
-              class="l-flex__none c-sibling-item o-button"
-              @click="search"
-            >
-              搜索
-            </button>
-          </div>
-          <el-row
-            v-if="videoOption.list.length"
-            :gutter="16"
-            class="rowheight"
-          >
-            <el-col
-              v-for="(video, index) in videoOption.list"
-              :key="index"
-              :span="8"
-              class="cameraRow"
-            >
-              <div
-                ref="videoB"
-                class="o-video bg-purple"
-                :style="{ height: videoheight }"
-                @click="toDetail(video)"
-              >
-                <video
-                  :ref="video.deviceId"
-                  style="width: 100%;"
-                  class="video"
-                  muted
-                  autoplay
-                  :poster="require('@/assets//video-post.png')"
-                />
-                <div
-                  v-show="!video.online"
-                  class="offLine"
-                >
-                  设备离线
-                </div>
-                <div class="o-video_buttom l-flex--row">
-                  <div class="l-flex__auto">{{ video.name }}</div>
-                  <template v-if="canEdit">
-                    <img
-                      class="o-video_edit"
-                      :src="imgUrl.edit"
-                      @click.stop="editbtn(video)"
-                    >
-                    <img
-                      class="o-video_delete"
-                      :src="imgUrl.delete"
-                      @click.stop="deletebtn(video)"
-                    >
-                  </template>
-                </div>
-              </div>
-            </el-col>
-          </el-row>
-          <div
-            v-if="!videoOption.list.length"
-            class="empty"
-          >无摄像头数据</div>
-          <pagination
-            :total="videoOption.totalCount"
-            :page-sizes="[6]"
-            :page.sync="videoOption.params.pageNum"
-            :limit.sync="videoOption.params.pageSize"
-            @pagination="getCamera"
-          />
-        </div>
-      </el-tab-pane>
-      <!-- <el-tab-pane label="视频回传" name="second">
-        <div class="has-padding">
-          <div class="l-flex--row c-table__header">
-            <div class="l-flex__auto c-sibling-item">
-              <el-checkbox-group v-model="checkList" @change="chechChange">
-                <el-checkbox label="视频回采"></el-checkbox>
-                <el-checkbox label="模拟终端"></el-checkbox>
-              </el-checkbox-group>
-            </div>
-            <div class="l-flex__none c-sibling-item">
-              <img
-                class="c-sibling-item"
-                @click="rowChange(24)"
-                :src="require('@/assets/icon_one_normal.png')"
-                alt=""
-              />
-              <img
-                class="c-sibling-item"
-                @click="rowChange(12)"
-                :src="require('@/assets/icon_four_normal.png')"
-                alt=""
-              />
-              <img
-                class="c-sibling-item"
-                @click="rowChange(8)"
-                :src="require('@/assets/icon_nine_focus.png')"
-                alt=""
-              />
-            </div>
-            <button
-              class="l-flex__none c-sibling-item o-button"
-              @click="search"
-            >
-              <i class="o-button__icon el-icon-full-screen" />
-              全屏
-            </button>
-          </div>
-          <el-row :gutter="16" class="rowheight">
-            <el-col
-              :span="rowNum"
-              v-for="(item, index) in returnList.list"
-              :key="index"
-              class="cameraRow"
-            >
-              <div class="return_item bg-purple"
-                ref="returnB"
-                :style="{ height: returnheight }">
-                <div class="o-video_buttom l-flex--row">
-                  <div class="l-flex__auto">{{ item.name }}</div>
-                  <img
-                    :src="require('@/assets/icon_address.png')"
-                    class="o-video_edit"
-                  />
-                  <div class="return_local">{{ item.name }}</div>
-                </div>
-              </div>
-            </el-col>
-          </el-row>
-        </div>
-      </el-tab-pane> -->
-    </el-tabs>
-    <!-- 新增和编辑 -->
-    <el-dialog
-      :title="dialogTitle"
-      :visible.sync="adding"
-      custom-class="c-dialog"
-      :close-on-click-modal="false"
-    >
-      <el-form
-        ref="cameraForm"
-        :model="camera"
-        :rules="cameraRules"
-        label-width="100px"
-        class="c-form"
-      >
-        <el-form-item
-          label="设备名称"
-          prop="name"
-        >
-          <el-input
-            v-model.number="camera.name"
-            maxlength="50"
-            show-word-limit
-            class="c-form__item c-form__item_padding"
-          />
-        </el-form-item>
-        <el-form-item
-          label="ID"
-          prop="deviceId"
-        >
-          <el-input
-            v-model.number="camera.deviceId"
-            maxlength="50"
-            :disabled="isEdit"
-            class="c-form__item"
-          />
-        </el-form-item>
-        <el-form-item
-          label="用户名"
-          prop="username"
-        >
-          <el-input
-            v-model.number="camera.username"
-            maxlength="50"
-            :disabled="isEdit"
-            class="c-form__item"
-          />
-        </el-form-item>
-        <el-form-item
-          v-if="!isEdit"
-          label="密码"
-          prop="password"
-        >
-          <el-input
-            v-model.number="camera.password"
-            maxlength="50"
-            type="password"
-            autocomplete="off"
-            class="c-form__item"
-          />
-        </el-form-item>
-        <el-form-item
-          label="备注"
-          prop="remark"
-        >
-          <el-input
-            v-model.number="camera.remark"
-            type="textarea"
-            maxlength="500"
-            class="c-form__item"
-            show-word-limit
-          />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <button
-          class="o-button"
-          @click="add('cameraForm')"
-        >
-          确定
-        </button>
-        <button
-          class="o-button cancel"
-          @click="handleCloseAddDialog('cameraForm')"
-        >
-          取消
-        </button>
-      </template>
-    </el-dialog>
-    <el-dialog
-      title
-      :fullscreen="true"
-      :visible="detailing"
-      :close-on-click-modal="false"
-      class="fulldialog"
-    >
-      <detail
-        v-if="detailing"
-        :detailobj="editOption"
-        @closeDetail="closeDetail"
-      />
-    </el-dialog>
-  </wrapper>
-</template>
-
-<script>
-import flvjs from 'flv.js'
-import {
-  getCamera,
-  addCamera,
-  updateCamera,
-  deleteCamera
-} from '@/api/camera'
-import { createListOptions } from '@/utils'
-import Detail from './components/Detail'
-
-const CAMERA_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${process.env.VUE_APP_GATEWAY || location.host}${process.env.VUE_APP_CAMERA_PROXY}`
-
-export default {
-  name: 'Camera',
-  components: {
-    Detail
-  },
-  data () {
-    return {
-      activeName: 'first',
-      searchname: '',
-      imgUrl: {
-        edit: require('@/assets/icon_edit.png'),
-        delete: require('@/assets/icon_delete.png')
-      },
-      videoOption: createListOptions({
-        pageSize: 6
-      }),
-      dialogTitle: '新增',
-      adding: false,
-      camera: {
-        name: '',
-        deviceId: '',
-        username: '',
-        password: '',
-        remark: ''
-      },
-      cameraRules: {
-        name: [{ required: true, message: '请填写设备名称', trigger: 'blur' }],
-        deviceId: [{ required: true, message: '请填写ID', trigger: 'blur' }],
-        username: [
-          { required: true, message: '请填写用户名', trigger: 'blur' }
-        ],
-        password: [{ required: true, message: '请填写密码', trigger: 'blur' }]
-      },
-      isEdit: true,
-      editOption: {},
-      detailing: false,
-      videoheight: '',
-      playerList: {},
-
-      checkList: [],
-      returnList: createListOptions({
-        pageSize: 9
-      }),
-      returnheight: 0,
-      rowNum: 8
-    }
-  },
-  computed: {
-    canEdit () {
-      return this.accessSet.has(this.Access.MANAGE_DEVICES)
-    }
-  },
-  created () {
-    this.getCamera()
-  },
-  beforeDestroy () {
-    this.destroyPlayer()
-  },
-  methods: {
-    rowChange (num) {
-      this.rowNum = num
-      this.$nextTick(() => {
-        this.returnheight = (this.$refs.returnB[0].clientWidth * 9) / 16 + 'px'
-      })
-    },
-    chechChange (val) {
-      console.log(val)
-    },
-    destroyPlayer () {
-      for (const key in this.playerList) {
-        if (Object.hasOwnProperty.call(this.playerList, key)) {
-          // const element = object[key];
-          if (this.playerList[key]) {
-            this.playerList[key].pause()
-            this.playerList[key].unload()
-            this.playerList[key].detachMediaElement()
-            this.playerList[key].destroy()
-            this.playerList[key] = null
-          }
-        }
-      }
-    },
-    handleClick () {},
-    search () {
-      this.getCamera()
-    },
-    getCamera () {
-      const options = this.videoOption
-      options.error = false
-      options.loading = true
-      options.params.name = this.searchname
-      getCamera(options.params)
-        .then(
-          ({ data, totalCount }) => {
-            options.totalCount = totalCount
-            options.list = data
-            // for (let i = 0; i < 34; i++) {
-            //   data.push({
-            //     deviceId: Math.random(),
-            //   })
-            // }
-            this.$nextTick(() => {
-              this.videoheight =
-                (this.$refs.videoB[0].clientWidth * 9) / 16 + 'px'
-              this.destroyPlayer()
-              for (let i = 0; i < data.length; i++) {
-                if (options.list[i].online) {
-                  this.getflv(options.list[i].deviceId)
-                }
-                // getOnline(options.list[i].deviceId).then(({ data }) => {
-                //   options.list[i].online = data
-                //   if (data) {
-                //     this.getflv(options.list[i].deviceId)
-                //   }
-                // })
-              }
-            })
-          },
-          () => {
-            options.error = true
-            options.list = []
-          }
-        )
-        .finally(() => {
-          options.loading = false
-        })
-    },
-    addCamera () {
-      addCamera({
-        name: this.camera.name,
-        deviceId: this.camera.deviceId,
-        username: this.camera.username,
-        password: this.camera.password,
-        remark: this.camera.remark
-      }).then(() => {
-        this.handleCloseAddDialog('cameraForm')
-        this.getCamera()
-      })
-    },
-    addbtn () {
-      this.camera = {
-        name: '',
-        deviceId: '',
-        username: '',
-        password: '',
-        remark: ''
-      }
-      this.dialogTitle = '新增'
-      this.isEdit = false
-      this.adding = true
-    },
-    add (formName) {
-      this.$refs[formName].validate((valid) => {
-        if (valid) {
-          if (this.isEdit) {
-            this.edit(this.editOption)
-          } else {
-            this.addCamera()
-          }
-        } else {
-          console.log('error submit!!')
-          return false
-        }
-      })
-    },
-    handleCloseAddDialog (formName) {
-      this.adding = false
-      this.$refs[formName].resetFields()
-    },
-    editbtn (item) {
-      this.adding = true
-      this.isEdit = true
-      this.dialogTitle = '编辑'
-
-      this.editOption = item
-      this.camera = {
-        name: item.name,
-        deviceId: item.deviceId,
-        username: item.username,
-        remark: item.remark
-      }
-    },
-    edit (item) {
-      updateCamera({
-        id: item.id,
-        name: this.camera.name,
-        deviceId: item.deviceId,
-        username: this.camera.username,
-        password: this.camera.password,
-        remark: this.camera.remark
-      }).then(() => {
-        this.handleCloseAddDialog('cameraForm')
-        this.getCamera()
-      })
-    },
-    deletebtn (item) {
-      const videoOption = this.videoOption
-      deleteCamera({ id: item.id, name: item.name }).then(() => {
-        if (videoOption.list.length === 1 && videoOption.params.pageNum > 1) {
-          videoOption.params.pageNum -= 1
-        }
-        this.getCamera()
-      })
-    },
-    getflv (deviceId) {
-      if (flvjs.isSupported()) {
-        // 创建一个flvjs实例
-        this.playerList[deviceId] = flvjs.createPlayer({
-          type: 'flv',
-          isLive: true,
-          hasAudio: false,
-          url: `${CAMERA_URL}/${deviceId}?authorization=${this.$keycloak.token}`
-        })
-        this.playerList[deviceId].on('error', (e) => {
-          console.log(e)
-        })
-        // 将实例挂载到video元素上面
-        this.playerList[deviceId].attachMediaElement(this.$refs[deviceId][0])
-        // player.currentTime = parseFloat(document.getElementsByName('seekpoint')[0].value);
-        try {
-          // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
-          this.playerList[deviceId].load()
-          this.playerList[deviceId].play()
-        } catch (error) {
-          console.log('连接websocker异常:' + error)
-          console.log(error)
-        }
-      }
-    },
-    toDetail (item) {
-      if (item.online) {
-        this.detailing = true
-        this.editOption = item
-        this.destroyPlayer()
-      }
-    },
-    closeDetail () {
-      this.detailing = false
-      this.getCamera()
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-//所有控件
-video::-webkit-media-controls-enclosure {
-  display: none;
-}
-.has-padding {
-  height: 100%;
-}
-.rowheight {
-  // height: calc(100% - 40px);
-  overflow: auto;
-}
-.c-tabs {
-  height: 100%;
-}
-.cameraRow {
-  margin-bottom: 16px;
-}
-.o-video {
-  // height: 190px;
-  background-color: #edf0f6;
-  position: relative;
-  overflow: hidden;
-  &_buttom {
-    position: absolute;
-    bottom: 0px;
-    width: 100%;
-    height: 58px;
-    background-image: linear-gradient(0deg, rgba(6, 11, 18, 0.9), transparent);
-    border-radius: 0px 0px 4px 4px;
-    color: #fff;
-    padding: 0 16px;
-  }
-  img {
-    cursor: pointer;
-  }
-  &_edit {
-    margin-right: 16px;
-  }
-  .offLine {
-    position: absolute;
-    left: 50%;
-    top: 50%;
-    color: #ff0000;
-    text-align: center;
-    transform: translate(-50%, -50%);
-  }
-}
-.return {
-  &_item {
-    background-color: #aaa;
-    width: 100%;
-    height: 150px;
-    position: relative;
-  }
-  &_local {
-    font-size: 12px;
-  }
-}
-.empty {
-  color: #aaa;
-  text-align: center;
-}
-</style>
-<style lang="scss">
-.fulldialog {
-  .el-dialog__header {
-    display: none;
-  }
-  .el-dialog__body {
-    padding: 0;
-    height: 100%;
-  }
-}
-.video {
-  .el-tabs__content {
-    height: calc(100% - 40px);
-  }
-  .el-tab-pane {
-    height: 100%;
-  }
-}
-.c-form__item_padding{
-  .el-input__inner{
-    padding-right: 50px;
-  }
-}
-</style>

+ 0 - 0
src/views/device/camera/components/Detail.vue → src/views/external/camera/components/Detail.vue


+ 605 - 2
src/views/external/camera/index.vue

@@ -1,9 +1,612 @@
 <template>
-  <div>Gateway</div>
+  <wrapper
+    fill
+    margin
+    background
+  >
+    <el-tabs
+      v-model="activeName"
+      v-loading="videoOption.loading"
+      class="c-tabs video"
+      @tab-click="handleClick"
+    >
+      <el-tab-pane
+        label="人流量监测"
+        name="first"
+      >
+        <div class="has-padding">
+          <div class="l-flex--row c-table__header">
+            <div class="l-flex__auto c-sibling-item">
+              <button
+                v-if="canEdit"
+                class="o-button"
+                @click="addbtn"
+              >
+                <i class="o-button__icon el-icon-circle-plus-outline" />新增
+              </button>
+            </div>
+            <search-input
+              v-model.trim="searchname"
+              class="l-flex__none c-sibling-item"
+              placeholder="搜索"
+              @search="search"
+            />
+            <button
+              class="l-flex__none c-sibling-item o-button"
+              @click="search"
+            >
+              搜索
+            </button>
+          </div>
+          <el-row
+            v-if="videoOption.list.length"
+            :gutter="16"
+            class="rowheight"
+          >
+            <el-col
+              v-for="(video, index) in videoOption.list"
+              :key="index"
+              :span="8"
+              class="cameraRow"
+            >
+              <div
+                ref="videoB"
+                class="o-video bg-purple"
+                :style="{ height: videoheight }"
+                @click="toDetail(video)"
+              >
+                <video
+                  :ref="video.deviceId"
+                  style="width: 100%;"
+                  class="video"
+                  muted
+                  autoplay
+                  :poster="require('@/assets//video-post.png')"
+                />
+                <div
+                  v-show="!video.online"
+                  class="offLine"
+                >
+                  设备离线
+                </div>
+                <div class="o-video_buttom l-flex--row">
+                  <div class="l-flex__auto">{{ video.name }}</div>
+                  <template v-if="canEdit">
+                    <img
+                      class="o-video_edit"
+                      :src="imgUrl.edit"
+                      @click.stop="editbtn(video)"
+                    >
+                    <img
+                      class="o-video_delete"
+                      :src="imgUrl.delete"
+                      @click.stop="deletebtn(video)"
+                    >
+                  </template>
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+          <div
+            v-if="!videoOption.list.length"
+            class="empty"
+          >无摄像头数据</div>
+          <pagination
+            :total="videoOption.totalCount"
+            :page-sizes="[6]"
+            :page.sync="videoOption.params.pageNum"
+            :limit.sync="videoOption.params.pageSize"
+            @pagination="getCamera"
+          />
+        </div>
+      </el-tab-pane>
+      <!-- <el-tab-pane label="视频回传" name="second">
+        <div class="has-padding">
+          <div class="l-flex--row c-table__header">
+            <div class="l-flex__auto c-sibling-item">
+              <el-checkbox-group v-model="checkList" @change="chechChange">
+                <el-checkbox label="视频回采"></el-checkbox>
+                <el-checkbox label="模拟终端"></el-checkbox>
+              </el-checkbox-group>
+            </div>
+            <div class="l-flex__none c-sibling-item">
+              <img
+                class="c-sibling-item"
+                @click="rowChange(24)"
+                :src="require('@/assets/icon_one_normal.png')"
+                alt=""
+              />
+              <img
+                class="c-sibling-item"
+                @click="rowChange(12)"
+                :src="require('@/assets/icon_four_normal.png')"
+                alt=""
+              />
+              <img
+                class="c-sibling-item"
+                @click="rowChange(8)"
+                :src="require('@/assets/icon_nine_focus.png')"
+                alt=""
+              />
+            </div>
+            <button
+              class="l-flex__none c-sibling-item o-button"
+              @click="search"
+            >
+              <i class="o-button__icon el-icon-full-screen" />
+              全屏
+            </button>
+          </div>
+          <el-row :gutter="16" class="rowheight">
+            <el-col
+              :span="rowNum"
+              v-for="(item, index) in returnList.list"
+              :key="index"
+              class="cameraRow"
+            >
+              <div class="return_item bg-purple"
+                ref="returnB"
+                :style="{ height: returnheight }">
+                <div class="o-video_buttom l-flex--row">
+                  <div class="l-flex__auto">{{ item.name }}</div>
+                  <img
+                    :src="require('@/assets/icon_address.png')"
+                    class="o-video_edit"
+                  />
+                  <div class="return_local">{{ item.name }}</div>
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-tab-pane> -->
+    </el-tabs>
+    <!-- 新增和编辑 -->
+    <el-dialog
+      :title="dialogTitle"
+      :visible.sync="adding"
+      custom-class="c-dialog"
+      :close-on-click-modal="false"
+    >
+      <el-form
+        ref="cameraForm"
+        :model="camera"
+        :rules="cameraRules"
+        label-width="100px"
+        class="c-form"
+      >
+        <el-form-item
+          label="设备名称"
+          prop="name"
+        >
+          <el-input
+            v-model.number="camera.name"
+            maxlength="50"
+            show-word-limit
+            class="c-form__item c-form__item_padding"
+          />
+        </el-form-item>
+        <el-form-item
+          label="ID"
+          prop="deviceId"
+        >
+          <el-input
+            v-model.number="camera.deviceId"
+            maxlength="50"
+            :disabled="isEdit"
+            class="c-form__item"
+          />
+        </el-form-item>
+        <el-form-item
+          label="用户名"
+          prop="username"
+        >
+          <el-input
+            v-model.number="camera.username"
+            maxlength="50"
+            :disabled="isEdit"
+            class="c-form__item"
+          />
+        </el-form-item>
+        <el-form-item
+          v-if="!isEdit"
+          label="密码"
+          prop="password"
+        >
+          <el-input
+            v-model.number="camera.password"
+            maxlength="50"
+            type="password"
+            autocomplete="off"
+            class="c-form__item"
+          />
+        </el-form-item>
+        <el-form-item
+          label="备注"
+          prop="remark"
+        >
+          <el-input
+            v-model.number="camera.remark"
+            type="textarea"
+            maxlength="500"
+            class="c-form__item"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <button
+          class="o-button"
+          @click="add('cameraForm')"
+        >
+          确定
+        </button>
+        <button
+          class="o-button cancel"
+          @click="handleCloseAddDialog('cameraForm')"
+        >
+          取消
+        </button>
+      </template>
+    </el-dialog>
+    <el-dialog
+      title
+      :fullscreen="true"
+      :visible="detailing"
+      :close-on-click-modal="false"
+      class="fulldialog"
+    >
+      <detail
+        v-if="detailing"
+        :detailobj="editOption"
+        @closeDetail="closeDetail"
+      />
+    </el-dialog>
+  </wrapper>
 </template>
 
 <script>
+import flvjs from 'flv.js'
+import {
+  getCamera,
+  addCamera,
+  updateCamera,
+  deleteCamera
+} from '@/api/camera'
+import { createListOptions } from '@/utils'
+import Detail from './components/Detail'
+
+const CAMERA_URL = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${process.env.VUE_APP_GATEWAY || location.host}${process.env.VUE_APP_CAMERA_PROXY}`
+
 export default {
-  name: 'Gateway'
+  name: 'Camera',
+  components: {
+    Detail
+  },
+  data () {
+    return {
+      activeName: 'first',
+      searchname: '',
+      imgUrl: {
+        edit: require('@/assets/icon_edit.png'),
+        delete: require('@/assets/icon_delete.png')
+      },
+      videoOption: createListOptions({
+        pageSize: 6
+      }),
+      dialogTitle: '新增',
+      adding: false,
+      camera: {
+        name: '',
+        deviceId: '',
+        username: '',
+        password: '',
+        remark: ''
+      },
+      cameraRules: {
+        name: [{ required: true, message: '请填写设备名称', trigger: 'blur' }],
+        deviceId: [{ required: true, message: '请填写ID', trigger: 'blur' }],
+        username: [
+          { required: true, message: '请填写用户名', trigger: 'blur' }
+        ],
+        password: [{ required: true, message: '请填写密码', trigger: 'blur' }]
+      },
+      isEdit: true,
+      editOption: {},
+      detailing: false,
+      videoheight: '',
+      playerList: {},
+
+      checkList: [],
+      returnList: createListOptions({
+        pageSize: 9
+      }),
+      returnheight: 0,
+      rowNum: 8
+    }
+  },
+  computed: {
+    canEdit () {
+      return this.accessSet.has(this.Access.MANAGE_DEVICES)
+    }
+  },
+  created () {
+    this.getCamera()
+  },
+  beforeDestroy () {
+    this.destroyPlayer()
+  },
+  methods: {
+    rowChange (num) {
+      this.rowNum = num
+      this.$nextTick(() => {
+        this.returnheight = (this.$refs.returnB[0].clientWidth * 9) / 16 + 'px'
+      })
+    },
+    chechChange (val) {
+      console.log(val)
+    },
+    destroyPlayer () {
+      for (const key in this.playerList) {
+        if (Object.hasOwnProperty.call(this.playerList, key)) {
+          // const element = object[key];
+          if (this.playerList[key]) {
+            this.playerList[key].pause()
+            this.playerList[key].unload()
+            this.playerList[key].detachMediaElement()
+            this.playerList[key].destroy()
+            this.playerList[key] = null
+          }
+        }
+      }
+    },
+    handleClick () {},
+    search () {
+      this.getCamera()
+    },
+    getCamera () {
+      const options = this.videoOption
+      options.error = false
+      options.loading = true
+      options.params.name = this.searchname
+      getCamera(options.params)
+        .then(
+          ({ data, totalCount }) => {
+            options.totalCount = totalCount
+            options.list = data
+            // for (let i = 0; i < 34; i++) {
+            //   data.push({
+            //     deviceId: Math.random(),
+            //   })
+            // }
+            this.$nextTick(() => {
+              this.videoheight =
+                (this.$refs.videoB[0].clientWidth * 9) / 16 + 'px'
+              this.destroyPlayer()
+              for (let i = 0; i < data.length; i++) {
+                if (options.list[i].online) {
+                  this.getflv(options.list[i].deviceId)
+                }
+                // getOnline(options.list[i].deviceId).then(({ data }) => {
+                //   options.list[i].online = data
+                //   if (data) {
+                //     this.getflv(options.list[i].deviceId)
+                //   }
+                // })
+              }
+            })
+          },
+          () => {
+            options.error = true
+            options.list = []
+          }
+        )
+        .finally(() => {
+          options.loading = false
+        })
+    },
+    addCamera () {
+      addCamera({
+        name: this.camera.name,
+        deviceId: this.camera.deviceId,
+        username: this.camera.username,
+        password: this.camera.password,
+        remark: this.camera.remark
+      }).then(() => {
+        this.handleCloseAddDialog('cameraForm')
+        this.getCamera()
+      })
+    },
+    addbtn () {
+      this.camera = {
+        name: '',
+        deviceId: '',
+        username: '',
+        password: '',
+        remark: ''
+      }
+      this.dialogTitle = '新增'
+      this.isEdit = false
+      this.adding = true
+    },
+    add (formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          if (this.isEdit) {
+            this.edit(this.editOption)
+          } else {
+            this.addCamera()
+          }
+        } else {
+          console.log('error submit!!')
+          return false
+        }
+      })
+    },
+    handleCloseAddDialog (formName) {
+      this.adding = false
+      this.$refs[formName].resetFields()
+    },
+    editbtn (item) {
+      this.adding = true
+      this.isEdit = true
+      this.dialogTitle = '编辑'
+
+      this.editOption = item
+      this.camera = {
+        name: item.name,
+        deviceId: item.deviceId,
+        username: item.username,
+        remark: item.remark
+      }
+    },
+    edit (item) {
+      updateCamera({
+        id: item.id,
+        name: this.camera.name,
+        deviceId: item.deviceId,
+        username: this.camera.username,
+        password: this.camera.password,
+        remark: this.camera.remark
+      }).then(() => {
+        this.handleCloseAddDialog('cameraForm')
+        this.getCamera()
+      })
+    },
+    deletebtn (item) {
+      const videoOption = this.videoOption
+      deleteCamera({ id: item.id, name: item.name }).then(() => {
+        if (videoOption.list.length === 1 && videoOption.params.pageNum > 1) {
+          videoOption.params.pageNum -= 1
+        }
+        this.getCamera()
+      })
+    },
+    getflv (deviceId) {
+      if (flvjs.isSupported()) {
+        // 创建一个flvjs实例
+        this.playerList[deviceId] = flvjs.createPlayer({
+          type: 'flv',
+          isLive: true,
+          hasAudio: false,
+          url: `${CAMERA_URL}/${deviceId}?authorization=${this.$keycloak.token}`
+        })
+        this.playerList[deviceId].on('error', (e) => {
+          console.log(e)
+        })
+        // 将实例挂载到video元素上面
+        this.playerList[deviceId].attachMediaElement(this.$refs[deviceId][0])
+        // player.currentTime = parseFloat(document.getElementsByName('seekpoint')[0].value);
+        try {
+          // 开始运行加载 只要流地址正常 就可以在h5页面中播放出画面了
+          this.playerList[deviceId].load()
+          this.playerList[deviceId].play()
+        } catch (error) {
+          console.log('连接websocker异常:' + error)
+          console.log(error)
+        }
+      }
+    },
+    toDetail (item) {
+      if (item.online) {
+        this.detailing = true
+        this.editOption = item
+        this.destroyPlayer()
+      }
+    },
+    closeDetail () {
+      this.detailing = false
+      this.getCamera()
+    }
+  }
 }
 </script>
+
+<style lang="scss" scoped>
+//所有控件
+video::-webkit-media-controls-enclosure {
+  display: none;
+}
+.has-padding {
+  height: 100%;
+}
+.rowheight {
+  // height: calc(100% - 40px);
+  overflow: auto;
+}
+.c-tabs {
+  height: 100%;
+}
+.cameraRow {
+  margin-bottom: 16px;
+}
+.o-video {
+  // height: 190px;
+  background-color: #edf0f6;
+  position: relative;
+  overflow: hidden;
+  &_buttom {
+    position: absolute;
+    bottom: 0px;
+    width: 100%;
+    height: 58px;
+    background-image: linear-gradient(0deg, rgba(6, 11, 18, 0.9), transparent);
+    border-radius: 0px 0px 4px 4px;
+    color: #fff;
+    padding: 0 16px;
+  }
+  img {
+    cursor: pointer;
+  }
+  &_edit {
+    margin-right: 16px;
+  }
+  .offLine {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    color: #ff0000;
+    text-align: center;
+    transform: translate(-50%, -50%);
+  }
+}
+.return {
+  &_item {
+    background-color: #aaa;
+    width: 100%;
+    height: 150px;
+    position: relative;
+  }
+  &_local {
+    font-size: 12px;
+  }
+}
+.empty {
+  color: #aaa;
+  text-align: center;
+}
+</style>
+<style lang="scss">
+.fulldialog {
+  .el-dialog__header {
+    display: none;
+  }
+  .el-dialog__body {
+    padding: 0;
+    height: 100%;
+  }
+}
+.video {
+  .el-tabs__content {
+    height: calc(100% - 40px);
+  }
+  .el-tab-pane {
+    height: 100%;
+  }
+}
+.c-form__item_padding{
+  .el-input__inner{
+    padding-right: 50px;
+  }
+}
+</style>

+ 262 - 0
src/views/external/transmitter/index.vue

@@ -0,0 +1,262 @@
+<template>
+  <wrapper
+    fill
+    margin
+    padding
+    background
+  >
+    <c-table
+      :curr="options"
+      @pagination="getList"
+    >
+      <template #header>
+        <div class="l-flex__auto c-sibling-item">
+          <button
+            class="o-button"
+            @click="toAdd"
+          >
+            <i class="o-button__icon el-icon-circle-plus-outline" />
+            新增
+          </button>
+        </div>
+        <el-select
+          v-model="options.params.manufacturerId"
+          class="l-flex__fill c-info__value"
+          placeholder="请选择厂家"
+          :loading="manufacturers.loading"
+          @visible-change="getReceivingCardManufacturers"
+          @change="onChangeProp('manufacturerId')"
+        >
+          <el-option
+            v-for="manufacturer in manufacturerList"
+            :key="manufacturer.value"
+            :label="manufacturer.label"
+            :value="manufacturer.value"
+          />
+        </el-select>
+      </template>
+      <el-table-column
+        prop="manufacturer"
+        label="厂商"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        prop="name"
+        label="设备名称"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        prop="model"
+        label="设备型号"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        prop="isSync"
+        label="异步盒"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        prop="通信方式"
+        label="protocal"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        label="操作"
+        align="center"
+        width="180"
+      >
+        <template v-slot="scope">
+          <div
+            class="c-table__btn u-pointer"
+            @click="toEdit(scope.row)"
+          >
+            编辑
+          </div>
+          <div
+            class="c-table__btn u-pointer"
+            @click="toDel(scope.row)"
+          >
+            删除
+          </div>
+        </template>
+      </el-table-column>
+    </c-table>
+    <el-dialog
+      title="添加发送控制设备"
+      :visible.sync="show"
+      custom-class="c-dialog"
+      :close-on-click-modal="false"
+      :before-close="close"
+    >
+      <div class="c-form">
+        <div class="c-form__section">
+          <span class="c-form__label large required">厂家:</span>
+          <el-select
+            v-model="transmitter.manufacturerId"
+            class="c-form__item"
+            placeholder="请选择厂家"
+            :loading="manufacturers.loading"
+            @visible-change="getReceivingCardManufacturers"
+          >
+            <el-option
+              v-for="manufacturer in manufacturers.list"
+              :key="manufacturer.value"
+              :label="manufacturer.label"
+              :value="manufacturer.value"
+            />
+          </el-select>
+        </div>
+        <div class="c-form__section">
+          <span class="c-form__label large required">设备名称:</span>
+          <el-input
+            v-model.trim="transmitter.name"
+            class="c-form__item"
+            maxlength="50"
+            show-word-limit
+          />
+        </div>
+        <div class="c-form__section">
+          <span class="c-form__label large required">设备型号:</span>
+          <el-input
+            v-model.trim="transmitter.model"
+            class="c-form__item"
+            maxlength="50"
+            show-word-limit
+          />
+        </div>
+        <div class="c-form__section">
+          <span class="c-form__label large required">通信类型:</span>
+          <el-select
+            v-model="transmitter.protocol"
+            class="c-form__item"
+            placeholder="请选择"
+          >
+            <el-option
+              v-for="protocol in protocols"
+              :key="protocol.value"
+              :label="protocol.label"
+              :value="protocol.value"
+            />
+          </el-select>
+        </div>
+        <div class="c-form__section">
+          <span class="c-form__label large">是否为异步盒:</span>
+          <el-switch
+            v-model="transmitter.async"
+            class="c-form__item"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+          />
+        </div>
+      </div>
+      <template #footer>
+        <button
+          class="o-button"
+          @click="save"
+        >
+          确定
+        </button>
+        <button
+          class="o-button cancel"
+          @click="close"
+        >
+          取消
+        </button>
+      </template>
+    </el-dialog>
+  </wrapper>
+</template>
+
+<script>
+import {
+  getDevices,
+  getReceivingCardManufacturers
+} from '@/api/device'
+import { createListOptions } from '@/utils'
+
+export default {
+  name: 'Transmitter',
+  data () {
+    return {
+      options: createListOptions({ manufacturerId: '' }),
+      manufacturers: {
+        loading: false,
+        loaded: false,
+        list: []
+      },
+      protocols: [
+        { value: 'net', label: '网络' },
+        { value: 'sdk', label: 'SDK' }
+      ],
+      show: false,
+      transmitter: {}
+    }
+  },
+  computed: {
+    manufacturerList () {
+      return [{ value: '', label: '全部厂家' }]
+    }
+  },
+  created () {
+    this.getList()
+  },
+  methods: {
+    search () {
+      const options = this.options
+      options.list = []
+      options.totalCount = 0
+      options.params.pageNum = 1
+      this.getList()
+    },
+    getList () {
+      const options = this.options
+      options.error = false
+      options.loading = true
+      return getDevices(options).then(({ data, totalCount }) => {
+        options.list = data
+        options.totalCount = totalCount
+      }, () => {
+        options.error = true
+        options.list = []
+      }).finally(() => {
+        options.loading = false
+      })
+    },
+    getReceivingCardManufacturers (visible) {
+      if (visible && !this.manufacturers.loading && !this.manufacturers.loaded) {
+        this.manufacturers.loading = true
+        getReceivingCardManufacturers().then(({ data }) => {
+          this.manufacturers.list = data.map(({ id, name }) => {
+            return { value: id, label: name }
+          })
+          this.manufacturers.loading = false
+          this.manufacturers.loaded = true
+        }, () => {
+          this.manufacturers.loading = false
+        })
+      }
+    },
+    toAdd () {
+      this.transmitter = {
+        manufacturerId: '',
+        name: '',
+        model: '',
+        protocol: '',
+        async: false
+      }
+      this.show = true
+    },
+    close () {
+      this.show = false
+    },
+    save () {
+
+    }
+  }
+}
+</script>