feat: 分屏展示
This commit is contained in:
parent
66c451122b
commit
5b6e0611c4
|
@ -35,8 +35,12 @@ export default {
|
||||||
// 更改国标ID
|
// 更改国标ID
|
||||||
updateGbChannelId: (id: string, data: any): any => server.put(`/media/gb28181-cascade/binding/${id}`, data),
|
updateGbChannelId: (id: string, data: any): any => server.put(`/media/gb28181-cascade/binding/${id}`, data),
|
||||||
// 查询通道分页列表
|
// 查询通道分页列表
|
||||||
queryChannelList: (data: any): any => server.post(`media/channel/_query`, data),
|
queryChannelList: (data: any): any => server.post(`/media/channel/_query`, data),
|
||||||
// 推送
|
// 推送
|
||||||
publish: (id: string, params: any) => server.get(`/media/gb28181-cascade/${id}/bindings/publish`, params)
|
publish: (id: string, params: any) => server.get(`/media/gb28181-cascade/${id}/bindings/publish`, params),
|
||||||
|
|
||||||
|
// 分屏展示接口
|
||||||
|
// 设备树
|
||||||
|
getMediaTree: (data?: any) => server.post<any>(`/media/device/_query/no-paging`, data),
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,400 @@
|
||||||
|
<!-- 分屏组件 -->
|
||||||
|
<template>
|
||||||
|
<div class="live-player-warp">
|
||||||
|
<div class="live-player-content">
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<div class="player-screen-tool" v-if="showScreen">
|
||||||
|
<a-radio-group
|
||||||
|
v-model:value="screen"
|
||||||
|
button-style="solid"
|
||||||
|
@change="handleScreenChange"
|
||||||
|
>
|
||||||
|
<a-radio-button :value="1">单屏</a-radio-button>
|
||||||
|
<a-radio-button :value="4">四分屏</a-radio-button>
|
||||||
|
<a-radio-button :value="9">九分屏</a-radio-button>
|
||||||
|
<a-radio-button :value="0">全屏</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<div class="screen-tool-save">
|
||||||
|
<a-tooltip title="可保存分屏配置记录">
|
||||||
|
<AIcon type="QuestionCircleOutlined" />
|
||||||
|
</a-tooltip>
|
||||||
|
<a-popover
|
||||||
|
v-model:visible="visible"
|
||||||
|
trigger="click"
|
||||||
|
title="分屏名称"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
name="name"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入名称',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 64,
|
||||||
|
message: '最多可输入64个字符',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="formData.name" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="saveHistory"
|
||||||
|
:loading="loading"
|
||||||
|
style="width: 100%; margin-top: 16px"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</a-button>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
<a-dropdown-button
|
||||||
|
type="primary"
|
||||||
|
@click="visible = true"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<a-empty v-if="!historyList.length" />
|
||||||
|
<a-menu-item
|
||||||
|
v-for="(item, index) in historyList"
|
||||||
|
:key="`his${index}`"
|
||||||
|
@click="handleHistory(item)"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
<a-popconfirm
|
||||||
|
title="确认删除?"
|
||||||
|
ok-text="确认"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="(e: any) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
deleteHistory(item.key);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
@click="
|
||||||
|
(e:any) =>
|
||||||
|
e?.stopPropagation()
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown-button>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 播放器 -->
|
||||||
|
<div class="player-body">
|
||||||
|
<div
|
||||||
|
ref="fullscreenRef"
|
||||||
|
class="player-screen"
|
||||||
|
:class="`screen-${screen}`"
|
||||||
|
>
|
||||||
|
<template v-for="(item, index) in players" :key="item.key">
|
||||||
|
<div
|
||||||
|
class="player-screen-item"
|
||||||
|
:class="{
|
||||||
|
active:
|
||||||
|
showScreen &&
|
||||||
|
playerActive === index &&
|
||||||
|
!isFullscreen,
|
||||||
|
'full-screen': isFullscreen,
|
||||||
|
}"
|
||||||
|
:style="{ display: item.show ? 'block' : 'none' }"
|
||||||
|
@click="playerActive = index"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="media-btn-refresh"
|
||||||
|
:style="{
|
||||||
|
display: item.url ? 'block' : 'none',
|
||||||
|
}"
|
||||||
|
@click="handleRefresh($event, item, index)"
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</div>
|
||||||
|
<LivePlayer :src="item.url" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 控制器 -->
|
||||||
|
</div>
|
||||||
|
<MediaTool @onMouseDown="handleMouseDown" @onMouseUp="handleMouseUp" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useFullscreen } from '@vueuse/core';
|
||||||
|
import {
|
||||||
|
deleteSearchHistory,
|
||||||
|
getSearchHistory,
|
||||||
|
saveSearchHistory,
|
||||||
|
} from '@/api/comm';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import LivePlayer from '@/components/Player/index.vue';
|
||||||
|
import MediaTool from '@/components/Player/mediaTool.vue';
|
||||||
|
|
||||||
|
type Player = {
|
||||||
|
id?: string;
|
||||||
|
url?: string;
|
||||||
|
channelId?: string;
|
||||||
|
key: string;
|
||||||
|
show: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ScreenProps {
|
||||||
|
url?: string;
|
||||||
|
id?: string;
|
||||||
|
channelId: string;
|
||||||
|
className?: string;
|
||||||
|
historyHandle?: (deviceId: string, channelId: string) => string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id 当前选中播发视频ID
|
||||||
|
* @param type 当前操作动作
|
||||||
|
*/
|
||||||
|
onMouseDown?: (deviceId: string, channelId: string, type: string) => void;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id 当前选中播发视频ID
|
||||||
|
* @param type 当前操作动作
|
||||||
|
*/
|
||||||
|
onMouseUp?: (deviceId: string, channelId: string, type: string) => void;
|
||||||
|
showScreen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<ScreenProps>();
|
||||||
|
|
||||||
|
const DEFAULT_SAVE_CODE = 'screen-save';
|
||||||
|
|
||||||
|
const screen = ref(1);
|
||||||
|
const players = ref<Player[]>([]);
|
||||||
|
const playerActive = ref(0);
|
||||||
|
const historyList = ref<any[]>([]);
|
||||||
|
const visible = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const fullscreenRef = ref(null);
|
||||||
|
const { isFullscreen, enter, exit, toggle } = useFullscreen();
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const formData = ref({
|
||||||
|
name: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const reloadPlayer = (
|
||||||
|
id: string,
|
||||||
|
channelId: string,
|
||||||
|
url: string,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
const olPlayers = [...players.value];
|
||||||
|
olPlayers[index] = {
|
||||||
|
id: '',
|
||||||
|
channelId: '',
|
||||||
|
url: '',
|
||||||
|
key: olPlayers[index].key,
|
||||||
|
show: true,
|
||||||
|
};
|
||||||
|
const newPlayer = {
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
channelId,
|
||||||
|
key: olPlayers[index].key,
|
||||||
|
show: true,
|
||||||
|
};
|
||||||
|
players.value = [...olPlayers];
|
||||||
|
setTimeout(() => {
|
||||||
|
olPlayers[index] = newPlayer;
|
||||||
|
players.value = [...olPlayers];
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceVideo = (id: string, channelId: string, url: string) => {
|
||||||
|
const olPlayers = [...players.value];
|
||||||
|
const newPlayer = {
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
channelId,
|
||||||
|
key: olPlayers[playerActive.value].key,
|
||||||
|
show: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (olPlayers[playerActive.value].url === url) {
|
||||||
|
// 刷新视频
|
||||||
|
reloadPlayer(id, channelId, url, playerActive.value);
|
||||||
|
} else {
|
||||||
|
olPlayers[playerActive.value] = newPlayer;
|
||||||
|
players.value = olPlayers;
|
||||||
|
}
|
||||||
|
if (playerActive.value === screen.value - 1) {
|
||||||
|
// 当前位置为分屏最后一位
|
||||||
|
playerActive.value = 0;
|
||||||
|
} else {
|
||||||
|
playerActive.value += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHistory = (item: any) => {
|
||||||
|
if (props.historyHandle) {
|
||||||
|
const log = JSON.parse(item.content || '{}');
|
||||||
|
screen.value = log.screen;
|
||||||
|
const oldPlayers = [...players.value];
|
||||||
|
|
||||||
|
players.value = oldPlayers.map((oldPlayer, index) => {
|
||||||
|
oldPlayer.show = false;
|
||||||
|
if (index < log.screen) {
|
||||||
|
const { deviceId, channelId } = log.players[index];
|
||||||
|
return {
|
||||||
|
...oldPlayer,
|
||||||
|
id: deviceId,
|
||||||
|
channelId: deviceId,
|
||||||
|
url: deviceId
|
||||||
|
? props.historyHandle!(deviceId, channelId)
|
||||||
|
: '',
|
||||||
|
show: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return oldPlayer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHistory = async () => {
|
||||||
|
const res = await getSearchHistory(DEFAULT_SAVE_CODE);
|
||||||
|
if (res.success) {
|
||||||
|
historyList.value = res.result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const deleteHistory = async (id: string) => {
|
||||||
|
const res = await deleteSearchHistory(DEFAULT_SAVE_CODE, id);
|
||||||
|
if (res.success) {
|
||||||
|
getHistory();
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveHistory = async () => {
|
||||||
|
formRef.value
|
||||||
|
.validate()
|
||||||
|
.then(async () => {
|
||||||
|
const param = {
|
||||||
|
name: formData.value.name,
|
||||||
|
content: JSON.stringify({
|
||||||
|
screen: screen,
|
||||||
|
players: players.value.map((item: any) => ({
|
||||||
|
deviceId: item.id,
|
||||||
|
channelId: item.channelId,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
const res = await saveSearchHistory(param, DEFAULT_SAVE_CODE);
|
||||||
|
loading.value = false;
|
||||||
|
if (res.success) {
|
||||||
|
visible.value = false;
|
||||||
|
getHistory();
|
||||||
|
message.success('保存成功');
|
||||||
|
} else {
|
||||||
|
message.error('保存失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const mediaInit = () => {
|
||||||
|
const newArr = [];
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
newArr.push({
|
||||||
|
id: '',
|
||||||
|
channelId: '',
|
||||||
|
url: '',
|
||||||
|
key: 'time_' + new Date().getTime() + i,
|
||||||
|
show: i === 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
players.value = newArr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScreenChange = (e: any) => {
|
||||||
|
if (e.target.value) {
|
||||||
|
screenChange(e.target.value);
|
||||||
|
} else {
|
||||||
|
// 全屏操作
|
||||||
|
toggle();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const screenChange = (index: number) => {
|
||||||
|
players.value = players.value.map((m: any, i: number) => ({
|
||||||
|
id: '',
|
||||||
|
channelId: '',
|
||||||
|
url: '',
|
||||||
|
updateTime: 0,
|
||||||
|
key: m.key,
|
||||||
|
show: i < index,
|
||||||
|
}));
|
||||||
|
playerActive.value = 0;
|
||||||
|
screen.value = index;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = (e: any, item: any, index: number) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (item.url) {
|
||||||
|
reloadPlayer(item.id!, item.channelId!, item.url!, index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击控制按钮
|
||||||
|
* @param type 控制类型
|
||||||
|
*/
|
||||||
|
const handleMouseDown = (type: string) => {
|
||||||
|
const { id, channelId } = players.value[playerActive.value];
|
||||||
|
console.log('players.value: ', players.value);
|
||||||
|
console.log('playerActive.value: ', playerActive.value);
|
||||||
|
console.log('id: ', id);
|
||||||
|
console.log('channelId: ', channelId);
|
||||||
|
if (id && channelId && props.onMouseDown) {
|
||||||
|
props.onMouseDown(id, channelId, type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleMouseUp = (type: string) => {
|
||||||
|
const { id, channelId } = players.value[playerActive.value];
|
||||||
|
if (id && channelId && props.onMouseUp) {
|
||||||
|
props.onMouseUp(id, channelId, type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.url,
|
||||||
|
(val) => {
|
||||||
|
if (val && props.id) {
|
||||||
|
replaceVideo(props.id, props.channelId, val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (props.showScreen !== false) {
|
||||||
|
getHistory();
|
||||||
|
}
|
||||||
|
mediaInit();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import './index.less';
|
||||||
|
</style>
|
|
@ -0,0 +1,82 @@
|
||||||
|
.live-player-warp {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.live-player-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.player-screen-tool {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
height: auto;
|
||||||
|
padding: 4px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-body {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.player-screen {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.screen-1 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.screen-4 {
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.screen-9 {
|
||||||
|
grid-template-rows: 1fr 1fr 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.screen-4,
|
||||||
|
&.screen-9 {
|
||||||
|
grid-gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border: 2px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-screen {
|
||||||
|
border: 1px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-screen-item {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.media-btn-refresh {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #bfbfbf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import vue3videoPlay from 'vue3-video-play';
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
src: { type: String, default: '' },
|
src: { type: String, default: '' },
|
||||||
type: { type: String, default: 'mp4' },
|
type: { type: String, default: 'mp4' },
|
||||||
|
width: { type: String, default: '500px' },
|
||||||
|
height: { type: String, default: '280px' },
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -21,8 +23,6 @@ watch(
|
||||||
|
|
||||||
const options = reactive({
|
const options = reactive({
|
||||||
...props,
|
...props,
|
||||||
width: '500px', //播放器高度
|
|
||||||
height: '280px', //播放器高度
|
|
||||||
color: '#409eff', //主题色
|
color: '#409eff', //主题色
|
||||||
title: '', //视频名称
|
title: '', //视频名称
|
||||||
// src: props.src,
|
// src: props.src,
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
@padding: 20px;
|
||||||
|
|
||||||
|
.split-screen {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.left-content {
|
||||||
|
width: 300px;
|
||||||
|
padding-right: @padding;
|
||||||
|
border-right: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
.online {
|
||||||
|
// color: @success-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
// color: @disabled-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-search {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-left: @padding;
|
||||||
|
|
||||||
|
.top {
|
||||||
|
display: flex;
|
||||||
|
flex-basis: 30px;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.live-player {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.live-player-content {
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.player-screen {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
&.screen-1 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.screen-4 {
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.screen-9 {
|
||||||
|
grid-template-rows: 1fr 1fr 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.screen-4,
|
||||||
|
&.screen-9 {
|
||||||
|
grid-gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-tools {
|
||||||
|
flex-basis: 280px;
|
||||||
|
padding: 50px 12px 0 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen {
|
||||||
|
@media (min-width: 1300px) {
|
||||||
|
.split-screen {
|
||||||
|
.left-content {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<a-card class="splitScreen">
|
||||||
|
<div class="split-screen">
|
||||||
|
<LeftTree @onSelect="mediaStart" />
|
||||||
|
<div class="right-content">
|
||||||
|
<ScreenPlayer
|
||||||
|
ref="player"
|
||||||
|
:id="deviceId"
|
||||||
|
:channelId="channelId"
|
||||||
|
:onMouseUp="(id, cId) => channelApi.ptzStop(id, cId)"
|
||||||
|
:onMouseDown="
|
||||||
|
(id, cId, type) => channelApi.ptzTool(id, cId, type)
|
||||||
|
"
|
||||||
|
:historyHandle="(dId, cId) => getMediaUrl(dId, cId)"
|
||||||
|
showScreen
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import LeftTree from './tree.vue';
|
||||||
|
import channelApi from '@/api/media/channel';
|
||||||
|
import ScreenPlayer from '@/components/Player/ScreenPlayer.vue';
|
||||||
|
|
||||||
|
const deviceId = ref('');
|
||||||
|
const channelId = ref('');
|
||||||
|
const player = ref();
|
||||||
|
|
||||||
|
const getMediaUrl = (dId: string, cId: string): string => {
|
||||||
|
return channelApi.ptzStart(dId, cId, 'mp4');
|
||||||
|
};
|
||||||
|
|
||||||
|
const mediaStart = (e: { cId: string; dId: string }) => {
|
||||||
|
channelId.value = e.cId;
|
||||||
|
deviceId.value = e.dId;
|
||||||
|
player.value?.replaceVideo(e.dId, e.cId, getMediaUrl(e.dId, e.cId));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import './index.less';
|
||||||
|
</style>
|
|
@ -0,0 +1,149 @@
|
||||||
|
<template>
|
||||||
|
<div class="left-content">
|
||||||
|
<a-tree
|
||||||
|
:height="700"
|
||||||
|
:show-line="true"
|
||||||
|
:show-icon="true"
|
||||||
|
:tree-data="treeData"
|
||||||
|
:loadData="onLoadData"
|
||||||
|
@select="onSelect"
|
||||||
|
></a-tree>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import cascadeApi from '@/api/media/cascade';
|
||||||
|
|
||||||
|
type Emits = {
|
||||||
|
(e: 'onSelect', data: { dId: string; cId: string }): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
interface DataNode {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
isLeaf?: boolean;
|
||||||
|
channelNumber?: number;
|
||||||
|
icon?: any;
|
||||||
|
status: {
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
children?: DataNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelect = (_: any, { node }: any) => {
|
||||||
|
emit('onSelect', { dId: node.deviceId, cId: node.channelId });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为子节点
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
|
const isLeaf = (node: any): boolean => {
|
||||||
|
if (node.channelNumber) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备列表
|
||||||
|
*/
|
||||||
|
const treeData = ref<any[]>([]);
|
||||||
|
const getDeviceList = async () => {
|
||||||
|
const res = await cascadeApi.getMediaTree({ paging: false });
|
||||||
|
if (res.success) {
|
||||||
|
treeData.value = res.result.map((m: any) => {
|
||||||
|
const extra: any = {};
|
||||||
|
extra.isLeaf = isLeaf(m);
|
||||||
|
return {
|
||||||
|
...m,
|
||||||
|
...extra,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getDeviceList();
|
||||||
|
|
||||||
|
const updateTreeData = (
|
||||||
|
list: DataNode[],
|
||||||
|
key: any,
|
||||||
|
children: DataNode[],
|
||||||
|
): DataNode[] => {
|
||||||
|
return list.map((node) => {
|
||||||
|
if (node.id === key) {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
children: node.children
|
||||||
|
? [...node.children, ...children]
|
||||||
|
: children,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
children: updateTreeData(node.children, key, children),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子节点
|
||||||
|
* @param key
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
const getChildren = (key: any, params: any): Promise<any> => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
const res = await cascadeApi.queryChannelList(params);
|
||||||
|
if (res.status === 200) {
|
||||||
|
const { total, pageIndex, pageSize } = res.result;
|
||||||
|
treeData.value = updateTreeData(
|
||||||
|
treeData.value,
|
||||||
|
key,
|
||||||
|
res.result.data.map((item: DataNode) => ({
|
||||||
|
...item,
|
||||||
|
// icon: (<AIcon type="VideoCameraOutlined" className={item.status.value}/>),
|
||||||
|
isLeaf: isLeaf(item),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (total > (pageIndex + 1) * pageSize) {
|
||||||
|
setTimeout(() => {
|
||||||
|
getChildren(key, {
|
||||||
|
...params,
|
||||||
|
pageIndex: params.pageIndex + 1,
|
||||||
|
});
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
resolve(res.result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLoadData = ({ key, children }: any): Promise<void> => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
if (children) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await getChildren(key, {
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 100,
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'deviceId',
|
||||||
|
value: key,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
Loading…
Reference in New Issue