Explorar el Código

Merge branch 'master' of http://1.94.244.160:3000/shuzhihua/pms-iot-vue

Zimo hace 1 día
padre
commit
4c66bfa5c4

+ 2 - 2
index.html

@@ -8,9 +8,9 @@
     <script type="text/javascript" src="/js/base64.min.js"></script>
     <script src="https://api.map.baidu.com/api?v=3.0&ak=c0crhdxQ5H7WcqbcazGr7mnHrLa4GmO0"></script>
 
-    <script src="/js/jessibuca-pro/jessibuca-pro.js"></script>
+    <!-- <script src="/js/jessibuca-pro/jessibuca-pro.js"></script> -->
     <!-- <script type="text/javascript" src="/js/EasyWasmPlayer.js"></script> -->
-    <script type="text/javascript" src="/js/EasyPlayer/EasyPlayer-pro.js"></script>
+    <!-- <script type="text/javascript" src="/js/EasyPlayer/EasyPlayer-pro.js"></script> -->
     <meta charset="UTF-8" />
     <link rel="icon" href="/favicon.ico" />
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />

+ 29 - 42
src/views/pms/device/index.vue

@@ -194,7 +194,7 @@
               >
                 <el-popover placement="bottom" :width="250" trigger="click">
                   <template #reference>
-                    <div class="flex items-center" @click.stop>
+                    <div class="flex items-center cursor-pointer" @click.stop>
                       <span> 油服编码 </span>
                       <Icon
                         icon="ep:arrow-down"
@@ -232,7 +232,7 @@
               >
                 <el-popover placement="bottom" :width="250" trigger="click">
                   <template #reference>
-                    <div class="flex items-center" @click.stop>
+                    <div class="flex items-center cursor-pointer gap-1" @click.stop>
                       <span> 历史编码 </span> <Icon icon="ep:arrow-down" />
                     </div>
                   </template>
@@ -265,7 +265,7 @@
               >
                 <el-popover placement="bottom" :width="250" trigger="click">
                   <template #reference>
-                    <div class="flex items-center" @click.stop>
+                    <div class="flex items-center cursor-pointer gap-1" @click.stop>
                       <span> 设备名称 </span> <Icon icon="ep:arrow-down" />
                     </div>
                   </template>
@@ -297,7 +297,7 @@
               >
                 <el-popover placement="bottom" :width="250" trigger="click">
                   <template #reference>
-                    <div class="flex items-center" @click.stop>
+                    <div class="flex items-center cursor-pointer gap-1" @click.stop>
                       <span> 设备号 </span> <Icon icon="ep:arrow-down" />
                     </div>
                   </template>
@@ -328,7 +328,7 @@
               >
                 <el-popover placement="bottom" :width="250" trigger="click">
                   <template #reference>
-                    <div class="flex items-center" @click.stop>
+                    <div class="flex items-center cursor-pointer gap-1" @click.stop>
                       <span> {{ t('iotDevice.dept') }} </span> <Icon icon="ep:arrow-down" />
                     </div>
                   </template>
@@ -359,7 +359,7 @@
             <template #header>
               <div class="flex items-center justify-center pb-[1px]">
                 <el-dropdown @command="handleCommand">
-                  <span class="text-[#ad9399] text-[12px] cursor-pointer flex items-center">
+                  <span class="text-[#ad9399] text-[12px] cursor-pointer flex items-center gap-1">
                     <span> 设备状态 </span> <Icon icon="ep:arrow-down" />
                   </span>
                   <template #dropdown>
@@ -388,7 +388,7 @@
             <template #header>
               <div class="flex items-center justify-center pb-[1px]">
                 <el-dropdown @command="handleAssetProperty">
-                  <span class="text-[#ad9399] text-[12px] cursor-pointer flex items-center">
+                  <span class="text-[#ad9399] text-[12px] cursor-pointer flex items-center gap-1">
                     <span> 资产性质 </span> <Icon icon="ep:arrow-down" />
                   </span>
                   <template #dropdown>
@@ -421,7 +421,7 @@
               >
                 <el-popover placement="bottom" :width="250" trigger="click">
                   <template #reference>
-                    <div class="flex items-center" @click.stop>
+                    <div class="flex items-center cursor-pointer gap-1" @click.stop>
                       <span> {{ t('iotDevice.assetClass') }} </span> <Icon icon="ep:arrow-down" />
                     </div>
                   </template>
@@ -447,7 +447,7 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="flex items-center" @click.stop>
+                  <div class="flex items-center cursor-pointer gap-1" @click.stop>
                     <span> 车牌号 </span> <Icon icon="ep:arrow-down" />
                   </div>
                 </template>
@@ -468,7 +468,7 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="flex items-center" @click.stop>
+                  <div class="flex items-center cursor-pointer gap-1" @click.stop>
                     <span>
                       {{ t('deviceForm.mfg') }}
                     </span>
@@ -476,31 +476,15 @@
                   </div>
                 </template>
                 <div class="flex items-center gap-2">
-                  <!-- <el-input
-                    v-model="queryParams.manufacturer"
-                    placeholder="请输入制造商"
-                    style="width: 180px"
-                    clearable
-                  />
-                  <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button> -->
-                  <!-- <el-tree-select
-                    :teleported="false"
-                    v-model="queryParams.manufacturerId"
-                    :data="brandList"
-                    :props="defaultProps"
-                    check-strictly
-                    node-key="id"
-                    filterable
-                    placeholder="请选择制造商"
-                    @change="handleQuery"
-                    style="width: 220px"
-                  /> -->
                   <el-select
                     v-model="queryParams.manufacturerId"
                     style="width: 220px"
                     filterable
                     :teleported="false"
+                    :loading="brandList.length === 0"
+                    loading-text="数据加载中..."
                     @change="handleQuery"
+                    @visible-change="handleBrandChange"
                   >
                     <el-option
                       v-for="item in brandList"
@@ -523,7 +507,7 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="flex items-center" @click.stop>
+                  <div class="flex items-center cursor-pointer gap-1" @click.stop>
                     <span> {{ t('deviceForm.brand') }} </span> <Icon icon="ep:arrow-down" />
                   </div>
                 </template>
@@ -548,7 +532,7 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="flex items-center" @click.stop>
+                  <div class="flex items-center cursor-pointer gap-1" @click.stop>
                     <span> {{ t('deviceForm.model') }} </span> <Icon icon="ep:arrow-down" />
                   </div>
                 </template>
@@ -573,7 +557,7 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="flex items-center" @click.stop>
+                  <div class="flex items-center cursor-pointer gap-1" @click.stop>
                     <span> {{ t('devicePerson.rp') }} </span> <Icon icon="ep:arrow-down" />
                   </div>
                 </template>
@@ -598,7 +582,7 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="flex items-center">
+                  <div class="flex items-center cursor-pointer gap-1">
                     <span> {{ t('deviceForm.useProject') }} </span> <Icon icon="ep:arrow-down" />
                   </div>
                 </template>
@@ -623,8 +607,9 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="flex items-center">
-                    <span> {{ t('deviceForm.assetOwner') }} </span> <Icon icon="ep:arrow-down" />
+                  <div class="flex items-center cursor-pointer gap-1">
+                    <span> {{ t('deviceForm.assetOwner') }} </span>
+                    <Icon icon="ep:arrow-down" />
                   </div>
                 </template>
                 <div class="flex items-center gap-2">
@@ -643,8 +628,8 @@
             <template #header>
               <el-popover placement="bottom" :width="250" trigger="click">
                 <template #reference>
-                  <div class="table-header-flex">
-                    <span>所在地点</span>
+                  <div class="table-header-flex cursor-pointer">
+                    <span class="pr-1">所在地点</span>
                     <Icon icon="ep:arrow-down" @click.stop />
                   </div>
                 </template>
@@ -708,7 +693,7 @@ import download from '@/utils/download'
 import { IotDeviceApi, IotDeviceVO } from '@/api/pms/device'
 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
 import DeptTree from '@/views/system/user/DeptTree.vue'
-import { useCache } from '@/hooks/web/useCache'
+
 import { buildSortingField } from '@/utils'
 import { defaultProps, handleTree } from '@/utils/tree'
 import * as ProductClassifyApi from '@/api/pms/productclassify'
@@ -877,8 +862,10 @@ const handleDetail = (id: number) => {
   push({ name: 'DeviceDetailInfo', params: { id } })
 }
 
-const handleUpload = (id: number) => {
-  push({ name: 'DeviceUpload', params: { id } })
+const handleBrandChange = async (visible) => {
+  if (visible && brandList.value.length === 0) {
+    brandList.value = await IotDeviceApi.getDeviceBrand()
+  }
 }
 
 /** 导出按钮操作 */
@@ -895,14 +882,14 @@ const handleExport = async () => {
     exportLoading.value = false
   }
 }
-const { wsCache } = useCache()
+
 /** 初始化 **/
 onMounted(async () => {
   productClassifyList.value = handleTree(
     await ProductClassifyApi.IotProductClassifyApi.getSimpleProductClassifyList()
   )
 
-  brandList.value = await IotDeviceApi.getDeviceBrand()
+  // brandList.value = await IotDeviceApi.getDeviceBrand()
   const sort = {
     field: 'sortColumn',
     order: 'asc'

+ 31 - 10
src/views/pms/video_center/sip/components/player/easy.vue

@@ -32,6 +32,10 @@ const props = defineProps({
   height: {
     type: String,
     default: '630px'
+  },
+  uid: {
+    type: String,
+    default: ''
   }
 })
 const playing = ref(false)
@@ -50,11 +54,27 @@ const config = reactive({
   WCS: true
 })
 
-const playCreate = () => {
+// 动态加载 EasyPlayer
+function loadEasyPlayer() {
+  return new Promise((resolve, reject) => {
+    if (window.EasyPlayerPro) {
+      resolve(window.EasyPlayerPro)
+      return
+    }
+
+    const script = document.createElement('script')
+    script.src = '/js/EasyPlayer/EasyPlayer-pro.js'
+    script.onload = () => resolve(window.EasyPlayerPro)
+    script.onerror = reject
+    document.head.appendChild(script)
+  })
+}
+
+const playCreate = async () => {
   const container = easyPlayerRef.value
   if (!container) return
 
-  const uid = route.params._uid || Date.now() // 使用时间戳作为唯一标识
+  const uid = props.uid || route.params._uid || Date.now() // 使用时间戳作为唯一标识
 
   easyplayer[uid] = new EasyPlayerPro(container, {
     isLive: config.isLive,
@@ -106,8 +126,9 @@ const playCreate = () => {
 }
 
 // 播放视频
-function play(url) {
-  const uid = route.params._uid || Date.now()
+async function play(url) {
+  await loadEasyPlayer()
+  const uid = props.uid || route.params._uid || Date.now() // 使用时间戳作为唯一标识
 
   // 确保容器已准备就绪
   if (!easyPlayerRef.value) {
@@ -141,7 +162,7 @@ function play(url) {
 }
 
 const pause = () => {
-  const uid = route.params._uid || Date.now()
+  const uid = props.uid || route.params._uid || Date.now() // 使用时间戳作为唯一标识
 
   if (easyplayer[uid]) {
     easyplayer[uid].pause()
@@ -151,7 +172,7 @@ const pause = () => {
 
 // 截图
 const screenshot = () => {
-  const uid = route.params._uid || Date.now()
+  const uid = props.uid || route.params._uid || Date.now() // 使用时间戳作为唯一标识
 
   if (easyplayer[uid]) {
     easyplayer[uid].screenshot()
@@ -160,7 +181,7 @@ const screenshot = () => {
 }
 
 const destroy = async () => {
-  const uid = route.params._uid || Date.now()
+  const uid = props.uid || route.params._uid || Date.now() // 使用时间戳作为唯一标识
 
   if (easyplayer[uid]) {
     await easyplayer[uid].destroy().then(() => {
@@ -183,7 +204,7 @@ const onMute = () => {
   //   playInfo.value.setMute(true)
   // }
 
-  const uid = route.params._uid || Date.now()
+  const uid = props.uid || route.params._uid || Date.now() // 使用时间戳作为唯一标识
 
   if (easyplayer[uid]) {
     easyplayer[uid].setMute(true)
@@ -285,8 +306,8 @@ onMounted(() => {
 })
 
 onUnmounted(() => {
-  if (easyplayer[route.params._uid]) {
-    easyplayer[route.params._uid].destroy()
+  if (easyplayer[props.uid || route.params._uid || Date.now()]) {
+    easyplayer[props.uid || route.params._uid || Date.now()].destroy()
   }
   playing.value = false
 })

+ 18 - 1
src/views/pms/video_center/sip/components/player/easyplayer.vue

@@ -36,6 +36,22 @@ const config = reactive({
   WCS: false
 })
 
+// 动态加载 EasyPlayer
+function loadEasyPlayer() {
+  return new Promise((resolve, reject) => {
+    if (window.EasyPlayerPro) {
+      resolve(window.EasyPlayerPro)
+      return
+    }
+
+    const script = document.createElement('script')
+    script.src = '/js/EasyPlayer/EasyPlayer-pro.js'
+    script.onload = () => resolve(window.EasyPlayerPro)
+    script.onerror = reject
+    document.head.appendChild(script)
+  })
+}
+
 // 方法定义
 const play = async (url) => {
   console.log('EasyPlayer 播放视频 url: ', url)
@@ -172,7 +188,8 @@ const handlePtz = (arrow) => {
   }
 }
 
-const playCreate = () => {
+const playCreate = async () => {
+  await loadEasyPlayer()
   const container = easyPlayerRef.value
   if (!container) return
 

+ 30 - 4
src/views/pms/video_center/sip/components/player/player.vue

@@ -7,13 +7,19 @@
       <div class="jb-pro-ptz-btns">
         <div class="jb-pro-ptz-btn">
           <div class="jb-pro-ptz-expand jb-pro-ptz-icon">
-            <div class="jb-pro-ptz-expand-icon-fangda icon" onclick="handlePtzScale('fangda')"></div>
+            <div
+              class="jb-pro-ptz-expand-icon-fangda icon"
+              onclick="handlePtzScale('fangda')"
+            ></div>
             <span class="icon-title-tips">
               <span class="icon-title">放大</span>
             </span>
           </div>
           <div class="jb-pro-ptz-narrow jb-pro-ptz-icon">
-            <div class="jb-pro-ptz-narrow-icon-suoxiao icon" onclick="handlePtzScale('suoxiao')"></div>
+            <div
+              class="jb-pro-ptz-narrow-icon-suoxiao icon"
+              onclick="handlePtzScale('suoxiao')"
+            ></div>
             <span class="icon-title-tips">
               <span class="icon-title">缩小</span>
             </span>
@@ -49,7 +55,10 @@
             </span>
           </div>
           <div class="jb-pro-ptz-focus-near jb-pro-ptz-icon">
-            <div class="jb-pro-ptz-focus-icon-jujiao- icon" onclick="handlePtzScale('jujiao-')"></div>
+            <div
+              class="jb-pro-ptz-focus-icon-jujiao- icon"
+              onclick="handlePtzScale('jujiao-')"
+            ></div>
             <span class="icon-title-tips">
               <span class="icon-title">聚焦-</span>
             </span>
@@ -132,7 +141,8 @@ onBeforeUnmount(() => {
 const currentInstanceId = ref(`player_${Date.now()}_${Math.floor(Math.random() * 10000)}`)
 
 // 初始化
-const init = () => {
+const init = async () => {
+  await loadJessibuca()
   var isSupportWebgpu = 'gpu' in navigator
   if (isSupportWebgpu) {
     console.log('支持webGPU')
@@ -152,6 +162,22 @@ const init = () => {
   })
 }
 
+// 动态加载 Jessibuca
+function loadJessibuca() {
+  return new Promise((resolve, reject) => {
+    if (window.Jessibuca) {
+      resolve(window.Jessibuca)
+      return
+    }
+
+    const script = document.createElement('script')
+    script.src = '/js/jessibuca-pro/jessibuca-pro.js'
+    script.onload = () => resolve(window.Jessibuca)
+    script.onerror = reject
+    document.head.appendChild(script)
+  })
+}
+
 // 初始化播放器
 const initplayer = () => {
   isPlaybackPause.value = false

+ 32 - 5
src/views/pms/video_center/sip/splitview.vue

@@ -84,6 +84,7 @@
                 v-if="videoUrl[i - 1]"
                 :ref="(el) => setPlayerRef(el, i - 1)"
                 :videourl="videoUrl[i - 1]"
+                :uid="playerUids[i - 1]"
                 :playerInfo="
                   playerInfo.filter(
                     (item) =>
@@ -122,6 +123,9 @@ const route = useRoute()
 // refs
 const playerRefs = ref([])
 
+// 为每个播放器生成稳定的 UID
+const playerUids = ref([])
+
 // 数据状态
 const videoUrl = ref([''])
 const spilt = ref(1) //分屏
@@ -291,17 +295,39 @@ const destroyAllPlayersAsync = async () => {
   }
 }
 
+// watch(
+//   () => spilt.value,
+//   async (newSplit, oldSplit) => {
+//     playerUids.value = Array.from({ length: newSplit }, (_, i) => `player_${i}`)
+//     // 先销毁所有现有播放器,确保资源释放
+//     await destroyAllPlayersAsync()
+//     // 如果当前有选中的设备
+//     if (currentDevice.value && availableChannels.value.length > 0) {
+//       await nextTick()
+
+//       // 只加载新出现的播放器,不重新加载已有播放器
+//       await loadAdditionalChannelsForSplit(newSplit, oldSplit)
+//     }
+//   },
+//   { immediate: false }
+// )
+
 watch(
   () => spilt.value,
   async (newSplit, oldSplit) => {
-    // 先销毁所有现有播放器,确保资源释放
-    await destroyAllPlayersAsync()
-    // 如果当前有选中的设备
+    playerUids.value = Array.from({ length: newSplit }, (_, i) => `player_${i}`)
+
+    // 直接重新加载所有视频
     if (currentDevice.value && availableChannels.value.length > 0) {
+      await destroyAllPlayersAsync()
       await nextTick()
 
-      // 只加载新出现的播放器,不重新加载已有播放器
-      await loadAdditionalChannelsForSplit(newSplit, oldSplit)
+      // 重新分配所有通道
+      await assignChannelsToPlayers(availableChannels.value)
+    } else {
+      // 清空所有位置
+      videoUrl.value = Array(newSplit).fill('')
+      playerRefs.value = []
     }
   },
   { immediate: false }
@@ -373,6 +399,7 @@ const handleFullscreenChange = () => {
 // 生命周期钩子
 onMounted(() => {
   checkPlayByParam()
+  playerUids.value = Array.from({ length: spilt.value }, (_, i) => `player_${i}`)
   document.addEventListener('fullscreenchange', handleFullscreenChange)
   document.addEventListener('webkitfullscreenchange', handleFullscreenChange)
   document.addEventListener('mozfullscreenchange', handleFullscreenChange)