Compare commits
37 Commits
bc401377a4
...
video2
| Author | SHA1 | Date | |
|---|---|---|---|
| 046aea0f55 | |||
| 3e9f7133de | |||
| affc881aaa | |||
| 87c115ab99 | |||
| 6fe84fe853 | |||
| b7499f12d1 | |||
| 724dc4a561 | |||
|
|
cee259db65 | ||
| 3853793886 | |||
| aa75cd9cb9 | |||
|
|
f09d8c972b | ||
| be75778018 | |||
| 72fcf0b531 | |||
| 406d502e60 | |||
|
|
cbedd59743 | ||
| 85a24f2147 | |||
| 04b25ab162 | |||
| 402e3c6e09 | |||
| c392ce026d | |||
| aa18dc7a4c | |||
|
|
467915db82 | ||
| b56a35cad4 | |||
|
|
937ab3a230 | ||
|
|
b459464052 | ||
| 9d134a2b9d | |||
|
|
afc316bbab | ||
| 1b4226d325 | |||
| 20049de695 | |||
| 095cd64373 | |||
| d6c4bd0406 | |||
| 03e0ea4cb8 | |||
| caa474a5a9 | |||
| 7edb14c031 | |||
| b1161d7479 | |||
| d1411314a5 | |||
| a23660efd6 | |||
| 90347ba87a |
BIN
dist01.zip
BIN
dist01.zip
Binary file not shown.
BIN
dist5.31.zip
BIN
dist5.31.zip
Binary file not shown.
56
package-lock.json
generated
56
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"hls.js": "^1.5.18",
|
"hls.js": "^1.5.18",
|
||||||
"jssip": "^3.10.1",
|
"jssip": "^3.10.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mpegts.js": "^1.8.0",
|
||||||
"pinia": "^2.2.6",
|
"pinia": "^2.2.6",
|
||||||
"pinia-plugin-persistedstate": "^4.2.0",
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
"pubsub-js": "^1.9.5",
|
"pubsub-js": "^1.9.5",
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
"vue-echarts": "^7.0.3",
|
"vue-echarts": "^7.0.3",
|
||||||
"vue-router": "^4.4.5",
|
"vue-router": "^4.4.5",
|
||||||
"vue3-seamless-scroll": "^2.0.1",
|
"vue3-seamless-scroll": "^2.0.1",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0",
|
||||||
|
"whepts": "^1.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
@@ -3201,6 +3203,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es6-promise": {
|
||||||
|
"version": "4.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||||
|
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
||||||
@@ -3327,6 +3335,12 @@
|
|||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/events": {
|
"node_modules/events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
@@ -4272,6 +4286,16 @@
|
|||||||
"ufo": "^1.5.4"
|
"ufo": "^1.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mpegts.js": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mpegts.js/-/mpegts.js-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-ZtujqtmTjWgcDDkoOnLvrOKUTO/MKgLHM432zGDI8oPaJ0S+ebPxg1nEpDpLw6I7KmV/GZgUIrfbWi3qqEircg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"es6-promise": "^4.2.5",
|
||||||
|
"webworkify-webpack": "github:xqq/webworkify-webpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mrmime": {
|
"node_modules/mrmime": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
|
||||||
@@ -4306,6 +4330,21 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nanostores": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.0.0 || >=22.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/neo-async": {
|
"node_modules/neo-async": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||||
@@ -6143,6 +6182,21 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webworkify-webpack": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "git+ssh://git@github.com/xqq/webworkify-webpack.git#24d1e719b4a6cac37a518b2bb10fe124527ef4ef",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/whepts": {
|
||||||
|
"version": "1.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/whepts/-/whepts-1.1.12.tgz",
|
||||||
|
"integrity": "sha512-KllGslriMAhD6BDBbG6X653M8XgeYqxnjuEyaB7U/1nIZN6NoAGSWP3Av46g690r2UaaI69bNzfPn4efJBd+kA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^5.0.4",
|
||||||
|
"nanostores": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"hls.js": "^1.5.18",
|
"hls.js": "^1.5.18",
|
||||||
"jssip": "^3.10.1",
|
"jssip": "^3.10.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mpegts.js": "^1.8.0",
|
||||||
"pinia": "^2.2.6",
|
"pinia": "^2.2.6",
|
||||||
"pinia-plugin-persistedstate": "^4.2.0",
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
"pubsub-js": "^1.9.5",
|
"pubsub-js": "^1.9.5",
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
"vue-echarts": "^7.0.3",
|
"vue-echarts": "^7.0.3",
|
||||||
"vue-router": "^4.4.5",
|
"vue-router": "^4.4.5",
|
||||||
"vue3-seamless-scroll": "^2.0.1",
|
"vue3-seamless-scroll": "^2.0.1",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0",
|
||||||
|
"whepts": "^1.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ export function getRegionsListApi(data){
|
|||||||
|
|
||||||
// 获取视频播放地址
|
// 获取视频播放地址
|
||||||
export function getPreviewUrlApi(data) {
|
export function getPreviewUrlApi(data) {
|
||||||
|
if(data.cameraIndexCode==undefined || !data.cameraIndexCode){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return request({
|
return request({
|
||||||
url: '/fjtcc-api/api/largeScreen/video/getPreviewUrl',
|
url: '/fjtcc-api/api/largeScreen/video/getPreviewUrl',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@@ -132,3 +135,11 @@ export function getVideCollectCateSort(data) {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
//今年游客总数
|
||||||
|
export function getSpotVisitor(data) {
|
||||||
|
return request({
|
||||||
|
url: '/fjtcc-api/api/largeScreen/spot/spotVisitor',
|
||||||
|
method: 'get',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import request from './request'
|
import request from './request'
|
||||||
|
|
||||||
export function getTrafficEventsApi() {
|
export function getTrafficEventsApi(type) {
|
||||||
return request({
|
return request({
|
||||||
url: '/fjtcc-api/api/largeScreen/monitor/trafficEvents',
|
url: '/fjtcc-api/api/largeScreen/monitor/trafficEvents?type='+type,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export function getVideoEventApi(scenicSpotId) {
|
export function getVideoEventApi(scenicSpotId,type) {
|
||||||
return request({
|
return request({
|
||||||
url: '/fjtcc-api/api/largeScreen/monitor/videoEvents?scenicSpotId='+scenicSpotId,
|
url: '/fjtcc-api/api/largeScreen/monitor/videoEvents?type='+type+'&scenicSpotId='+scenicSpotId,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const instance = axios.create({
|
|||||||
timeout: 100000,
|
timeout: 100000,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: mode == 'dev' ? devToken : proToken,
|
Authorization: mode == 'dev' ? devToken : proToken,
|
||||||
|
'User-Type': '1',
|
||||||
'Content-Type': 'application/json;charset=UTF-8'
|
'Content-Type': 'application/json;charset=UTF-8'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -40,10 +41,11 @@ instance.interceptors.response.use(
|
|||||||
if (res.data.code == 200) {
|
if (res.data.code == 200) {
|
||||||
return res.data
|
return res.data
|
||||||
} else {
|
} else {
|
||||||
ElMessage({
|
console.error("接口请求错误:"+res.data.msg)
|
||||||
message: res.data.msg,
|
// ElMessage({
|
||||||
type: 'error'
|
// message: res.data.msg,
|
||||||
})
|
// type: 'error'
|
||||||
|
// })
|
||||||
return Promise.reject(res.data)
|
return Promise.reject(res.data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ export function getListApi() {
|
|||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 统计
|
||||||
|
export function getDetailApi(id) {
|
||||||
|
return request({
|
||||||
|
url: '/fjtcc-api/api/largeScreen/workorder/detail/'+id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
// 统计
|
// 统计
|
||||||
export function getTotalApi() {
|
export function getTotalApi() {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/d-icon-1.png
Normal file
BIN
src/assets/images/d-icon-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/loading.gif
Normal file
BIN
src/assets/images/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
352
src/components/HlsPlayer/index-bak.vue
Normal file
352
src/components/HlsPlayer/index-bak.vue
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="isActive" class="myVideo-container">
|
||||||
|
<video
|
||||||
|
class="myVideo"
|
||||||
|
ref="videoElement"
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
:controls="false"
|
||||||
|
disablePictureInPicture
|
||||||
|
></video>
|
||||||
|
<!-- <div v-if="loading" class="loading-overlay pointer-events-none">
|
||||||
|
<div class="loading-text">{{ loadingText }}</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Hls from 'hls.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HlsPlayer',
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hls: null,
|
||||||
|
video: null,
|
||||||
|
loading: false,
|
||||||
|
loadingText: '加载中...',
|
||||||
|
retryCount: 0,
|
||||||
|
maxRetries: 3,
|
||||||
|
isReady: false,
|
||||||
|
playAttempts: 0,
|
||||||
|
maxPlayAttempts: 3,
|
||||||
|
cleanupTimeout: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
videoStyle() {
|
||||||
|
const style = {}
|
||||||
|
if (this.width) {
|
||||||
|
style.width = typeof this.width === 'number' ? `${this.width}px` : this.width
|
||||||
|
}
|
||||||
|
if (this.height) {
|
||||||
|
style.height = typeof this.height === 'number' ? `${this.height}px` : this.height
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isActive: {
|
||||||
|
handler(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.$nextTick(this.initializePlayer)
|
||||||
|
} else {
|
||||||
|
this.immediateCleanup()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
url(newUrl) {
|
||||||
|
// console.log(newUrl,'77777777777777777777777777777777')
|
||||||
|
if (newUrl && this.isActive) {
|
||||||
|
this.initializePlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.video = this.$refs.videoElement
|
||||||
|
this.registerVideoEvents()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
registerVideoEvents() {
|
||||||
|
const events = ['canplay', 'waiting', 'playing', 'error', 'stalled', 'suspend']
|
||||||
|
|
||||||
|
events.forEach((event) => {
|
||||||
|
this.video.addEventListener(event, this.handleVideoEvent)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleVideoEvent(event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'canplay':
|
||||||
|
this.isReady = true
|
||||||
|
break
|
||||||
|
case 'waiting':
|
||||||
|
this.showLoadingIndicator('缓冲中...')
|
||||||
|
break
|
||||||
|
case 'playing':
|
||||||
|
this.loading = false
|
||||||
|
break
|
||||||
|
case 'error':
|
||||||
|
this.handlePlaybackError()
|
||||||
|
break
|
||||||
|
case 'stalled':
|
||||||
|
this.showLoadingIndicator('播放卡顿...')
|
||||||
|
break
|
||||||
|
case 'suspend':
|
||||||
|
this.cleanupNetworkResources()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
immediateCleanup() {
|
||||||
|
// 立即停止网络请求
|
||||||
|
if (this.hls) {
|
||||||
|
this.hls.stopLoad()
|
||||||
|
this.hls.detachMedia()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟完全清理以避免播放卡顿
|
||||||
|
this.cleanupTimeout = setTimeout(() => {
|
||||||
|
// 仅在不重新初始化播放器时才调用 fullCleanup
|
||||||
|
if (!this.url || !this.isActive) {
|
||||||
|
this.fullCleanup()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
|
||||||
|
fullCleanup() {
|
||||||
|
if (this.hls) {
|
||||||
|
this.hls.destroy()
|
||||||
|
this.hls = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.video) {
|
||||||
|
this.video.pause()
|
||||||
|
this.video.removeAttribute('src')
|
||||||
|
this.video.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
this.retryCount = 0
|
||||||
|
this.isReady = false
|
||||||
|
this.playAttempts = 0
|
||||||
|
|
||||||
|
if (this.cleanupTimeout) {
|
||||||
|
clearTimeout(this.cleanupTimeout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initializePlayer() {
|
||||||
|
if (!this.isActive || !this.url) return
|
||||||
|
|
||||||
|
// 如果是重新初始化播放器,先清理已存在的资源
|
||||||
|
if (this.hls) {
|
||||||
|
this.immediateCleanup()
|
||||||
|
}
|
||||||
|
this.hls = new Hls({
|
||||||
|
// 内存优化配置
|
||||||
|
maxBufferSize: 0, // 降低缓冲区大小(15MB)
|
||||||
|
maxBufferLength: 0.1, // 更小的缓冲窗口
|
||||||
|
liveSyncDuration: 1, // 紧跟直播点
|
||||||
|
liveMaxLatencyDuration: 5, // 最大延迟5秒
|
||||||
|
liveDurationInfinity: true,
|
||||||
|
lowLatencyMode: true, // 启用低延迟模式
|
||||||
|
maxMaxBufferLength: 60,
|
||||||
|
backBufferLength: 1, // 减少保留的缓冲数据
|
||||||
|
manifestLoadingTimeOut: 10000,
|
||||||
|
manifestLoadingMaxRetry: 5,
|
||||||
|
fragLoadingTimeOut: 5000,
|
||||||
|
fragLoadingMaxRetry: 2,
|
||||||
|
enableWorker: true, // 启用Web Worker
|
||||||
|
recycleVideoFrames: true, // 启用帧回收
|
||||||
|
|
||||||
|
startLevel: -1,
|
||||||
|
autoStartLoad: true,
|
||||||
|
maxBufferHole: 2, // 允许更大的时间缺口
|
||||||
|
highBufferWatchdogPeriod: 4, // 延长监控周期
|
||||||
|
nudgeMaxRetry: 5, // 增加微调重试次数
|
||||||
|
nudgeOffset: 0.05, // 微调步长(秒)
|
||||||
|
jumpGaps: false,
|
||||||
|
stallThreshold: 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hls.attachMedia(this.video)
|
||||||
|
this.hls.loadSource(this.url)
|
||||||
|
|
||||||
|
// 事件处理
|
||||||
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
this.loading = false
|
||||||
|
this.retryCount = 0
|
||||||
|
this.safePlay()
|
||||||
|
})
|
||||||
|
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
console.log('核心视频错误',data.type)
|
||||||
|
// this.hls.startLoad(); //重连
|
||||||
|
if (data.type === Hls.ErrorTypes.BUFFER_STALLED_ERROR) {
|
||||||
|
console.error('缓冲停滞错误,尝试重新加载视频');
|
||||||
|
this.hls.startLoad(); // 尝试重新加载视频
|
||||||
|
}
|
||||||
|
this.handleHlsError(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
||||||
|
this.cleanupNetworkResources()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
initVideo() {
|
||||||
|
this.beforeDestroy()
|
||||||
|
this.hls = new Hls({
|
||||||
|
maxBufferLength: 30, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
this.hls.attachMedia(this.video)
|
||||||
|
this.hls.loadSource(this.url)
|
||||||
|
|
||||||
|
// 事件处理
|
||||||
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
this.loading = false
|
||||||
|
this.retryCount = 0
|
||||||
|
this.safePlay()
|
||||||
|
})
|
||||||
|
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
this.hls.startLoad(); //重连
|
||||||
|
this.handleHlsError(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
||||||
|
this.cleanupNetworkResources()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cleanupNetworkResources() {
|
||||||
|
// 清理已播放的缓冲数据
|
||||||
|
if (this.hls && this.video) {
|
||||||
|
try {
|
||||||
|
const currentTime = this.video.currentTime
|
||||||
|
this.hls.mediaBuffer = null
|
||||||
|
if (this.hls.bufferTimer) {
|
||||||
|
clearInterval(this.hls.bufferTimer)
|
||||||
|
}
|
||||||
|
this.hls.flushBuffer()
|
||||||
|
this.video.currentTime = currentTime
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Buffer cleanup error:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleHlsError(data) {
|
||||||
|
// console.error('HLS Error:', data)
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
this.retryLoad()
|
||||||
|
break
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
this.hls.recoverMediaError()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.fullCleanup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
retryLoad() {
|
||||||
|
if (this.retryCount < this.maxRetries) {
|
||||||
|
this.retryCount++
|
||||||
|
this.showLoadingIndicator(`重试第 ${this.retryCount} 次...`)
|
||||||
|
setTimeout(() => this.initializePlayer(), 2000)
|
||||||
|
} else {
|
||||||
|
this.showLoadingIndicator('视频加载失败')
|
||||||
|
this.fullCleanup()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showLoadingIndicator(text) {
|
||||||
|
this.loading = true
|
||||||
|
this.loadingText = text
|
||||||
|
},
|
||||||
|
|
||||||
|
async safePlay() {
|
||||||
|
if (!this.video || this.playAttempts >= this.maxPlayAttempts) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.playAttempts++
|
||||||
|
if (this.video.readyState >= 2) {
|
||||||
|
await this.video.play()
|
||||||
|
this.playAttempts = 0
|
||||||
|
} else {
|
||||||
|
setTimeout(this.safePlay, 500)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('播放失败:', error)
|
||||||
|
setTimeout(this.safePlay, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 移除所有事件监听
|
||||||
|
if (this.video) {
|
||||||
|
const events = ['canplay', 'waiting', 'playing', 'error', 'stalled', 'suspend']
|
||||||
|
events.forEach((event) => {
|
||||||
|
this.video.removeEventListener(event, this.handleVideoEvent)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.fullCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.myVideo-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.myVideo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
// aspect-ratio: 16/9;
|
||||||
|
/* border: 1px solid #ccc; */
|
||||||
|
// border-radius: vw(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: white;
|
||||||
|
padding: vw(10);
|
||||||
|
border-radius: vw(4);
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
<div v-show="isActive" class="myVideo-container">
|
<div v-show="isActive" class="myVideo-container">
|
||||||
<video
|
<video
|
||||||
class="myVideo"
|
class="myVideo"
|
||||||
|
id="video"
|
||||||
ref="videoElement"
|
ref="videoElement"
|
||||||
|
preload="auto"
|
||||||
muted
|
muted
|
||||||
playsinline
|
autoplay
|
||||||
:controls="false"
|
:controls="false"
|
||||||
disablePictureInPicture
|
|
||||||
></video>
|
></video>
|
||||||
<!-- <div v-if="loading" class="loading-overlay pointer-events-none">
|
<!-- <div v-if="loading" class="loading-overlay pointer-events-none">
|
||||||
<div class="loading-text">{{ loadingText }}</div>
|
<div class="loading-text">{{ loadingText }}</div>
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
|
import WebRTCWhep from 'whepts'
|
||||||
export default {
|
export default {
|
||||||
name: 'HlsPlayer',
|
name: 'HlsPlayer',
|
||||||
props: {
|
props: {
|
||||||
@@ -120,6 +121,7 @@
|
|||||||
immediateCleanup() {
|
immediateCleanup() {
|
||||||
// 立即停止网络请求
|
// 立即停止网络请求
|
||||||
if (this.hls) {
|
if (this.hls) {
|
||||||
|
this.hls.close();
|
||||||
this.hls.stopLoad()
|
this.hls.stopLoad()
|
||||||
this.hls.detachMedia()
|
this.hls.detachMedia()
|
||||||
}
|
}
|
||||||
@@ -158,6 +160,22 @@
|
|||||||
initializePlayer() {
|
initializePlayer() {
|
||||||
if (!this.isActive || !this.url) return
|
if (!this.isActive || !this.url) return
|
||||||
|
|
||||||
|
if(this.url.startsWith('http://192.168.77.200:8050/')){
|
||||||
|
this.hls = new WebRTCWhep({
|
||||||
|
url: this.url, // WHEP 服务器地址
|
||||||
|
container: this.video, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
this.hls.on('error', (error) => {
|
||||||
|
console.error('错误:', error.message, error.type)
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
this.initializePlayer();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.hls.on('play:failed', (err) => {
|
||||||
|
// this.initializePlayer();
|
||||||
|
})
|
||||||
|
}else{
|
||||||
// 如果是重新初始化播放器,先清理已存在的资源
|
// 如果是重新初始化播放器,先清理已存在的资源
|
||||||
if (this.hls) {
|
if (this.hls) {
|
||||||
this.immediateCleanup()
|
this.immediateCleanup()
|
||||||
@@ -211,6 +229,8 @@
|
|||||||
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
this.hls.on(Hls.Events.BUFFER_EOS, () => {
|
||||||
this.cleanupNetworkResources()
|
this.cleanupNetworkResources()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
initVideo() {
|
initVideo() {
|
||||||
this.beforeDestroy()
|
this.beforeDestroy()
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '2%',
|
left: '2%',
|
||||||
right: '2%',
|
right: '5%',
|
||||||
bottom: '5%',
|
bottom: '5%',
|
||||||
top: '10%',
|
top: '10%',
|
||||||
containLabel: true
|
containLabel: true
|
||||||
|
|||||||
@@ -48,10 +48,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const { id, setOption,chartVal,dispose ,clearOption} = useEchart()
|
const { id,chart, setOption,chartVal,dispose ,clearOption} = useEchart()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let condShow = ref(0)
|
let condShow = ref(0)
|
||||||
let aIndex = 1
|
let aIndex = 1
|
||||||
var colorList = []
|
var colorList = []
|
||||||
@@ -61,38 +58,33 @@
|
|||||||
(newVal) => {
|
(newVal) => {
|
||||||
|
|
||||||
aIndex+=1
|
aIndex+=1
|
||||||
|
|
||||||
if(aIndex>=3&&!newVal.length){
|
if(aIndex>=3&&!newVal.length){
|
||||||
condShow.value = 1
|
condShow.value = 1
|
||||||
|
|
||||||
}
|
}
|
||||||
if (newVal.length > 0) {
|
if (newVal.length > 0) {
|
||||||
|
colorList = []
|
||||||
console.log(colorList.value,'colorList')
|
|
||||||
condShow.value = 2
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
clearOption();
|
||||||
init()
|
init()
|
||||||
// defaultCofig.legend.formatter = (name) => {
|
|
||||||
// let percent = props.dataList.find((item) => item.name == name).value
|
|
||||||
// return name + '\u3000' + `${percent}%`
|
|
||||||
// }
|
|
||||||
// defaultCofig.series[0].data = props.dataList
|
|
||||||
// defaultCofig.series[0].label.formatter = () => {
|
|
||||||
// return `{value|${props.total}}` + '\n' + `{name|${props.label} }`
|
|
||||||
// }
|
|
||||||
// setOption({
|
|
||||||
// ...defaultCofig,
|
|
||||||
// ...props.config
|
|
||||||
// })
|
|
||||||
|
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const init = ()=>{
|
const init = ()=>{
|
||||||
|
if(condShow.value===2){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearOption()
|
||||||
|
colorList = [];
|
||||||
|
condShow.value = 2
|
||||||
|
const validDataList = props.dataList.filter(item => {
|
||||||
|
return item && item.name && (item.value || item.value === 0);
|
||||||
|
});
|
||||||
props.dataList.forEach((item,index)=>{
|
props.dataList.forEach((item,index)=>{
|
||||||
|
|
||||||
if(item.name=='负面'){
|
if(item.name=='负面'){
|
||||||
@@ -121,7 +113,17 @@
|
|||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontSize: fitChartSize(14)
|
fontSize: fitChartSize(14)
|
||||||
}
|
},
|
||||||
|
formatter: (name) => {
|
||||||
|
// let percent = props.dataList.find((item) => item.name == name).value
|
||||||
|
const item = validDataList.find((item) => item.name === name);
|
||||||
|
|
||||||
|
const percent = item ? item.value : 0;
|
||||||
|
|
||||||
|
const displayName = name || '';
|
||||||
|
return `${displayName}\u3000${percent}%`;
|
||||||
|
|
||||||
|
},
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
@@ -155,22 +157,50 @@
|
|||||||
labelLine: {
|
labelLine: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
data: []
|
data: validDataList
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
defaultCofig.legend.formatter = (name) => {
|
// defaultCofig.series[0].data = props.dataList
|
||||||
let percent = props.dataList.find((item) => item.name == name).value
|
// defaultCofig.legend.formatter = (name) => {
|
||||||
return name + '\u3000' + `${percent}%`
|
// let percent = props.dataList.find((item) => item.name == name).value
|
||||||
|
// if(name){
|
||||||
|
// return name + '\u3000' + `${percent}%`
|
||||||
|
// }else{
|
||||||
|
// return name + '\u3000' + `${0}%`
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// defaultCofig.series[0].label.formatter = () => {
|
||||||
|
// return `{value|${props.total}}` + '\n' + `{name|${props.label} }`
|
||||||
|
// }
|
||||||
|
setOption({},true)
|
||||||
|
const changeChart = setOption({
|
||||||
|
...defaultCofig,
|
||||||
|
...props.config
|
||||||
|
})
|
||||||
|
|
||||||
|
changeChart.off('legendselectchanged');
|
||||||
|
changeChart.on('legendselectchanged', function (e) {
|
||||||
|
console.log(e,'e')
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
}
|
}
|
||||||
defaultCofig.series[0].data = props.dataList
|
|
||||||
defaultCofig.series[0].label.formatter = () => {
|
|
||||||
return `{value|${props.total}}` + '\n' + `{name|${props.label} }`
|
|
||||||
}
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.dataList.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseFloat(item.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
defaultCofig.series[0].label.formatter = `{value|${parseInt(echartsNum/100*props.total)}}` + '\n' + `{name|${props.label}}`;
|
||||||
setOption({
|
setOption({
|
||||||
...defaultCofig,
|
...defaultCofig,
|
||||||
...props.config
|
...props.config
|
||||||
})
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="action-item">
|
<div class="action-item">
|
||||||
<div class="video-follow" @click="handleCollectAdd(cameraIndexCode, isDiy, index)" v-if="isDiy==0">收藏</div>
|
<div class="video-follow" @click="handleCollectAdd()" v-if="isDayCurr==0">收藏</div>
|
||||||
<div class="video-follow" @click="handleCollectAdd(cameraIndexCode, isDiy, index)" v-else="isDiy==1">取消收藏</div>
|
<div class="video-follow" @click="handleCollectAdd()" v-if="isDayCurr==1">取消收藏</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -66,13 +66,20 @@
|
|||||||
import { postVideoControlApi,postVideoCollectApi } from '@/api/monitor'
|
import { postVideoControlApi,postVideoCollectApi } from '@/api/monitor'
|
||||||
import { getColletDiyApi } from '@/api/home'
|
import { getColletDiyApi } from '@/api/home'
|
||||||
import pubSub from 'pubsub-js'
|
import pubSub from 'pubsub-js'
|
||||||
const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
// const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
||||||
const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
// const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
||||||
const UP = 'UP' // 上转
|
// const UP = 'UP' // 上转
|
||||||
const DOWN = 'DOWN' // 下转
|
// const DOWN = 'DOWN' // 下转
|
||||||
const LEFT = 'LEFT' // 左转
|
// const LEFT = 'LEFT' // 左转
|
||||||
const RIGHT = 'RIGHT' // 右转
|
// const RIGHT = 'RIGHT' // 右转
|
||||||
const STOP = 'STOP' // 停止操作
|
// const STOP = 'STOP' // 停止操作
|
||||||
|
const Z00M_IN = 'zoomin' // 焦距变大
|
||||||
|
const Z00M_OUT = 'zoomout' // 焦距变小
|
||||||
|
const UP = 'up' // 上转
|
||||||
|
const DOWN = 'down' // 下转
|
||||||
|
const LEFT = 'left' // 左转
|
||||||
|
const RIGHT = 'right' // 右转
|
||||||
|
const STOP = 'stop' // 停止操作
|
||||||
let ACTION = '0'
|
let ACTION = '0'
|
||||||
let command = ref('')
|
let command = ref('')
|
||||||
|
|
||||||
@@ -103,8 +110,8 @@
|
|||||||
watch(
|
watch(
|
||||||
() =>modelValue.value,
|
() =>modelValue.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
// colletCond.value = props.isCollect
|
colletCond.value = props.isCollect
|
||||||
// isDayCurr.value = props.isDiy
|
isDayCurr.value = props.isDiy
|
||||||
console.log(props.isDiy,'val[0].value')
|
console.log(props.isDiy,'val[0].value')
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -114,7 +121,7 @@
|
|||||||
)
|
)
|
||||||
const emit = defineEmits(['isDiyChange']);
|
const emit = defineEmits(['isDiyChange']);
|
||||||
// 收藏
|
// 收藏
|
||||||
const handleCollectAdd = async (id, status, index) => {
|
const handleCollectAdd = async () => {
|
||||||
await getColletDiyApi({
|
await getColletDiyApi({
|
||||||
cameraIndexCode:props.cameraIndexCode,
|
cameraIndexCode:props.cameraIndexCode,
|
||||||
isDiy: props.isDiy == 0 ? 1 : 0
|
isDiy: props.isDiy == 0 ? 1 : 0
|
||||||
@@ -149,6 +156,7 @@
|
|||||||
const handleAction = async (e) => {
|
const handleAction = async (e) => {
|
||||||
if (e == STOP) {
|
if (e == STOP) {
|
||||||
ACTION = '1'
|
ACTION = '1'
|
||||||
|
command.value = e
|
||||||
} else {
|
} else {
|
||||||
ACTION = '0'
|
ACTION = '0'
|
||||||
command.value = e
|
command.value = e
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
v-for="(item, index) in navList"
|
v-for="(item, index) in navList"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
{{ item.dictLabel }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export function useEchart() {
|
|||||||
const setOption = (params, update = false) => {
|
const setOption = (params, update = false) => {
|
||||||
initChart()
|
initChart()
|
||||||
chart.setOption(params, update)
|
chart.setOption(params, update)
|
||||||
|
return chart;
|
||||||
}
|
}
|
||||||
const clearOption = () => {
|
const clearOption = () => {
|
||||||
console.log('clearooooooooooooooooo')
|
|
||||||
// 将series设置为空数组,可以清空图表内容
|
// 将series设置为空数组,可以清空图表内容
|
||||||
chart.setOption({
|
chart.setOption({
|
||||||
series:[]
|
series:[]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export function useWebSocket(url) {
|
|||||||
let socket = ref(null) // socket对象
|
let socket = ref(null) // socket对象
|
||||||
let isConnected = ref(false) // 是否连接成功
|
let isConnected = ref(false) // 是否连接成功
|
||||||
let dataRes = ref(null) // 存储推送数据
|
let dataRes = ref(null) // 存储推送数据
|
||||||
|
let timer=null;
|
||||||
const connectWebSocket = () => {
|
const connectWebSocket = () => {
|
||||||
socket.value = new WebSocket(url, 'echo-protocol', {
|
socket.value = new WebSocket(url, 'echo-protocol', {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -28,6 +28,19 @@ export function useWebSocket(url) {
|
|||||||
socket.value.onclose = (event) => {
|
socket.value.onclose = (event) => {
|
||||||
isConnected.value = false
|
isConnected.value = false
|
||||||
console.log('WebSocket close:',url, event)
|
console.log('WebSocket close:',url, event)
|
||||||
|
if(!timer){
|
||||||
|
timer =setInterval(() => {
|
||||||
|
if(!isConnected.value){
|
||||||
|
console.log('重连中...')
|
||||||
|
connectWebSocket()
|
||||||
|
}else if(isConnected.value){
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
@click="handleItem(item)"
|
@click="handleItem(item)"
|
||||||
>
|
>
|
||||||
<div class="item-unfollow" @click.stop="handleCollect(item.cameraIndexCode, index)">取消关注</div>
|
<div class="item-unfollow" @click.stop="handleCollect(item.cameraIndexCode, index)">取消关注</div>
|
||||||
<video class="item-video" :id="'video' + index" muted autoplay :controls="false">
|
<video class="item-video" :id="'videoall' + index" muted autoplay :controls="false">
|
||||||
<source type="application/x-mpegURL" />
|
<source type="application/x-mpegURL" />
|
||||||
</video>
|
</video>
|
||||||
<p>
|
<p>
|
||||||
@@ -36,18 +36,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getVideoListApi,getColletListApi } from '@/api/home'
|
import {getVideoListApi, getColletListApi, getPreviewUrlApi} from '@/api/home'
|
||||||
import { postVideoCollectApi } from '@/api/monitor'
|
import { postVideoCollectApi } from '@/api/monitor'
|
||||||
|
|
||||||
import primary from '@/assets/images/item-primary.png'
|
import primary from '@/assets/images/item-primary.png'
|
||||||
|
|
||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
import pubSub from 'pubsub-js'
|
import pubSub from 'pubsub-js'
|
||||||
|
import WebRTCWhep from "whepts";
|
||||||
|
|
||||||
let modelValue = defineModel()
|
let modelValue = defineModel()
|
||||||
let isCollect = ref(0)
|
let isCollect = ref(0)
|
||||||
let list = ref([])
|
let list = ref([])
|
||||||
let hlsRefs = []
|
let hlsRefs = []
|
||||||
|
let webrtcRefs = []
|
||||||
let total = ref(0)
|
let total = ref(0)
|
||||||
let src = ref('')
|
let src = ref('')
|
||||||
let cameraIndexCode = ref('')
|
let cameraIndexCode = ref('')
|
||||||
@@ -75,9 +77,14 @@
|
|||||||
getVideoList()
|
getVideoList()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const handleItem = (item) => {
|
const handleItem = async (item) => {
|
||||||
console.log(item, 'iscollect')
|
console.log(item, 'iscollect')
|
||||||
src.value = item.hlsUrl
|
let res = await getPreviewUrlApi({
|
||||||
|
cameraIndexCode: item.cameraIndexCode,
|
||||||
|
type: 'hls',
|
||||||
|
subStream:0
|
||||||
|
})
|
||||||
|
src.value = res.data.url
|
||||||
isCollect.value = item.isCollect
|
isCollect.value = item.isCollect
|
||||||
isDiy.value = item.isDiy
|
isDiy.value = item.isDiy
|
||||||
cameraIndexCode.value = item.cameraIndexCode
|
cameraIndexCode.value = item.cameraIndexCode
|
||||||
@@ -104,33 +111,84 @@
|
|||||||
})
|
})
|
||||||
hlsRefs = []
|
hlsRefs = []
|
||||||
}
|
}
|
||||||
|
if (webrtcRefs.length > 0) {
|
||||||
|
webrtcRefs.map((item) => {
|
||||||
|
item.close()
|
||||||
|
})
|
||||||
|
webrtcRefs = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const pageNumChange = () => {
|
const pageNumChange = () => {
|
||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
list.value = []
|
list.value = []
|
||||||
getVideoList()
|
getVideoList()
|
||||||
}
|
}
|
||||||
|
const createPlayer = (cameraIndexCode,videoElement) => {
|
||||||
|
getPreviewUrlApi({
|
||||||
|
type: 'hls',
|
||||||
|
cameraIndexCode: cameraIndexCode,
|
||||||
|
subStream:1
|
||||||
|
}).then(res=>{
|
||||||
|
const url = res.data.url;
|
||||||
|
if(url.startsWith('http://192.168.77.200:8050/')){
|
||||||
|
const player = new WebRTCWhep({
|
||||||
|
url:url, // WHEP 服务器地址
|
||||||
|
container: videoElement, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
player.on('error', (error) => {
|
||||||
|
console.error('错误:', error.message, error.type)
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
createPlayer(cameraIndexCode,videoElement)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player.on('play:failed', (err) => {
|
||||||
|
// createPlayer(cameraIndexCode,videoElement);
|
||||||
|
})
|
||||||
|
webrtcRefs.push(player)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
const player = new Hls({
|
||||||
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
player.loadSource(url)
|
||||||
|
player.attachMedia(videoElement)
|
||||||
|
player.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
videoElement.play()
|
||||||
|
})
|
||||||
|
player.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
// 根据错误类型进行处理
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.log('网络错误,尝试重新加载');
|
||||||
|
player.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.log('媒体错误,尝试修复');
|
||||||
|
player.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('无法恢复的错误,销毁播放器');
|
||||||
|
hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hlsRefs.push(player)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
const getVideoList = async () => {
|
const getVideoList = async () => {
|
||||||
let res = await getColletListApi(params)
|
let res = await getColletListApi(params)
|
||||||
list.value = res.data
|
list.value = res.data
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
list.value.forEach(async (item, index) => {
|
list.value.forEach(async (item, index) => {
|
||||||
var video = document.getElementById(`video${index}`)
|
var video = document.getElementById(`videoall${index}`)
|
||||||
const hls = new Hls({
|
createPlayer(item.cameraIndexCode,video);
|
||||||
enableWorker: false, // 禁用 Worker 来避免额外的线程
|
|
||||||
enableSoftwareAES: true, // 使用软件解码器以避免硬件解码的额外请求
|
|
||||||
cache: true, // 启用缓存
|
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
|
||||||
maxBufferSize: 20 * 1000 * 1000 // 最大缓冲大小(字节)
|
|
||||||
})
|
|
||||||
hls.loadSource(item.hlsUrl)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
video.play()
|
|
||||||
})
|
|
||||||
hlsRefs.push(hls)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,25 +10,14 @@
|
|||||||
:key="index"
|
:key="index"
|
||||||
@click="handleItem(item)"
|
@click="handleItem(item)"
|
||||||
>
|
>
|
||||||
<HlsPlayer :url="item.hlsUrl" />
|
|
||||||
<div class="item-unfollow" @click.stop="handleUnfollow(item.cameraIndexCode, index)">取消关注</div>
|
|
||||||
<!-- <div>
|
|
||||||
<p class="item-title--primary">
|
|
||||||
{{ item.cameraName || item.cameraIndexCode }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<video
|
<video
|
||||||
class="item-img"
|
class="item-img"
|
||||||
:id="'video' + index"
|
:id="'video' + index"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
:controls="false"
|
:controls="false"
|
||||||
:src="item.hlsUrl"
|
></video>
|
||||||
controlsList="nodownload"
|
<div class="item-unfollow" @click.stop="handleUnfollow(item.cameraIndexCode, index)">取消关注</div>
|
||||||
>
|
|
||||||
<source src="" type="application/x-mpegURL" />
|
|
||||||
</video>
|
|
||||||
</div> -->
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +39,7 @@
|
|||||||
|
|
||||||
import { useWebSocket } from '@/hooks/socket'
|
import { useWebSocket } from '@/hooks/socket'
|
||||||
import { mode, socketBaseUrl, proSocketBaseUrl } from '@/utils/config'
|
import { mode, socketBaseUrl, proSocketBaseUrl } from '@/utils/config'
|
||||||
|
import WebRTCWhep from 'whepts'
|
||||||
|
|
||||||
const { dataRes } = useWebSocket(
|
const { dataRes } = useWebSocket(
|
||||||
`${mode == 'dev' ? socketBaseUrl : proSocketBaseUrl}/ws/securityAlerts`
|
`${mode == 'dev' ? socketBaseUrl : proSocketBaseUrl}/ws/securityAlerts`
|
||||||
@@ -69,12 +59,17 @@ let isCollect = ref(0)
|
|||||||
let cameraIndexCode = ref('')
|
let cameraIndexCode = ref('')
|
||||||
let videoShow = ref(false)
|
let videoShow = ref(false)
|
||||||
let allShow = ref(false)
|
let allShow = ref(false)
|
||||||
|
let webrtcRefs = []
|
||||||
let hlsRefs = []
|
let hlsRefs = []
|
||||||
let timer = null
|
let timer = null
|
||||||
let isDiy = ref(0)
|
let isDiy = ref(0)
|
||||||
const handleItem = (item) => {
|
const handleItem = async (item) => {
|
||||||
console.log(item,'1111111111111111111111111')
|
let res = await getPreviewUrlApi({
|
||||||
src.value = item.hlsUrl
|
cameraIndexCode: item.cameraIndexCode,
|
||||||
|
type: 'hls',
|
||||||
|
subStream:0
|
||||||
|
})
|
||||||
|
src.value = res.data.url
|
||||||
cameraIndexCode.value = item.cameraIndexCode
|
cameraIndexCode.value = item.cameraIndexCode
|
||||||
isCollect.value = item.isCollect
|
isCollect.value = item.isCollect
|
||||||
isDiy.value = item.isDiy
|
isDiy.value = item.isDiy
|
||||||
@@ -82,50 +77,93 @@ let isCollect = ref(0)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const postVideoRemain = () => {
|
const postVideoRemain = () => {
|
||||||
timer = setInterval(() => {
|
// timer = setInterval(() => {
|
||||||
if(!list.value.length) return false;
|
// if(!list.value.length) return false;
|
||||||
postVideoRemainApi({
|
// postVideoRemainApi({
|
||||||
cameraIndexCode: list.value.map((item) => item.cameraIndexCode)
|
// cameraIndexCode: list.value.map((item) => item.cameraIndexCode)
|
||||||
})
|
// })
|
||||||
}, 1500)
|
// }, 1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPreviewUrl = async (code) => {
|
const getPreviewUrl = async (code) => {
|
||||||
let res = await getPreviewUrlApi({
|
let res = await getPreviewUrlApi({
|
||||||
cameraIndexCode: code,
|
cameraIndexCode: code,
|
||||||
type: 'hls'
|
type: 'hls',
|
||||||
|
subStream:1
|
||||||
})
|
})
|
||||||
src.value = res.data.url
|
src.value = res.data.url
|
||||||
videoShow.value = true
|
videoShow.value = true
|
||||||
}
|
}
|
||||||
|
const createPlayer = (cameraIndexCode,videoElement) => {
|
||||||
|
getPreviewUrlApi({
|
||||||
|
type: 'hls',
|
||||||
|
cameraIndexCode: cameraIndexCode,
|
||||||
|
subStream:0
|
||||||
|
}).then(res=>{
|
||||||
|
const url = res.data.url;
|
||||||
|
if(url.startsWith('http://192.168.77.200:8050/')){
|
||||||
|
const player = new WebRTCWhep({
|
||||||
|
url:url, // WHEP 服务器地址
|
||||||
|
container: videoElement, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
player.on('error', (error) => {
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
createPlayer(cameraIndexCode,videoElement);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player.on('play:failed', (err) => {
|
||||||
|
// createPlayer(cameraIndexCode,videoElement);
|
||||||
|
})
|
||||||
|
webrtcRefs.push(player)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
const player = new Hls({
|
||||||
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
player.loadSource(url)
|
||||||
|
player.attachMedia(videoElement)
|
||||||
|
player.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
videoElement.play()
|
||||||
|
})
|
||||||
|
player.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
// 根据错误类型进行处理
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.log('网络错误,尝试重新加载');
|
||||||
|
player.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.log('媒体错误,尝试修复');
|
||||||
|
player.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('无法恢复的错误,销毁播放器');
|
||||||
|
// hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hlsRefs.push(player)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
const getVideoList = async () => {
|
const getVideoList = async () => {
|
||||||
let res = await getColletListApi({
|
let res = await getColletListApi({
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
})
|
})
|
||||||
list.value = res.data
|
list.value = res.data
|
||||||
postVideoRemain()
|
|
||||||
if (timer) clearInterval(timer)
|
if (timer) clearInterval(timer)
|
||||||
// console.log(list.value,'list.valuelist.valuelist.valuelist.value')
|
nextTick(() => {
|
||||||
// nextTick(() => {
|
list.value.forEach(async (item, index) => {
|
||||||
// list.value.forEach(async (item, index) => {
|
var videoElement = document.getElementById(`video${index}`)
|
||||||
// var video = document.getElementById(`video${index}`)
|
createPlayer(item.cameraIndexCode,videoElement);
|
||||||
// const hls = new Hls({
|
})
|
||||||
// enableWorker: false, // 禁用 Worker 来避免额外的线程
|
})
|
||||||
// enableSoftwareAES: true, // 使用软件解码器以避免硬件解码的额外请求
|
|
||||||
// cache: true, // 启用缓存
|
|
||||||
// maxBufferLength: 10, // 最大缓冲长度(秒)
|
|
||||||
// maxMaxBufferLength: 15, // 缓冲区长度的上限
|
|
||||||
// maxBufferSize: 20 * 1000 * 1000 // 最大缓冲大小(字节)
|
|
||||||
// })
|
|
||||||
// hls.loadSource(item.hlsUrl)
|
|
||||||
// hls.attachMedia(video)
|
|
||||||
// hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
// video.play()
|
|
||||||
// })
|
|
||||||
// hlsRefs.push(hls)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
watch(
|
watch(
|
||||||
() => list.value,
|
() => list.value,
|
||||||
@@ -143,12 +181,22 @@ let isCollect = ref(0)
|
|||||||
})
|
})
|
||||||
hlsRefs = []
|
hlsRefs = []
|
||||||
}
|
}
|
||||||
|
if(webrtcRefs.length>0){
|
||||||
|
webrtcRefs.map((item) => {
|
||||||
|
try{
|
||||||
|
item.close()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
webrtcRefs = [];
|
||||||
|
}
|
||||||
getVideoList()
|
getVideoList()
|
||||||
}
|
}
|
||||||
const onVideoCollect = () => {
|
const onVideoCollect = () => {
|
||||||
pubSub.subscribe('videoCollect', () => {
|
pubSub.subscribe('videoCollect', () => {
|
||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
// getVideoList()
|
getVideoList()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,7 +191,6 @@
|
|||||||
|
|
||||||
// 点击导航
|
// 点击导航
|
||||||
const handleNav = (item) => {
|
const handleNav = (item) => {
|
||||||
console.log(item,router.currentRoute.value.path,'测试一下 ')
|
|
||||||
if (isSkip.value) {
|
if (isSkip.value) {
|
||||||
router.push(item.path)
|
router.push(item.path)
|
||||||
} else {
|
} else {
|
||||||
@@ -214,7 +213,6 @@
|
|||||||
otherLeftLabel.value = '其他酒店'
|
otherLeftLabel.value = '其他酒店'
|
||||||
otherRightLabel.value = '其他场馆'
|
otherRightLabel.value = '其他场馆'
|
||||||
pubSub.publish('hotelChange', item)
|
pubSub.publish('hotelChange', item)
|
||||||
// console.log(item,'hotelChange')
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,8 +262,7 @@
|
|||||||
isSkip.value = false
|
isSkip.value = false
|
||||||
isBack.value = true
|
isBack.value = true
|
||||||
let res = await getSpotListApi()
|
let res = await getSpotListApi()
|
||||||
navLeft.value = res.data
|
navLeft.value = res.data.slice(0,3)
|
||||||
|
|
||||||
current.value = res.data[0].id
|
current.value = res.data[0].id
|
||||||
title.value = navLeft.value[0].name
|
title.value = navLeft.value[0].name
|
||||||
pubSub.publish('scenicChange', navLeft.value[0])
|
pubSub.publish('scenicChange', navLeft.value[0])
|
||||||
@@ -309,7 +306,7 @@
|
|||||||
scenicSpotId: '',
|
scenicSpotId: '',
|
||||||
id:10086,
|
id:10086,
|
||||||
},
|
},
|
||||||
...spotRes.data
|
...spotRes.data.slice(0,3)
|
||||||
]
|
]
|
||||||
if(monitorDefaultData.value){
|
if(monitorDefaultData.value){
|
||||||
current.value = monitorDefaultData.value.id
|
current.value = monitorDefaultData.value.id
|
||||||
@@ -371,11 +368,10 @@
|
|||||||
let hotelRes = await getHotelListApi({ hotelStadiumType: 1 })
|
let hotelRes = await getHotelListApi({ hotelStadiumType: 1 })
|
||||||
navLeft.value = hotelRes.data.slice(0, 3)
|
navLeft.value = hotelRes.data.slice(0, 3)
|
||||||
current.value = navLeft.value[0].id
|
current.value = navLeft.value[0].id
|
||||||
otherLeftNav.value = hotelRes.data.slice(3, hotelRes.data.length - 1)
|
otherLeftNav.value = hotelRes.data.slice(3, hotelRes.data.length)
|
||||||
let venueRes = await getHotelListApi({ hotelStadiumType: 2 })
|
let venueRes = await getHotelListApi({ hotelStadiumType: 2 })
|
||||||
// console.log(venueRes,'venueRes')
|
|
||||||
navRight.value = venueRes.data.slice(0, 3)
|
navRight.value = venueRes.data.slice(0, 3)
|
||||||
otherRightNav.value = venueRes.data.slice(3, venueRes.data.length - 1)
|
otherRightNav.value = venueRes.data.slice(3, venueRes.data.length)
|
||||||
pubSub.publish('hotelChange', hotelRes.data[0])
|
pubSub.publish('hotelChange', hotelRes.data[0])
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const useScenicStore = defineStore('scenic', () => {
|
|||||||
infoList: [
|
infoList: [
|
||||||
{ name: '游玩舒适度', type: 0, value: '空闲' },
|
{ name: '游玩舒适度', type: 0, value: '空闲' },
|
||||||
// { name: '景区安全', type: 0, value: '安全' },
|
// { name: '景区安全', type: 0, value: '安全' },
|
||||||
{ name: '通景交通', type: 0, value: '通畅' },
|
{ name: '交通拥堵度', type: 0, value: '通畅' },
|
||||||
{ name: '停车场负荷', type: 0, value: '空闲' }
|
{ name: '停车场负荷', type: 0, value: '空闲' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ export const proSocketBaseUrl = 'ws://192.168.77.200:8060'
|
|||||||
export const mode = 'pro' // 测试 dev 正式 pro
|
export const mode = 'pro' // 测试 dev 正式 pro
|
||||||
|
|
||||||
export const devToken =
|
export const devToken =
|
||||||
'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImE1OWFmNWYwLTU3OWItNDJkNy1hZDJhLTY0Y2JlODA5ZWI1NiJ9.BTxvu6jUWbN0qONWf5K6VzXopE8T8qXzKuX-mij21VJT4U0LdgnqToyqeNDQ2OyJ6cvpdJBzQ9mEEb-dnwrTpQ'
|
'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImE1OWFmNWYwLTU3OWItNDJkNy1hZDJhLTY0Y2JlODA5ZWI1NiJ9.BTxvu6jUWbN0qONWf5K6VzXopE8T8qXzKuX-mij21VJT4U0LdgnqToyqeNDQ2OyJ6cvpdJBzQ9mEEb-dnwrTpQ11'
|
||||||
export const proToken =
|
export const proToken =
|
||||||
'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImE1OWFmNWYwLTU3OWItNDJkNy1hZDJhLTY0Y2JlODA5ZWI1NiJ9.dSLZekRsYf5ZZDCYqFEOgHTi4GeHD0m10gGHXrbgpc-hD52Zt7Vw05cxhQ-lzY29yf2IxH0oYi28DBfHdtf9SA'
|
'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImE1OWFmNWYwLTU3OWItNDJkNy1hZDJhLTY0Y2JlODA5ZWI1NiJ9.dSLZekRsYf5ZZDCYqFEOgHTi4GeHD0m10gGHXrbgpc-hD52Zt7Vw05cxhQ-lzY29yf2IxH0oYi28DBfHdtf9SA'
|
||||||
|
|||||||
@@ -35,14 +35,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<video
|
<video
|
||||||
class="video-item__video"
|
class="video-item__video"
|
||||||
:id="'monitorVideo' + element.cameraIndexCode"
|
:id="'collectmonitorVideo' + element.cameraIndexCode"
|
||||||
preload="auto"
|
preload="auto"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
:controls="false"
|
:controls="false"
|
||||||
/>
|
/>
|
||||||
<p class="video-item__title--primary">
|
<p class="video-item__title--primary">
|
||||||
{{ element.cameraName || element.scenicAreaId }}
|
{{ element.cameraName || element.cameraIndexCode }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,29 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="pagination">
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="params.pageNum"
|
|
||||||
:page-size="params.pageSize"
|
|
||||||
:total="total"
|
|
||||||
background
|
|
||||||
layout="prev, pager, next"
|
|
||||||
@current-change="currentChange"
|
|
||||||
/>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <ul class="videos">
|
|
||||||
<li class="video-item" v-for="item in 8" :key="item">
|
|
||||||
<img src="@/assets/images/sxzd.png" alt="" />
|
|
||||||
<p>
|
|
||||||
<span>核心路段:这是一条信息说明</span>
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
</ul> -->
|
|
||||||
<!-- <div class="pagination-box">
|
|
||||||
<el-pagination background layout="prev, pager, next" :total="1000" />
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
<VideoDialog v-model="show" :cameraIndexCode="cameraIndexCode" @isDiyChange="isDiyChange" :isDiy="isDiy" :isCollect="isCollect" :src="videoSrc" />
|
<VideoDialog v-model="show" :cameraIndexCode="cameraIndexCode" @isDiyChange="isDiyChange" :isDiy="isDiy" :isCollect="isCollect" :src="videoSrc" />
|
||||||
</template>
|
</template>
|
||||||
@@ -93,16 +71,25 @@
|
|||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
import emptyIco from '@/assets/images/n-icon.png'
|
import emptyIco from '@/assets/images/n-icon.png'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
import WebRTCWhep from 'whepts'
|
||||||
const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
// const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
||||||
const UP = 'UP' // 上转
|
// const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
||||||
const DOWN = 'DOWN' // 下转
|
// const UP = 'UP' // 上转
|
||||||
const LEFT = 'LEFT' // 左转
|
// const DOWN = 'DOWN' // 下转
|
||||||
const RIGHT = 'RIGHT' // 右转
|
// const LEFT = 'LEFT' // 左转
|
||||||
const STOP = 'STOP' // 停止操作
|
// const RIGHT = 'RIGHT' // 右转
|
||||||
|
// const STOP = 'STOP' // 停止操作
|
||||||
|
const Z00M_IN = 'zoomin' // 焦距变大
|
||||||
|
const Z00M_OUT = 'zoomout' // 焦距变小
|
||||||
|
const UP = 'up' // 上转
|
||||||
|
const DOWN = 'down' // 下转
|
||||||
|
const LEFT = 'left' // 左转
|
||||||
|
const RIGHT = 'right' // 右转
|
||||||
|
const STOP = 'stop' // 停止操作
|
||||||
let cond = ref(false)
|
let cond = ref(false)
|
||||||
let ACTION = '0'
|
let ACTION = '0'
|
||||||
let hlsRefs = []
|
let hlsRefs = []
|
||||||
|
let webrtcRefs = []
|
||||||
let hlsRef = null
|
let hlsRef = null
|
||||||
let timer = null
|
let timer = null
|
||||||
let videoLog = ref(1)
|
let videoLog = ref(1)
|
||||||
@@ -169,7 +156,6 @@
|
|||||||
params.businessVideoDisplayPosition = ''
|
params.businessVideoDisplayPosition = ''
|
||||||
let res = await getVideCollectCate(params)
|
let res = await getVideCollectCate(params)
|
||||||
videoList.value = res.data
|
videoList.value = res.data
|
||||||
console.log(res,videoList.value.length,'ressssssssssssss')
|
|
||||||
if(videoList.value.length<=3){
|
if(videoList.value.length<=3){
|
||||||
grad.value = 3
|
grad.value = 3
|
||||||
}else if(videoList.value.length<=6){
|
}else if(videoList.value.length<=6){
|
||||||
@@ -205,6 +191,7 @@
|
|||||||
const handleAction = async (e) => {
|
const handleAction = async (e) => {
|
||||||
if (e == STOP) {
|
if (e == STOP) {
|
||||||
ACTION = '1'
|
ACTION = '1'
|
||||||
|
command.value = e
|
||||||
} else {
|
} else {
|
||||||
ACTION = '0'
|
ACTION = '0'
|
||||||
command.value = e
|
command.value = e
|
||||||
@@ -237,7 +224,8 @@
|
|||||||
show.value = true
|
show.value = true
|
||||||
let res = await getPreviewUrlApi({
|
let res = await getPreviewUrlApi({
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
cameraIndexCode:itemCode
|
cameraIndexCode:itemCode,
|
||||||
|
subStream:0
|
||||||
})
|
})
|
||||||
cameraIndexCode.value = itemCode;
|
cameraIndexCode.value = itemCode;
|
||||||
isCollect.value = resource.isCollect
|
isCollect.value = resource.isCollect
|
||||||
@@ -248,9 +236,23 @@
|
|||||||
const clearHlsRefs = () => {
|
const clearHlsRefs = () => {
|
||||||
if (hlsRefs.length > 0) {
|
if (hlsRefs.length > 0) {
|
||||||
hlsRefs.map((item) => {
|
hlsRefs.map((item) => {
|
||||||
|
try{
|
||||||
item.destroy()
|
item.destroy()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
hlsRefs = []
|
hlsRefs = []
|
||||||
|
}
|
||||||
|
if(webrtcRefs.length>0){
|
||||||
|
webrtcRefs.map((item) => {
|
||||||
|
try{
|
||||||
|
item.close()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
webrtcRefs = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 分页
|
// 分页
|
||||||
@@ -279,6 +281,64 @@
|
|||||||
if (type == 100) initVideo()
|
if (type == 100) initVideo()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
const createPlayer = (cameraIndexCode,videoElement) => {
|
||||||
|
getPreviewUrlApi({
|
||||||
|
type: 'hls',
|
||||||
|
cameraIndexCode: cameraIndexCode,
|
||||||
|
subStream:1
|
||||||
|
}).then(res=>{
|
||||||
|
const url = res.data.url;
|
||||||
|
if(url.startsWith('http://192.168.77.200:8050/')){
|
||||||
|
const player = new WebRTCWhep({
|
||||||
|
url: url, // WHEP 服务器地址
|
||||||
|
container: videoElement, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
player.on('error', (error) => {
|
||||||
|
console.error('错误:', error.message, error.type)
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
createPlayer(cameraIndexCode,videoElement);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player.on('play:failed', (err) => {
|
||||||
|
// console.log('播放失败:', err)
|
||||||
|
// createPlayer(cameraIndexCode,videoElement);
|
||||||
|
})
|
||||||
|
webrtcRefs.push(player)
|
||||||
|
}else{
|
||||||
|
const player = new Hls({
|
||||||
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
player.loadSource(url)
|
||||||
|
player.attachMedia(videoElement)
|
||||||
|
player.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
videoElement.play()
|
||||||
|
})
|
||||||
|
player.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
// 根据错误类型进行处理
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.log('网络错误,尝试重新加载');
|
||||||
|
player.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.log('媒体错误,尝试修复');
|
||||||
|
player.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('无法恢复的错误,销毁播放器');
|
||||||
|
// hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hlsRefs.push(player)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
const initVideo = () => {
|
const initVideo = () => {
|
||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -286,20 +346,8 @@
|
|||||||
|
|
||||||
it.videos.forEach((item,index)=>{
|
it.videos.forEach((item,index)=>{
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const video = document.getElementById(`monitorVideo${item.cameraIndexCode}`)
|
const video = document.getElementById(`collectmonitorVideo${item.cameraIndexCode}`)
|
||||||
if(item.hlsUrl){
|
createPlayer(item.cameraIndexCode,video);
|
||||||
const hls = new Hls({
|
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
|
||||||
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
|
||||||
})
|
|
||||||
hls.loadSource(item.hlsUrl)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
video.play()
|
|
||||||
})
|
|
||||||
hlsRefs.push(hls)
|
|
||||||
}
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -318,18 +366,18 @@
|
|||||||
)
|
)
|
||||||
// 更新视频
|
// 更新视频
|
||||||
const postVideoRemain = async () => {
|
const postVideoRemain = async () => {
|
||||||
timer = setInterval(() => {
|
// timer = setInterval(() => {
|
||||||
clearInterval(timer)
|
// clearInterval(timer)
|
||||||
videoList.value.forEach((items,index)=>{
|
// videoList.value.forEach((items,index)=>{
|
||||||
setTimeout(()=>{
|
// setTimeout(()=>{
|
||||||
postVideoRemainApi({
|
// postVideoRemainApi({
|
||||||
cameraIndexCode: items.videos.map((item) => item.cameraIndexCode)
|
// cameraIndexCode: items.videos.map((item) => item.cameraIndexCode)
|
||||||
})
|
// })
|
||||||
},1500)
|
// },1500)
|
||||||
|
//
|
||||||
})
|
// })
|
||||||
|
//
|
||||||
}, 1500)
|
// }, 1500)
|
||||||
}
|
}
|
||||||
const getVideoRegions = async () => {
|
const getVideoRegions = async () => {
|
||||||
let res = await getVideoRegionsApi({
|
let res = await getVideoRegionsApi({
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -138,7 +138,23 @@
|
|||||||
} else {
|
} else {
|
||||||
params.series[0].data = getSeriesData()
|
params.series[0].data = getSeriesData()
|
||||||
}
|
}
|
||||||
setOption(params)
|
const chart = setOption(params)
|
||||||
|
chart.on('legendselectchanged', function (e) {
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.list.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseInt(item.count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
params.series[0].label.formatter = `{label|拥堵次数}` + '\n' + `{value|${echartsNum}}`;
|
||||||
|
setOption(params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {})
|
onMounted(() => {})
|
||||||
|
|||||||
@@ -1,25 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="video-box">
|
<div class="video-box">
|
||||||
<div class="left-nav">
|
<div class="left-nav">
|
||||||
<div v-if="showNav" class="ul">
|
|
||||||
<div
|
|
||||||
class="li"
|
|
||||||
:class="{ active: current == index }"
|
|
||||||
v-for="(item, index) in navList"
|
|
||||||
:key="index"
|
|
||||||
@click="handleNav(index)"
|
|
||||||
>
|
|
||||||
<span v-if="!params.businessScenicArea && index == 0"> 核心路段 </span>
|
|
||||||
<span v-else>
|
|
||||||
{{ item.dictLabel }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:class="{ active: current == 3 }"
|
|
||||||
@click="handleNav(3)" class="li">
|
|
||||||
<span>自定义</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bom-box">
|
<div class="bom-box">
|
||||||
<Title2 title="检索" />
|
<Title2 title="检索" />
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
@@ -55,7 +36,7 @@
|
|||||||
<div v-if="videoLog == 1" class="video-wrapper">
|
<div v-if="videoLog == 1" class="video-wrapper">
|
||||||
<div class="video-list">
|
<div class="video-list">
|
||||||
<div class="empty-box" v-if="videoList.length==0&&cond">
|
<div class="empty-box" v-if="videoList.length==0&&cond">
|
||||||
未接入
|
{{cond?'未接入':'加载中...'}}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="video-item"
|
class="video-item"
|
||||||
@@ -77,17 +58,18 @@
|
|||||||
>关注
|
>关注
|
||||||
</div>
|
</div>
|
||||||
<video
|
<video
|
||||||
class="video-item__video"
|
:id="'hotelmonitorVideo' + index"
|
||||||
:id="'monitorVideo' + index"
|
|
||||||
preload="auto"
|
preload="auto"
|
||||||
|
class="video-item__video"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
:controls="false"
|
:controls="false"
|
||||||
>
|
>
|
||||||
|
|
||||||
<source type="application/x-mpegURL"/>
|
<source type="application/x-mpegURL"/>
|
||||||
</video>
|
</video>
|
||||||
<p class="video-item__title--primary">
|
<p class="video-item__title--primary">
|
||||||
{{ item.cameraName || item.scenicAreaId }}
|
{{ item.cameraName || item.cameraIndexCode }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,15 +128,15 @@
|
|||||||
class="item"
|
class="item"
|
||||||
v-for="(item, index) in videoList"
|
v-for="(item, index) in videoList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="handleItemVideo(item.hlsUrl, 101, item.handleItemVideo,item)"
|
@click="handleItemVideo(item.hlsUrl, 101, item.cameraIndexCode,item)"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="item-title--primary">
|
<p class="item-title--primary">
|
||||||
{{ item.cameraName || item.scenicAreaId }}
|
{{ item.cameraName || item.cameraIndexCode }}
|
||||||
</p>
|
</p>
|
||||||
<video
|
<video
|
||||||
class="item-img"
|
class="item-img"
|
||||||
:id="'monitorVideo' + index"
|
:id="'hotelmonitorVideo' + index"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
:controls="false"
|
:controls="false"
|
||||||
@@ -196,16 +178,26 @@
|
|||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
import emptyIco from '@/assets/images/n-icon.png'
|
import emptyIco from '@/assets/images/n-icon.png'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
// import mpegtsjs from "mpegts.js";
|
||||||
const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
import WebRTCWhep from "whepts";
|
||||||
const UP = 'UP' // 上转
|
// const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
||||||
const DOWN = 'DOWN' // 下转
|
// const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
||||||
const LEFT = 'LEFT' // 左转
|
// const UP = 'UP' // 上转
|
||||||
const RIGHT = 'RIGHT' // 右转
|
// const DOWN = 'DOWN' // 下转
|
||||||
const STOP = 'STOP' // 停止操作
|
// const LEFT = 'LEFT' // 左转
|
||||||
|
// const RIGHT = 'RIGHT' // 右转
|
||||||
|
// const STOP = 'STOP' // 停止操作
|
||||||
|
const Z00M_IN = 'zoomin' // 焦距变大
|
||||||
|
const Z00M_OUT = 'zoomout' // 焦距变小
|
||||||
|
const UP = 'up' // 上转
|
||||||
|
const DOWN = 'down' // 下转
|
||||||
|
const LEFT = 'left' // 左转
|
||||||
|
const RIGHT = 'right' // 右转
|
||||||
|
const STOP = 'stop' // 停止操作
|
||||||
let cond = ref(false)
|
let cond = ref(false)
|
||||||
let ACTION = '0'
|
let ACTION = '0'
|
||||||
let hlsRefs = []
|
let hlsRefs = []
|
||||||
|
let webrtcRefs = []
|
||||||
let hlsRef = null
|
let hlsRef = null
|
||||||
let timer = null
|
let timer = null
|
||||||
let videoLog = ref(1)
|
let videoLog = ref(1)
|
||||||
@@ -242,11 +234,14 @@
|
|||||||
isCollect: status == 0 ? 1 : 0
|
isCollect: status == 0 ? 1 : 0
|
||||||
})
|
})
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
|
if(thisVideo.value){
|
||||||
thisVideo.value.isCollect=1
|
thisVideo.value.isCollect=1
|
||||||
|
}
|
||||||
videoList.value[index].isCollect = 1
|
videoList.value[index].isCollect = 1
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
if(thisVideo.value){
|
||||||
thisVideo.value.isCollect=0
|
thisVideo.value.isCollect=0
|
||||||
|
}
|
||||||
videoList.value[index].isCollect = 0
|
videoList.value[index].isCollect = 0
|
||||||
}
|
}
|
||||||
pubSub.publish('videoCollect', id)
|
pubSub.publish('videoCollect', id)
|
||||||
@@ -255,6 +250,7 @@
|
|||||||
const handleAction = async (e) => {
|
const handleAction = async (e) => {
|
||||||
if (e == STOP) {
|
if (e == STOP) {
|
||||||
ACTION = '1'
|
ACTION = '1'
|
||||||
|
command.value = e
|
||||||
} else {
|
} else {
|
||||||
ACTION = '0'
|
ACTION = '0'
|
||||||
command.value = e
|
command.value = e
|
||||||
@@ -275,7 +271,12 @@
|
|||||||
// 返回列表
|
// 返回列表
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
videoLog.value = 1
|
videoLog.value = 1
|
||||||
|
try{
|
||||||
hlsRef.destroy()
|
hlsRef.destroy()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
initVideo()
|
initVideo()
|
||||||
}
|
}
|
||||||
let isCollect = ref(0)
|
let isCollect = ref(0)
|
||||||
@@ -285,7 +286,8 @@
|
|||||||
show.value = true
|
show.value = true
|
||||||
let res = await getPreviewUrlApi({
|
let res = await getPreviewUrlApi({
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
cameraIndexCode:itemCode
|
cameraIndexCode:itemCode,
|
||||||
|
subStream:0
|
||||||
})
|
})
|
||||||
cameraIndexCode.value = itemCode;
|
cameraIndexCode.value = itemCode;
|
||||||
isCollect.value = resource.isCollect
|
isCollect.value = resource.isCollect
|
||||||
@@ -296,10 +298,21 @@
|
|||||||
const clearHlsRefs = () => {
|
const clearHlsRefs = () => {
|
||||||
if (hlsRefs.length > 0) {
|
if (hlsRefs.length > 0) {
|
||||||
hlsRefs.map((item) => {
|
hlsRefs.map((item) => {
|
||||||
|
try{
|
||||||
item.destroy()
|
item.destroy()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
hlsRefs = []
|
hlsRefs = []
|
||||||
}
|
}
|
||||||
|
if (webrtcRefs.length > 0) {
|
||||||
|
webrtcRefs.map((item) => {
|
||||||
|
item.close()
|
||||||
|
})
|
||||||
|
webrtcRefs = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 分页
|
// 分页
|
||||||
const currentChange = (e) => {
|
const currentChange = (e) => {
|
||||||
@@ -307,6 +320,66 @@
|
|||||||
videoList.value = []
|
videoList.value = []
|
||||||
getRegionsList()
|
getRegionsList()
|
||||||
}
|
}
|
||||||
|
const createPlayer = (cameraIndexCode,videoElement) => {
|
||||||
|
getPreviewUrlApi({
|
||||||
|
type: 'hls',
|
||||||
|
cameraIndexCode: cameraIndexCode,
|
||||||
|
subStream:0
|
||||||
|
}).then(res=>{
|
||||||
|
const url = res.data.url;
|
||||||
|
console.log('url',url)
|
||||||
|
if(url.startsWith('http://192.168.77.200:8050/')){
|
||||||
|
const player = new WebRTCWhep({
|
||||||
|
url:url, // WHEP 服务器地址
|
||||||
|
container: videoElement, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
player.on('error', (error) => {
|
||||||
|
console.error('错误:', error.message, error.type)
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
createPlayer(cameraIndexCode,videoElement);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player.on('play:failed', (err) => {
|
||||||
|
console.error('错误:',err)
|
||||||
|
// createPlayer(cameraIndexCode,videoElement);
|
||||||
|
})
|
||||||
|
webrtcRefs.push(player)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
const player = new Hls({
|
||||||
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
player.loadSource(url)
|
||||||
|
player.attachMedia(videoElement)
|
||||||
|
player.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
videoElement.play()
|
||||||
|
})
|
||||||
|
player.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
// 根据错误类型进行处理
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.log('网络错误,尝试重新加载');
|
||||||
|
player.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.log('媒体错误,尝试修复');
|
||||||
|
player.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('无法恢复的错误,销毁播放器');
|
||||||
|
// hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hlsRefs.push(player)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
//获取酒店视频列表
|
//获取酒店视频列表
|
||||||
const getRegionsList = async()=>{
|
const getRegionsList = async()=>{
|
||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
@@ -321,39 +394,8 @@
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if(!videoList.value.length) return false
|
if(!videoList.value.length) return false
|
||||||
videoList.value.forEach(async (x, index) => {
|
videoList.value.forEach(async (x, index) => {
|
||||||
const video = document.getElementById(`monitorVideo${index}`)
|
const video = document.getElementById(`hotelmonitorVideo${index}`)
|
||||||
const hls = new Hls({
|
createPlayer(x.cameraIndexCode,video);
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
|
||||||
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
|
||||||
})
|
|
||||||
hls.loadSource(x.hlsUrl)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
video.play()
|
|
||||||
})
|
|
||||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
|
||||||
console.error('HLS 播放器遇到错误:', data);
|
|
||||||
// 根据错误类型进行处理
|
|
||||||
// initVideo()
|
|
||||||
if (data.fatal) {
|
|
||||||
switch (data.type) {
|
|
||||||
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
||||||
console.log('网络错误,尝试重新加载');
|
|
||||||
hls.startLoad();
|
|
||||||
break;
|
|
||||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
||||||
console.log('媒体错误,尝试修复');
|
|
||||||
hls.recoverMediaError();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log('无法恢复的错误,销毁播放器');
|
|
||||||
// hls.destroy();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
hlsRefs.push(hls)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -368,11 +410,34 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
let thisVideo = ref(null)
|
let thisVideo = ref(null)
|
||||||
const handleItemVideo = (url, type, code,item) => {
|
const handleItemVideo = async (url, type, code, item) => {
|
||||||
|
let res = await getPreviewUrlApi({
|
||||||
|
cameraIndexCode: code,
|
||||||
|
type: 'hls',
|
||||||
|
subStream:0
|
||||||
|
})
|
||||||
|
url = res.data.url
|
||||||
thisVideo.value = item
|
thisVideo.value = item
|
||||||
videoLog.value = 2
|
videoLog.value = 2
|
||||||
cameraIndexCode.value = code
|
cameraIndexCode.value = code
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if (url.startsWith('http://192.168.77.200:8050/')) {
|
||||||
|
hlsRef = new WebRTCWhep({
|
||||||
|
url: url, // WHEP 服务器地址
|
||||||
|
container: videoRef.value, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
hlsRef.on('error', (error) => {
|
||||||
|
console.error('错误:', error.message, error.type)
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
handleItemVideo(url, type, code, item);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hlsRef.on('play:failed', (err) => {
|
||||||
|
console.log('播放失败,尝试重新加载');
|
||||||
|
// handleItemVideo(url, type, code, item);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
hlsRef = new Hls({
|
hlsRef = new Hls({
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
@@ -383,6 +448,8 @@
|
|||||||
hlsRef.on(Hls.Events.MANIFEST_PARSED, () => {
|
hlsRef.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
videoRef.value.play()
|
videoRef.value.play()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (type == 100) initVideo()
|
if (type == 100) initVideo()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
@@ -390,18 +457,8 @@
|
|||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
videoList.value.forEach(async (item, index) => {
|
videoList.value.forEach(async (item, index) => {
|
||||||
const video = document.getElementById(`monitorVideo${index}`)
|
const video = document.getElementById(`hotelmonitorVideo${index}`)
|
||||||
const hls = new Hls({
|
createPlayer(item.cameraIndexCode,video);
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
|
||||||
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
|
||||||
})
|
|
||||||
hls.loadSource(item.hlsUrl)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
video.play()
|
|
||||||
})
|
|
||||||
hlsRefs.push(hls)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -416,25 +473,25 @@
|
|||||||
)
|
)
|
||||||
// 更新视频
|
// 更新视频
|
||||||
const postVideoRemain = async () => {
|
const postVideoRemain = async () => {
|
||||||
timer = setInterval(() => {
|
// timer = setInterval(() => {
|
||||||
postVideoRemainApi({
|
// postVideoRemainApi({
|
||||||
cameraIndexCode: videoList.value.map((item) => item.cameraIndexCode)
|
// cameraIndexCode: videoList.value.map((item) => item.cameraIndexCode)
|
||||||
})
|
// })
|
||||||
}, 1500)
|
// }, 1500)
|
||||||
}
|
}
|
||||||
const getVideoRegions = async () => {
|
const getVideoRegions = async () => {
|
||||||
let res = await getVideoRegionsApi({
|
let res = await getVideoRegionsApi({
|
||||||
cameraName: cameraName.value,
|
cameraName: cameraName.value,
|
||||||
businessScenicArea: params.businessScenicArea
|
businessScenicArea: params.businessScenicArea
|
||||||
})
|
})
|
||||||
console.log(res,11111111111111)
|
if(res.data.length>0){
|
||||||
regionList.value = res.data
|
regionList.value = res.data[0].resourcesList;
|
||||||
regionList.value.forEach((item,index)=>{
|
regionList.value[0].show = false
|
||||||
// item.show = true
|
}
|
||||||
item.videoResources=item.resourcesList[0].videoResources
|
// regionList.value.forEach((item,index)=>{
|
||||||
})
|
// // item.show = true
|
||||||
regionList.value[0].show = true
|
// item.videoResources=item.resourcesList[0].videoResources
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
const handleRegions = (e) => {
|
const handleRegions = (e) => {
|
||||||
regionList.value[e].show = !regionList.value[e].show
|
regionList.value[e].show = !regionList.value[e].show
|
||||||
@@ -447,6 +504,7 @@
|
|||||||
hotelChange = pubSub.subscribe('hotelChange', (msg, data) => {
|
hotelChange = pubSub.subscribe('hotelChange', (msg, data) => {
|
||||||
cameraName.value = ''
|
cameraName.value = ''
|
||||||
params.businessScenicArea = data.name
|
params.businessScenicArea = data.name
|
||||||
|
regionList.value=[];
|
||||||
getVideoRegions()
|
getVideoRegions()
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
},
|
},
|
||||||
total: {
|
total: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: () => 123456
|
default: () => 0
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -42,7 +42,14 @@
|
|||||||
itemGap: fitChartSize(6),
|
itemGap: fitChartSize(6),
|
||||||
formatter: (name) => {
|
formatter: (name) => {
|
||||||
let obj = props.dataList.find((item) => item.name == name)
|
let obj = props.dataList.find((item) => item.name == name)
|
||||||
return `{name|${name}} {value|${obj?.value}}{value|%}`
|
let total = getTotal();
|
||||||
|
if(total==0){
|
||||||
|
return `{name|${name}} {value|0}{value|%}`
|
||||||
|
}else{
|
||||||
|
let value = ((obj?.value/total).toFixed(4)*100).toFixed(2);
|
||||||
|
return `{name|${name}} {value|${value}}{value|%}`
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
textStyle: {
|
textStyle: {
|
||||||
rich: {
|
rich: {
|
||||||
@@ -126,7 +133,26 @@
|
|||||||
return `{value|${getTotal()}}` + '\n' + `{name|告警总数}`
|
return `{value|${getTotal()}}` + '\n' + `{name|告警总数}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const changeChart = setOption(params)
|
||||||
|
changeChart.off('legendselectchanged');
|
||||||
|
changeChart.on('legendselectchanged', function (e) {
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.dataList.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseFloat(item.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
params.series[0].label.formatter = () => {
|
||||||
|
return `{value|${echartsNum}}` + '\n' + `{name|告警总数}`
|
||||||
|
}
|
||||||
setOption(params)
|
setOption(params)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
:end-val="aiAnalyzeData.allAnalysisPoints"
|
:end-val="aiAnalyzeData.allAnalysisPoints"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="traffic-item" @click="showAbnormalList">
|
<div class="traffic-item" @click="showAbnormalList('curr')">
|
||||||
<span class="traffic-item__title">异常点位</span>
|
<span class="traffic-item__title">异常点位</span>
|
||||||
<countup :class="aiAnalyzeData.abnormalPoints>0?'traffic-item__value--error':'traffic-item__value--success'" :end-val="aiAnalyzeData.abnormalPoints" />
|
<countup :class="aiAnalyzeData.abnormalPoints>0?'traffic-item__value--error':'traffic-item__value--success'" :end-val="aiAnalyzeData.abnormalPoints" />
|
||||||
</div>
|
</div>
|
||||||
@@ -28,15 +28,15 @@
|
|||||||
<span class="scenic-item__label">核心景区分析点位</span>
|
<span class="scenic-item__label">核心景区分析点位</span>
|
||||||
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.coreMonitoringPoints" />
|
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.coreMonitoringPoints" />
|
||||||
</div>
|
</div>
|
||||||
<div class="scenic-item" @click="showAbnormalList" style="cursor: pointer;" >
|
<div class="scenic-item" @click="showAbnormalList('curr')" style="cursor: pointer;" >
|
||||||
<span class="scenic-item__label">异常点位</span>
|
<span class="scenic-item__label">异常点位</span>
|
||||||
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.abnormalPoints" />
|
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.abnormalPoints" />
|
||||||
</div>
|
</div>
|
||||||
<div class="scenic-item" @click="showAbnormalList" style="cursor: pointer;" >
|
<div class="scenic-item" @click="showAbnormalList('curr')" style="cursor: pointer;" >
|
||||||
<span class="scenic-item__label">异常告警</span>
|
<span class="scenic-item__label">异常告警</span>
|
||||||
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.abnormalAlarm" />
|
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.abnormalAlarm" />
|
||||||
</div>
|
</div>
|
||||||
<div class="scenic-item">
|
<div class="scenic-item" @click="showAbnormalList('deal')" style="cursor: pointer;">
|
||||||
<span class="scenic-item__label">已解除</span>
|
<span class="scenic-item__label">已解除</span>
|
||||||
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.handled" />
|
<countup class="scenic-item__value" :end-val="scenicAiAnalyzeData.handled" />
|
||||||
</div>
|
</div>
|
||||||
@@ -67,15 +67,15 @@
|
|||||||
:end-val="trafficAiAnalyzeData.coreMonitoringPoints"
|
:end-val="trafficAiAnalyzeData.coreMonitoringPoints"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="scenic-item" style="cursor: pointer" @click="showTrafficEvent">
|
<div class="scenic-item" style="cursor: pointer" @click="showTrafficEvent('curr')">
|
||||||
<span class="scenic-item__label">拥堵点位</span>
|
<span class="scenic-item__label">拥堵点位</span>
|
||||||
<countup class="scenic-item__value" :end-val="trafficAiAnalyzeData.abnormalPoints" />
|
<countup class="scenic-item__value" :end-val="trafficAiAnalyzeData.abnormalPoints" />
|
||||||
</div>
|
</div>
|
||||||
<div class="scenic-item" style="cursor: pointer" @click="showTrafficEvent">
|
<div class="scenic-item" style="cursor: pointer" @click="showTrafficEvent('curr')">
|
||||||
<span class="scenic-item__label">拥堵告警</span>
|
<span class="scenic-item__label">拥堵告警</span>
|
||||||
<countup class="scenic-item__value" :end-val="trafficAiAnalyzeData.abnormalWarnings" />
|
<countup class="scenic-item__value" :end-val="trafficAiAnalyzeData.abnormalWarnings" />
|
||||||
</div>
|
</div>
|
||||||
<div class="scenic-item">
|
<div class="scenic-item" style="cursor: pointer" @click="showTrafficEvent('deal')">
|
||||||
<span class="scenic-item__label">已解除</span>
|
<span class="scenic-item__label">已解除</span>
|
||||||
<countup class="scenic-item__value" :end-val="trafficAiAnalyzeData.handled" />
|
<countup class="scenic-item__value" :end-val="trafficAiAnalyzeData.handled" />
|
||||||
</div>
|
</div>
|
||||||
@@ -105,9 +105,9 @@
|
|||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<img v-if="scenicSpotId == '龙桥河'" class="map-img" src="@/assets/images/lqh.jpg" alt="" /> -->
|
<img v-if="scenicSpotId == '龙桥河'" class="map-img" src="@/assets/images/lqh.jpg" alt="" /> -->
|
||||||
<iframe v-if="scenicSpotId == 'root000000'" width="100%" height="100%" src="/map/sxzd/bdc.html"></iframe>
|
<iframe v-if="scenicSpotId == 'root000000'" width="100%" height="100%" src="http://192.168.77.200/map/sxzd/bdc.html"></iframe>
|
||||||
<iframe v-if="scenicSpotId == 'root00000000'" width="100%" height="100%" src="/map/sxzd/sxzd.html"></iframe>
|
<iframe v-if="scenicSpotId == 'root00000000'" width="100%" height="100%" src="http://192.168.77.200/map/sxzd/sxzd.html"></iframe>
|
||||||
<iframe v-if="scenicSpotId == '龙桥河'" width="100%" height="100%" src="/map/lqh/lqh.html"></iframe>
|
<iframe v-if="scenicSpotId == '龙桥河'" width="100%" height="100%" src="http://192.168.77.200/map/lqh/lqh.html"></iframe>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="monitor">
|
<div class="monitor">
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
:end-val="pointAlarmData.analysisPoints"
|
:end-val="pointAlarmData.analysisPoints"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="monitor-statistics-item" @click="showAbnormalList" style="cursor: pointer;" >
|
<div class="monitor-statistics-item" @click="showAbnormalList('curr')" style="cursor: pointer;" >
|
||||||
<span class="monitor-statistics-item__label">异常点位</span>
|
<span class="monitor-statistics-item__label">异常点位</span>
|
||||||
<countup
|
<countup
|
||||||
class="monitor-statistics-item__value"
|
class="monitor-statistics-item__value"
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg" style="cursor: pointer;" >
|
<div class="bg" style="cursor: pointer;" >
|
||||||
<Title3 title="今日异常告警" @click="showAbnormalList" />
|
<Title3 title="今日异常告警" @click="showAbnormalList('all')" />
|
||||||
<Line
|
<Line
|
||||||
:width="370"
|
:width="370"
|
||||||
:height="180"
|
:height="180"
|
||||||
@@ -155,20 +155,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="traffic-alarm-statistics">
|
<div class="traffic-alarm-statistics">
|
||||||
<img class="traffic-alarm-statistics-icon" src="@/assets/images/t-ico-2.png" />
|
<img class="traffic-alarm-statistics-icon" src="@/assets/images/t-ico-2.png" />
|
||||||
<div class="traffic-alarm-statistics-item" @click="showAbnormalList" style="cursor: pointer;">
|
<div class="traffic-alarm-statistics-item" @click="showAbnormalList('curr')" style="cursor: pointer;">
|
||||||
<span class="traffic-alarm-statistics-item__label">当前告警</span>
|
<span class="traffic-alarm-statistics-item__label">当前告警</span>
|
||||||
<countup
|
<countup
|
||||||
:class="pointAlarmData.abnormalAlarm>0?'traffic-alarm-statistics-item__error':'traffic-alarm-statistics-item__value'"
|
:class="pointAlarmData.abnormalAlarm>0?'traffic-alarm-statistics-item__error':'traffic-alarm-statistics-item__value'"
|
||||||
:end-val="pointAlarmData.abnormalAlarm"
|
:end-val="pointAlarmData.abnormalAlarm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="traffic-alarm-statistics-item">
|
<div class="traffic-alarm-statistics-item" @click="showAbnormalList('all')" style="cursor: pointer;">
|
||||||
<span class="traffic-alarm-statistics-item__label">异常告警</span>
|
<span class="traffic-alarm-statistics-item__label">异常告警</span>
|
||||||
<countup class="traffic-alarm-statistics-item__value"
|
<countup class="traffic-alarm-statistics-item__value"
|
||||||
:end-val="pointAlarmData.allAbnormalAlarm"
|
:end-val="pointAlarmData.allAbnormalAlarm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="traffic-alarm-statistics-item">
|
<div class="traffic-alarm-statistics-item" @click="showAbnormalList('deal')" style="cursor: pointer;">
|
||||||
<span class="traffic-alarm-statistics-item__label">已解除告警</span>
|
<span class="traffic-alarm-statistics-item__label">已解除告警</span>
|
||||||
<countup
|
<countup
|
||||||
class="traffic-alarm-statistics-item__value traffic-alarm-statistics-item__value"
|
class="traffic-alarm-statistics-item__value traffic-alarm-statistics-item__value"
|
||||||
@@ -188,8 +188,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<video-dialog v-model="videoShow" :src="src" :cameraIndexCode="cameraIndexCode" />
|
<video-dialog v-model="videoShow" :src="src" :cameraIndexCode="cameraIndexCode" />
|
||||||
<warn-list v-model="warnShow" :scenicSpotId="scenicSpotId" />
|
<warn-list v-model="warnShow" :type="warnType" :scenicSpotId="scenicSpotId" />
|
||||||
<traffic-list v-model="trafficEventShow" />
|
<traffic-list v-model="trafficEventShow" :type="trafficEventType" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -247,17 +247,22 @@
|
|||||||
let cameraIndexCode = ref('')
|
let cameraIndexCode = ref('')
|
||||||
let videoShow = ref(false)
|
let videoShow = ref(false)
|
||||||
let warnShow = ref(false)
|
let warnShow = ref(false)
|
||||||
|
let warnType = ref('')
|
||||||
let trafficEventShow = ref(false)
|
let trafficEventShow = ref(false)
|
||||||
const showAbnormalList = function (){
|
let trafficEventType = ref('curr')
|
||||||
|
const showAbnormalList = function (type){
|
||||||
|
warnType.value = type;
|
||||||
warnShow.value = true;
|
warnShow.value = true;
|
||||||
}
|
}
|
||||||
const showTrafficEvent = function(){
|
const showTrafficEvent = function(type){
|
||||||
|
trafficEventType.value = type;
|
||||||
trafficEventShow.value = true;
|
trafficEventShow.value = true;
|
||||||
}
|
}
|
||||||
window.addEventListener("message", async(e) => {
|
window.addEventListener("message", async(e) => {
|
||||||
let {code,data} = await getPreviewUrlApi({
|
let {code,data} = await getPreviewUrlApi({
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
cameraIndexCode:e.data.cameraIndexCode
|
cameraIndexCode:e.data.cameraIndexCode,
|
||||||
|
subStream:0
|
||||||
})
|
})
|
||||||
if(code===200){
|
if(code===200){
|
||||||
src.value = data.url
|
src.value = data.url
|
||||||
|
|||||||
@@ -10,9 +10,8 @@
|
|||||||
:key="index"
|
:key="index"
|
||||||
@click="handleNav(index)"
|
@click="handleNav(index)"
|
||||||
>
|
>
|
||||||
<!-- <span v-if="!params.businessScenicArea && index == 0"> 核心路段 </span> -->
|
|
||||||
<span>
|
<span>
|
||||||
{{ item.dictLabel }}
|
{{ item }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -94,16 +93,21 @@
|
|||||||
@click.stop="handleCollect(item.cameraIndexCode, item.isCollect, index)"
|
@click.stop="handleCollect(item.cameraIndexCode, item.isCollect, index)"
|
||||||
>关注
|
>关注
|
||||||
</div>
|
</div>
|
||||||
|
<view class="video-item__video">
|
||||||
<video
|
<video
|
||||||
class="video-item__video"
|
|
||||||
:id="'monitorVideo' + index"
|
:id="'monitorVideo' + index"
|
||||||
preload="auto"
|
preload="auto"
|
||||||
|
class="video-item__video"
|
||||||
muted
|
muted
|
||||||
autoplay
|
autoplay
|
||||||
:controls="false"
|
:controls="false"
|
||||||
>
|
>
|
||||||
|
|
||||||
<source type="application/x-mpegURL"/>
|
<source type="application/x-mpegURL"/>
|
||||||
</video>
|
</video>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
<p class="video-item__title--primary">
|
<p class="video-item__title--primary">
|
||||||
{{ item.cameraName || item.cameraIndexCode }}
|
{{ item.cameraName || item.cameraIndexCode }}
|
||||||
</p>
|
</p>
|
||||||
@@ -129,8 +133,10 @@
|
|||||||
</video>
|
</video>
|
||||||
<div class="action-box">
|
<div class="action-box">
|
||||||
<div class="action-item">
|
<div class="action-item">
|
||||||
<span class="item-sc" @click="handleCollect(thisVideo.cameraIndexCode, thisVideo.isCollect, index)" v-if="thisVideo.isCollect==0">关注</span>
|
<span class="item-sc" @click="handleCollect(thisVideo.cameraIndexCode, thisVideo.isCollect, null)"
|
||||||
<span class="item-sc" @click="handleCollect(thisVideo.cameraIndexCode, thisVideo.isCollect, index)" v-else="thisVideo.isCollect==1">取消关注</span>
|
v-if="thisVideo.isCollect==0">关注</span>
|
||||||
|
<span class="item-sc" @click="handleCollect(thisVideo.cameraIndexCode, thisVideo.isCollect, null)"
|
||||||
|
v-else="thisVideo.isCollect==1">取消关注</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-item">
|
<div class="action-item">
|
||||||
<img src="@/assets/images/plus.png" title="焦距变大" @click="handleAction(Z00M_IN)"/>
|
<img src="@/assets/images/plus.png" title="焦距变大" @click="handleAction(Z00M_IN)"/>
|
||||||
@@ -150,8 +156,10 @@
|
|||||||
<img src="@/assets/images/right.png" title="右转" @click="handleAction(RIGHT)"/>
|
<img src="@/assets/images/right.png" title="右转" @click="handleAction(RIGHT)"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-item">
|
<div class="action-item">
|
||||||
<span class="item-sc" @click="handleCollectAdd(thisVideo.cameraIndexCode, thisVideo.isDiy, index)" v-if="thisVideo.isDiy==0">收藏</span>
|
<span class="item-sc" @click="handleCollectAdd(thisVideo.cameraIndexCode, thisVideo.isDiy, null)"
|
||||||
<span class="item-sc" @click="handleCollectAdd(thisVideo.cameraIndexCode, thisVideo.isDiy, index)" v-else="thisVideo.isDiy==1">取消收藏</span>
|
v-if="thisVideo.isDiy==0">收藏</span>
|
||||||
|
<span class="item-sc" @click="handleCollectAdd(thisVideo.cameraIndexCode, thisVideo.isDiy, null)"
|
||||||
|
v-else="thisVideo.isDiy==1">取消收藏</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,7 +176,7 @@
|
|||||||
class="item"
|
class="item"
|
||||||
v-for="(item, index) in videoList"
|
v-for="(item, index) in videoList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="handleItemVideo(item.hlsUrl, 101, item.handleItemVideo,item)"
|
@click="handleItemVideo(item.hlsUrl, 101, item.cameraIndexCode,item)"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="item-title--primary">
|
<p class="item-title--primary">
|
||||||
@@ -195,6 +203,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {getVideoListApi, getPreviewUrlApi, getColletListApi, getColletDiyListApi, getColletDiyApi} from '@/api/home'
|
import {getVideoListApi, getPreviewUrlApi, getColletListApi, getColletDiyListApi, getColletDiyApi} from '@/api/home'
|
||||||
|
import WebRTCWhep from 'whepts'
|
||||||
import {
|
import {
|
||||||
getVideoTypeApi,
|
getVideoTypeApi,
|
||||||
getVideoRegionsApi,
|
getVideoRegionsApi,
|
||||||
@@ -206,14 +215,22 @@
|
|||||||
import {debounce} from 'lodash'
|
import {debounce} from 'lodash'
|
||||||
import pubSub from 'pubsub-js'
|
import pubSub from 'pubsub-js'
|
||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
|
import HlsPlayer from "@/components/HlsPlayer/index.vue";
|
||||||
|
|
||||||
const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
// const Z00M_IN = 'ZOOM_IN' // 焦距变大
|
||||||
const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
// const Z00M_OUT = 'ZOOM_OUT' // 焦距变小
|
||||||
const UP = 'UP' // 上转
|
// const UP = 'UP' // 上转
|
||||||
const DOWN = 'DOWN' // 下转
|
// const DOWN = 'DOWN' // 下转
|
||||||
const LEFT = 'LEFT' // 左转
|
// const LEFT = 'LEFT' // 左转
|
||||||
const RIGHT = 'RIGHT' // 右转
|
// const RIGHT = 'RIGHT' // 右转
|
||||||
const STOP = 'STOP' // 停止操作
|
// const STOP = 'STOP' // 停止操作
|
||||||
|
const Z00M_IN = 'zoomin' // 焦距变大
|
||||||
|
const Z00M_OUT = 'zoomout' // 焦距变小
|
||||||
|
const UP = 'up' // 上转
|
||||||
|
const DOWN = 'down' // 下转
|
||||||
|
const LEFT = 'left' // 左转
|
||||||
|
const RIGHT = 'right' // 右转
|
||||||
|
const STOP = 'stop' // 停止操作
|
||||||
let ACTION = '0'
|
let ACTION = '0'
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
@@ -245,39 +262,28 @@
|
|||||||
businessVideoDisplayPosition: ''
|
businessVideoDisplayPosition: ''
|
||||||
})
|
})
|
||||||
let hlsRefs = []
|
let hlsRefs = []
|
||||||
|
let webrtcRefs = []
|
||||||
let hlsRef = null
|
let hlsRef = null
|
||||||
let timer = null
|
let timer = null
|
||||||
|
|
||||||
const postVideoRemain = async () => {
|
const postVideoRemain = async () => {
|
||||||
if(!videoList.value.length) return false;
|
// if (!videoList.value.length) return false;
|
||||||
timer = setInterval(() => {
|
// timer = setInterval(() => {
|
||||||
postVideoRemainApi({
|
// postVideoRemainApi({
|
||||||
cameraIndexCode: videoList.value.map((item) => item.cameraIndexCode)
|
// cameraIndexCode: videoList.value.map((item) => item.cameraIndexCode)
|
||||||
})
|
// })
|
||||||
}, 2000)
|
// }, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const initVideo = () => {
|
const initVideo = () => {
|
||||||
clearHlsRefs()
|
clearHlsRefs()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
videoList.value.forEach(async (item, index) => {
|
videoList.value.forEach(async (item, index) => {
|
||||||
const video = document.getElementById(`monitorVideo${index}`)
|
const videoElement = document.getElementById(`monitorVideo${index}`)
|
||||||
const hls = new Hls({
|
createPlayer(item.cameraIndexCode,videoElement);
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
|
||||||
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
|
||||||
})
|
|
||||||
hls.loadSource(item.hlsUrl)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
video.play()
|
|
||||||
})
|
|
||||||
hlsRefs.push(hls)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const onInput = debounce((e) => {
|
const onInput = debounce((e) => {
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
getVideoRegions('search')
|
getVideoRegions('search')
|
||||||
} else {
|
} else {
|
||||||
@@ -288,10 +294,24 @@
|
|||||||
const clearHlsRefs = () => {
|
const clearHlsRefs = () => {
|
||||||
if (hlsRefs.length > 0) {
|
if (hlsRefs.length > 0) {
|
||||||
hlsRefs.map((item) => {
|
hlsRefs.map((item) => {
|
||||||
|
try{
|
||||||
item.destroy()
|
item.destroy()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
hlsRefs = []
|
hlsRefs = []
|
||||||
}
|
}
|
||||||
|
if(webrtcRefs.length>0){
|
||||||
|
webrtcRefs.map((item) => {
|
||||||
|
try{
|
||||||
|
item.close()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
webrtcRefs = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const currentChange = (e) => {
|
const currentChange = (e) => {
|
||||||
if (navList.value.length == current.value) {
|
if (navList.value.length == current.value) {
|
||||||
@@ -334,15 +354,39 @@
|
|||||||
}
|
}
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
videoLog.value = 1
|
videoLog.value = 1
|
||||||
|
try{
|
||||||
hlsRef.destroy()
|
hlsRef.destroy()
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
initVideo()
|
initVideo()
|
||||||
}
|
}
|
||||||
let thisVideo = ref(null)
|
let thisVideo = ref(null)
|
||||||
const handleItemVideo = (url, type, code,item) => {
|
const handleItemVideo = async (url, type, code, item) => {
|
||||||
|
let res = await getPreviewUrlApi({
|
||||||
|
cameraIndexCode: code,
|
||||||
|
type: 'hls',
|
||||||
|
subStream:0
|
||||||
|
})
|
||||||
|
url = res.data.url
|
||||||
thisVideo.value = item
|
thisVideo.value = item
|
||||||
videoLog.value = 2
|
videoLog.value = 2
|
||||||
cameraIndexCode.value = code
|
cameraIndexCode.value = code
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if (url.startsWith('http://192.168.77.200:8050/')) {
|
||||||
|
hlsRef = new WebRTCWhep({
|
||||||
|
url: url, // WHEP 服务器地址
|
||||||
|
container: videoRef.value, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
hlsRef.on('error', (error) => {
|
||||||
|
console.error('错误:', error.message, error.type)
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
handleItemVideo(url, type, code, item);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
hlsRef = new Hls({
|
hlsRef = new Hls({
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
@@ -353,12 +397,15 @@
|
|||||||
hlsRef.on(Hls.Events.MANIFEST_PARSED, () => {
|
hlsRef.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
videoRef.value.play()
|
videoRef.value.play()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (type == 100) initVideo()
|
if (type == 100) initVideo()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
const handleAction = async (e) => {
|
const handleAction = async (e) => {
|
||||||
if (e == STOP) {
|
if (e == STOP) {
|
||||||
ACTION = '1'
|
ACTION = '1'
|
||||||
|
command.value = e
|
||||||
} else {
|
} else {
|
||||||
ACTION = '0'
|
ACTION = '0'
|
||||||
command.value = e
|
command.value = e
|
||||||
@@ -387,9 +434,9 @@
|
|||||||
show.value = true
|
show.value = true
|
||||||
let res = await getPreviewUrlApi({
|
let res = await getPreviewUrlApi({
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
cameraIndexCode:itemCode
|
cameraIndexCode: itemCode,
|
||||||
|
subStream:0
|
||||||
})
|
})
|
||||||
console.log(res,'rrrrrrrrrrrrrr')
|
|
||||||
cameraIndexCode.value = itemCode;
|
cameraIndexCode.value = itemCode;
|
||||||
isCollect.value = resource.isCollect
|
isCollect.value = resource.isCollect
|
||||||
isDiy.value = resource.isDiy
|
isDiy.value = resource.isDiy
|
||||||
@@ -401,11 +448,21 @@
|
|||||||
isCollect: status == 0 ? 1 : 0
|
isCollect: status == 0 ? 1 : 0
|
||||||
})
|
})
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
|
if(thisVideo.value){
|
||||||
thisVideo.value.isCollect =1
|
thisVideo.value.isCollect =1
|
||||||
|
}
|
||||||
|
if(index!=null){
|
||||||
videoList.value[index].isCollect = 1
|
videoList.value[index].isCollect = 1
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
if(thisVideo.value){
|
||||||
thisVideo.value.isCollect = 0
|
thisVideo.value.isCollect = 0
|
||||||
|
}
|
||||||
|
if(index!=null){
|
||||||
videoList.value[index].isCollect = 0
|
videoList.value[index].isCollect = 0
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pubSub.publish('videoCollect', id)
|
pubSub.publish('videoCollect', id)
|
||||||
}
|
}
|
||||||
@@ -438,6 +495,61 @@
|
|||||||
},
|
},
|
||||||
{immediate: true}
|
{immediate: true}
|
||||||
)
|
)
|
||||||
|
const createPlayer = (cameraIndexCode,videoElement) => {
|
||||||
|
getPreviewUrlApi({
|
||||||
|
type: 'hls',
|
||||||
|
cameraIndexCode: cameraIndexCode,
|
||||||
|
subStream:1
|
||||||
|
}).then(res=>{
|
||||||
|
const url = res.data.url;
|
||||||
|
if(url.startsWith('http://192.168.77.200:8050/')){
|
||||||
|
const player = new WebRTCWhep({
|
||||||
|
url: url, // WHEP 服务器地址
|
||||||
|
container: videoElement, // 视频播放容器
|
||||||
|
iceServers: [{ urls: 'turn:192.168.77.200:3478',username: 'ZLMediaKit',credential: 'ZLMediaKit'}]
|
||||||
|
})
|
||||||
|
player.on('error', (error) => {
|
||||||
|
console.error('错误:', error.message, error.type)
|
||||||
|
if(error.type ==='REQUEST_ERROR' || error.type ==='NOT_FOUND_ERROR'){
|
||||||
|
createPlayer(cameraIndexCode,videoElement)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
webrtcRefs.push(player)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
const player = new Hls({
|
||||||
|
maxBufferLength: 10, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
||||||
|
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
||||||
|
})
|
||||||
|
player.loadSource(url)
|
||||||
|
player.attachMedia(videoElement)
|
||||||
|
player.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
videoElement.play()
|
||||||
|
})
|
||||||
|
player.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
// 根据错误类型进行处理
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.log('网络错误,尝试重新加载');
|
||||||
|
player.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.log('媒体错误,尝试修复');
|
||||||
|
player.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('无法恢复的错误,销毁播放器');
|
||||||
|
// hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hlsRefs.push(player)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
const getVideoList = async (val) => {
|
const getVideoList = async (val) => {
|
||||||
try {
|
try {
|
||||||
if (loading.value) return
|
if (loading.value) return
|
||||||
@@ -445,54 +557,17 @@
|
|||||||
if (val == 3) {
|
if (val == 3) {
|
||||||
params.businessVideoDisplayPosition = ''
|
params.businessVideoDisplayPosition = ''
|
||||||
} else {
|
} else {
|
||||||
params.businessVideoDisplayPosition = navList.value[current.value].dictValue
|
params.businessVideoDisplayPosition = navList.value[current.value]
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await getVideoListApi(params)
|
let res = await getVideoListApi(params)
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
if (res.data.length > 0) {
|
if (res.data.length > 0) {
|
||||||
|
|
||||||
videoList.value = res.data
|
videoList.value = res.data
|
||||||
// console.log(videoList.value,'1111111111111111111111')
|
|
||||||
// postVideoRemain()
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
videoList.value.forEach(async (x, index) => {
|
videoList.value.forEach(async (x, index) => {
|
||||||
const video = document.getElementById(`monitorVideo${index}`)
|
const videoElement = document.getElementById(`monitorVideo${index}`)
|
||||||
const hls = new Hls({
|
createPlayer(x.cameraIndexCode,videoElement);
|
||||||
maxBufferLength: 10, // 最大缓冲长度(秒)
|
|
||||||
maxMaxBufferLength: 15, // 缓冲区长度的上限
|
|
||||||
maxBufferSize: 30 * 1000 * 1000 // 最大缓冲大小(字节)
|
|
||||||
})
|
|
||||||
hls.loadSource(x.hlsUrl)
|
|
||||||
hls.attachMedia(video)
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
video.play()
|
|
||||||
})
|
|
||||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
|
||||||
// console.error('HLS 播放器遇到错误:', data);
|
|
||||||
// hls.startLoad();
|
|
||||||
// initVideo()
|
|
||||||
// 根据错误类型进行处理
|
|
||||||
if (data.fatal) {
|
|
||||||
switch (data.type) {
|
|
||||||
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
||||||
console.log('网络错误,尝试重新加载');
|
|
||||||
hls.startLoad();
|
|
||||||
break;
|
|
||||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
||||||
console.log('媒体错误,尝试修复');
|
|
||||||
hls.recoverMediaError();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log('无法恢复的错误,销毁播放器');
|
|
||||||
|
|
||||||
// hls.destroy();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
hlsRefs.push(hls)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -594,11 +669,13 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
// left:vw(-15);
|
// left:vw(-15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-item__child-item {
|
.tree-item__child-item {
|
||||||
.new-title:last-child {
|
.new-title:last-child {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
&-box {
|
&-box {
|
||||||
margin-top: vh(16);
|
margin-top: vh(16);
|
||||||
@@ -607,37 +684,44 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
padding: vw(16);
|
padding: vw(16);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #0a4190;
|
background: #0a4190;
|
||||||
border-radius: vw(8);
|
border-radius: vw(8);
|
||||||
|
|
||||||
> img {
|
> img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: vw(34);
|
width: vw(34);
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
margin: 0 vw(16);
|
margin: 0 vw(16);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: vw(16);
|
font-size: vw(16);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pause {
|
.pause {
|
||||||
margin: 0 vw(10);
|
margin: 0 vw(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-sc {
|
.item-sc {
|
||||||
padding: vw(10);
|
padding: vw(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//背景色设置为透明
|
//背景色设置为透明
|
||||||
:deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
//输入框颜色
|
//输入框颜色
|
||||||
:deep(.el-input__inner) {
|
:deep(.el-input__inner) {
|
||||||
background-color: rgba(0, 0, 0, 0) !important;
|
background-color: rgba(0, 0, 0, 0) !important;
|
||||||
@@ -650,17 +734,21 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-input__inner) {
|
:deep(.el-input__inner) {
|
||||||
height: vh(36);
|
height: vh(36);
|
||||||
font-size: vw(16);
|
font-size: vw(16);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-nav {
|
.left-nav {
|
||||||
margin: 0 vw(8);
|
margin: 0 vw(8);
|
||||||
width: vw(250);
|
width: vw(250);
|
||||||
background: linear-gradient(321deg, #0b2f64 0%, #062b57 91%, rgba(5, 40, 79, 0) 100%);
|
background: linear-gradient(321deg, #0b2f64 0%, #062b57 91%, rgba(5, 40, 79, 0) 100%);
|
||||||
|
|
||||||
.bom-box {
|
.bom-box {
|
||||||
margin-top: vh(20);
|
margin-top: vh(20);
|
||||||
|
|
||||||
.search-box {
|
.search-box {
|
||||||
border-radius: vw(2);
|
border-radius: vw(2);
|
||||||
height: vh(36);
|
height: vh(36);
|
||||||
@@ -689,15 +777,18 @@
|
|||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: vw(4); /* 滚动条的宽度 */
|
width: vw(4); /* 滚动条的宽度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条轨道 */
|
/* 滚动条轨道 */
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: 'transparent'; /* 轨道的背景色 */
|
background: 'transparent'; /* 轨道的背景色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条滑块 */
|
/* 滚动条滑块 */
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
||||||
border-radius: 5px; /* 滑块的圆角 */
|
border-radius: 5px; /* 滑块的圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-item {
|
.tree-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -706,20 +797,24 @@
|
|||||||
&:nth-child(1) {
|
&:nth-child(1) {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__node {
|
&__node {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
margin-left: vw(-8);
|
margin-left: vw(-8);
|
||||||
width: vw(16);
|
width: vw(16);
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon-up {
|
&__icon-up {
|
||||||
@extend .tree-item__icon;
|
@extend .tree-item__icon;
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
padding: 0 vw(5);
|
padding: 0 vw(5);
|
||||||
display: block;
|
display: block;
|
||||||
@@ -730,12 +825,14 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__child {
|
&__child {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: vh(20);
|
margin-top: vh(20);
|
||||||
// margin-left: vw(20);
|
// margin-left: vw(20);
|
||||||
// border-left: vw(2) solid #37d8fc;
|
// border-left: vw(2) solid #37d8fc;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-top__icon {
|
&-top__icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: vw(-8);
|
left: vw(-8);
|
||||||
@@ -743,6 +840,7 @@
|
|||||||
width: vw(16);
|
width: vw(16);
|
||||||
height: vw(16);
|
height: vw(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-bottom__icon {
|
&-bottom__icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: vw(-8);
|
left: vw(-8);
|
||||||
@@ -750,6 +848,7 @@
|
|||||||
width: vw(16);
|
width: vw(16);
|
||||||
height: vw(16);
|
height: vw(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__child-item {
|
&__child-item {
|
||||||
padding: vh(0) vw(20) vh(20) vw(10);
|
padding: vh(0) vw(20) vh(20) vw(10);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -762,6 +861,7 @@
|
|||||||
// display: flex;
|
// display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
|
||||||
&:nth-last-of-type(1) {
|
&:nth-last-of-type(1) {
|
||||||
// padding: vh(0) vw(20);
|
// padding: vh(0) vw(20);
|
||||||
// padding-right:0;
|
// padding-right:0;
|
||||||
@@ -771,6 +871,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ul {
|
.ul {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: vw(18);
|
font-size: vw(18);
|
||||||
@@ -783,15 +884,18 @@
|
|||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: vw(4); /* 滚动条的宽度 */
|
width: vw(4); /* 滚动条的宽度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条轨道 */
|
/* 滚动条轨道 */
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: 'transparent'; /* 轨道的背景色 */
|
background: 'transparent'; /* 轨道的背景色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条滑块 */
|
/* 滚动条滑块 */
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
||||||
border-radius: 5px; /* 滑块的圆角 */
|
border-radius: 5px; /* 滑块的圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.li {
|
.li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: vw(250);
|
width: vw(250);
|
||||||
@@ -802,6 +906,7 @@
|
|||||||
background: url('/src/assets/images/m-nav-bg-1.png');
|
background: url('/src/assets/images/m-nav-bg-1.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
background: url('/src/assets/images/m-nav-bg-2.png');
|
background: url('/src/assets/images/m-nav-bg-2.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
@@ -818,6 +923,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-image: url('/src/assets/images/log-v-bg.png');
|
background-image: url('/src/assets/images/log-v-bg.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
padding: vh(10) vw(30);
|
padding: vh(10) vw(30);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -825,12 +931,14 @@
|
|||||||
bottom: vh(20);
|
bottom: vh(20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-list {
|
&-list {
|
||||||
gap: vw(8);
|
gap: vw(8);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
// width: vw(686);
|
// width: vw(686);
|
||||||
@@ -839,12 +947,14 @@
|
|||||||
padding: vh(10) vw(10);
|
padding: vh(10) vw(10);
|
||||||
background-image: url('/src/assets/images/item-primary.png');
|
background-image: url('/src/assets/images/item-primary.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.video-item__follow {
|
.video-item__follow {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item__follow {
|
&-item__follow {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: none;
|
display: none;
|
||||||
@@ -862,6 +972,7 @@
|
|||||||
background-image: url('@/assets/images/unfollow.png');
|
background-image: url('@/assets/images/unfollow.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item__unfollow {
|
&-item__unfollow {
|
||||||
@extend .video-item__follow;
|
@extend .video-item__follow;
|
||||||
background-image: url('@/assets/images/unfollow.png');
|
background-image: url('@/assets/images/unfollow.png');
|
||||||
@@ -870,6 +981,7 @@
|
|||||||
&-item__inner {
|
&-item__inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item__title {
|
&-item__title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -883,24 +995,29 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item__title--error {
|
&-item__title--error {
|
||||||
@extend .video-item__title;
|
@extend .video-item__title;
|
||||||
background-color: rgba(226, 27, 27, 0.72);
|
background-color: rgba(226, 27, 27, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item__title--primary {
|
&-item__title--primary {
|
||||||
@extend .video-item__title;
|
@extend .video-item__title;
|
||||||
background-color: rgba(4, 30, 69, 0.72);
|
background-color: rgba(4, 30, 69, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item__video {
|
&-item__video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: vh(366);
|
height: vh(366);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-detail {
|
&-detail {
|
||||||
margin-left: vw(10);
|
margin-left: vw(10);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-detail__wrapper {
|
&-detail__wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: vh(40) vw(50);
|
padding: vh(40) vw(50);
|
||||||
@@ -909,6 +1026,7 @@
|
|||||||
background-image: url('/src/assets/images/one-video-bg.png');
|
background-image: url('/src/assets/images/one-video-bg.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-detail__title {
|
&-detail__title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: vw(50);
|
left: vw(50);
|
||||||
@@ -923,33 +1041,39 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: rgba(4, 30, 69, 0.5);
|
background: rgba(4, 30, 69, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-detail__video {
|
&-detail__video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: vh(820);
|
height: vh(820);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-right {
|
&-right {
|
||||||
margin-left: vw(8);
|
margin-left: vw(8);
|
||||||
width: vw(440);
|
width: vw(440);
|
||||||
height: vh(956);
|
height: vh(956);
|
||||||
background: #082f5a;
|
background: #082f5a;
|
||||||
|
|
||||||
.back-box {
|
.back-box {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-right: vw(20);
|
padding-right: vw(20);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
width: vw(30);
|
width: vw(30);
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-right: vw(10);
|
margin-right: vw(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: vw(20);
|
font-size: vw(20);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@@ -982,9 +1106,11 @@
|
|||||||
padding: vw(10);
|
padding: vw(10);
|
||||||
background-image: url('@/assets/images/item-primary.png');
|
background-image: url('@/assets/images/item-primary.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -997,14 +1123,17 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-title--error {
|
&-title--error {
|
||||||
@extend .item-title;
|
@extend .item-title;
|
||||||
background-color: rgba(226, 27, 27, 0.72);
|
background-color: rgba(226, 27, 27, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-title--primary {
|
&-title--primary {
|
||||||
@extend .item-title;
|
@extend .item-title;
|
||||||
background-color: rgba(4, 30, 69, 0.72);
|
background-color: rgba(4, 30, 69, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-img {
|
&-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: vh(164);
|
height: vh(164);
|
||||||
@@ -1013,6 +1142,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-live {
|
.video-live {
|
||||||
.video-rt {
|
.video-rt {
|
||||||
width: vw(400);
|
width: vw(400);
|
||||||
@@ -1028,29 +1158,35 @@
|
|||||||
border-image: linear-gradient(180deg, rgba(0, 150, 255, 1), rgba(0, 90, 153, 0)) 1 1;
|
border-image: linear-gradient(180deg, rgba(0, 150, 255, 1), rgba(0, 90, 153, 0)) 1 1;
|
||||||
margin-left: vw(10);
|
margin-left: vw(10);
|
||||||
padding: vw(20);
|
padding: vw(20);
|
||||||
|
|
||||||
.rt-v-box {
|
.rt-v-box {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
/* 滚动条整体样式 */
|
/* 滚动条整体样式 */
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: vw(4); /* 滚动条的宽度 */
|
width: vw(4); /* 滚动条的宽度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条轨道 */
|
/* 滚动条轨道 */
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: 'transparent'; /* 轨道的背景色 */
|
background: 'transparent'; /* 轨道的背景色 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条滑块 */
|
/* 滚动条滑块 */
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
||||||
border-radius: 5px; /* 滑块的圆角 */
|
border-radius: 5px; /* 滑块的圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
background-image: url('/src/assets/images/nav-l-t-bg.png');
|
background-image: url('/src/assets/images/nav-l-t-bg.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
margin-bottom: vh(10);
|
margin-bottom: vh(10);
|
||||||
position: relative;
|
position: relative;
|
||||||
left: vw(-20);
|
left: vw(-20);
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin-left: vw(30);
|
margin-left: vw(30);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
@@ -1067,6 +1203,7 @@
|
|||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-video {
|
.rt-video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: vh(300);
|
height: vh(300);
|
||||||
@@ -1076,6 +1213,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-bottom: vh(2);
|
margin-bottom: vh(2);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -1094,9 +1232,11 @@
|
|||||||
text-transform: none;
|
text-transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-error-bg {
|
.v-error-bg {
|
||||||
background-image: url('/src/assets/images/v-item-bg-1.png');
|
background-image: url('/src/assets/images/v-item-bg-1.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
|
||||||
.desc {
|
.desc {
|
||||||
background: rgba(226, 27, 27, 0.5);
|
background: rgba(226, 27, 27, 0.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,11 +72,12 @@
|
|||||||
show: true, // 显示dataZoom组件
|
show: true, // 显示dataZoom组件
|
||||||
xAxisIndex: [0], // 控制第一个x轴
|
xAxisIndex: [0], // 控制第一个x轴
|
||||||
start: 0, // 初始起始位置
|
start: 0, // 初始起始位置
|
||||||
end: 50 // 初始结束位置,可以根据数据量调整
|
end: 60 // 初始结束位置,可以根据数据量调整
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
|
offset: 15,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
show: true,
|
show: true,
|
||||||
fontSize: fitChartSize(12),
|
fontSize: fitChartSize(12),
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
{
|
{
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: getSeriesData(),
|
data: getSeriesData(),
|
||||||
barWidth: fitChartSize(40),
|
barWidth: fitChartSize(20),
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="z-dialog">
|
<div class="z-dialog">
|
||||||
<el-dialog title="核心路段拥堵告警" v-model="modelValue" align-center :modal="false" :show-close="false">
|
<el-dialog :title="props.type=='deal'?'已解除告警':'核心路段拥堵告警'" v-model="modelValue" align-center :modal="false" :show-close="false">
|
||||||
<img class="close" src="@/assets/images/close.png" @click="handleClose" />
|
<img class="close" src="@/assets/images/close.png" @click="handleClose" />
|
||||||
<div class="no-data" v-if="cond&&!list.length">
|
<div class="no-data" v-if="cond&&!list.length">
|
||||||
暂无拥堵告警
|
{{props.type=='deal'?'暂无数据':'暂无拥堵告警'}}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<ul class="list" >
|
<ul class="list" >
|
||||||
@@ -32,6 +32,10 @@
|
|||||||
import pubSub from 'pubsub-js'
|
import pubSub from 'pubsub-js'
|
||||||
|
|
||||||
let props = defineProps({
|
let props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'curr'
|
||||||
|
},
|
||||||
events: {
|
events: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@@ -127,7 +131,7 @@
|
|||||||
}
|
}
|
||||||
let cond = ref(false)
|
let cond = ref(false)
|
||||||
const getVideoList = async () => {
|
const getVideoList = async () => {
|
||||||
let res = await getTrafficEventsApi();
|
let res = await getTrafficEventsApi(props.type);
|
||||||
list.value = res.data
|
list.value = res.data
|
||||||
setTimeout(()=>{cond.value = true},1500)
|
setTimeout(()=>{cond.value = true},1500)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="z-dialog">
|
<div class="z-dialog">
|
||||||
<el-dialog title="异常点位告警" v-model="modelValue" align-center :modal="false" :show-close="false">
|
<el-dialog :title="props.type=='deal'?'已解除异常':'异常点位告警'" v-model="modelValue" align-center :modal="false" :show-close="false">
|
||||||
<img class="close" src="@/assets/images/close.png" @click="handleClose" />
|
<img class="close" src="@/assets/images/close.png" @click="handleClose" />
|
||||||
<div class="no-data" v-if="cond&&!list.length">
|
<div class="no-data" v-if="cond&&!list.length">
|
||||||
暂无异常情况
|
{{props.type=='deal'?'暂无数据':'暂无异常情况'}}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
@@ -44,6 +44,10 @@
|
|||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'curr'
|
||||||
|
},
|
||||||
events: {
|
events: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@@ -135,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
let cond = ref(false)
|
let cond = ref(false)
|
||||||
const getVideoList = async () => {
|
const getVideoList = async () => {
|
||||||
let res = await getVideoEventApi(props.scenicSpotId);
|
let res = await getVideoEventApi(props.scenicSpotId,props.type);
|
||||||
list.value = res.data
|
list.value = res.data
|
||||||
setTimeout(()=>{cond.value = true},1500)
|
setTimeout(()=>{cond.value = true},1500)
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -228,6 +232,20 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
padding:vw(10);
|
padding:vw(10);
|
||||||
|
overflow: auto;
|
||||||
|
/* 滚动条整体样式 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: vh(4); /* 滚动条的宽度 */
|
||||||
|
}
|
||||||
|
/* 滚动条轨道 */
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: 'transparent'; /* 轨道的背景色 */
|
||||||
|
}
|
||||||
|
/* 滚动条滑块 */
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
||||||
|
border-radius: 5px; /* 滑块的圆角 */
|
||||||
|
}
|
||||||
.item {
|
.item {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 33%;
|
width: 33%;
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<a @click="hanldeToDetails" class="look-btn">
|
<a @click="hanldeToDetails" class="look-btn">
|
||||||
查看详情
|
<!-- 查看详情-->
|
||||||
<!-- <img src="@/assets/images/d-ico-1.png" /> -->
|
<img src="@/assets/images/d-icon-1.png" />
|
||||||
</a>
|
</a>
|
||||||
<iframe v-if="scenicSpotId == 'root000000'" width="100%" height="100%" src="/map/sxzd/bdc.html"></iframe>
|
<iframe v-if="scenicSpotId == 'root000000'" width="100%" height="100%" src="http://192.168.77.200/map/sxzd/bdc.html"></iframe>
|
||||||
<iframe v-if="scenicSpotId == 'root00000000'" width="100%" height="100%" src="/map/sxzd/sxzd.html"></iframe>
|
<iframe v-if="scenicSpotId == 'root00000000'" width="100%" height="100%" src="http://192.168.77.200//map/sxzd/sxzd.html"></iframe>
|
||||||
<iframe v-if="scenicSpotId == '龙桥河'" width="100%" height="100%" src="/map/lqh/lqh.html"></iframe>
|
<iframe v-if="scenicSpotId == '龙桥河'" width="100%" height="100%" src="http://192.168.77.200/map/lqh/lqh.html"></iframe>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -107,12 +107,21 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div>订票时间</div>
|
<div>订票时间</div>
|
||||||
<div>订票数量</div>
|
<div>订票数量</div>
|
||||||
|
<template v-if="scenicSpotId == 'root00000000'">
|
||||||
|
<div>徒步订票</div>
|
||||||
|
</template>
|
||||||
|
<div>团队订票</div>
|
||||||
|
<div>散客订票</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<div class="item" v-for="(item, index) in gridData" :key="index">
|
<div class="item" v-for="(item, index) in gridData" :key="index">
|
||||||
<div>{{ item.time }}</div>
|
<div>{{ item.time }}</div>
|
||||||
<div>{{ item.value }}</div>
|
<div>{{ item.value }}</div>
|
||||||
|
<template v-if="scenicSpotId == 'root00000000'">
|
||||||
|
<div>{{ item.walk }}</div>
|
||||||
|
</template>
|
||||||
|
<div>{{ item.group }}</div>
|
||||||
|
<div>{{ item.single }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +174,8 @@ import pubSub from 'pubsub-js'
|
|||||||
window.addEventListener("message", async(e) => {
|
window.addEventListener("message", async(e) => {
|
||||||
let {code,data} = await getPreviewUrlApi({
|
let {code,data} = await getPreviewUrlApi({
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
cameraIndexCode:e.data.cameraIndexCode
|
cameraIndexCode:e.data.cameraIndexCode,
|
||||||
|
subStream:0
|
||||||
})
|
})
|
||||||
if(code===200){
|
if(code===200){
|
||||||
src.value = data.url
|
src.value = data.url
|
||||||
@@ -177,11 +187,9 @@ import pubSub from 'pubsub-js'
|
|||||||
let gridData = ref([])
|
let gridData = ref([])
|
||||||
let gridTitle = ref('')
|
let gridTitle = ref('')
|
||||||
const handlePiaoPop = async()=>{
|
const handlePiaoPop = async()=>{
|
||||||
console.log(777777)
|
|
||||||
dialogTableVisible.value = true
|
dialogTableVisible.value = true
|
||||||
let res = await getSpotTicketDate({scenicSpotId:props.scenicSpotId})
|
let res = await getSpotTicketDate({scenicSpotId:props.scenicSpotId})
|
||||||
gridData.value = res.data
|
gridData.value = res.data
|
||||||
console.log(res,'res')
|
|
||||||
}
|
}
|
||||||
let myElement = ref(false)
|
let myElement = ref(false)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -318,14 +326,14 @@ import pubSub from 'pubsub-js'
|
|||||||
padding: vw(20);
|
padding: vw(20);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #0a4190;
|
//background: #0a4190;
|
||||||
border-radius: vw(4);
|
border-radius: vw(4);
|
||||||
font-size: vw(16);
|
font-size: vw(16);
|
||||||
font-weight:700;
|
font-weight:700;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
img{
|
img{
|
||||||
width:vw(100);
|
width:vw(80);
|
||||||
// height:;
|
// height:;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,11 +423,11 @@ import pubSub from 'pubsub-js'
|
|||||||
}
|
}
|
||||||
.tag--important {
|
.tag--important {
|
||||||
@extend .tag;
|
@extend .tag;
|
||||||
background: #feae00;
|
background:#d9011b;
|
||||||
}
|
}
|
||||||
.tag--warn {
|
.tag--warn {
|
||||||
@extend .tag;
|
@extend .tag;
|
||||||
background: #d9011b;
|
background: #feae00;
|
||||||
}
|
}
|
||||||
.tag--normal {
|
.tag--normal {
|
||||||
@extend .tag;
|
@extend .tag;
|
||||||
@@ -428,7 +436,7 @@ import pubSub from 'pubsub-js'
|
|||||||
.content {
|
.content {
|
||||||
margin-left: vw(4);
|
margin-left: vw(4);
|
||||||
padding: 0 vw(10);
|
padding: 0 vw(10);
|
||||||
width: vw(760);
|
width: vw(460);
|
||||||
height: vh(24);
|
height: vh(24);
|
||||||
line-height: vh(24);
|
line-height: vh(24);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -119,10 +119,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="box-2">
|
<div class="box-2">
|
||||||
<Title1 title="异常信息 " />
|
<Title1 title="异常信息 " />
|
||||||
<!-- @click="handleToWorkOrder" -->
|
|
||||||
<div style="cursor: pointer;" class="count-box flex">
|
<div style="cursor: pointer;" class="count-box flex">
|
||||||
<count-item
|
<count-item
|
||||||
v-for="item in headList"
|
v-for="item in headList"
|
||||||
|
@click="showAbnormalList(item)"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:count="item.count"
|
:count="item.count"
|
||||||
:type="item.type"
|
:type="item.type"
|
||||||
@@ -347,12 +347,14 @@
|
|||||||
/>
|
/>
|
||||||
<traMap :longitude="longitude" :latitude="latitude" v-model="traMapShow"></traMap>
|
<traMap :longitude="longitude" :latitude="latitude" v-model="traMapShow"></traMap>
|
||||||
<all-list :events="scenicSpotId" v-model="allShow" />
|
<all-list :events="scenicSpotId" v-model="allShow" />
|
||||||
|
<warn-list v-model="warnShow" :type="warnType" :scenicSpotId="scenicSpotId" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import countup from 'vue-countup-v3'
|
import countup from 'vue-countup-v3'
|
||||||
import PubSub from 'pubsub-js'
|
import PubSub from 'pubsub-js'
|
||||||
import allList from './allList'
|
import allList from './allList'
|
||||||
|
import warnList from './warnList.vue'
|
||||||
import carIcon from '@/assets/images/car.png'
|
import carIcon from '@/assets/images/car.png'
|
||||||
import carStopIcon from '@/assets/images/car-stop.png'
|
import carStopIcon from '@/assets/images/car-stop.png'
|
||||||
import carOfflineIcon from '@/assets/images/car-offline.png'
|
import carOfflineIcon from '@/assets/images/car-offline.png'
|
||||||
@@ -381,6 +383,7 @@
|
|||||||
//景区排队
|
//景区排队
|
||||||
let allShow = ref(false)
|
let allShow = ref(false)
|
||||||
let events = ref({})
|
let events = ref({})
|
||||||
|
let warnType = ref("curr")
|
||||||
|
|
||||||
const handleLineUp = (item)=>{
|
const handleLineUp = (item)=>{
|
||||||
allShow.value = true
|
allShow.value = true
|
||||||
@@ -393,10 +396,11 @@
|
|||||||
hIndex.value = index;
|
hIndex.value = index;
|
||||||
}
|
}
|
||||||
const handlePop2 = async()=>{
|
const handlePop2 = async()=>{
|
||||||
|
if(props.scenicSpotId=="root00000000"){
|
||||||
dialogTableVisible2.value = true
|
dialogTableVisible2.value = true
|
||||||
let res = await getSpotPassengerFlow({scenicSpotId:props.scenicSpotId})
|
let res = await getSpotPassengerFlow({scenicSpotId:props.scenicSpotId})
|
||||||
tjArr.value = res.data
|
tjArr.value = res.data
|
||||||
console.log(res,'res')
|
}
|
||||||
}
|
}
|
||||||
let show = ref(false)
|
let show = ref(false)
|
||||||
let scenicChange = null
|
let scenicChange = null
|
||||||
@@ -406,6 +410,17 @@
|
|||||||
//查看交通信息
|
//查看交通信息
|
||||||
let latitude = ref('')
|
let latitude = ref('')
|
||||||
let longitude = ref('')
|
let longitude = ref('')
|
||||||
|
let warnShow = ref(false)
|
||||||
|
const showAbnormalList = function (item){
|
||||||
|
if(item.name=='今日异常总数'){
|
||||||
|
warnType.value = 'all'
|
||||||
|
}else if(item.name=='当前异常数'){
|
||||||
|
warnType.value = 'curr'
|
||||||
|
}else if(item.name=='已解除异常数'){
|
||||||
|
warnType.value = 'deal'
|
||||||
|
}
|
||||||
|
warnShow.value = true;
|
||||||
|
}
|
||||||
const hanldeLookMap = () => {
|
const hanldeLookMap = () => {
|
||||||
router.push('/traffic')
|
router.push('/traffic')
|
||||||
// traMapShow.value = true
|
// traMapShow.value = true
|
||||||
|
|||||||
@@ -151,7 +151,23 @@
|
|||||||
params.series[0].label.formatter = formatLabel()
|
params.series[0].label.formatter = formatLabel()
|
||||||
params.series[0].data = getSeriesData()
|
params.series[0].data = getSeriesData()
|
||||||
}
|
}
|
||||||
setOption(params)
|
const chart = setOption(params);
|
||||||
|
chart.on('legendselectchanged', function (e) {
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.list.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseInt(item.count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
params.series[0].label.formatter = `{value|${echartsNum}}` + '\n' + `{name|${props.subTitle}}`;
|
||||||
|
setOption(params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
311
src/views/scenic/components/warnList.vue
Normal file
311
src/views/scenic/components/warnList.vue
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
<template>
|
||||||
|
<div class="z-dialog">
|
||||||
|
<el-dialog :title="props.type=='deal'?'已解除异常':'异常点位告警'" v-model="modelValue" align-center :modal="false" :show-close="false">
|
||||||
|
<img class="close" src="@/assets/images/close.png" @click="handleClose" />
|
||||||
|
<div class="no-data" v-if="cond&&!list.length">
|
||||||
|
{{props.type=='deal'?'暂无数据':'暂无异常情况'}}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<ul class="list" >
|
||||||
|
<li
|
||||||
|
class="item"
|
||||||
|
:style="{ backgroundImage: `url(${primary})` }"
|
||||||
|
v-for="(item, index) in list"
|
||||||
|
:key="index"
|
||||||
|
@click="handleImage(item)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="item-title-box"
|
||||||
|
><div class="item-title">{{ item.analyzeTypeName}}</div><div class="item-num">{{ item.number}}人</div></div>
|
||||||
|
<img style="width: 100%;height: 100%;" :src="item.imgurl || imageEmpty">
|
||||||
|
<p style="display: flex;justify-content: space-between;">
|
||||||
|
<span> {{ item.cameraName || item.cameraIndexCode }}</span>
|
||||||
|
<span class="cursorPointer" @click.stop="handleItem(item.cameraIndexCode)">查看监控></span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
<video-dialog v-model="videoShow" :src="src" :isDiy="isDiy" :isCollect="isCollect" :cameraIndexCode="cameraIndexCode" />
|
||||||
|
<image-dialog v-model="imageShow" :src="imgSrc"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getVideoEventApi,getVideoEventObjImgApi,getPreviewUrlSubApi } from '@/api/monitor'
|
||||||
|
|
||||||
|
import primary from '@/assets/images/item-primary.png'
|
||||||
|
|
||||||
|
import imageEmpty from '@/assets/images/imgloading.png'
|
||||||
|
import pubSub from 'pubsub-js'
|
||||||
|
|
||||||
|
let props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'curr'
|
||||||
|
},
|
||||||
|
scenicSpotId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
})
|
||||||
|
let modelValue = defineModel()
|
||||||
|
let isCollect = ref(0)
|
||||||
|
let list = ref([])
|
||||||
|
let hlsRefs = []
|
||||||
|
let total = ref(0)
|
||||||
|
let src = ref('')
|
||||||
|
let imgSrc = ref('')
|
||||||
|
let imageShow = ref(false)
|
||||||
|
let cameraIndexCode = ref('')
|
||||||
|
let videoShow = ref(false)
|
||||||
|
let isDiy = ref(0)
|
||||||
|
let params = reactive({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 6,
|
||||||
|
businessScenicArea:'',
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => modelValue.value,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
params.pageNum = 1
|
||||||
|
cond.value = false
|
||||||
|
params.businessScenicArea = props.events
|
||||||
|
list.value = []
|
||||||
|
setTimeout(() => {
|
||||||
|
getVideoList()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const onVideoCollect = () => {
|
||||||
|
pubSub.subscribe('videoCollect', () => {
|
||||||
|
clearHlsRefs()
|
||||||
|
getVideoList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleImage = (item) => {
|
||||||
|
getVideoEventObjImgApi(item.objId).then(res=>{
|
||||||
|
imgSrc.value = res.data.url
|
||||||
|
imageShow.value = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const handleItem = async (id) => {
|
||||||
|
let {code,data} = await getPreviewUrlSubApi({
|
||||||
|
type: 'hls',
|
||||||
|
cameraIndexCode:id
|
||||||
|
})
|
||||||
|
if(code===200){
|
||||||
|
src.value = data.url
|
||||||
|
isCollect.value = data.isCollect
|
||||||
|
isDiy.value = data.isDiy
|
||||||
|
cameraIndexCode.value = id
|
||||||
|
videoShow.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleCollect = async (id, status) => {
|
||||||
|
await postVideoCollectApi({
|
||||||
|
cameraIndexCode:id,
|
||||||
|
isCollect: status == 0 ? 1 : 0
|
||||||
|
})
|
||||||
|
clearHlsRefs()
|
||||||
|
params.pageNum = 1
|
||||||
|
getVideoList()
|
||||||
|
pubSub.publish('videoCollect')
|
||||||
|
}
|
||||||
|
const handleClose = () => {
|
||||||
|
clearHlsRefs()
|
||||||
|
modelValue.value = false
|
||||||
|
}
|
||||||
|
const clearHlsRefs = () => {
|
||||||
|
if (hlsRefs.length > 0) {
|
||||||
|
hlsRefs.map((item) => {
|
||||||
|
item.destroy()
|
||||||
|
})
|
||||||
|
hlsRefs = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pageNumChange = () => {
|
||||||
|
cond.value = false
|
||||||
|
clearHlsRefs()
|
||||||
|
list.value = []
|
||||||
|
getVideoList()
|
||||||
|
}
|
||||||
|
let cond = ref(false)
|
||||||
|
const getVideoList = async () => {
|
||||||
|
let res = await getVideoEventApi(props.scenicSpotId,props.type);
|
||||||
|
list.value = res.data
|
||||||
|
setTimeout(()=>{cond.value = true},1500)
|
||||||
|
nextTick(() => {
|
||||||
|
list.value.forEach(async (item, index) => {
|
||||||
|
getVideoEventObjImgApi(item.objId).then(res=>{
|
||||||
|
list.value[index].imgurl = res.data.url
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
onVideoCollect()
|
||||||
|
})
|
||||||
|
onUnmounted(() => {})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.z-dialog {
|
||||||
|
:deep(.el-dialog) {
|
||||||
|
width: vw(2100);
|
||||||
|
padding: vw(8);
|
||||||
|
background-image: url('@/assets/images/dialog-bg.png') !important;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: vw(4); /* 滚动条的宽度 */
|
||||||
|
}
|
||||||
|
/* 滚动条轨道 */
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: 'transparent'; /* 轨道的背景色 */
|
||||||
|
}
|
||||||
|
/* 滚动条滑块 */
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
||||||
|
border-radius: 5px; /* 滑块的圆角 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.el-dialog__header) {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
margin-top: vw(10);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
:deep(.el-dialog__title) {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-title-box{
|
||||||
|
font-weight: 400;
|
||||||
|
z-index: 99;
|
||||||
|
height: vw(50);
|
||||||
|
|
||||||
|
font-size: vw(24);
|
||||||
|
color: #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
left: 3%;
|
||||||
|
|
||||||
|
.item-title{
|
||||||
|
padding: vw(10);
|
||||||
|
display: inline-block;
|
||||||
|
background-image: url('@/assets/images/unfollow.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
.item-num{
|
||||||
|
margin-left: vw(6);
|
||||||
|
padding: vw(5);
|
||||||
|
display: inline-block;
|
||||||
|
color: #ffffff;
|
||||||
|
background-color:red;
|
||||||
|
border-radius: 15%;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.no-data{
|
||||||
|
height:vh(860);
|
||||||
|
line-height: vh(860);
|
||||||
|
text-align: center;
|
||||||
|
font-size:vw(30);
|
||||||
|
color:#02f9fa;
|
||||||
|
}
|
||||||
|
.cursorPointer{
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
//margin-top: vw(5);
|
||||||
|
gap: vw(8);
|
||||||
|
height: vh(860);
|
||||||
|
overflow: auto;
|
||||||
|
/* 滚动条整体样式 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: vw(4); /* 滚动条的宽度 */
|
||||||
|
}
|
||||||
|
/* 滚动条轨道 */
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: 'transparent'; /* 轨道的背景色 */
|
||||||
|
}
|
||||||
|
/* 滚动条滑块 */
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
||||||
|
border-radius: 5px; /* 滑块的圆角 */
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
padding:vw(10);
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
width: 33%;
|
||||||
|
height: vh(400);
|
||||||
|
padding: vw(12);
|
||||||
|
margin-top:vh(20);
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-image: url('@/assets/images/item-primary.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
&-video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
> p {
|
||||||
|
position: absolute;
|
||||||
|
bottom: vw(12);
|
||||||
|
width: calc(100% - vw(24));
|
||||||
|
padding: vw(10) 0;
|
||||||
|
background: rgba(4, 30, 69, 0.72);
|
||||||
|
> span {
|
||||||
|
padding-left: vw(10);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: vw(14);
|
||||||
|
line-height: vw(14);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-unfollow {
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
right: vw(4);
|
||||||
|
top: vw(4);
|
||||||
|
z-index: 99;
|
||||||
|
width: vw(64);
|
||||||
|
height: vw(30);
|
||||||
|
text-align: center;
|
||||||
|
line-height: vw(30);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: vw(12);
|
||||||
|
color: #ffffff;
|
||||||
|
background-image: url('@/assets/images/unfollow.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pagination {
|
||||||
|
padding-top: vw(20);
|
||||||
|
margin-right: vw(20);
|
||||||
|
padding-bottom: vw(20);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: vw(20);
|
||||||
|
top: vw(20);
|
||||||
|
width: vw(60);
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -144,6 +144,13 @@
|
|||||||
scenicSpotId: scenicSpotId.value
|
scenicSpotId: scenicSpotId.value
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
sendMessage(
|
||||||
|
JSON.stringify({
|
||||||
|
action: 'start',
|
||||||
|
type: 'secureData',
|
||||||
|
scenicSpotId: scenicSpotId.value
|
||||||
|
})
|
||||||
|
)
|
||||||
sendMessage(
|
sendMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
action: 'start',
|
action: 'start',
|
||||||
|
|||||||
@@ -56,11 +56,11 @@
|
|||||||
font-size: vw(13);
|
font-size: vw(13);
|
||||||
color: #0084ff;
|
color: #0084ff;
|
||||||
height: vh(40);
|
height: vh(40);
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(3, 78, 153, 0.3);
|
background: rgba(3, 78, 153, 0.3);
|
||||||
& > p {
|
& > p {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
color: #ffffff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -531,6 +531,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: vh(40);
|
height: vh(40);
|
||||||
|
cursor: pointer;
|
||||||
&:nth-child(2n + 1) {
|
&:nth-child(2n + 1) {
|
||||||
background: rgba(3, 78, 153, 0.3);
|
background: rgba(3, 78, 153, 0.3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<!-- <div class="header-left__camera" @click="videoShow = true">道路监控</div> -->
|
<!-- <div class="header-left__camera" @click="videoShow = true">道路监控</div> -->
|
||||||
<!-- <div class="header-left__point" @click="videoShow = true">3号点位</div> -->
|
<!-- <div class="header-left__point" @click="videoShow = true">3号点位</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="header-status">{{ item.congestLevelText }} </div>
|
<div class="header-status" v-if="item.congestLevel>0">{{ item.congestLevelText }} </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="statistics">
|
<div class="statistics">
|
||||||
<div class="statistics-item">
|
<div class="statistics-item">
|
||||||
@@ -191,7 +191,8 @@
|
|||||||
const getPreviewUrl = async (code) => {
|
const getPreviewUrl = async (code) => {
|
||||||
let res = await getPreviewUrlApi({
|
let res = await getPreviewUrlApi({
|
||||||
cameraIndexCode: code,
|
cameraIndexCode: code,
|
||||||
type: 'hls'
|
type: 'hls',
|
||||||
|
subStream:0
|
||||||
})
|
})
|
||||||
src.value = res.data.url
|
src.value = res.data.url
|
||||||
videoShow.value = true
|
videoShow.value = true
|
||||||
|
|||||||
@@ -8,24 +8,236 @@
|
|||||||
<span :class="item.level">{{ item.level_text }}</span>
|
<span :class="item.level">{{ item.level_text }}</span>
|
||||||
<p>{{ item.title }}</p>
|
<p>{{ item.title }}</p>
|
||||||
<span class="time">{{ item.time }}</span>
|
<span class="time">{{ item.time }}</span>
|
||||||
|
<span class="btn" @click="showDetail(item.id)">详情</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<el-dialog :title="'工单详情'" center v-model="dialogVisible">
|
||||||
|
<div class="bom-box">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="m-modal">
|
||||||
|
<div class="item-title--primary">工单信息</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">所属景区:</span>
|
||||||
|
<span class="value">{{ detail.road }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">工单类型:</span>
|
||||||
|
<span class="value">{{ detail.type }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">紧急程度:</span>
|
||||||
|
<span :class="'value '+detail.level">{{ detail.levelText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">具体地点:</span>
|
||||||
|
<span class="value">{{ detail.address }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">创建时间:</span>
|
||||||
|
<span class="value">{{ detail.createTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">完成期限:</span>
|
||||||
|
<span class="value">{{ detail.completionDeadline }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">创建人:</span>
|
||||||
|
<span class="value">{{ detail.unidName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">状态:</span>
|
||||||
|
<span class="value important" v-if="detail.status==0">待处理</span>
|
||||||
|
<span class="value warn" v-else-if="detail.status==1">进行中</span>
|
||||||
|
<span class="value normal" v-else-if="detail.status==2">已完成</span>
|
||||||
|
<span class="value" v-else>已关闭</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">工单内容:</span>
|
||||||
|
<span class="value">{{ detail.title }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item" v-if="detail.imgs">
|
||||||
|
<el-image class="img" :preview-src-list="detail.imgs.split(',')" :src="src" v-for="(src, index) in detail.imgs.split(',')"></el-image>
|
||||||
|
</div>
|
||||||
|
<div class="item-title--warning" v-if="detail.follow && detail.follow.length>0">跟进信息</div>
|
||||||
|
<template v-for="item in detail.follow">
|
||||||
|
<div class="item-col" >
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">跟进人:</span>
|
||||||
|
<span class="value">{{ item.unidName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">跟进地点:</span>
|
||||||
|
<span class="value">{{ item.address }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">完成率:</span>
|
||||||
|
<span class="value">{{ item.rate }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<div class="detail-item" style="flex: 2">
|
||||||
|
<span class="label">跟进内容:</span>
|
||||||
|
<span class="value">{{ item.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">跟进时间:</span>
|
||||||
|
<span class="value">{{ item.createTime }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item" v-if="item.img">
|
||||||
|
<el-image class="img" :preview-src-list="item.img.split(',')" :src="src" v-for="(src, index) in item.img.split(',')"></el-image>
|
||||||
|
</div>
|
||||||
|
<div style="border-bottom: 1px solid #ffffff;margin: 10px 0;"></div>
|
||||||
|
</template>
|
||||||
|
<div class="item-title--warning" v-if="detail.evaluate && detail.evaluate.length>0">评价信息</div>
|
||||||
|
<template v-for="item in detail.evaluate">
|
||||||
|
<div class="item-col" >
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">评价人:</span>
|
||||||
|
<span class="value warn">{{ item.unidName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">评价内容:</span>
|
||||||
|
<span class="value">{{ item.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">评价时间:</span>
|
||||||
|
<span class="value">{{ item.createTime }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item" v-if="item.img">
|
||||||
|
<el-image class="img" :preview-src-list="item.img.split(',')" :src="src" v-for="(src, index) in item.img.split(',')"></el-image>
|
||||||
|
</div>
|
||||||
|
<div style="border-bottom: 1px solid #ffffff;margin: 10px 0;"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getListApi } from '@/api/workOrder'
|
import {getDetailApi, getListApi} from '@/api/workOrder'
|
||||||
|
let dialogVisible = ref(false)
|
||||||
let list = ref([])
|
let list = ref([])
|
||||||
|
let detail = ref({})
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
let res = await getListApi()
|
let res = await getListApi()
|
||||||
list.value = res.data
|
list.value = res.data
|
||||||
}
|
}
|
||||||
|
const showDetail = async (id) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
let res = await getDetailApi(id)
|
||||||
|
console.log( res.data);
|
||||||
|
detail.value = res.data
|
||||||
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-dialog__headerbtn .el-dialog__close){
|
||||||
|
color:#fff;
|
||||||
|
font-size:40px;
|
||||||
|
position:relative;
|
||||||
|
top:15px;
|
||||||
|
right:15px;
|
||||||
|
}
|
||||||
|
:deep(.el-dialog__header.show-close){
|
||||||
|
padding-right:0;
|
||||||
|
}
|
||||||
|
:deep(.el-dialog) {
|
||||||
|
background: url('/src/assets/images/map-bg-2.png') no-repeat top center;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
width:vw(1500);
|
||||||
|
}
|
||||||
|
:deep(.el-dialog__title) {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.m-modal{
|
||||||
|
padding: vw(30);
|
||||||
|
.normal {
|
||||||
|
padding:3px 10px;
|
||||||
|
background: #2380fb;
|
||||||
|
}
|
||||||
|
.warn {
|
||||||
|
padding:3px 10px;
|
||||||
|
background: #feae00;
|
||||||
|
}
|
||||||
|
.important {
|
||||||
|
padding:3px 10px;
|
||||||
|
background: #d9011b;
|
||||||
|
}
|
||||||
|
.item-title{
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: vw(30);
|
||||||
|
&--primary {
|
||||||
|
@extend .item-title;
|
||||||
|
color: #02f9fa;
|
||||||
|
background-image: url('@/assets/images/mask-primary.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
&--error {
|
||||||
|
@extend .item-title;
|
||||||
|
background-image: url('@/assets/images/mask-error.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
&--warning {
|
||||||
|
@extend .item-title;
|
||||||
|
background-image: url('@/assets/images/mask-warning.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
&--success {
|
||||||
|
@extend .item-title;
|
||||||
|
background-image: url('@/assets/images/mask-success.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-col{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.detail-item {
|
||||||
|
flex:1;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
.img{
|
||||||
|
width:vw(200);
|
||||||
|
height: vw(200);
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bom-box {
|
||||||
|
.modal-body {
|
||||||
|
padding: 16px;
|
||||||
|
color:#fff;
|
||||||
|
height:vh(780);
|
||||||
|
overflow:auto;
|
||||||
|
/* 滚动条整体样式 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: vw(4); /* 滚动条的宽度 */
|
||||||
|
}
|
||||||
|
/* 滚动条轨道 */
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: 'transparent'; /* 轨道的背景色 */
|
||||||
|
}
|
||||||
|
/* 滚动条滑块 */
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
|
||||||
|
border-radius: 5px; /* 滑块的圆角 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.work-box-1 {
|
.work-box-1 {
|
||||||
width: vw(815);
|
width: vw(815);
|
||||||
height: vh(950);
|
height: vh(950);
|
||||||
@@ -61,6 +273,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: vh(8) vh(10);
|
padding: vh(8) vh(10);
|
||||||
|
font-size: vw(14);
|
||||||
.label {
|
.label {
|
||||||
width: vw(60);
|
width: vw(60);
|
||||||
height: vh(24);
|
height: vh(24);
|
||||||
@@ -84,7 +297,7 @@
|
|||||||
@extend .label;
|
@extend .label;
|
||||||
background: #d9011b;
|
background: #d9011b;
|
||||||
}
|
}
|
||||||
.time {
|
.time,.btn {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: vw(12);
|
font-size: vw(12);
|
||||||
color: rgba(255, 255, 255, 0.6);
|
color: rgba(255, 255, 255, 0.6);
|
||||||
@@ -94,9 +307,15 @@
|
|||||||
text-transform: none;
|
text-transform: none;
|
||||||
margin-left: vw(30);
|
margin-left: vw(30);
|
||||||
}
|
}
|
||||||
|
.btn{
|
||||||
|
font-size: vw(18);
|
||||||
|
font-weight: bolder;
|
||||||
|
color: #2380fb;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
p {
|
p {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: vw(15);
|
//font-size: vw(15);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
|||||||
@@ -12,12 +12,13 @@
|
|||||||
<div class="item item1">
|
<div class="item item1">
|
||||||
工单完成数 <span class="color1"><countup :end-val="totalData.complete" /></span>
|
工单完成数 <span class="color1"><countup :end-val="totalData.complete" /></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="item item3">
|
|
||||||
紧急工单数 <span class=""><countup :end-val="totalData.warn" /></span>
|
|
||||||
</div>
|
|
||||||
<div class="item item2">
|
<div class="item item2">
|
||||||
重要工单数 <span class=""><countup :end-val="totalData.important" /></span>
|
重要工单数 <span class=""><countup :end-val="totalData.important" /></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="item item3">
|
||||||
|
紧急工单数 <span class=""><countup :end-val="totalData.warn" /></span>
|
||||||
|
</div>
|
||||||
<div class="item item1">
|
<div class="item item1">
|
||||||
普通工单数 <span class=""><countup :end-val="totalData.normal" /></span>
|
普通工单数 <span class=""><countup :end-val="totalData.normal" /></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,24 +95,24 @@
|
|||||||
<p class="value">{{ item.value }}%</p>
|
<p class="value">{{ item.value }}%</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div v-if="pointRankData.length > 0" class="alarm">
|
<!-- <div v-if="pointRankData.length > 0" class="alarm">-->
|
||||||
<Title2 title="异常点位告警排名" />
|
<!-- <Title2 title="异常点位告警排名" />-->
|
||||||
<ul class="alarm__wrapper">
|
<!-- <ul class="alarm__wrapper">-->
|
||||||
<li class="alarm-item" v-for="(item, index) in pointRankData" :key="index">
|
<!-- <li class="alarm-item" v-for="(item, index) in pointRankData" :key="index">-->
|
||||||
<p
|
<!-- <p-->
|
||||||
class="alarm-item__rank"
|
<!-- class="alarm-item__rank"-->
|
||||||
:class="{
|
<!-- :class="{-->
|
||||||
'alarm-item__rank--error': index == 0,
|
<!-- 'alarm-item__rank--error': index == 0,-->
|
||||||
'alarm-item__rank--warning': index == 1,
|
<!-- 'alarm-item__rank--warning': index == 1,-->
|
||||||
'alarm-item__rank--primary': index == 2
|
<!-- 'alarm-item__rank--primary': index == 2-->
|
||||||
}"
|
<!-- }"-->
|
||||||
>
|
<!-- >-->
|
||||||
{{ index + 1 }}
|
<!-- {{ index + 1 }}-->
|
||||||
</p>
|
<!-- </p>-->
|
||||||
<p class="alarm-item__content">{{ item.link_title }}</p>
|
<!-- <p class="alarm-item__content">{{ item.link_title }}</p>-->
|
||||||
</li>
|
<!-- </li>-->
|
||||||
</ul>
|
<!-- </ul>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -288,7 +289,6 @@
|
|||||||
height: vh(58);
|
height: vh(58);
|
||||||
line-height: vh(58);
|
line-height: vh(58);
|
||||||
padding-left: vw(10);
|
padding-left: vw(10);
|
||||||
text-align: center;
|
|
||||||
margin: 0 vw(15);
|
margin: 0 vw(15);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: vw(14);
|
font-size: vw(14);
|
||||||
@@ -296,6 +296,7 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
align-items: baseline;
|
||||||
span {
|
span {
|
||||||
font-size: vw(24);
|
font-size: vw(24);
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -311,11 +312,11 @@
|
|||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
.item2 {
|
.item2 {
|
||||||
background-image: url('@/assets/images/work-n-bg-2.png');
|
background-image: url('@/assets/images/work-n-bg-3.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
.item3 {
|
.item3 {
|
||||||
background-image: url('@/assets/images/work-n-bg-3.png');
|
background-image: url('@/assets/images/work-n-bg-2.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,20 +16,20 @@
|
|||||||
<div class="statistics-item">
|
<div class="statistics-item">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
<img class="statistics-item__icon" src="@/assets/images/dot-error.svg" />
|
<img class="statistics-item__icon" src="@/assets/images/dot-error.svg" />
|
||||||
<span class="statistics-item__label">紧急消息</span>
|
<span class="statistics-item__label">重要消息</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="statistics-item__value--error">
|
<div class="statistics-item__value--error">
|
||||||
<countup :end-val="countInfo.warn" />
|
<countup :end-val="countInfo.important" />
|
||||||
<span class="statistics-item__value-suffix">条</span>
|
<span class="statistics-item__value-suffix">条</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="statistics-item">
|
<div class="statistics-item">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
<img class="statistics-item__icon" src="@/assets/images/dot-warning.svg" />
|
<img class="statistics-item__icon" src="@/assets/images/dot-warning.svg" />
|
||||||
<span class="statistics-item__label">重要消息</span>
|
<span class="statistics-item__label">紧急消息</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="statistics-item__value--warning">
|
<div class="statistics-item__value--warning">
|
||||||
<countup :end-val="countInfo.important" />
|
<countup :end-val="countInfo.warn" />
|
||||||
<span class="statistics-item__value-suffix">条</span>
|
<span class="statistics-item__value-suffix">条</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,14 +45,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-box">
|
<div class="chart-box">
|
||||||
<pie
|
<!-- <pie-->
|
||||||
v-for="(item, index) in newsStateList"
|
<!-- v-for="(item, index) in newsStateList"-->
|
||||||
:key="index"
|
<!-- :key="index"-->
|
||||||
:value="item.value"
|
<!-- :value="item.value"-->
|
||||||
:label="item.name"
|
<!-- :label="item.name"-->
|
||||||
:width="150"
|
<!-- :width="150"-->
|
||||||
:height="150"
|
<!-- :height="150"-->
|
||||||
/>
|
<!-- />-->
|
||||||
|
<newsRate :dataList="newsStateList" />
|
||||||
|
<ul class="chart__legend">
|
||||||
|
<li class="chart__legend-item" v-for="(item, index) in newsStateList" :key="index">
|
||||||
|
<p class="dot" :style="{ background: colors[index] }" />
|
||||||
|
<p class="name">{{ item.name }}</p>
|
||||||
|
<p class="value">{{ item.value }}</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="work-box-1">
|
<div class="work-box-1">
|
||||||
@@ -79,7 +87,8 @@
|
|||||||
import countup from 'vue-countup-v3'
|
import countup from 'vue-countup-v3'
|
||||||
import pie from './pie.vue'
|
import pie from './pie.vue'
|
||||||
import { getNewsListApi, getNewsStateApi, getNewsTotalApi } from '@/api/news'
|
import { getNewsListApi, getNewsStateApi, getNewsTotalApi } from '@/api/news'
|
||||||
|
import NewsRate from "@/views/workOrder/components/newsRate.vue";
|
||||||
|
const colors = ['#FDC40A', '#FF5232', '#50F0A6', '#5FDFFA']
|
||||||
let list = ref([])
|
let list = ref([])
|
||||||
let countInfo = ref({
|
let countInfo = ref({
|
||||||
important: 0,
|
important: 0,
|
||||||
@@ -109,6 +118,69 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.chart {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: vw(20) vw(15);
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
width: vw(740);
|
||||||
|
height: vh(370);
|
||||||
|
padding: 0 vw(20);
|
||||||
|
background-image: url('@/assets/images/bg-3.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__legend {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: vh(40);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: vh(8);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 150, 255, 0.34) 0%,
|
||||||
|
rgba(0, 150, 255, 0) 100%
|
||||||
|
);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
width: vw(4);
|
||||||
|
height: vh(40);
|
||||||
|
background-color: #0096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: vw(10);
|
||||||
|
height: vw(10);
|
||||||
|
margin: 0 vw(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: vw(12);
|
||||||
|
color: #ffffff;
|
||||||
|
width: vw(130);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: vw(15);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.work-box-3 {
|
.work-box-3 {
|
||||||
width: vw(840);
|
width: vw(840);
|
||||||
margin-top: vh(120);
|
margin-top: vh(120);
|
||||||
|
|||||||
164
src/views/workOrder/components/newsRate.vue
Normal file
164
src/views/workOrder/components/newsRate.vue
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div class="alarmRate" :id="id" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { fitChartSize } from '@/utils/dataUtil'
|
||||||
|
import { useEchart } from '@/hooks/echart'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
type: Number,
|
||||||
|
default: () => 0
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ['#FDC40A', '#FF5232', '#50F0A6', '#5FDFFA']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { id, chart, setOption } = useEchart()
|
||||||
|
|
||||||
|
let params = null
|
||||||
|
|
||||||
|
let defaultCofig = {
|
||||||
|
color: [],
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
y: 'center',
|
||||||
|
left: '50%',
|
||||||
|
itemWidth: fitChartSize(12),
|
||||||
|
itemHeight: fitChartSize(12),
|
||||||
|
itemGap: fitChartSize(6),
|
||||||
|
formatter: (name) => {
|
||||||
|
let obj = props.dataList.find((item) => item.name == name)
|
||||||
|
let total = getTotal();
|
||||||
|
if(total==0){
|
||||||
|
return `{name|${name}} {value|0}{value|%}`
|
||||||
|
}else{
|
||||||
|
let value = ((obj?.value/total).toFixed(4)*100).toFixed(2);
|
||||||
|
return `{name|${name}} {value|${value}}{value|%}`
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
rich: {
|
||||||
|
name: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: fitChartSize(12)
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: fitChartSize(12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
center: ['24%', '50%'],
|
||||||
|
radius: ['35%', '50%'],
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: fitChartSize(4),
|
||||||
|
borderColor: '#093672'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'center',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
rich: {
|
||||||
|
value: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: fitChartSize(16),
|
||||||
|
fontWeight: 'bold',
|
||||||
|
padding: [0, 0, 5, 0]
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: fitChartSize(12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let getTotal = () => {
|
||||||
|
return props.dataList.reduce((per, cur) => {
|
||||||
|
return per + cur.value
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.dataList,
|
||||||
|
() => {
|
||||||
|
if (props.dataList.length > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
init()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
if (!params) {
|
||||||
|
defaultCofig.color = props.colors
|
||||||
|
defaultCofig.series[0].data = props.dataList
|
||||||
|
defaultCofig.series[0].label.formatter = () => {
|
||||||
|
return `{value|${getTotal()}}` + '\n' + `{name|消息总数}`
|
||||||
|
}
|
||||||
|
params = {
|
||||||
|
...defaultCofig,
|
||||||
|
...props.config
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params.series[0].data = props.dataList
|
||||||
|
params.series[0].label.formatter = () => {
|
||||||
|
return `{value|${getTotal()}}` + '\n' + `{name|消息总数}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const changeChart = setOption(params)
|
||||||
|
changeChart.off('legendselectchanged');
|
||||||
|
changeChart.on('legendselectchanged', function (e) {
|
||||||
|
var echartsArr = [];
|
||||||
|
for (let key in e.selected) {
|
||||||
|
if (e.selected[key]) {
|
||||||
|
echartsArr.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var echartsNum = 0;
|
||||||
|
props.dataList.forEach(item => {
|
||||||
|
if(echartsArr.includes(item.name)){
|
||||||
|
echartsNum += parseFloat(item.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
params.series[0].label.formatter = () => {
|
||||||
|
return `{value|${echartsNum}}` + '\n' + `{name|告警总数}`
|
||||||
|
}
|
||||||
|
setOption(params)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.alarmRate {
|
||||||
|
width: vw(380);
|
||||||
|
height: vh(180);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -33,7 +33,7 @@ export default defineConfig({
|
|||||||
// target: 'http://localhost:63343/',
|
// target: 'http://localhost:63343/',
|
||||||
// changeOrigin: true
|
// changeOrigin: true
|
||||||
// },
|
// },
|
||||||
// '/lqh': {
|
// '/map/lqh': {
|
||||||
// // 目标服务器的地址
|
// // 目标服务器的地址
|
||||||
// target: 'http://localhost:63343/',
|
// target: 'http://localhost:63343/',
|
||||||
// changeOrigin: true
|
// changeOrigin: true
|
||||||
|
|||||||
Reference in New Issue
Block a user