|
|
@@ -0,0 +1,459 @@
|
|
|
+<template>
|
|
|
+ <n-form
|
|
|
+ class="tag-info"
|
|
|
+ label-placement="top"
|
|
|
+ label-align="left"
|
|
|
+ require-mark-placement="right"
|
|
|
+ size="medium"
|
|
|
+ flex-1
|
|
|
+ :style="style"
|
|
|
+ v-if="formData.length || groupId"
|
|
|
+ >
|
|
|
+ <n-form-item
|
|
|
+ v-for="(item, i) in formData"
|
|
|
+ :key="i"
|
|
|
+ :label="item.label"
|
|
|
+ :path="item.value"
|
|
|
+ :label-placement="
|
|
|
+ ['video', 'video_s', 'img', 'ball_ptz'].includes(item.type) ? 'top' : 'left'
|
|
|
+ "
|
|
|
+ label-width="auto"
|
|
|
+ show-require-mark
|
|
|
+ label-style="color:#fff"
|
|
|
+ >
|
|
|
+ <template v-if="item.type === 'input'">
|
|
|
+ <n-input v-if="isUpd" style="flex: 1" v-model:value="item.value" />
|
|
|
+ <span v-else color="#8ac5ff">{{ item.value }}</span>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else-if="item.type === 'img'">
|
|
|
+ <PubUpload
|
|
|
+ v-if="isUpd"
|
|
|
+ v-model:fileList="item.fileList"
|
|
|
+ :type="item.type"
|
|
|
+ :isUpd="isUpd"
|
|
|
+ ></PubUpload>
|
|
|
+ <div class="w-full flex flex-col gap-y-4" v-else>
|
|
|
+ <n-image
|
|
|
+ v-for="(img, i) in item.fileList"
|
|
|
+ class="w-full"
|
|
|
+ :key="i"
|
|
|
+ :img-props="{ className: 'w-full' }"
|
|
|
+ :src="img.url"
|
|
|
+ :render-toolbar="renderToolbar"
|
|
|
+ />
|
|
|
+ <n-empty v-if="!item.fileList.length" size="large" description="暂无图片" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="item.type === 'video'">
|
|
|
+ <PubUpload
|
|
|
+ v-if="isUpd"
|
|
|
+ v-model:fileList="item.fileList"
|
|
|
+ :type="item.type"
|
|
|
+ :isUpd="isUpd"
|
|
|
+ ></PubUpload>
|
|
|
+
|
|
|
+ <div class="tag-info-video-wrapper" v-else>
|
|
|
+ <PubPlayer
|
|
|
+ ref="pubPlayerRefs"
|
|
|
+ :id="dplayerId + video.id"
|
|
|
+ v-for="video in item.fileList"
|
|
|
+ :key="video.url"
|
|
|
+ :url="video.url"
|
|
|
+ v-bind="$attrs"
|
|
|
+ />
|
|
|
+ <div class="w-full flex justify-center">
|
|
|
+ <n-empty v-if="!item.fileList.length" size="large" description="暂无视频" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="item.type === 'video_s'">
|
|
|
+ <n-input v-if="isUpd" style="flex: 1" v-model:value="item.value" />
|
|
|
+ <UseFullscreen
|
|
|
+ v-if="!isUpd && resizeViode"
|
|
|
+ class="flex-1"
|
|
|
+ v-slot="{ toggle, isFullscreen }"
|
|
|
+ >
|
|
|
+ <div class="relative flex-1 h-full popover-screen-full-target">
|
|
|
+ <!-- 全屏按钮 -->
|
|
|
+ <div
|
|
|
+ v-if="item.value"
|
|
|
+ class="all-screen popover-screen-full-trigger"
|
|
|
+ cursor-pointer
|
|
|
+ z-99
|
|
|
+ absolute
|
|
|
+ right-8px
|
|
|
+ top-8px
|
|
|
+ >
|
|
|
+ <Icon
|
|
|
+ icon="icon-park-outline:off-screen"
|
|
|
+ color="#8ac5ff"
|
|
|
+ width="25"
|
|
|
+ height="25"
|
|
|
+ v-if="isFullscreen"
|
|
|
+ @click="toggle"
|
|
|
+ />
|
|
|
+ <Icon
|
|
|
+ icon="iconamoon:screen-full"
|
|
|
+ color="#8ac5ff"
|
|
|
+ width="25"
|
|
|
+ height="25"
|
|
|
+ @click="toggle"
|
|
|
+ v-else
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="item.value && item.loading"
|
|
|
+ w-full
|
|
|
+ h-full
|
|
|
+ flex
|
|
|
+ flex-justify-center
|
|
|
+ flex-items-center
|
|
|
+ absolute
|
|
|
+ >
|
|
|
+ <Icon icon="loading" width="30px" height="30px" color="#8ac5ff" />
|
|
|
+ </div>
|
|
|
+ <pub-video
|
|
|
+ v-if="item.value"
|
|
|
+ class="h-full"
|
|
|
+ :newClass="[
|
|
|
+ 'tag_video' + item.id,
|
|
|
+ videoClass,
|
|
|
+ isFullscreen ? 'h-full w-auto! mx-0 mx-auto' : ''
|
|
|
+ ]"
|
|
|
+ />
|
|
|
+ <div v-else class="w-full flex justify-center">
|
|
|
+ <n-empty v-if="!item.fileList.length" size="large" description="暂无" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </UseFullscreen>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="item.type === 'ball_ptz'">
|
|
|
+ <div class="w-full" v-for="(v, ii) in item.value" :key="ii">
|
|
|
+ <template v-if="isUpd || groupId">
|
|
|
+ <n-input style="flex: 1" v-model:value="v.url" />
|
|
|
+ <n-divider dashed>
|
|
|
+ <span class="text-12px text-#ccc">配置项</span>
|
|
|
+ </n-divider>
|
|
|
+ <n-grid x-gap="12" :cols="2">
|
|
|
+ <n-gi>
|
|
|
+ <n-form-item
|
|
|
+ show-require-mark
|
|
|
+ label-style="color:#fff"
|
|
|
+ label-align="left"
|
|
|
+ label-placement="left"
|
|
|
+ label="IP"
|
|
|
+ >
|
|
|
+ <n-input v-model:value="v.ip" plaeceholder="请输入" />
|
|
|
+ </n-form-item>
|
|
|
+ </n-gi>
|
|
|
+ <n-gi>
|
|
|
+ <n-form-item
|
|
|
+ show-require-mark
|
|
|
+ label-style="color:#fff"
|
|
|
+ label-align="left"
|
|
|
+ label-placement="left"
|
|
|
+ label="端口"
|
|
|
+ >
|
|
|
+ <n-input v-model:value="v.port" plaeceholder="请输入" />
|
|
|
+ </n-form-item>
|
|
|
+ </n-gi>
|
|
|
+ <n-gi>
|
|
|
+ <n-form-item
|
|
|
+ show-require-mark
|
|
|
+ label-style="color:#fff"
|
|
|
+ label-align="left"
|
|
|
+ label-placement="left"
|
|
|
+ label="用户名"
|
|
|
+ >
|
|
|
+ <n-input v-model:value="v.user" plaeceholder="请输入" />
|
|
|
+ </n-form-item>
|
|
|
+ </n-gi>
|
|
|
+ <n-gi>
|
|
|
+ <n-form-item
|
|
|
+ show-require-mark
|
|
|
+ label-style="color:#fff"
|
|
|
+ label-align="left"
|
|
|
+ label-placement="left"
|
|
|
+ label="密码"
|
|
|
+ >
|
|
|
+ <n-input
|
|
|
+ type="password"
|
|
|
+ show-password-on="mousedown"
|
|
|
+ v-model:value="v.password"
|
|
|
+ plaeceholder="请输入"
|
|
|
+ />
|
|
|
+ </n-form-item>
|
|
|
+ </n-gi>
|
|
|
+ </n-grid>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <UseFullscreen class="flex-1" v-slot="{ toggle, isFullscreen }">
|
|
|
+ <div
|
|
|
+ :class="[
|
|
|
+ 'relative flex-1 h-full popover-screen-full-target overflow-hidden',
|
|
|
+ isFullscreen ? 'fullscreen' : ''
|
|
|
+ ]"
|
|
|
+ v-if="resizeViode"
|
|
|
+ >
|
|
|
+ <!-- 全屏按钮 -->
|
|
|
+ <div
|
|
|
+ v-if="item.value"
|
|
|
+ class="all-screen popover-screen-full-trigger"
|
|
|
+ cursor-pointer
|
|
|
+ z-99
|
|
|
+ absolute
|
|
|
+ right-8px
|
|
|
+ top-8px
|
|
|
+ >
|
|
|
+ <Icon
|
|
|
+ icon="icon-park-outline:off-screen"
|
|
|
+ color="#8ac5ff"
|
|
|
+ width="25"
|
|
|
+ height="25"
|
|
|
+ v-if="isFullscreen"
|
|
|
+ @click="toggle"
|
|
|
+ />
|
|
|
+ <Icon
|
|
|
+ icon="iconamoon:screen-full"
|
|
|
+ color="#8ac5ff"
|
|
|
+ width="25"
|
|
|
+ height="25"
|
|
|
+ @click="toggle"
|
|
|
+ v-else
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="item.value && item.loading"
|
|
|
+ w-full
|
|
|
+ h-full
|
|
|
+ flex
|
|
|
+ flex-justify-center
|
|
|
+ flex-items-center
|
|
|
+ absolute
|
|
|
+ >
|
|
|
+ <Icon icon="loading" width="30px" height="30px" color="#8ac5ff" />
|
|
|
+ </div>
|
|
|
+ <pub-video
|
|
|
+ v-if="item.value"
|
|
|
+ class="h-full"
|
|
|
+ :newClass="[
|
|
|
+ 'tag_video' + item.id,
|
|
|
+ videoClass,
|
|
|
+ isFullscreen ? 'h-full w-auto! mx-0 mx-auto' : ''
|
|
|
+ ]"
|
|
|
+ />
|
|
|
+ <div v-else class="w-full flex justify-center">
|
|
|
+ <n-empty v-if="!item.fileList.length" size="large" description="暂无" />
|
|
|
+ </div>
|
|
|
+ <!-- <joystick :KeyId="item.key" class-name="transform-scale-40" :index="ii" /> -->
|
|
|
+ </div>
|
|
|
+ </UseFullscreen>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </n-form-item>
|
|
|
+ </n-form>
|
|
|
+ <n-empty v-else description="请选择标签组"></n-empty>
|
|
|
+</template>
|
|
|
+<script setup>
|
|
|
+ // import Joystick from '../pages/Glance/components/Joystick.vue'
|
|
|
+ import { NInput, NForm, NFormItem, NGrid, NGi, NDivider, NEmpty, NImage } from 'naive-ui'
|
|
|
+ import _ from 'lodash-es'
|
|
|
+ import { uuid } from '@/utils'
|
|
|
+ import 'xgplayer/dist/index.min.css'
|
|
|
+ import useWorker from 'omnimatrix-video-player'
|
|
|
+ import { UseFullscreen } from '@vueuse/components'
|
|
|
+ import { useOutsideSystemStore } from '@/stores/modules/system.js'
|
|
|
+
|
|
|
+ const useSystem = useOutsideSystemStore()
|
|
|
+ const pubPlayerRefs = ref([])
|
|
|
+ const { API_GETGROUP_FORMAT_GET } = useRequest()
|
|
|
+ const props = defineProps({
|
|
|
+ tag: {
|
|
|
+ type: Object
|
|
|
+ },
|
|
|
+ content: {
|
|
|
+ type: Array
|
|
|
+ },
|
|
|
+ isUpd: {
|
|
|
+ type: Boolean
|
|
|
+ },
|
|
|
+ groupId: {
|
|
|
+ type: String
|
|
|
+ },
|
|
|
+ dplayerId: {
|
|
|
+ type: String,
|
|
|
+ default: 'myDplayer'
|
|
|
+ },
|
|
|
+ style: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({
|
|
|
+ maxHeight: '400px',
|
|
|
+ overflow: 'auto'
|
|
|
+ })
|
|
|
+ },
|
|
|
+ videoClass: {
|
|
|
+ type: String
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const formData = ref([])
|
|
|
+ const originalFormData = ref([])
|
|
|
+
|
|
|
+ const renderToolbar = (props) => {
|
|
|
+ return h('div', { class: 'flex gap-2' }, [
|
|
|
+ props.nodes.rotateCounterclockwise,
|
|
|
+ props.nodes.rotateClockwise,
|
|
|
+ props.nodes.resizeToOriginalSize,
|
|
|
+ props.nodes.zoomOut,
|
|
|
+ props.nodes.zoomIn,
|
|
|
+ props.nodes.close
|
|
|
+ ])
|
|
|
+ }
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => props.isUpd,
|
|
|
+ (isUpd) => {
|
|
|
+ if (!isUpd) {
|
|
|
+ update()
|
|
|
+ } else {
|
|
|
+ clearWorker()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => props.groupId,
|
|
|
+ (newV) => {
|
|
|
+ API_GETGROUP_FORMAT_GET({ GroupId: newV }).then((res) => {
|
|
|
+ formData.value = res.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ value:
|
|
|
+ item.type === 'ball_ptz'
|
|
|
+ ? [
|
|
|
+ {
|
|
|
+ url: '',
|
|
|
+ ip: '',
|
|
|
+ port: '8000',
|
|
|
+ user: '',
|
|
|
+ password: ''
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ : '',
|
|
|
+ fileList: []
|
|
|
+ }))
|
|
|
+ })
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ const resizeViode = ref(true)
|
|
|
+ // 处理数据回显
|
|
|
+ const getGroupFormat = (tagCon) => {
|
|
|
+ if (!props.tag) return
|
|
|
+ API_GETGROUP_FORMAT_GET({ GroupId: props.tag.groupId }).then((res) => {
|
|
|
+ res.map((format) => {
|
|
|
+ tagCon.forEach((item) => {
|
|
|
+ if (format.key === item.key) {
|
|
|
+ Object.assign(format, item)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ const formatRes = res.map((item) => {
|
|
|
+ let typeWay = ['img', 'video'].includes(item.type)
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ id: uuid(),
|
|
|
+ fileList: typeWay
|
|
|
+ ? item.value.map((url) => ({
|
|
|
+ id: uuid(),
|
|
|
+ name: url.split('/')[url.split('/').length - 1],
|
|
|
+ url,
|
|
|
+ path: url,
|
|
|
+ fullPath: '/' + url.split('/')[url.split('/').length - 1]
|
|
|
+ }))
|
|
|
+ : [],
|
|
|
+ value: !typeWay ? (item.type === 'ball_ptz' ? item.value : item.value.join('')) : '',
|
|
|
+ loading: false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ formData.value = formatRes
|
|
|
+ originalFormData.value = _.cloneDeepWith(formatRes)
|
|
|
+ initVideo(formData.value)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const workerObj = ref([])
|
|
|
+ provide('workerObj', workerObj)
|
|
|
+ const initVideo = async (arr) => {
|
|
|
+ nextTick(() => {
|
|
|
+ arr.forEach((item) => {
|
|
|
+ if (!item.value) return
|
|
|
+ const isBallPtz = item.type === 'ball_ptz'
|
|
|
+ if (item.type === 'video_s' || isBallPtz) {
|
|
|
+ item.loading = true
|
|
|
+ const url = `wss://${useSystem.ipF}/VideoShow/Rtsp?rtsp=${isBallPtz ? item.value[0].url : item.value}`
|
|
|
+ workerObj.value.push(
|
|
|
+ useWorker(url, '.tag_video' + item.id, () => {
|
|
|
+ item.loading = false
|
|
|
+ })
|
|
|
+ )
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const clearWorker = () => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ resizeViode.value = false
|
|
|
+ for (let i = 0; i < workerObj.value.length; i++) {
|
|
|
+ workerObj.value[i].worker.terminate()
|
|
|
+ workerObj.value[i].WebSocketWork.terminate()
|
|
|
+ }
|
|
|
+ workerObj.value = []
|
|
|
+ setTimeout(() => {
|
|
|
+ resizeViode.value = true
|
|
|
+ resolve()
|
|
|
+ }, 60)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭线程
|
|
|
+ // const closeWorker = (index) => {
|
|
|
+ // if (!workerObj[index]) return
|
|
|
+ // workerObj[index].terminate()
|
|
|
+ // }
|
|
|
+ const update = () => {
|
|
|
+ getGroupFormat(props.content)
|
|
|
+ }
|
|
|
+ onMounted(() => {
|
|
|
+ update()
|
|
|
+ })
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ clearWorker()
|
|
|
+ pubPlayerRefs.value?.forEach((playerRef) => {
|
|
|
+ playerRef.player.pause()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ defineExpose({
|
|
|
+ formData,
|
|
|
+ originalFormData,
|
|
|
+ update,
|
|
|
+ workerObj
|
|
|
+ })
|
|
|
+</script>
|
|
|
+<style scoped lang="scss">
|
|
|
+ .tag-info {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ &-video-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 15px;
|
|
|
+ overflow-x: auto;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|