视频播放调试完成
This commit is contained in:
parent
a40836ea9b
commit
8cc362bc82
|
|
@ -9,11 +9,13 @@
|
|||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@kjgl77/datav-vue3": "^1.7.4",
|
||||
"@vueuse/core": "^13.4.0",
|
||||
"axios": "1.7.7",
|
||||
"codess": "^1.1.5",
|
||||
"copy-to-clipboard": "3.3.3",
|
||||
"cropperjs": "1.6.2",
|
||||
"echarts": "5.5.1",
|
||||
"flv.js": "^1.6.2",
|
||||
"glob": "11.0.0",
|
||||
"mime": "4.0.4",
|
||||
"mitt": "3.0.1",
|
||||
|
|
@ -31,6 +33,7 @@
|
|||
"vuedraggable": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@vitejs/plugin-vue": "5.1.2",
|
||||
"naive-ui": "^2.42.0",
|
||||
"vite": "5.4.1"
|
||||
|
|
@ -627,6 +630,74 @@
|
|||
"@vueuse/core": "^10.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@kjgl77/datav-vue3/node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/core": {
|
||||
"version": "10.11.1",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/core/-/core-10.11.1.tgz",
|
||||
"integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.20",
|
||||
"@vueuse/metadata": "10.11.1",
|
||||
"@vueuse/shared": "10.11.1",
|
||||
"vue-demi": ">=0.14.8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/metadata": {
|
||||
"version": "10.11.1",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/metadata/-/metadata-10.11.1.tgz",
|
||||
"integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@kjgl77/datav-vue3/node_modules/@vueuse/shared": {
|
||||
"version": "10.11.1",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/shared/-/shared-10.11.1.tgz",
|
||||
"integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue-demi": ">=0.14.8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@kjgl77/datav-vue3/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
|
@ -914,9 +985,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vicons/ionicons5": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vicons/ionicons5/-/ionicons5-0.13.0.tgz",
|
||||
"integrity": "sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
|
|
@ -1029,90 +1107,41 @@
|
|||
"integrity": "sha512-L2MCDD8l7yC62Te5UUyPVpmexhL9ipVnYRw9CsWfm/BGRL5FwDX4a25bcJ/OJSD3+Hx+k/a8LDKcG2AFdJV3BA=="
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "10.11.1",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/core/-/core-10.11.1.tgz",
|
||||
"integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
|
||||
"version": "13.4.0",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/core/-/core-13.4.0.tgz",
|
||||
"integrity": "sha512-OnK7zW3bTq/QclEk17+vDFN3tuAm8ONb9zQUIHrYQkkFesu3WeGUx/3YzpEp+ly53IfDAT9rsYXgGW6piNZC5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.20",
|
||||
"@vueuse/metadata": "10.11.1",
|
||||
"@vueuse/shared": "10.11.1",
|
||||
"vue-demi": ">=0.14.8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/core/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "13.4.0",
|
||||
"@vueuse/shared": "13.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "10.11.1",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/metadata/-/metadata-10.11.1.tgz",
|
||||
"integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
|
||||
"version": "13.4.0",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/metadata/-/metadata-13.4.0.tgz",
|
||||
"integrity": "sha512-CPDQ/IgOeWbqItg1c/pS+Ulum63MNbpJ4eecjFJqgD/JUCJ822zLfpw6M9HzSvL6wbzMieOtIAW/H8deQASKHg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "10.11.1",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/shared/-/shared-10.11.1.tgz",
|
||||
"integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
|
||||
"version": "13.4.0",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@vueuse/shared/-/shared-13.4.0.tgz",
|
||||
"integrity": "sha512-+AxuKbw8R1gYy5T21V5yhadeNM7rJqb4cPaRI9DdGnnNl3uqXh+unvQ3uCaA2DjYLbNr1+l7ht/B4qEsRegX6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue-demi": ">=0.14.8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
|
|
@ -1869,6 +1898,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
|
||||
|
|
@ -2014,6 +2049,16 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/flv.js": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/flv.js/-/flv.js-1.6.2.tgz",
|
||||
"integrity": "sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"es6-promise": "^4.2.8",
|
||||
"webworkify-webpack": "^2.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
|
|
@ -4464,6 +4509,12 @@
|
|||
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="
|
||||
},
|
||||
"node_modules/webworkify-webpack": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz",
|
||||
"integrity": "sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@kjgl77/datav-vue3": "^1.7.4",
|
||||
"@vueuse/core": "^13.4.0",
|
||||
"axios": "1.7.7",
|
||||
"codess": "^1.1.5",
|
||||
"copy-to-clipboard": "3.3.3",
|
||||
"cropperjs": "1.6.2",
|
||||
"echarts": "5.5.1",
|
||||
"flv.js": "^1.6.2",
|
||||
"glob": "11.0.0",
|
||||
"mime": "4.0.4",
|
||||
"mitt": "3.0.1",
|
||||
|
|
@ -34,6 +36,7 @@
|
|||
"vuedraggable": "4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@vitejs/plugin-vue": "5.1.2",
|
||||
"naive-ui": "^2.42.0",
|
||||
"vite": "5.4.1"
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<!-- 模态框通用样式 -->
|
||||
<n-card
|
||||
size="huge"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
:bordered="false"
|
||||
class="modal-container"
|
||||
:style="{ width: width, height: height }"
|
||||
>
|
||||
<n-flex justify="space-between" style="height: 40px">
|
||||
<n-gradient-text
|
||||
style="font-size: 20px; font-weight: 600; letter-spacing: 2px"
|
||||
gradient="linear-gradient(90deg, #CDF7FD 0%, #DAFAFE 20%, #CDF7FD 40%, #DAFAFE 60%, #CDF7FD 80%, #DAFAFE 100%)"
|
||||
>
|
||||
{{ modalTitle }}
|
||||
</n-gradient-text>
|
||||
|
||||
<img
|
||||
class="close-icon"
|
||||
src="@/assets/home-imgs/close.png"
|
||||
@click="onHandleCloseModal"
|
||||
/>
|
||||
</n-flex>
|
||||
|
||||
<div class="modal-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
width: {
|
||||
type: String,
|
||||
default: '80%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
const emits = defineEmits(['onHandleCloseModal'])
|
||||
|
||||
// 关闭模态框
|
||||
const onHandleCloseModal = () => {
|
||||
emits('onHandleCloseModal')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: url('@/assets/home-imgs/modal-bg.png') no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
flex: 1;
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<!-- 视频播放器 Flv格式视频 -->
|
||||
<video
|
||||
:id="videoId"
|
||||
autoplay
|
||||
controls
|
||||
@timeupdate="progress($event)"
|
||||
style="width: 100%; height: 100%; border-radius: 12px"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import flv from 'flv.js'
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
|
||||
let flvPlayerList = []
|
||||
let replayCount = 0
|
||||
|
||||
let errorMsg = ref('')
|
||||
const playUrl = ref('')
|
||||
const dfp = defineProps({
|
||||
cameraNode: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
steamType: {
|
||||
type: String,
|
||||
default: 'HTTP-FLV',
|
||||
},
|
||||
videoId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const playvideo = () => {
|
||||
errorMsg.value = ''
|
||||
if (flv.isSupported()) {
|
||||
stopvideo()
|
||||
|
||||
nextTick(() => {
|
||||
let videoElement = document.getElementById(dfp.videoId)
|
||||
|
||||
console.log(videoElement, 'videoElement')
|
||||
videoElement.contorls = true
|
||||
let flvPlayer = flv.createPlayer({
|
||||
type: 'flv',
|
||||
isLive: true,
|
||||
hasAudio: false,
|
||||
url: playUrl.value,
|
||||
})
|
||||
flvPlayer.attachMediaElement(videoElement)
|
||||
flvPlayer.load()
|
||||
setTimeout(function () {
|
||||
flvPlayer.play()
|
||||
}, 100)
|
||||
flvPlayer.on('error', function (error) {
|
||||
errorMsg.value = error
|
||||
if (replayCount > 0) {
|
||||
return
|
||||
}
|
||||
replayCount++
|
||||
setTimeout(function () {
|
||||
console.log('flvjs replayCount', replayCount)
|
||||
playvideo()
|
||||
}, 100)
|
||||
})
|
||||
flvPlayer.on('scriptdata_arrived', function (e) {
|
||||
errorMsg.value = ''
|
||||
flvPlayerList.push(flvPlayer)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
message: '当前浏览器不支持flvjs播放',
|
||||
type: 'error',
|
||||
duration: 2000,
|
||||
})
|
||||
}
|
||||
}
|
||||
const stopvideo = async () => {
|
||||
if (flvPlayerList.length) {
|
||||
flvPlayerList.forEach((item) => {
|
||||
if (item) {
|
||||
item.unload()
|
||||
item.detachMediaElement()
|
||||
item.destroy()
|
||||
}
|
||||
})
|
||||
flvPlayerList = []
|
||||
}
|
||||
}
|
||||
|
||||
const progress = (e) => {
|
||||
let self = this
|
||||
var bf = e.srcElement.buffered
|
||||
var currentTime = e.srcElement.currentTime
|
||||
if (bf.length > 0) {
|
||||
let end = bf.end(0)
|
||||
if (end - currentTime > 10) {
|
||||
e.srcElement.currentTime = end - 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dfp.cameraNode,
|
||||
() => {
|
||||
errorMsg.value = ''
|
||||
console.log(dfp.cameraNode, 'dfp.cameraNode')
|
||||
if (dfp.cameraNode.steamURL) {
|
||||
playUrl.value =
|
||||
dfp.cameraNode.steamURL +
|
||||
'&puid=' +
|
||||
dfp.cameraNode.puid +
|
||||
'&idx=' +
|
||||
dfp.cameraNode.idx +
|
||||
'&stream=' +
|
||||
dfp.cameraNode.stream
|
||||
|
||||
// console.log(dfp.cameraNode.steamURL, 'playUrl.value')
|
||||
console.log(playUrl.value, 'playUrl.value')
|
||||
playvideo()
|
||||
} else {
|
||||
stopvideo()
|
||||
}
|
||||
},
|
||||
|
||||
{ deep: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -11,7 +11,7 @@ const timeout = 13000 //api请求超时时间
|
|||
|
||||
export const service = axios.create({
|
||||
//可创建多个 axios实例
|
||||
baseURL: baseApiURL, //设置公共的请求前缀
|
||||
baseURL: '', //设置公共的请求前缀
|
||||
timeout: timeout, //超时终止请求
|
||||
})
|
||||
|
||||
|
|
@ -38,10 +38,11 @@ service.interceptors.response.use(
|
|||
msg: '请求发生错误',
|
||||
})
|
||||
}
|
||||
const status = data.status
|
||||
const status = data?.status || response.status
|
||||
switch (status) {
|
||||
case 200:
|
||||
return data
|
||||
// return data
|
||||
return Promise.resolve(response)
|
||||
case 401: //表示需要重新登录
|
||||
if (!modelShow) {
|
||||
modelShow = true
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
/** 用户全局数据 */
|
||||
|
||||
import { defineStore } from 'pinia';
|
||||
import allStorage from '@/action/storageManage';
|
||||
import { defineStore } from 'pinia'
|
||||
import allStorage from '@/action/storageManage'
|
||||
|
||||
export const userDataStore = defineStore('userDataStore', {
|
||||
state: () => {
|
||||
const userStorage = allStorage.userStorage();
|
||||
const userStorage = allStorage.userStorage()
|
||||
/** 校验数据 */
|
||||
let userInfo = userStorage.value;
|
||||
let userInfo = userStorage.value
|
||||
if (typeof userInfo !== 'object') {
|
||||
userInfo = {};
|
||||
userInfo = {}
|
||||
}
|
||||
return {
|
||||
userInfo: userInfo || {}, //当前登录用户的基础数据
|
||||
|
|
@ -18,30 +18,30 @@ export const userDataStore = defineStore('userDataStore', {
|
|||
userMenuList: [], //用于展示的菜单列表,结构树形化
|
||||
tagsMap: {}, // 页面标签MAP,layoutName为键名
|
||||
iframeList: [], //iframe 数组,iframe也属于标签,跟标签挂钩
|
||||
};
|
||||
}
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
setUserInfo(value) {
|
||||
this.userInfo = value || {};
|
||||
this.userInfo = value || {}
|
||||
/** 存入缓存 */
|
||||
const userStorage = allStorage.userStorage();
|
||||
userStorage.value = value;
|
||||
const userStorage = allStorage.userStorage()
|
||||
userStorage.value = value
|
||||
},
|
||||
setUserMenuConfigNameMap(value) {
|
||||
this.userMenuConfigNameMap = value || {};
|
||||
this.userMenuConfigNameMap = value || {}
|
||||
},
|
||||
setUserMenuConfigPathMap(value) {
|
||||
this.userMenuConfigPathMap = value || {};
|
||||
this.userMenuConfigPathMap = value || {}
|
||||
},
|
||||
setUserMenuList(value) {
|
||||
this.userMenuList = value || [];
|
||||
this.userMenuList = value || []
|
||||
},
|
||||
setTagsMap(value) {
|
||||
this.tagsMap = value || {};
|
||||
this.tagsMap = value || {}
|
||||
},
|
||||
setIframeList(value) {
|
||||
this.iframeList = value || [];
|
||||
this.iframeList = value || []
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { service } from '@/http/request'
|
||||
// 初始化登录
|
||||
export const initLoginApi = (data) => {
|
||||
return service.post('/third-party/login', data)
|
||||
}
|
||||
|
||||
// 获取设备的URL地址
|
||||
export const getDeviceUrlApi = (data) => {
|
||||
return service.post('/third-party/stream/live', data)
|
||||
}
|
||||
|
||||
// 调整设备的摄像机视角
|
||||
export const changeDeviceCameraApi = (data) => {
|
||||
return service.post('/third-party/ptz/start/turn', data)
|
||||
}
|
||||
// 停止设备的摄像机视角
|
||||
export const stopDeviceCameraApi = (data) => {
|
||||
return service.post('/third-party/ptz/stop/turn', data)
|
||||
}
|
||||
|
|
@ -1,16 +1,33 @@
|
|||
<template>
|
||||
<!-- 中一 ---- 自巡检视角 -->
|
||||
<div class="center-one child-container">
|
||||
<div class="view-title">自巡检视角</div>
|
||||
<div class="view-title" v-if="!fullScreenVisible">自巡检视角</div>
|
||||
<div class="video-container">
|
||||
<video controls style="width: 100%; height: 100%; border-radius: 12px" autoplay>
|
||||
<!-- <video controls style="width: 100%; height: 100%; border-radius: 12px" autoplay>
|
||||
<source src="@/assets/video/测试.mp4" type="video/mp4" autoplay />
|
||||
</video>
|
||||
</video> -->
|
||||
<FlvPlayer :cameraNode="cameraNode_2" :videoId="props.videoId" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
<script setup>
|
||||
import FlvPlayer from '@/components/FlvPlayer/index.vue'
|
||||
const props = defineProps({
|
||||
cameraNode_2: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
videoId: {
|
||||
type: String,
|
||||
default: 'video-5',
|
||||
},
|
||||
fullScreenVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.center-one {
|
||||
|
|
|
|||
|
|
@ -45,25 +45,29 @@
|
|||
class="arrow-top hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnUp')"
|
||||
/>
|
||||
<img
|
||||
class="arrow-right hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnRight')"
|
||||
/>
|
||||
<img
|
||||
class="arrow-bottom hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnDown')"
|
||||
/>
|
||||
<img
|
||||
class="arrow-left hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnLeft')"
|
||||
/>
|
||||
|
||||
<!-- 中间的按钮 -->
|
||||
<div class="row-3-item-1-center">
|
||||
<div class="row-3-item-1-center" @click="handleStopCamera">
|
||||
<img
|
||||
class="center-icon"
|
||||
src="@/assets/home-imgs/control-2-stop.png"
|
||||
|
|
@ -84,8 +88,13 @@
|
|||
style="margin-bottom: 2px"
|
||||
src="@/assets/home-imgs/control-2-add.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('ZoomIn')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/home-imgs/control-2-reduce.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('ZoomOut')"
|
||||
/>
|
||||
<img src="@/assets/home-imgs/control-2-reduce.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
|
|
@ -245,6 +254,8 @@
|
|||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
import { changeDeviceCameraApi, stopDeviceCameraApi } from '@/utils/initLogin'
|
||||
|
||||
const upDownHeight = ref(30)
|
||||
const container = ref(null)
|
||||
const draggable = ref(null)
|
||||
|
|
@ -333,6 +344,28 @@ const handleChange4 = (e) => {
|
|||
const formatTooltip = (value) => {
|
||||
return `${value}%`
|
||||
}
|
||||
|
||||
const handleChangeCamera = async (direction) => {
|
||||
// console.log(direction)
|
||||
const res = await changeDeviceCameraApi({
|
||||
token: localStorage.getItem('token'),
|
||||
puid: '201115200268437643',
|
||||
idx: 1,
|
||||
motion: direction,
|
||||
})
|
||||
|
||||
console.log(res, '调整位置---')
|
||||
}
|
||||
|
||||
const handleStopCamera = async () => {
|
||||
const res = await stopDeviceCameraApi({
|
||||
token: localStorage.getItem('token'),
|
||||
puid: '201115200268437643',
|
||||
idx: 1,
|
||||
})
|
||||
|
||||
console.log(res, '停止位置---')
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.control-deck {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
<!-- 左侧盒子1 -->
|
||||
<div class="left-one child-container">
|
||||
<n-flex justify="space-between" v-if="isShowAllBtns">
|
||||
<div class="btns" @click="onHandlePresetSetting" v-if="!fullScreenVisible">
|
||||
预置位配置
|
||||
</div>
|
||||
<div class="btns" @click="onHandleInspectionTask" v-if="!fullScreenVisible">
|
||||
巡视任务
|
||||
</div>
|
||||
<div class="btns" @click="onHandleOperationPanel" v-if="!fullScreenVisible">
|
||||
操作面板
|
||||
</div>
|
||||
|
|
@ -21,15 +27,43 @@
|
|||
</n-flex>
|
||||
|
||||
<div class="video-container" :style="{ marginTop: isShowAllBtns ? '24px' : '0' }">
|
||||
<video controls style="width: 100%; height: 100%; border-radius: 12px" autoplay>
|
||||
<source src="@/assets/video/测试.mp4" type="video/mp4" autoplay />
|
||||
</video>
|
||||
<FlvPlayer :cameraNode="cameraNode" :videoId="props.videoId" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预置位配置 -->
|
||||
<n-modal v-model:show="presetSettingVisible">
|
||||
<PresetSetting
|
||||
v-if="showModal === 1"
|
||||
@onHandleCloseModal="onHandleCloseModal"
|
||||
:presetSettingVisible="presetSettingVisible"
|
||||
/>
|
||||
<InspectionTask
|
||||
v-if="showModal === 2"
|
||||
@onHandleCloseModal="onHandleCloseModal"
|
||||
@onHandleAddInspectionTask="onHandleAddInspectionTask"
|
||||
/>
|
||||
</n-modal>
|
||||
|
||||
<!-- 新增巡视任务 -->
|
||||
<n-modal v-model:show="addInspectionTaskVisible">
|
||||
<AddOrEditForm
|
||||
v-if="addInspectionTaskVisible"
|
||||
@onHandleCloseModalInner="onHandleCloseModalInner"
|
||||
/>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import PresetSetting from './modal-content/preset-setting.vue'
|
||||
import InspectionTask from './modal-content/inspection-task.vue'
|
||||
import AddOrEditForm from './modal-content/add-or-edit-form.vue'
|
||||
|
||||
import FlvPlayer from '@/components/FlvPlayer/index.vue'
|
||||
const presetSettingVisible = ref(false)
|
||||
const addInspectionTaskVisible = ref(false)
|
||||
const showModal = ref(1)
|
||||
|
||||
const props = defineProps({
|
||||
fullScreenVisible: {
|
||||
|
|
@ -40,8 +74,49 @@ const props = defineProps({
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
cameraNode: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
videoId: {
|
||||
type: String,
|
||||
default: 'video-1',
|
||||
},
|
||||
})
|
||||
const emits = defineEmits(['onHandleOperationPanel', 'onHandleFullScreen'])
|
||||
const emits = defineEmits([
|
||||
'onHandleOperationPanel',
|
||||
'onHandleFullScreen',
|
||||
'onHandleFullScreenToggle',
|
||||
'onHandleChangeView',
|
||||
])
|
||||
|
||||
// 预置位配置
|
||||
const onHandlePresetSetting = () => {
|
||||
showModal.value = 1
|
||||
presetSettingVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭预置位配置
|
||||
const onHandleCloseModal = () => {
|
||||
showModal.value = null
|
||||
presetSettingVisible.value = false
|
||||
}
|
||||
|
||||
// 巡视任务
|
||||
const onHandleInspectionTask = () => {
|
||||
showModal.value = 2
|
||||
presetSettingVisible.value = true
|
||||
}
|
||||
|
||||
// 新增巡视任务
|
||||
const onHandleAddInspectionTask = () => {
|
||||
addInspectionTaskVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭新增或修改巡视任务
|
||||
const onHandleCloseModalInner = () => {
|
||||
addInspectionTaskVisible.value = false
|
||||
}
|
||||
|
||||
// 打开操作面板
|
||||
const onHandleOperationPanel = () => {
|
||||
|
|
@ -59,7 +134,9 @@ const onHandleBackFullScreen = () => {
|
|||
}
|
||||
|
||||
// 视角切换
|
||||
const onHandleChangeView = () => {}
|
||||
const onHandleChangeView = () => {
|
||||
emits('onHandleChangeView')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -69,12 +146,14 @@ const onHandleChangeView = () => {}
|
|||
height: 100%; /* 确保容器有高度 */
|
||||
|
||||
.btns {
|
||||
width: 160px;
|
||||
height: 50px;
|
||||
// width: 90px;
|
||||
padding: 0 10px;
|
||||
height: 36px;
|
||||
background: url('@/assets/home-imgs/button-bg.png') no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
line-height: 50px;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<!-- 新增或编辑弹框 -->
|
||||
<DialogModal
|
||||
:width="`60%`"
|
||||
:height="`100vh`"
|
||||
:modalTitle="addOrEditTitle"
|
||||
@onHandleCloseModal="onHandleCloseModalInner"
|
||||
>
|
||||
<n-form ref="addOrEditFormRef" size="small" label-placement="left" style="margin-top: 10px">
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="设备名称">
|
||||
<n-input
|
||||
v-model:value="deviceName"
|
||||
placeholder="任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="底盘名称">
|
||||
<n-input
|
||||
v-model:value="deviceName"
|
||||
placeholder="任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="任务地图">
|
||||
<n-input
|
||||
v-model:value="deviceName"
|
||||
placeholder="任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="巡检次数">
|
||||
<n-input
|
||||
v-model:value="deviceName"
|
||||
placeholder="任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="智能事件">
|
||||
<n-input
|
||||
v-model:value="deviceName"
|
||||
placeholder="任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="任务名称">
|
||||
<n-input
|
||||
v-model:value="deviceName"
|
||||
placeholder="任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="任务周期">
|
||||
<n-input
|
||||
v-model:value="deviceName"
|
||||
placeholder="任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="开始时间">
|
||||
<n-button type="info" @click="onHandleOpenTimePicker">
|
||||
<template #icon>
|
||||
<NIcon>
|
||||
<AddCircleSharp />
|
||||
</NIcon>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid
|
||||
x-gap="24"
|
||||
:cols="24"
|
||||
v-for="(item, index) in timeSelectList"
|
||||
:key="item.week"
|
||||
style="margin-bottom: 10px"
|
||||
>
|
||||
<n-gi :span="6">
|
||||
<n-time-picker
|
||||
clearable
|
||||
@confirm="handleConfirm"
|
||||
default-formatted-value="00:00:00"
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi :span="6">
|
||||
<n-select v-model:value="item.week" :options="options" />
|
||||
</n-gi>
|
||||
<n-gi :span="6" v-if="index > 0">
|
||||
<n-button quaternary @click="onHandleDeleteTimeSelect(index)">
|
||||
<template #icon>
|
||||
<NIcon color="#F90202">
|
||||
<TrashSharp />
|
||||
</NIcon>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { ref } from 'vue'
|
||||
import { NIcon, useMessage } from 'naive-ui'
|
||||
import { AddCircleSharp, TrashSharp } from '@vicons/ionicons5'
|
||||
const message = useMessage()
|
||||
const addOrEditTitle = ref('新增巡视任务') // 新增或编辑弹框标题
|
||||
const value = ref('1')
|
||||
const deviceName = ref('')
|
||||
const timeSelectList = ref([{ times: '00:00:00', week: '' }])
|
||||
const options = ref([
|
||||
{
|
||||
label: '星期一',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '星期二',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '星期三',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: '星期四',
|
||||
value: '4',
|
||||
},
|
||||
{
|
||||
label: '星期五',
|
||||
value: '5',
|
||||
},
|
||||
{
|
||||
label: '星期六',
|
||||
value: '6',
|
||||
},
|
||||
{
|
||||
label: '星期日 ',
|
||||
value: '7',
|
||||
},
|
||||
])
|
||||
const emits = defineEmits(['onHandleCloseModalInner'])
|
||||
// 关闭弹框
|
||||
const onHandleCloseModalInner = () => {
|
||||
emits('onHandleCloseModalInner')
|
||||
}
|
||||
|
||||
// 新增时间选择器
|
||||
const onHandleOpenTimePicker = () => {
|
||||
console.log('新增时间选择器')
|
||||
if (timeSelectList.value.length < 7) {
|
||||
timeSelectList.value.push({ times: '00:00:00', week: '' })
|
||||
} else {
|
||||
// 先清除之前的消失提示防止重复提示
|
||||
message.warning('最多添加7个开始时间', { duration: 1000 })
|
||||
}
|
||||
}
|
||||
|
||||
// 删除时间选择器
|
||||
const onHandleDeleteTimeSelect = (index) => {
|
||||
timeSelectList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleConfirm = (value) => {
|
||||
console.log(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
<template>
|
||||
<!-- 巡视任务 -->
|
||||
<DialogModal
|
||||
@onHandleCloseModal="onHandleCloseModal"
|
||||
:modalTitle="modalTitle"
|
||||
:height="`90vh`"
|
||||
:width="`90%`"
|
||||
>
|
||||
<!-- 巡视任务-->
|
||||
<div class="inspection-task-container" ref="inspectionTaskContainer">
|
||||
<n-form
|
||||
inline
|
||||
ref="formRef"
|
||||
size="small"
|
||||
label-placement="left"
|
||||
style="margin-top: 10px"
|
||||
>
|
||||
<n-form-item>
|
||||
<n-input placeholder="任务名称" clearable style="width: 240px" />
|
||||
</n-form-item>
|
||||
<n-button type="info"> 查询</n-button>
|
||||
<n-button color="#6E90A9" style="margin-left: 8px" @click="onHandleAdd">
|
||||
新增
|
||||
</n-button>
|
||||
</n-form>
|
||||
|
||||
<n-data-table :columns="columns" :data="data" />
|
||||
|
||||
<div style="margin-top: 10px; display: flex; justify-content: flex-end">
|
||||
<n-pagination
|
||||
:page-count="100"
|
||||
show-size-picker
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { AddCircleSharp } from '@vicons/ionicons5'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import { NButton, NSwitch } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const emits = defineEmits(['onHandleCloseModal', 'onHandleAddInspectionTask'])
|
||||
const modalTitle = ref('巡视任务') // 弹框标题
|
||||
const addOrEditVisible = ref(false) // 新增或编辑弹框
|
||||
const addOrEditTitle = ref('新增巡视任务') // 新增或编辑弹框标题
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
const btnList = [
|
||||
{
|
||||
label: '编辑',
|
||||
type: 'info',
|
||||
btnType: 1,
|
||||
},
|
||||
{
|
||||
label: '立即执行',
|
||||
type: 'info',
|
||||
btnType: 2,
|
||||
},
|
||||
{
|
||||
label: '下发',
|
||||
type: 'info',
|
||||
btnType: 3,
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
type: 'info',
|
||||
btnType: 4,
|
||||
},
|
||||
]
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '任务ID',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '任务名',
|
||||
key: 'tags',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '地盘名称',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '任务频次',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '执行时间',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '任务状态',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
},
|
||||
|
||||
{
|
||||
title: '启用',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.isEnable,
|
||||
onChange: (value) => onHandleSwitch(row, value),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'age',
|
||||
align: 'center',
|
||||
width: 260,
|
||||
render(row) {
|
||||
const buttonS = btnList.map((btn) => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
onClick: () => onHandleBtn(row, btn.btnType),
|
||||
type: btn.type,
|
||||
style: {
|
||||
marginRight: '4px',
|
||||
},
|
||||
},
|
||||
{ default: () => btn.label },
|
||||
)
|
||||
})
|
||||
return buttonS
|
||||
},
|
||||
},
|
||||
])
|
||||
const data = ref([
|
||||
{
|
||||
age: '2025-06-01 10:00:00',
|
||||
tags: ['nice'],
|
||||
isEnable: true,
|
||||
},
|
||||
{
|
||||
age: '2025-06-01 10:00:00',
|
||||
tags: ['nice'],
|
||||
isEnable: false,
|
||||
},
|
||||
{
|
||||
age: '2025-06-01 10:00:00',
|
||||
tags: ['nice'],
|
||||
isEnable: false,
|
||||
},
|
||||
])
|
||||
|
||||
// 新增按钮
|
||||
const onHandleAdd = () => {
|
||||
console.log('新增')
|
||||
// addOrEditVisible.value = true
|
||||
// addOrEditTitle.value = '新增巡视任务'
|
||||
emits('onHandleAddInspectionTask')
|
||||
}
|
||||
|
||||
// 按钮操作组
|
||||
const onHandleBtn = (row, type) => {
|
||||
console.log(row, type)
|
||||
// 1. 编辑 2. 立即执行 3. 下发 4. 删除
|
||||
switch (type) {
|
||||
case 1:
|
||||
onHandleEditTable(row)
|
||||
break
|
||||
case 2:
|
||||
onHandleImmediateExecution(row)
|
||||
break
|
||||
case 3:
|
||||
onHandleSend(row)
|
||||
break
|
||||
case 4:
|
||||
onHandleDelete(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑
|
||||
const onHandleEditTable = (row) => {
|
||||
console.log(row)
|
||||
addOrEditVisible.value = true
|
||||
addOrEditTitle.value = '编辑巡视任务'
|
||||
}
|
||||
|
||||
// 立即执行
|
||||
const onHandleImmediateExecution = (row) => {
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
// 下发
|
||||
const onHandleSend = (row) => {
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
// 删除
|
||||
const onHandleDelete = (row) => {
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
// 关闭弹框外层
|
||||
const onHandleCloseModal = () => {
|
||||
emits('onHandleCloseModal')
|
||||
}
|
||||
|
||||
// 关闭弹框内层
|
||||
const onHandleCloseModalInner = () => {
|
||||
addOrEditVisible.value = false
|
||||
}
|
||||
|
||||
// 启用开关
|
||||
const onHandleSwitch = (row, value) => {
|
||||
console.log(row, value)
|
||||
|
||||
row.isEnable = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plane-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
<template>
|
||||
<!-- 预置位配置 -->
|
||||
<DialogModal @onHandleCloseModal="onHandleCloseModal" :modalTitle="modalTitle" :height="`90vh`">
|
||||
<!-- 平面图操作区域 -->
|
||||
<div class="plane-map-container" ref="planeMapContainer">
|
||||
<svg
|
||||
ref="svgMap"
|
||||
class="floor-plan"
|
||||
:viewBox="`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
@click="handleMapClick"
|
||||
>
|
||||
<!-- 平面图背景 -->
|
||||
<image
|
||||
v-if="imgLoaded"
|
||||
:href="demoImg"
|
||||
:width="viewBox.width"
|
||||
:height="viewBox.height"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
style="object-fit: cover"
|
||||
/>
|
||||
|
||||
<!-- 动态渲染标记点 -->
|
||||
<circle
|
||||
v-for="(point, index) in points"
|
||||
:key="index"
|
||||
:cx="point.x"
|
||||
:cy="point.y"
|
||||
r="8"
|
||||
fill="red"
|
||||
@click.stop="selectPoint(index)"
|
||||
@mousedown="startDrag(index, $event)"
|
||||
/>
|
||||
|
||||
<!-- 动态渲染连线 -->
|
||||
<line
|
||||
v-for="(line, index) in lines"
|
||||
:key="'line-' + index"
|
||||
:x1="line.start.x"
|
||||
:y1="line.start.y"
|
||||
:x2="line.end.x"
|
||||
:y2="line.end.y"
|
||||
stroke="blue"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="map-control-container">
|
||||
<n-button type="info" @click="handleZoomIn">放大</n-button>
|
||||
<n-button type="info" @click="handleZoomOut">缩小</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { refThrottled, useMouseInElement } from '@vueuse/core'
|
||||
import { ref, onMounted, nextTick, watch } from 'vue'
|
||||
// import { CashOutline as CashIcon } from '@vicons/ionicons5'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import demoImg from '@/assets/demo.png'
|
||||
|
||||
const emits = defineEmits(['onHandleCloseModal'])
|
||||
const modalTitle = ref('预置位配置')
|
||||
const svgMap = refThrottled(null)
|
||||
const points = ref([])
|
||||
const lines = ref([])
|
||||
const imgLoaded = ref(false)
|
||||
const imgNaturalSize = ref({ width: 0, height: 0 })
|
||||
|
||||
const props = defineProps({
|
||||
presetSettingVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 新增:响应式 viewBox 控制
|
||||
const viewBox = ref({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
|
||||
// 新增:容器尺寸跟踪
|
||||
const containerSize = ref({ width: 0, height: 0 })
|
||||
const planeMapContainer = ref(null)
|
||||
|
||||
// 关闭模态框
|
||||
const onHandleCloseModal = () => {
|
||||
emits('onHandleCloseModal')
|
||||
}
|
||||
|
||||
// 拖拽标记点
|
||||
const startDrag = (index, e) => {
|
||||
e.preventDefault()
|
||||
const { x: startX, y: startY } = points.value[index]
|
||||
const onMove = (moveEvent) => {
|
||||
const rect = svgMap.value.getBoundingClientRect()
|
||||
points.value[index] = {
|
||||
x: moveEvent.clientX - rect.left,
|
||||
y: moveEvent.clientY - rect.top,
|
||||
}
|
||||
}
|
||||
const onUp = () => {
|
||||
window.removeEventListener('mousemove', onMove)
|
||||
window.removeEventListener('mouseup', onUp)
|
||||
}
|
||||
window.addEventListener('mousemove', onMove)
|
||||
window.addEventListener('mouseup', onUp)
|
||||
}
|
||||
|
||||
// 连线示例(假设连接第一个和第二个点)
|
||||
const connectPoints = () => {
|
||||
if (points.value.length >= 2) {
|
||||
lines.value.push({
|
||||
start: points.value[0],
|
||||
end: points.value[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 监听容器尺寸变化
|
||||
nextTick(() => {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
if (entries[0]) {
|
||||
containerSize.value = {
|
||||
width: entries[0].contentRect.width,
|
||||
height: entries[0].contentRect.height,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (planeMapContainer.value) {
|
||||
resizeObserver.observe(planeMapContainer.value)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 修改点击事件处理(考虑缩放后的坐标)
|
||||
const handleMapClick = (e) => {
|
||||
if (!svgMap.value) return
|
||||
|
||||
const pt = svgMap.value.createSVGPoint()
|
||||
pt.x = e.clientX
|
||||
pt.y = e.clientY
|
||||
const svgPt = pt.matrixTransform(svgMap.value.getScreenCTM().inverse())
|
||||
|
||||
points.value.push({
|
||||
x: svgPt.x,
|
||||
y: svgPt.y,
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.presetSettingVisible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
loadImage()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
const initMapSize = () => {
|
||||
if (!planeMapContainer.value) return
|
||||
|
||||
containerSize.value = {
|
||||
width: planeMapContainer.value.clientWidth,
|
||||
height: planeMapContainer.value.clientHeight,
|
||||
}
|
||||
|
||||
// 初始化 viewBox(根据您的业务逻辑调整)
|
||||
viewBox.value.width = containerSize.value.width
|
||||
viewBox.value.height = containerSize.value.height
|
||||
}
|
||||
|
||||
// 加载图片并获取真实尺寸
|
||||
const loadImage = () => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
imgNaturalSize.value = {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
}
|
||||
imgLoaded.value = true
|
||||
adjustViewBox()
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.error('图片加载失败,请检查路径:', demoImg)
|
||||
}
|
||||
img.src = demoImg // 使用导入的图片路径
|
||||
}
|
||||
|
||||
// 调整viewBox
|
||||
const adjustViewBox = () => {
|
||||
if (!planeMapContainer.value || !imgNaturalSize.value.width) return
|
||||
|
||||
const container = planeMapContainer.value
|
||||
const containerRatio = container.clientWidth / container.clientHeight
|
||||
const imgRatio = imgNaturalSize.value.width / imgNaturalSize.value.height
|
||||
|
||||
if (containerRatio > imgRatio) {
|
||||
// 容器更宽,按高度适配
|
||||
viewBox.value = {
|
||||
x: (imgNaturalSize.value.width - imgNaturalSize.value.height * containerRatio) / 2,
|
||||
y: 0,
|
||||
width: imgNaturalSize.value.height * containerRatio,
|
||||
height: imgNaturalSize.value.height,
|
||||
}
|
||||
} else {
|
||||
// 容器更高,按宽度适配
|
||||
viewBox.value = {
|
||||
x: 0,
|
||||
y: (imgNaturalSize.value.height - imgNaturalSize.value.width / containerRatio) / 2,
|
||||
width: imgNaturalSize.value.width,
|
||||
height: imgNaturalSize.value.width / containerRatio,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleZoomIn = () => {
|
||||
console.log('放大')
|
||||
viewBox.value.width = viewBox.value.width * 1.1
|
||||
viewBox.value.height = viewBox.value.height * 1.1
|
||||
}
|
||||
|
||||
const handleZoomOut = () => {
|
||||
console.log('缩小')
|
||||
viewBox.value.width = viewBox.value.width * 0.9
|
||||
viewBox.value.height = viewBox.value.height * 0.9
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plane-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.map-control-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
<template>
|
||||
<!-- 预置位配置 -->
|
||||
<DialogModal @onHandleCloseModal="onHandleCloseModal" :modalTitle="modalTitle" :height="`90vh`">
|
||||
<!-- 平面图操作区域 -->
|
||||
<div class="plane-map-container" ref="planeMapContainer">
|
||||
<svg
|
||||
ref="svgMap"
|
||||
class="floor-plan"
|
||||
:viewBox="`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
@click="handleMapClick"
|
||||
>
|
||||
<!-- 平面图背景 -->
|
||||
<image
|
||||
v-if="imgLoaded"
|
||||
:href="demoImg"
|
||||
:width="viewBox.width"
|
||||
:height="viewBox.height"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
style="object-fit: cover"
|
||||
/>
|
||||
|
||||
<!-- 动态渲染标记点 -->
|
||||
<circle
|
||||
v-for="(point, index) in points"
|
||||
:key="index"
|
||||
:cx="point.x"
|
||||
:cy="point.y"
|
||||
r="8"
|
||||
fill="red"
|
||||
@click.stop="selectPoint(index)"
|
||||
@mousedown="startDrag(index, $event)"
|
||||
/>
|
||||
|
||||
<!-- 动态渲染连线 -->
|
||||
<line
|
||||
v-for="(line, index) in lines"
|
||||
:key="'line-' + index"
|
||||
:x1="line.start.x"
|
||||
:y1="line.start.y"
|
||||
:x2="line.end.x"
|
||||
:y2="line.end.y"
|
||||
stroke="blue"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="map-control-container">
|
||||
<n-button type="info" @click="handleZoomIn">放大</n-button>
|
||||
<n-button type="info" @click="handleZoomOut">缩小</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { refThrottled, useMouseInElement } from '@vueuse/core'
|
||||
import { ref, onMounted, nextTick, watch } from 'vue'
|
||||
// import { CashOutline as CashIcon } from '@vicons/ionicons5'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import demoImg from '@/assets/demo.png'
|
||||
|
||||
const emits = defineEmits(['onHandleCloseModal'])
|
||||
const modalTitle = ref('预置位配置')
|
||||
const svgMap = refThrottled(null)
|
||||
const points = ref([])
|
||||
const lines = ref([])
|
||||
const imgLoaded = ref(false)
|
||||
const imgNaturalSize = ref({ width: 0, height: 0 })
|
||||
|
||||
const props = defineProps({
|
||||
presetSettingVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 新增:响应式 viewBox 控制
|
||||
const viewBox = ref({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
|
||||
// 新增:容器尺寸跟踪
|
||||
const containerSize = ref({ width: 0, height: 0 })
|
||||
const planeMapContainer = ref(null)
|
||||
|
||||
// 关闭模态框
|
||||
const onHandleCloseModal = () => {
|
||||
emits('onHandleCloseModal')
|
||||
}
|
||||
|
||||
// 拖拽标记点
|
||||
const startDrag = (index, e) => {
|
||||
e.preventDefault()
|
||||
const { x: startX, y: startY } = points.value[index]
|
||||
const onMove = (moveEvent) => {
|
||||
const rect = svgMap.value.getBoundingClientRect()
|
||||
points.value[index] = {
|
||||
x: moveEvent.clientX - rect.left,
|
||||
y: moveEvent.clientY - rect.top,
|
||||
}
|
||||
}
|
||||
const onUp = () => {
|
||||
window.removeEventListener('mousemove', onMove)
|
||||
window.removeEventListener('mouseup', onUp)
|
||||
}
|
||||
window.addEventListener('mousemove', onMove)
|
||||
window.addEventListener('mouseup', onUp)
|
||||
}
|
||||
|
||||
// 连线示例(假设连接第一个和第二个点)
|
||||
const connectPoints = () => {
|
||||
if (points.value.length >= 2) {
|
||||
lines.value.push({
|
||||
start: points.value[0],
|
||||
end: points.value[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 监听容器尺寸变化
|
||||
nextTick(() => {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
if (entries[0]) {
|
||||
containerSize.value = {
|
||||
width: entries[0].contentRect.width,
|
||||
height: entries[0].contentRect.height,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (planeMapContainer.value) {
|
||||
resizeObserver.observe(planeMapContainer.value)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 修改点击事件处理(考虑缩放后的坐标)
|
||||
const handleMapClick = (e) => {
|
||||
if (!svgMap.value) return
|
||||
|
||||
const pt = svgMap.value.createSVGPoint()
|
||||
pt.x = e.clientX
|
||||
pt.y = e.clientY
|
||||
const svgPt = pt.matrixTransform(svgMap.value.getScreenCTM().inverse())
|
||||
|
||||
points.value.push({
|
||||
x: svgPt.x,
|
||||
y: svgPt.y,
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.presetSettingVisible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
loadImage()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
const initMapSize = () => {
|
||||
if (!planeMapContainer.value) return
|
||||
|
||||
containerSize.value = {
|
||||
width: planeMapContainer.value.clientWidth,
|
||||
height: planeMapContainer.value.clientHeight,
|
||||
}
|
||||
|
||||
// 初始化 viewBox(根据您的业务逻辑调整)
|
||||
viewBox.value.width = containerSize.value.width
|
||||
viewBox.value.height = containerSize.value.height
|
||||
}
|
||||
|
||||
// 加载图片并获取真实尺寸
|
||||
const loadImage = () => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
imgNaturalSize.value = {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
}
|
||||
imgLoaded.value = true
|
||||
adjustViewBox()
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.error('图片加载失败,请检查路径:', demoImg)
|
||||
}
|
||||
img.src = demoImg // 使用导入的图片路径
|
||||
}
|
||||
|
||||
// 调整viewBox
|
||||
const adjustViewBox = () => {
|
||||
if (!planeMapContainer.value || !imgNaturalSize.value.width) return
|
||||
|
||||
const container = planeMapContainer.value
|
||||
const containerRatio = container.clientWidth / container.clientHeight
|
||||
const imgRatio = imgNaturalSize.value.width / imgNaturalSize.value.height
|
||||
|
||||
if (containerRatio > imgRatio) {
|
||||
// 容器更宽,按高度适配
|
||||
viewBox.value = {
|
||||
x: (imgNaturalSize.value.width - imgNaturalSize.value.height * containerRatio) / 2,
|
||||
y: 0,
|
||||
width: imgNaturalSize.value.height * containerRatio,
|
||||
height: imgNaturalSize.value.height,
|
||||
}
|
||||
} else {
|
||||
// 容器更高,按宽度适配
|
||||
viewBox.value = {
|
||||
x: 0,
|
||||
y: (imgNaturalSize.value.height - imgNaturalSize.value.width / containerRatio) / 2,
|
||||
width: imgNaturalSize.value.width,
|
||||
height: imgNaturalSize.value.width / containerRatio,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleZoomIn = () => {
|
||||
console.log('放大')
|
||||
viewBox.value.width = viewBox.value.width * 1.1
|
||||
viewBox.value.height = viewBox.value.height * 1.1
|
||||
}
|
||||
|
||||
const handleZoomOut = () => {
|
||||
console.log('缩小')
|
||||
viewBox.value.width = viewBox.value.width * 0.9
|
||||
viewBox.value.height = viewBox.value.height * 0.9
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plane-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.map-control-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
>
|
||||
<template #error>
|
||||
<n-icon :size="100" color="lightGrey">
|
||||
<ImageOutlineIcon />
|
||||
<!-- <ImageOutlineIcon /> -->
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-image>
|
||||
|
|
|
|||
|
|
@ -4,54 +4,56 @@
|
|||
<!-- 机器人首页 -->
|
||||
<!-- 使用grid布局 -->
|
||||
<!-- 非全屏模式 -->
|
||||
<div class="robot-container" v-if="!fullScreenVisible">
|
||||
<div class="robot-container">
|
||||
<!-- 左一 -->
|
||||
<div class="robot-1">
|
||||
<div
|
||||
:style="{
|
||||
gridColumn: fullScreenVisible ? '1 / 9' : '1 / 4',
|
||||
gridRow: fullScreenVisible ? '1 / 13' : '1 / 7',
|
||||
}"
|
||||
>
|
||||
<LeftOne
|
||||
:videoId="'video-1'"
|
||||
:cameraNode="cameraNode_1"
|
||||
:fullScreenVisible="fullScreenVisible"
|
||||
@onHandleChangeView="onHandleChangeView"
|
||||
@onHandleOperationPanel="onHandleOperationPanel"
|
||||
@onHandleFullScreenToggle="onHandleFullScreenToggle"
|
||||
@onHandleFullScreenToggle="onHandleFullScreenToggleL"
|
||||
/>
|
||||
</div>
|
||||
<!-- 左二 -->
|
||||
<div class="robot-2">
|
||||
<div class="robot-2" v-if="!fullScreenVisible">
|
||||
<LeftTwo />
|
||||
</div>
|
||||
|
||||
<!-- 中一 -->
|
||||
<div class="robot-3">
|
||||
<CenterOne />
|
||||
<div
|
||||
:style="{
|
||||
gridColumn: fullScreenVisible ? '9 / 13' : '4 / 10',
|
||||
gridRow: fullScreenVisible ? '1 / 6' : '1 / 8',
|
||||
}"
|
||||
>
|
||||
<CenterOne
|
||||
:videoId="'video-2'"
|
||||
:cameraNode_2="cameraNode_2"
|
||||
:fullScreenVisible="fullScreenVisible"
|
||||
/>
|
||||
</div>
|
||||
<!-- 中二 -->
|
||||
<div class="robot-4">
|
||||
<div class="robot-4" v-if="!fullScreenVisible">
|
||||
<CenterTwo />
|
||||
</div>
|
||||
|
||||
<!-- 右一 -->
|
||||
<div class="robot-5">
|
||||
<div class="robot-5" v-if="!fullScreenVisible">
|
||||
<RightOne />
|
||||
</div>
|
||||
<!-- 右二 -->
|
||||
<div class="robot-6">
|
||||
<div class="robot-6" v-if="!fullScreenVisible">
|
||||
<RightTwo />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 全屏模式 -->
|
||||
<div class="full-screen-container" v-if="fullScreenVisible">
|
||||
<div class="full-screen-left1">
|
||||
<LeftOne
|
||||
:fullScreenVisible="fullScreenVisible"
|
||||
@onHandleFullScreenToggle="onHandleFullScreenToggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="full-screen-right1">
|
||||
<LeftOne
|
||||
:isShowAllBtns="false"
|
||||
:fullScreenVisible="fullScreenVisible"
|
||||
@onHandleFullScreenToggle="onHandleFullScreenToggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="full-screen-right2">
|
||||
<div class="robot-7" v-if="fullScreenVisible">
|
||||
<ControlDeck />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -69,6 +71,7 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { initLoginApi, getDeviceUrlApi } from '@/utils/initLogin'
|
||||
import { useScale } from '@/hooks/useScale' // 引入自定义 Hook
|
||||
import LeftOne from './components/left-one.vue'
|
||||
import LeftTwo from './components/left-two.vue'
|
||||
|
|
@ -84,6 +87,25 @@ const fullScreenVisible = ref(false) // 全屏状态
|
|||
// 使用 useScale Hook(直接调用,无需在 setup() 里)
|
||||
const { baseWidth, baseHeight, scale } = useScale(appRef)
|
||||
|
||||
const cameraNode_1 = ref({
|
||||
puid: '201115200268437643',
|
||||
idx: 0,
|
||||
stream: 0,
|
||||
name: '',
|
||||
operateType: 1,
|
||||
steamCode: '',
|
||||
steamURL: '',
|
||||
})
|
||||
const cameraNode_2 = ref({
|
||||
puid: '201115200268437643',
|
||||
idx: 1,
|
||||
stream: 0,
|
||||
name: '',
|
||||
operateType: 1,
|
||||
steamCode: '',
|
||||
steamURL: '',
|
||||
})
|
||||
|
||||
const operationPanelVisible = ref(false) // 操作面板是否显示
|
||||
|
||||
// 打开操作面板
|
||||
|
|
@ -91,9 +113,63 @@ const onHandleOperationPanel = (visible) => {
|
|||
operationPanelVisible.value = visible
|
||||
}
|
||||
// 全屏控制
|
||||
const onHandleFullScreenToggle = (visible) => {
|
||||
const onHandleFullScreenToggleL = (visible) => {
|
||||
fullScreenVisible.value = visible
|
||||
}
|
||||
// 视角切换
|
||||
const onHandleChangeView = () => {
|
||||
const tempIdx = cameraNode_1.value.idx
|
||||
cameraNode_1.value.idx = cameraNode_2.value.idx
|
||||
cameraNode_2.value.idx = tempIdx
|
||||
const temp = cameraNode_1.value.steamURL
|
||||
cameraNode_1.value.steamURL = cameraNode_2.value.steamURL
|
||||
cameraNode_2.value.steamURL = temp
|
||||
}
|
||||
|
||||
const loginParams = ref({
|
||||
// - 登录平台IP
|
||||
address: '60.168.132.97',
|
||||
port: '49988',
|
||||
// - 登录平台用户名
|
||||
user: 'admin',
|
||||
// - 登录平台密码
|
||||
password: 'Aa123456',
|
||||
// - 登录平台企业ID
|
||||
epid: 'YDJFXK',
|
||||
// - 登录平台是否通过网闸模式
|
||||
bfix: 0,
|
||||
})
|
||||
|
||||
// 初始化登录
|
||||
const initLoginData = async () => {
|
||||
initLoginApi(loginParams.value).then((res) => {
|
||||
localStorage.setItem('token', res?.data?.token)
|
||||
getDeviceData()
|
||||
})
|
||||
}
|
||||
|
||||
// 获取视频地址
|
||||
const getDeviceData = async () => {
|
||||
const { data: res } = await getDeviceUrlApi({
|
||||
token: localStorage.getItem('token'),
|
||||
puid: '201115200268437643',
|
||||
idx: 0,
|
||||
stream: 0,
|
||||
type: 'HTTP-FLV',
|
||||
})
|
||||
const { data: res2 } = await getDeviceUrlApi({
|
||||
token: localStorage.getItem('token'),
|
||||
puid: '201115200268437643',
|
||||
idx: 1,
|
||||
stream: 0,
|
||||
type: 'HTTP-FLV',
|
||||
})
|
||||
|
||||
cameraNode_1.value.steamURL = res?.streamUrl
|
||||
cameraNode_2.value.steamURL = res2?.streamUrl
|
||||
}
|
||||
|
||||
initLoginData()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
@ -138,6 +214,11 @@ const onHandleFullScreenToggle = (visible) => {
|
|||
grid-row: 8 / 13;
|
||||
}
|
||||
|
||||
.robot-7 {
|
||||
grid-column: 9 / 13;
|
||||
grid-row: 6 / 13;
|
||||
}
|
||||
|
||||
.full-screen-left1 {
|
||||
grid-column: 1 / 9;
|
||||
grid-row: 1 / 13;
|
||||
|
|
|
|||
Loading…
Reference in New Issue