Ver Fonte

feat: 增加项目详情中对摄像头的绑定,及查看摄像头操作

yanglzh há 4 meses atrás
pai
commit
baabab1121

+ 7 - 0
src/utils/origin.ts

@@ -39,6 +39,13 @@ export function getRuleServerOrigin(urlStr: string = '') {
   return origin + urlStr
 }
 
+// 流媒体服务
+export function getMediaOrigin(urlStr: string = '') {
+  const origin = import.meta.env.VITE_SERVER_ORIGIN || window.location.origin
+  const nginxProxy = import.meta.env.VITE_MEDIA_SERVER_URL
+  return origin + nginxProxy + urlStr
+}
+
 export function getSSEOrigin(urlStr: string = '') {
   const origin = import.meta.env.VITE_SERVER_ORIGIN || window.location.origin
   const nginxProxy = import.meta.env.VITE_NGINX_PROXY

+ 2 - 2
src/views/iot/projects/detail/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page">
     <el-card shadow="nover" class="small-padding">
-      <el-tabs :model-value="'0'" size="small" class="h-full">
+      <el-tabs :model-value="'3'" size="small" class="h-full">
         <el-tab-pane label="项目概况" name="0" lazy>
           <InfoVue></InfoVue>
         </el-tab-pane>
@@ -25,7 +25,7 @@
 <script lang="ts" setup>
 import TopoVue from './topo.vue'
 import InfoVue from './info.vue'
-// import VideoVue from './video.vue'
+import VideoVue from './video.vue'
 import DeviceVue from './device.vue'
 import SceneVue from './scene.vue'
 

+ 123 - 56
src/views/iot/projects/detail/video.vue

@@ -2,19 +2,28 @@
   <div class="tab-content h-full">
     <div class="subtitle"><span></span> <el-button type="primary" size="small" @click="addDevice()">添加监控</el-button></div>
     <el-table :data="tableData" style="width: 100%" v-loading="loading" max-height="calc(100vh - 280px)">
-      <el-table-column label="操作" width="120" align="center">
+      <el-table-column prop="DeviceName" label="设备名称" min-width="130" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="DeviceID" label="设备ID" min-width="210" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="Name" label="通道名称" min-width="140" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="Model" label="型号" width="100" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="Manufacturer" label="厂商" width="100" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="LiveStatus" label="状态" width="100" align="center" :formatter="formatLiveStatus" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="NetAddr" label="网络地址" width="180" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="LastKeepaliveAt" label="最后心跳时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="RegisterTime" label="注册时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="UpdateTime" label="更新时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column label="操作" width="120" align="center" fixed="right">
         <template #default="scope">
-          <el-button size="small" text type="primary" v-auth="'xxx'" @click="$router.push('/iotmanager/scene/manage/' + scope.row.id)">监控详情</el-button>
+          <el-button size="small" text type="primary" v-auth="'xxx'" @click="view(scope.row)">查看监控</el-button>
           <el-button size="small" text type="warning" v-auth="'xxx'" @click="onDel(scope.row)">解绑</el-button>
         </template>
       </el-table-column>
     </el-table>
-    <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
     <el-dialog title="添加监控" v-model="isShowDialog" width="400">
       <el-form v-if="isShowDialog">
-        <el-form-item label="监控" style="margin-bottom: 0;">
+        <el-form-item label="监控" style="margin-bottom: 0">
           <el-select v-model="form.resourcesKey" filterable placeholder="选择监控" :clearable="false" style="width: 100%">
-            <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option v-for="item in options" :disabled="item.disabled" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
       </el-form>
@@ -25,90 +34,148 @@
         </span>
       </template>
     </el-dialog>
+    <el-dialog title="预览监控" v-model="isShowPreviewDialog" width="90vh">
+      <template #header>
+        <span class="dialog-footer">
+          <el-button type="primary" size="small" @click="openToNewWindow">新窗口预览</el-button>
+        </span>
+      </template>
+      <iframe v-if="isShowPreviewDialog" :src="previewUrl" style="width: 100%; height: 50vh"></iframe>
+    </el-dialog>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { reactive, ref } from 'vue';
-import topoApi from '/@/api/configuration';
-import api from '/@/api/projects';
-import { useSearch } from '/@/hooks/useCommon';
-import { useRoute } from 'vue-router';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { reactive, ref } from "vue";
+import api from "/@/api/projects";
+import { useRoute } from "vue-router";
+import { dayjs, ElMessage, ElMessageBox } from "element-plus";
+import axios from "axios";
+import { getMediaOrigin } from "/@/utils/origin";
 const route = useRoute();
 
-const props = defineProps({ resourcesTypes: Number })
-const isShowDialog = ref(false)
-const options = ref<any[]>([])
-const resourcesTypes = props.resourcesTypes
-const projectsCode = route.params.id
+const props = defineProps({ resourcesTypes: Number });
+const isShowDialog = ref(false);
+const isShowPreviewDialog = ref(false);
+const loading = ref(false);
+const previewUrl = ref("");
+const options = ref<any[]>([]);
+const tableData = ref<any[]>([]);
+const channels = ref<any[]>([]);
+const resourcesTypes = props.resourcesTypes!;
+const projectsCode = route.params.id;
 
 const baseForm = {
-  resourcesKey: '',
+  resourcesKey: "",
   projectsCode,
-  resourcesTypes
-}
+  resourcesTypes,
+};
 
 const form = reactive({
-  ...baseForm
-})
-
-const { params, tableData, getList, loading } = useSearch<any[]>(topoApi.getList, 'data', { ids: [], status: undefined });
+  ...baseForm,
+});
 
 getOptions();
-getSourceList();
 
 function getSourceList() {
   api.getProjectResourcesByCode({ projectsCode }).then((res: any) => {
-    params.ids = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes).map((item: any) => item.resourcesKey)
-    if (!params.ids.length) {
-      params.pageNum = 1
-      tableData.value = []
-    } else {
-      getList();
-    }
-  })
+    const ids = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes).map((item: any) => item.resourcesKey);
+
+    const tableDataList: any[] = [];
+
+    ids.forEach((id: string) => {
+      const [deviceID, channelID] = id.split("-");
+      const channel = channels.value.find((channel: any) => channel.ID === deviceID && channel.DeviceID === channelID);
+      if (channel) {
+        options.value.find((item: any) => item.value === id).disabled = true;
+        tableDataList.push(channel);
+      }
+    });
+
+    tableData.value = tableDataList;
+  });
 }
 
 function getOptions() {
-  topoApi.getList({ status: -1, pageNum: 1, pageSize: 500 }).then((res: any) => {
-    options.value = (res?.data || []).map((item: any) => ({ label: item.name, value: item.id }))
-  })
+  // 获取流媒体设备列表,拼接成通道列表 设备ID-通道ID
+  axios.get(getMediaOrigin("/gb28181/api/list?format=json")).then((res: any) => {
+    // console.log(res.data.data);
+    const optionsList: any[] = [];
+    const channelsList: any[] = [];
+    res.data.data.forEach((item: any) => {
+      item.Channels.forEach((channel: any) => {
+        optionsList.push({ label: item.Name + " - " + channel.Name, value: item.ID + "-" + channel.DeviceID });
+        channelsList.push({
+          ...channel,
+          DeviceName: item.Name,
+          NetAddr: item.NetAddr,
+          RegisterTime: item.RegisterTime,
+          UpdateTime: item.UpdateTime,
+          LastKeepaliveAt: item.LastKeepaliveAt,
+          ID: item.ID,
+          DeviceID: channel.DeviceID,
+        });
+      });
+    });
+    options.value = optionsList;
+    channels.value = channelsList;
+    getSourceList();
+  });
 }
 
 function onCancel() {
-  Object.assign(form, baseForm)
-  isShowDialog.value = false
+  Object.assign(form, baseForm);
+  isShowDialog.value = false;
 }
 
 function onSubmit() {
-  if (!form.resourcesKey) return ElMessage('请先选择监控')
-  api.bindResources(form).then((res: any) => {
-    onCancel()
-    getSourceList()
-    ElMessage.success('添加成功')
-  })
+  if (!form.resourcesKey) return ElMessage("请先选择监控");
+  api.bindResources(form).then(() => {
+    onCancel();
+    getSourceList();
+    ElMessage.success("添加成功");
+  });
 }
 
 function addDevice() {
-  isShowDialog.value = true
+  isShowDialog.value = true;
+}
+
+function view(row: any) {
+  previewUrl.value = import.meta.env.VITE_SERVER_ORIGIN + "/plugin/media/index.html?ID=" + row.ID + "&DeviceID=" + row.DeviceID + "#/screen-preview";
+  isShowPreviewDialog.value = true;
+}
+
+function openToNewWindow() {
+  window.open(previewUrl.value);
+  isShowPreviewDialog.value = false;
 }
 
 function onDel(row: any) {
-  ElMessageBox.confirm(`确定要解绑该监控?`, '提示', {
-    confirmButtonText: '确认',
-    cancelButtonText: '取消',
-    type: 'warning',
+  ElMessageBox.confirm(`确定要解绑该监控?`, "提示", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
   }).then(() => {
-    api.unbindResources({
-      projectsCode,
-      resourcesTypes,
-      resourcesKey: row.id
-    }).then(() => {
-      getSourceList()
-      ElMessage.success('解绑成功')
-    })
+    api
+      .unbindResources({
+        projectsCode,
+        resourcesTypes,
+        resourcesKey: row.ID + "-" + row.DeviceID,
+      })
+      .then(() => {
+        getSourceList();
+        ElMessage.success("解绑成功");
+      });
   });
 }
 
+function formatTime(row: any, column: any) {
+  return dayjs(row[column.property]).format("YYYY-MM-DD HH:mm:ss");
+}
+
+function formatLiveStatus(row: any, column: any) {
+  // 0 代表空闲,1 代表正在调用 invite,2 代表正在拉流
+  return row[column.property] === 0 ? "空闲" : row[column.property] === 1 ? "调用中" : "拉流中";
+}
 </script>