|
|
@ -237,7 +237,9 @@ |
|
|
|
|
|
|
|
|
<aside v-if="treeMounted" v-show="treeVisible" class="portal-device-tree"> |
|
|
<aside v-if="treeMounted" v-show="treeVisible" class="portal-device-tree"> |
|
|
<portal-device-tree |
|
|
<portal-device-tree |
|
|
|
|
|
:key="portalTreeStationCode || 'all'" |
|
|
:selected-code="selectedPortalCameraCode" |
|
|
:selected-code="selectedPortalCameraCode" |
|
|
|
|
|
:station-code="portalTreeStationCode" |
|
|
@channels-loaded="handlePortalTreeChannels" |
|
|
@channels-loaded="handlePortalTreeChannels" |
|
|
@open="openPortalChannel" |
|
|
@open="openPortalChannel" |
|
|
/> |
|
|
/> |
|
|
@ -272,6 +274,7 @@ import { |
|
|
} from "@/api/portal"; |
|
|
} from "@/api/portal"; |
|
|
import Hls from "hls.js"; |
|
|
import Hls from "hls.js"; |
|
|
import flvjs from "flv.js"; |
|
|
import flvjs from "flv.js"; |
|
|
|
|
|
import axios from "axios"; |
|
|
import DefectTrendChart from "./components/DefectTrendChart"; |
|
|
import DefectTrendChart from "./components/DefectTrendChart"; |
|
|
import PortalDeviceTree from "./components/PortalDeviceTree.vue"; |
|
|
import PortalDeviceTree from "./components/PortalDeviceTree.vue"; |
|
|
import Navbar from "@/layout/components/Navbar"; |
|
|
import Navbar from "@/layout/components/Navbar"; |
|
|
@ -406,6 +409,10 @@ export default { |
|
|
treeMounted: false, |
|
|
treeMounted: false, |
|
|
portalPlayers: {}, |
|
|
portalPlayers: {}, |
|
|
portalStartTimers: {}, |
|
|
portalStartTimers: {}, |
|
|
|
|
|
portalStartRequests: {}, |
|
|
|
|
|
portalRequestSource: null, |
|
|
|
|
|
portalAlive: true, |
|
|
|
|
|
portalReleased: false, |
|
|
overviewRequestSerial: 0, |
|
|
overviewRequestSerial: 0, |
|
|
trendRequestSerial: 0 |
|
|
trendRequestSerial: 0 |
|
|
}; |
|
|
}; |
|
|
@ -446,6 +453,10 @@ export default { |
|
|
.filter(video => String(video.stationId) === String(this.videoStationId)) |
|
|
.filter(video => String(video.stationId) === String(this.videoStationId)) |
|
|
.slice(0, 4); |
|
|
.slice(0, 4); |
|
|
}, |
|
|
}, |
|
|
|
|
|
portalTreeStationCode() { |
|
|
|
|
|
const station = this.stationList.find(item => String(item.id) === String(this.videoStationId)); |
|
|
|
|
|
return station && station.code ? station.code : ""; |
|
|
|
|
|
}, |
|
|
portalVideoSlots() { |
|
|
portalVideoSlots() { |
|
|
return Array.from({ length: 4 }, (_, index) => this.videoList[index] || null); |
|
|
return Array.from({ length: 4 }, (_, index) => this.videoList[index] || null); |
|
|
}, |
|
|
}, |
|
|
@ -472,8 +483,7 @@ export default { |
|
|
this.loadPortalData(); |
|
|
this.loadPortalData(); |
|
|
}, |
|
|
}, |
|
|
beforeDestroy() { |
|
|
beforeDestroy() { |
|
|
this.pauseDefectCarousel(); |
|
|
|
|
|
this.stopPortalVideos(); |
|
|
|
|
|
|
|
|
this.releasePortalPage(); |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
selectMonitorStation(station) { |
|
|
selectMonitorStation(station) { |
|
|
@ -491,6 +501,18 @@ export default { |
|
|
this.trendStationId = station.code; |
|
|
this.trendStationId = station.code; |
|
|
this.trendType = algCategory; |
|
|
this.trendType = algCategory; |
|
|
}, |
|
|
}, |
|
|
|
|
|
portalRequestConfig() { |
|
|
|
|
|
if (!this.portalRequestSource) this.portalRequestSource = axios.CancelToken.source(); |
|
|
|
|
|
return { cancelToken: this.portalRequestSource.token }; |
|
|
|
|
|
}, |
|
|
|
|
|
cancelPortalRequests() { |
|
|
|
|
|
if (!this.portalRequestSource) return; |
|
|
|
|
|
this.portalRequestSource.cancel("portal requests cancelled"); |
|
|
|
|
|
this.portalRequestSource = null; |
|
|
|
|
|
}, |
|
|
|
|
|
isPortalCancel(error) { |
|
|
|
|
|
return !this.portalAlive || axios.isCancel(error); |
|
|
|
|
|
}, |
|
|
loadPortalData() { |
|
|
loadPortalData() { |
|
|
return this.loadStations().then(() => Promise.all([ |
|
|
return this.loadStations().then(() => Promise.all([ |
|
|
this.loadInspectionOverview(), |
|
|
this.loadInspectionOverview(), |
|
|
@ -500,7 +522,7 @@ export default { |
|
|
])); |
|
|
])); |
|
|
}, |
|
|
}, |
|
|
loadStations() { |
|
|
loadStations() { |
|
|
return getStationList({}).then(response => { |
|
|
|
|
|
|
|
|
return getStationList({}, this.portalRequestConfig()).then(response => { |
|
|
const rows = Array.isArray(response && response.data) ? response.data : []; |
|
|
const rows = Array.isArray(response && response.data) ? response.data : []; |
|
|
this.stationList = rows.map(item => ({ |
|
|
this.stationList = rows.map(item => ({ |
|
|
id: item.id, |
|
|
id: item.id, |
|
|
@ -511,7 +533,8 @@ export default { |
|
|
enabled: String(item.enabled), |
|
|
enabled: String(item.enabled), |
|
|
status: String(item.enabled) === "1" ? "normal" : "abnormal" |
|
|
status: String(item.enabled) === "1" ? "normal" : "abnormal" |
|
|
})); |
|
|
})); |
|
|
}).catch(() => { |
|
|
|
|
|
|
|
|
}).catch((error) => { |
|
|
|
|
|
if (this.isPortalCancel(error)) return; |
|
|
this.stationList = []; |
|
|
this.stationList = []; |
|
|
}); |
|
|
}); |
|
|
}, |
|
|
}, |
|
|
@ -519,7 +542,7 @@ export default { |
|
|
const params = {}; |
|
|
const params = {}; |
|
|
const station = this.stationList.find(item => String(item.id) === String(this.videoStationId)); |
|
|
const station = this.stationList.find(item => String(item.id) === String(this.videoStationId)); |
|
|
if (station && station.code) params.stationCode = station.code; |
|
|
if (station && station.code) params.stationCode = station.code; |
|
|
return getChannelStats(params).then(response => { |
|
|
|
|
|
|
|
|
return getChannelStats(params, this.portalRequestConfig()).then(response => { |
|
|
const data = response && response.data ? response.data : {}; |
|
|
const data = response && response.data ? response.data : {}; |
|
|
const counts = Array.isArray(data.deviceSourceCounts) ? data.deviceSourceCounts : []; |
|
|
const counts = Array.isArray(data.deviceSourceCounts) ? data.deviceSourceCounts : []; |
|
|
const cameraCount = { ...EMPTY_CAMERA, total: toCount(data.totalCount) }; |
|
|
const cameraCount = { ...EMPTY_CAMERA, total: toCount(data.totalCount) }; |
|
|
@ -532,19 +555,21 @@ export default { |
|
|
else cameraCount.security += count; |
|
|
else cameraCount.security += count; |
|
|
}); |
|
|
}); |
|
|
this.cameraCount = cameraCount; |
|
|
this.cameraCount = cameraCount; |
|
|
}).catch(() => { |
|
|
|
|
|
|
|
|
}).catch((error) => { |
|
|
|
|
|
if (this.isPortalCancel(error)) return; |
|
|
this.cameraCount = { ...EMPTY_CAMERA }; |
|
|
this.cameraCount = { ...EMPTY_CAMERA }; |
|
|
}); |
|
|
}); |
|
|
}, |
|
|
}, |
|
|
loadVideoChannels() { |
|
|
loadVideoChannels() { |
|
|
return getChannelTree({ onlineStatus: "1", patroldeviceTypeFlag: "ipc" }).then(response => { |
|
|
|
|
|
|
|
|
return getChannelTree({ onlineStatus: "1", patroldeviceTypeFlag: "ipc" }, { skipWorkspaceStation: true, ...this.portalRequestConfig() }).then(response => { |
|
|
const nodes = flattenNodes(Array.isArray(response && response.data) ? response.data : []); |
|
|
const nodes = flattenNodes(Array.isArray(response && response.data) ? response.data : []); |
|
|
const videos = nodes |
|
|
const videos = nodes |
|
|
.filter(node => node.channelFlag && (String(node.patroldeviceOnlineStatus || "1") === "1")) |
|
|
.filter(node => node.channelFlag && (String(node.patroldeviceOnlineStatus || "1") === "1")) |
|
|
.map(this.normalizePortalVideo); |
|
|
.map(this.normalizePortalVideo); |
|
|
this.portalChannels = videos; |
|
|
this.portalChannels = videos; |
|
|
return this.replacePortalVideos(videos); |
|
|
return this.replacePortalVideos(videos); |
|
|
}).catch(() => { |
|
|
|
|
|
|
|
|
}).catch((error) => { |
|
|
|
|
|
if (this.isPortalCancel(error)) return; |
|
|
this.portalChannels = []; |
|
|
this.portalChannels = []; |
|
|
return this.replacePortalVideos([]); |
|
|
return this.replacePortalVideos([]); |
|
|
}); |
|
|
}); |
|
|
@ -563,6 +588,8 @@ export default { |
|
|
}, |
|
|
}, |
|
|
replacePortalVideos(videos) { |
|
|
replacePortalVideos(videos) { |
|
|
return this.stopPortalVideos().then(() => { |
|
|
return this.stopPortalVideos().then(() => { |
|
|
|
|
|
// 切页后停流才返回时,不能再把播放器挂回去。 |
|
|
|
|
|
if (!this.portalAlive) return; |
|
|
this.videoList = (videos || []).slice(0, 4); |
|
|
this.videoList = (videos || []).slice(0, 4); |
|
|
this.activeVideoIndex = 0; |
|
|
this.activeVideoIndex = 0; |
|
|
this.$nextTick(() => this.startDefaultPortalVideos()); |
|
|
this.$nextTick(() => this.startDefaultPortalVideos()); |
|
|
@ -574,19 +601,25 @@ export default { |
|
|
}); |
|
|
}); |
|
|
}, |
|
|
}, |
|
|
async startPortalVideo(video, index) { |
|
|
async startPortalVideo(video, index) { |
|
|
if (!video || !video.stationCode || !video.cameraCode) return; |
|
|
|
|
|
|
|
|
if (!this.portalAlive || !video || !video.stationCode || !video.cameraCode) return; |
|
|
this.clearPortalStartTimer(index); |
|
|
this.clearPortalStartTimer(index); |
|
|
|
|
|
this.cancelPortalStart(index); |
|
|
this.destroyPortalPlayer(index); |
|
|
this.destroyPortalPlayer(index); |
|
|
this.$set(video, "loading", true); |
|
|
this.$set(video, "loading", true); |
|
|
this.$set(video, "playbackError", false); |
|
|
this.$set(video, "playbackError", false); |
|
|
this.$set(video, "playUrl", ""); |
|
|
this.$set(video, "playUrl", ""); |
|
|
this.$set(video, "flvUrl", ""); |
|
|
this.$set(video, "flvUrl", ""); |
|
|
|
|
|
const request = startRealtimeVideo({ |
|
|
|
|
|
stationCode: video.stationCode, |
|
|
|
|
|
cameraCode: video.cameraCode |
|
|
|
|
|
}); |
|
|
|
|
|
this.$set(this.portalStartRequests, index, request); |
|
|
try { |
|
|
try { |
|
|
const response = await startRealtimeVideo({ |
|
|
|
|
|
stationCode: video.stationCode, |
|
|
|
|
|
cameraCode: video.cameraCode |
|
|
|
|
|
}); |
|
|
|
|
|
if (this.videoList[index] !== video) return; |
|
|
|
|
|
|
|
|
const response = await request; |
|
|
|
|
|
if (this.portalStartRequests[index] === request) { |
|
|
|
|
|
this.$delete(this.portalStartRequests, index); |
|
|
|
|
|
} |
|
|
|
|
|
if (!this.portalAlive || this.videoList[index] !== video) return; |
|
|
const stream = pickPortalStream(response); |
|
|
const stream = pickPortalStream(response); |
|
|
this.$set(video, "streamId", stream.streamId); |
|
|
this.$set(video, "streamId", stream.streamId); |
|
|
this.$set(video, "playUrl", stream.hlsUrl); |
|
|
this.$set(video, "playUrl", stream.hlsUrl); |
|
|
@ -598,6 +631,11 @@ export default { |
|
|
} |
|
|
} |
|
|
this.$nextTick(() => this.attachPortalPlayer(video, index)); |
|
|
this.$nextTick(() => this.attachPortalPlayer(video, index)); |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
|
|
|
if (this.portalStartRequests[index] === request) { |
|
|
|
|
|
this.$delete(this.portalStartRequests, index); |
|
|
|
|
|
} |
|
|
|
|
|
// 切页取消的请求不要再改状态,组件可能已经销毁。 |
|
|
|
|
|
if (!this.portalAlive || (error && error.code === "ERR_CANCELED")) return; |
|
|
this.$set(video, "loading", false); |
|
|
this.$set(video, "loading", false); |
|
|
this.$set(video, "playbackError", true); |
|
|
this.$set(video, "playbackError", true); |
|
|
} |
|
|
} |
|
|
@ -605,10 +643,14 @@ export default { |
|
|
attachPortalPlayer(video, index) { |
|
|
attachPortalPlayer(video, index) { |
|
|
const ref = this.$refs[`portalVideo${index}`]; |
|
|
const ref = this.$refs[`portalVideo${index}`]; |
|
|
const element = Array.isArray(ref) ? ref[0] : ref; |
|
|
const element = Array.isArray(ref) ? ref[0] : ref; |
|
|
if (!video || this.videoList[index] !== video) return; |
|
|
|
|
|
|
|
|
if (!this.portalAlive || !video || this.videoList[index] !== video) return; |
|
|
if (!element) { |
|
|
if (!element) { |
|
|
|
|
|
// 切走时视频节点会被拆掉。这里再排一次 nextTick,离开后会一直空转。 |
|
|
this.$nextTick(() => { |
|
|
this.$nextTick(() => { |
|
|
if (this.videoList[index] === video) this.attachPortalPlayer(video, index); |
|
|
|
|
|
|
|
|
if (!this.portalAlive || this.videoList[index] !== video) return; |
|
|
|
|
|
const retryRef = this.$refs[`portalVideo${index}`]; |
|
|
|
|
|
const retryElement = Array.isArray(retryRef) ? retryRef[0] : retryRef; |
|
|
|
|
|
if (retryElement) this.attachPortalPlayer(video, index); |
|
|
}); |
|
|
}); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
@ -763,10 +805,22 @@ export default { |
|
|
window.clearTimeout(this.portalStartTimers[index]); |
|
|
window.clearTimeout(this.portalStartTimers[index]); |
|
|
this.$delete(this.portalStartTimers, index); |
|
|
this.$delete(this.portalStartTimers, index); |
|
|
}, |
|
|
}, |
|
|
|
|
|
cancelPortalStart(index) { |
|
|
|
|
|
const request = this.portalStartRequests[index]; |
|
|
|
|
|
if (!request) return; |
|
|
|
|
|
this.$delete(this.portalStartRequests, index); |
|
|
|
|
|
if (typeof request.cancel === "function") request.cancel(); |
|
|
|
|
|
}, |
|
|
|
|
|
cancelPortalStarts() { |
|
|
|
|
|
Object.keys(this.portalStartRequests).forEach(index => this.cancelPortalStart(index)); |
|
|
|
|
|
}, |
|
|
stopPortalVideo(video, index) { |
|
|
stopPortalVideo(video, index) { |
|
|
this.clearPortalStartTimer(index); |
|
|
this.clearPortalStartTimer(index); |
|
|
|
|
|
this.cancelPortalStart(index); |
|
|
this.destroyPortalPlayer(index); |
|
|
this.destroyPortalPlayer(index); |
|
|
|
|
|
// 拉流还没成功就切走时,没有可停的流。再发 stop 只会继续占浏览器连接。 |
|
|
if (!video || !video.stationCode || !video.cameraCode) return Promise.resolve(null); |
|
|
if (!video || !video.stationCode || !video.cameraCode) return Promise.resolve(null); |
|
|
|
|
|
if (!video.streamId && !video.playUrl && !video.flvUrl) return Promise.resolve(null); |
|
|
return stopRealtimeVideo({ stationCode: video.stationCode, cameraCode: video.cameraCode }).catch(() => null); |
|
|
return stopRealtimeVideo({ stationCode: video.stationCode, cameraCode: video.cameraCode }).catch(() => null); |
|
|
}, |
|
|
}, |
|
|
stopPortalVideos() { |
|
|
stopPortalVideos() { |
|
|
@ -801,7 +855,7 @@ export default { |
|
|
loadInspectionOverview() { |
|
|
loadInspectionOverview() { |
|
|
const requestSerial = ++this.overviewRequestSerial; |
|
|
const requestSerial = ++this.overviewRequestSerial; |
|
|
const params = this.overviewStationId ? { stationCode: this.overviewStationId } : {}; |
|
|
const params = this.overviewStationId ? { stationCode: this.overviewStationId } : {}; |
|
|
return getInspectionOverview(params).then(response => { |
|
|
|
|
|
|
|
|
return getInspectionOverview(params, this.portalRequestConfig()).then(response => { |
|
|
if (requestSerial !== this.overviewRequestSerial) return; |
|
|
if (requestSerial !== this.overviewRequestSerial) return; |
|
|
const rows = Array.isArray(response && response.data) ? response.data : []; |
|
|
const rows = Array.isArray(response && response.data) ? response.data : []; |
|
|
const rowByCategory = rows.reduce((result, item) => { |
|
|
const rowByCategory = rows.reduce((result, item) => { |
|
|
@ -823,7 +877,8 @@ export default { |
|
|
importantDefects: toCount(item.seriousCount) + toCount(item.urgentCount) |
|
|
importantDefects: toCount(item.seriousCount) + toCount(item.urgentCount) |
|
|
}; |
|
|
}; |
|
|
}); |
|
|
}); |
|
|
}).catch(() => { |
|
|
|
|
|
|
|
|
}).catch((error) => { |
|
|
|
|
|
if (this.isPortalCancel(error)) return; |
|
|
if (requestSerial === this.overviewRequestSerial) { |
|
|
if (requestSerial === this.overviewRequestSerial) { |
|
|
this.overviewList = createEmptyOverview(); |
|
|
this.overviewList = createEmptyOverview(); |
|
|
} |
|
|
} |
|
|
@ -836,7 +891,7 @@ export default { |
|
|
dateType: this.trendGranularity |
|
|
dateType: this.trendGranularity |
|
|
}; |
|
|
}; |
|
|
if (this.trendStationId) params.stationCode = this.trendStationId; |
|
|
if (this.trendStationId) params.stationCode = this.trendStationId; |
|
|
return getDefectTrend(params).then(response => { |
|
|
|
|
|
|
|
|
return getDefectTrend(params, this.portalRequestConfig()).then(response => { |
|
|
if (requestSerial !== this.trendRequestSerial) return; |
|
|
if (requestSerial !== this.trendRequestSerial) return; |
|
|
const rows = (Array.isArray(response && response.data) ? response.data : []) |
|
|
const rows = (Array.isArray(response && response.data) ? response.data : []) |
|
|
.slice() |
|
|
.slice() |
|
|
@ -850,7 +905,8 @@ export default { |
|
|
{ name: "重大缺陷", data: rows.map(item => toCount(item.urgentCount)) } |
|
|
{ name: "重大缺陷", data: rows.map(item => toCount(item.urgentCount)) } |
|
|
] |
|
|
] |
|
|
}; |
|
|
}; |
|
|
}).catch(() => { |
|
|
|
|
|
|
|
|
}).catch((error) => { |
|
|
|
|
|
if (this.isPortalCancel(error)) return; |
|
|
if (requestSerial === this.trendRequestSerial) { |
|
|
if (requestSerial === this.trendRequestSerial) { |
|
|
this.trend = createEmptyTrend(); |
|
|
this.trend = createEmptyTrend(); |
|
|
} |
|
|
} |
|
|
@ -897,15 +953,28 @@ export default { |
|
|
this.defectCarouselTimer = null; |
|
|
this.defectCarouselTimer = null; |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
releasePortalPage() { |
|
|
|
|
|
// 点击切换和组件销毁都会走到这里,只释放一次,避免停流发两遍。 |
|
|
|
|
|
if (this.portalReleased) return; |
|
|
|
|
|
this.portalReleased = true; |
|
|
|
|
|
// 先标记页面已离开,再取消拉流。否则慢返回的 start 还会去挂播放器。 |
|
|
|
|
|
this.portalAlive = false; |
|
|
|
|
|
this.pauseDefectCarousel(); |
|
|
|
|
|
this.cancelPortalRequests(); |
|
|
|
|
|
this.cancelPortalStarts(); |
|
|
|
|
|
this.stopPortalVideos(); |
|
|
|
|
|
}, |
|
|
onNavClick(item) { |
|
|
onNavClick(item) { |
|
|
if (item.key === "home") { |
|
|
if (item.key === "home") { |
|
|
this.$router.push("/portal"); |
|
|
|
|
|
|
|
|
if (this.$route.path !== "/portal") this.$router.push("/portal").catch(() => undefined); |
|
|
} else if (item.key === "patrol") { |
|
|
} else if (item.key === "patrol") { |
|
|
// 动环巡检:进入单站系统(即原后台,默认当前部署站的首页), |
|
|
// 动环巡检:进入单站系统(即原后台,默认当前部署站的首页), |
|
|
// 打上来源标记后单站内保留 module-subbar 横向导航,便于返回门户 |
|
|
// 打上来源标记后单站内保留 module-subbar 横向导航,便于返回门户 |
|
|
this.$store.dispatch("siteContext/markFromPortal"); |
|
|
this.$store.dispatch("siteContext/markFromPortal"); |
|
|
this.$store.dispatch("siteContext/exitSite"); |
|
|
this.$store.dispatch("siteContext/exitSite"); |
|
|
this.$router.push("/index"); |
|
|
|
|
|
|
|
|
// 先把首页还没回来的请求清掉,再跳转。否则拉流会占住浏览器连接,动环巡检的列表一直 pending。 |
|
|
|
|
|
this.releasePortalPage(); |
|
|
|
|
|
this.$router.push("/index").catch(() => undefined); |
|
|
} else if (item.key === "system") { |
|
|
} else if (item.key === "system") { |
|
|
this.$router.push("/system/user"); |
|
|
this.$router.push("/system/user"); |
|
|
} |
|
|
} |
|
|
|