fix: bug#17651、17633、视频分享点位bug及样式

* fix: bug#17651、17633、视频分享点位bug及样式
This commit is contained in:
qiaochuLei 2023-08-24 18:04:15 +08:00 committed by GitHub
parent fe3b636a78
commit 02eb16d0eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 421 additions and 45 deletions

View File

@ -35,7 +35,7 @@ type PlayerProps = {
updateTime?: number; updateTime?: number;
key?: string | number; key?: string | number;
loading?: boolean; loading?: boolean;
protocol?: 'mp4' | 'flv' | 'hls'; protocol?: 'mp4' | 'flv' | 'hls' | 'rtc';
onDestroy?: (e?: any) => void; onDestroy?: (e?: any) => void;
onMessage?: (msg: any) => void; onMessage?: (msg: any) => void;
onError?: (err: any) => void; onError?: (err: any) => void;

View File

@ -150,43 +150,50 @@ const showNotification = (message: string, description: string, key?: string, sh
* @returns {Promise<never>} * @returns {Promise<never>}
*/ */
const errorHandler = (error: any) => { const errorHandler = (error: any) => {
if (error.response) { if (error.response) {
const data = error.response.data const data = error.response.data
const status = error.response.status const status = error.response.status
if (data?.code === 'license required') { if(data instanceof Blob){
Modal.error({ data.text().then((res)=>{
key: 'License', showNotification(error.message, (JSON.parse(res).message + '').substr(0,90))
title: 'License已到期或者错误',
content: h(
'a',
{
onClick: () =>
{
Modal.destroyAll?.();
window.location.href = '/#/init-license';
}
},
'请更新License'
)
}) })
} else if (status === 403) { }else{
showNotification('Forbidden', (data.message + '').substr(0, 90), '403') if (data?.code === 'license required') {
} else if (status === 500) { Modal.error({
showNotification('Server Side Error', (data.message + '').substr(0, 90), '500') key: 'License',
} else if (status === 400) { title: 'License已到期或者错误',
showNotification('Request Error', (data.message + '').substr(0, 90), '400') content: h(
} else if (status === 401) { 'a',
showNotification('Unauthorized', '用户未登录', '401') {
setTimeout(() => { onClick: () =>
cleanToken() {
router.replace({ Modal.destroyAll?.();
path: LoginPath window.location.href = '/#/init-license';
}) }
}, 0) },
} else if (status === 404) { '请更新License'
const data = error?.response?.data )
const message = error?.response?.data?.message || `${data?.error} ${data?.path}` })
showNotification(error?.code, message, '404') } else if (status === 403) {
showNotification('Forbidden', (data.message + '').substr(0, 90), '403')
} else if (status === 500) {
showNotification('Server Side Error', (data.message + '').substr(0, 90), '500')
} else if (status === 400) {
showNotification('Request Error', (data.message + '').substr(0, 90), '400')
} else if (status === 401) {
showNotification('Unauthorized', '用户未登录', '401')
setTimeout(() => {
cleanToken()
router.replace({
path: LoginPath
})
}, 0)
} else if (status === 404) {
const data = error?.response?.data
const message = error?.response?.data?.message || `${data?.error} ${data?.path}`
showNotification(error?.code, message, '404')
}
} }
} else if (error.response === undefined) { } else if (error.response === undefined) {
if (error.message.includes('timeout')) { if (error.message.includes('timeout')) {

View File

@ -49,6 +49,7 @@
:hasPermission="`link/AccessConfig:${ :hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update' id === ':id' ? 'add' : 'update'
}`" }`"
:loading="loading"
> >
保存 保存
</PermissionButton> </PermissionButton>
@ -88,6 +89,7 @@ const route = useRoute();
const view = route.query.view as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const loading = ref(false)
const props = defineProps({ const props = defineProps({
provider: { provider: {
type: Object, type: Object,
@ -106,6 +108,7 @@ const formState = ref<FormState>({
description: '', description: '',
}); });
const onFinish = async (values: any) => { const onFinish = async (values: any) => {
loading.value = true
const providerId = props.provider.id; const providerId = props.provider.id;
const params = { const params = {
...values, ...values,
@ -124,6 +127,7 @@ const onFinish = async (values: any) => {
setTimeout(() => window.close(), 300); setTimeout(() => window.close(), 300);
} }
} }
loading.value = false
}; };
onMounted(() => { onMounted(() => {

View File

@ -328,6 +328,7 @@
:hasPermission="`link/AccessConfig:${ :hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update' id === ':id' ? 'add' : 'update'
}`" }`"
:loading="loading"
> >
保存 保存
</PermissionButton> </PermissionButton>
@ -376,6 +377,7 @@ const route = useRoute();
const view = route.query.view as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const loading = ref(false)
const props = defineProps({ const props = defineProps({
provider: { provider: {
type: Object, type: Object,
@ -434,6 +436,7 @@ const procotolSearch = (value: string) => {
const saveData = async () => { const saveData = async () => {
const data: any = await formRef2.value?.validate(); const data: any = await formRef2.value?.validate();
loading.value = true
const params = { const params = {
...data, ...data,
configuration: { configuration: {
@ -460,6 +463,7 @@ const saveData = async () => {
setTimeout(() => window.close(), 300); setTimeout(() => window.close(), 300);
} }
} }
loading.value = false
}; };
const queryProcotolList = async (id: string, params = {}) => { const queryProcotolList = async (id: string, params = {}) => {

View File

@ -406,6 +406,7 @@
:hasPermission="`link/AccessConfig:${ :hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update' id === ':id' ? 'add' : 'update'
}`" }`"
:loading="loading"
> >
保存 保存
</PermissionButton> </PermissionButton>
@ -486,6 +487,7 @@ const formData = ref<Form>({
description: '', description: '',
}); });
const loading = ref(false)
const current = ref(0); const current = ref(0);
const stepCurrent = ref(0); const stepCurrent = ref(0);
const steps = ref(['接入配置', '消息协议', '完成']); const steps = ref(['接入配置', '消息协议', '完成']);
@ -515,6 +517,7 @@ const procotolSearch = (value: string) => {
const saveData = async () => { const saveData = async () => {
const data: any = await formRef2.value?.validate(); const data: any = await formRef2.value?.validate();
loading.value = true
const params = { const params = {
...data, ...data,
configuration: { configuration: {
@ -542,6 +545,7 @@ const saveData = async () => {
setTimeout(() => window.close(), 300); setTimeout(() => window.close(), 300);
} }
} }
loading.value =false
}; };
const queryProcotolList = async (id: string, params = {}) => { const queryProcotolList = async (id: string, params = {}) => {

View File

@ -49,6 +49,7 @@
}`" }`"
html-type="submit" html-type="submit"
type="primary" type="primary"
:loading="loading"
> >
保存 保存
</PermissionButton> </PermissionButton>
@ -88,6 +89,7 @@ const route = useRoute();
const view = route.query.view as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const loading = ref(false)
const props = defineProps({ const props = defineProps({
provider: { provider: {
type: Object, type: Object,
@ -106,6 +108,7 @@ const formState = ref<FormState>({
description: '', description: '',
}); });
const onFinish = async (values: any) => { const onFinish = async (values: any) => {
loading.value = true
const providerId = props.provider.id; const providerId = props.provider.id;
const params = { const params = {
...values, ...values,
@ -124,6 +127,7 @@ const onFinish = async (values: any) => {
setTimeout(() => window.close(), 300); setTimeout(() => window.close(), 300);
} }
} }
loading.value = false
}; };
onMounted(() => { onMounted(() => {

View File

@ -156,6 +156,7 @@
:hasPermission="`link/AccessConfig:${ :hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update' id === ':id' ? 'add' : 'update'
}`" }`"
:loading="loading"
> >
保存 保存
</PermissionButton> </PermissionButton>
@ -187,6 +188,7 @@
:hasPermission="`link/AccessConfig:${ :hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update' id === ':id' ? 'add' : 'update'
}`" }`"
:loading="loading"
> >
保存 保存
</PermissionButton> </PermissionButton>
@ -223,6 +225,7 @@ const route = useRoute();
const view = route.query.view as string; const view = route.query.view as string;
const id = route.params.id as string; const id = route.params.id as string;
const loading = ref(false)
const props = defineProps({ const props = defineProps({
provider: { provider: {
type: Object, type: Object,
@ -252,6 +255,7 @@ const networkList: any = ref([]);
const allNetworkList: any = ref([]); const allNetworkList: any = ref([]);
const onFinish = async (values: any) => { const onFinish = async (values: any) => {
loading.value = true
const providerId = props.provider.id; const providerId = props.provider.id;
const params = { const params = {
...values, ...values,
@ -270,6 +274,7 @@ const onFinish = async (values: any) => {
setTimeout(() => window.close(), 300); setTimeout(() => window.close(), 300);
} }
} }
loading.value = false
}; };
const checkedChange = (id: string) => { const checkedChange = (id: string) => {

View File

@ -50,6 +50,7 @@
:hasPermission="`link/AccessConfig:${ :hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update' id === ':id' ? 'add' : 'update'
}`" }`"
:loading="loading"
> >
保存 保存
</PermissionButton> </PermissionButton>
@ -104,6 +105,7 @@ const props = defineProps({
}, },
}); });
const loading = ref(false);
const channel = ref(props.provider.channel); const channel = ref(props.provider.channel);
const formState = ref<FormState>({ const formState = ref<FormState>({
@ -111,6 +113,7 @@ const formState = ref<FormState>({
description: '', description: '',
}); });
const onFinish = async (values: any) => { const onFinish = async (values: any) => {
loading.value = true
const params = { const params = {
...values, ...values,
provider: 'fixed-media', provider: 'fixed-media',
@ -132,6 +135,7 @@ const onFinish = async (values: any) => {
history.back(); history.back();
} }
} }
loading.value = false
}; };
onMounted(() => { onMounted(() => {

View File

@ -4,7 +4,7 @@
:columns="columns" :columns="columns"
:dataSource="dataSource" :dataSource="dataSource"
:pagination="false" :pagination="false"
:scroll="{ y: 200 }" :scroll="share ? { y: 560} :{ y: 200 }"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'actions'"> <template v-if="column.dataIndex === 'actions'">
@ -61,6 +61,10 @@ const props = defineProps({
type: Object as PropType<Partial<Record<string, any>>>, type: Object as PropType<Partial<Record<string, any>>>,
default: () => ({}), default: () => ({}),
}, },
share:{
type:Boolean,
default:false
}
}); });
const emits = defineEmits(['refresh']) const emits = defineEmits(['refresh'])

View File

@ -41,7 +41,7 @@ const url = ref('');
const urlRef = ref<HTMLInputElement>() const urlRef = ref<HTMLInputElement>()
watchEffect(() => { watchEffect(() => {
url.value = `${window.location.origin}#/media/device/Share?deviceId=${props.data.deviceId}&channelId=${props.data.channelId}&type=${route.query.type}&${TOKEN_KEY}=${token}` url.value = `${window.location.origin}#/media/device/Share?deviceId=${props.data.deviceId}&channelId=${props.data.channelId}&type=${route.query.type}&id=${props.data.id}&${TOKEN_KEY}=${token}`
}) })
const onCopy = () => { const onCopy = () => {

View File

@ -3,7 +3,8 @@
<j-modal <j-modal
v-model:visible="_vis" v-model:visible="_vis"
title="播放" title="播放"
:width="_type ? 1200 : 900" :width="type === 'share'? '100%' : _type ? 1200 : 900"
:class="{share: type === 'share'}"
:maskClosable="false" :maskClosable="false"
@ok="_vis = false" @ok="_vis = false"
:destroyOnClose="true" :destroyOnClose="true"
@ -20,6 +21,7 @@
<j-radio-button value="mp4">MP4</j-radio-button> <j-radio-button value="mp4">MP4</j-radio-button>
<j-radio-button value="flv">FLV</j-radio-button> <j-radio-button value="flv">FLV</j-radio-button>
<j-radio-button value="m3u8">HLS</j-radio-button> <j-radio-button value="m3u8">HLS</j-radio-button>
<j-radio-button value='rtc'>RTC</j-radio-button>
</j-radio-group> </j-radio-group>
<div class="media-live-share" v-if="type !== 'share'"> <div class="media-live-share" v-if="type !== 'share'">
<j-button type="link" @click="onShare" <j-button type="link" @click="onShare"
@ -132,9 +134,9 @@
</div> </div>
</div> </div>
<template #footer> <template #footer>
<j-space> <j-space v-if="type !== 'share'">
<j-button :disabled="type === 'share'" @click="_vis = false">取消</j-button> <j-button @click="_vis = false">取消</j-button>
<j-button :disabled="type === 'share'" @click="_vis = false" type="primary">确定</j-button> <j-button @click="_vis = false" type="primary">确定</j-button>
</j-space> </j-space>
</template> </template>
</j-modal> </j-modal>
@ -179,7 +181,7 @@ const player = ref();
// //
const url = ref(''); const url = ref('');
// //
const mediaType = ref<'mp4' | 'flv' | 'hls'>('mp4'); const mediaType = ref<'mp4' | 'flv' | 'hls' | 'rtc'>('mp4');
const showTool = ref(false); const showTool = ref(false);
const showToolLock = ref(false); const showToolLock = ref(false);

View File

@ -0,0 +1,336 @@
<template>
<div style="width: 95%; margin: 0 auto;">
<div class="media-live-tool">
<j-radio-group
v-model:value="mediaType"
button-style="solid"
@change="mediaStart"
>
<j-radio-button value="mp4">MP4</j-radio-button>
<j-radio-button value="flv">FLV</j-radio-button>
<j-radio-button value="m3u8">HLS</j-radio-button>
<j-radio-button value='rtc'>RTC</j-radio-button>
</j-radio-group>
<div class="media-live-share" v-if="type !== 'share'">
<j-button type="link" @click="onShare"
><AIcon type="ShareAltOutlined" />分享视频</j-button
>
</div>
</div>
<div class="media-live">
<div class="media-live-video">
<div
:class="mediaToolClass"
@mouseleave="mouseleave"
@mouseenter="showTool = true"
>
<div class="tool-item" v-if="type !== 'share'">
<template v-if="isRecord === 0">
<j-dropdown
trigger="click"
@visibleChange="visibleChange"
@click="showToolLock = true"
>
<div>开始录像</div>
<template #overlay>
<j-menu @click="recordStart">
<j-menu-item
key="true"
v-if="_type"
>
<span style="padding-right: 12px"
>本地存储</span
>
<j-tooltip title="存储在设备本地">
<a-icon
type="QuestionCircleOutlined"
/>
</j-tooltip>
</j-menu-item>
<j-menu-item key="false">
<span style="padding-right: 12px"
>云端存储</span
>
<j-tooltip title="存储在服务器中">
<a-icon
type="QuestionCircleOutlined"
/>
</j-tooltip>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</template>
<div v-else-if="isRecord === 1">请求录像中</div>
<div
v-else-if="isRecord === 2"
@click.stop="recordStop"
>
停止录像
</div>
</div>
<div class="tool-item" @click.stop="handleRefresh">
刷新
</div>
<div class="tool-item">
<j-popconfirm
title="重置将断开直播, 可能会影响其他播放者"
@confirm="handleReset"
>
重置
</j-popconfirm>
</div>
</div>
<LivePlayer
ref="player"
:live="true"
:url="url"
:protocol="mediaType"
autoplay
/>
</div>
<div class="media-live-actions" v-if="_type">
<div class="actions-tool">
<MediaTool
@onMouseDown="handleMouseDown"
@onMouseUp="handleMouseUp"
>
<template #center>
<div class="center">
<div>转速控制</div>
<j-dropdown>
<span
>{{ _speed }}<AIcon type="DownOutlined"
/></span>
<template #overlay>
<j-menu @click="onMenuChange">
<j-menu-item
:key="item.value"
v-for="item in speedList"
>
{{ item.label }}
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</div>
</template>
</MediaTool>
</div>
<Preset :data="data" @refresh="onRefresh" :share="true"/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import LivePlayer from '@/components/Player/index.vue';
import MediaTool from '@/components/Player/mediaTool.vue';
import channelApi from '@/api/media/channel';
import Share from './Share.vue';
import Preset from './Preset.vue';
type Emits = {
(e: 'update:visible', data: boolean): void;
(e: 'refresh'): void;
};
const emit = defineEmits<Emits>();
const props = defineProps({
visible: { type: Boolean, default: false },
data: {
type: Object as PropType<Partial<Record<string, any>>>,
default: () => ({}),
},
type: {
type: String as PropType<'share' | 'normal'>,
default: 'normal',
},
});
const route = useRoute();
const _vis = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
});
//
const player = ref();
//
const url = ref('');
//
const mediaType = ref<'mp4' | 'flv' | 'hls' | 'rtc'>('mp4');
const showTool = ref(false);
const showToolLock = ref(false);
const visible = ref(false);
const _type = computed(() => {
return route.query.type !== 'fixed-media'
})
const speedList = [
{ label: '高', value: 180 },
{ label: '中', value: 90 },
{ label: '低', value: 45 },
];
const speed = ref(90);
const _speed = computed(() => {
return speedList.find((item) => item.value === speed.value)?.label;
});
const onMenuChange = (val: any) => {
speed.value = val.key;
};
const mouseleave = () => {
if (!showToolLock.value) {
showTool.value = false;
}
};
const visibleChange = (v: boolean) => {
showTool.value = v;
};
const getPopupContainer = (trigger: HTMLElement) => {
return trigger?.parentNode || document.body;
};
const mediaToolClass = computed(() => {
return {
'media-tool': true,
'media-tool-show': showTool.value,
};
});
/**
* 媒体开始播放
*/
const mediaStart = () => {
url.value = channelApi.ptzStart(
props.data.deviceId,
props.data.channelId,
mediaType.value,
);
};
//
const isRecord = ref(0); // 0 1 2
/**
* 查询录像状态
*/
const getIsRecord = async () => {
const { result } = await channelApi.ptzIsRecord(
props.data.deviceId,
props.data.channelId,
);
isRecord.value = result ? 2 : 0;
};
/**
* 开始录像
*/
const recordStart = async ({ key }: { key: string }) => {
showToolLock.value = false;
showTool.value = false;
isRecord.value = 1;
const local = key === 'true';
const res = await channelApi
.recordStart(props.data.deviceId, props.data.channelId, { local })
.catch(() => ({ success: false }));
if (res.success) {
isRecord.value = 2;
} else {
isRecord.value = 0;
}
};
/**
* 停止录像
*/
const recordStop = async () => {
const res = await channelApi.recordStop(
props.data.deviceId,
props.data.channelId,
);
if (res.success) {
isRecord.value = 0;
}
};
/**
* 刷新
*/
const handleRefresh = () => {
// player.value.play();
url.value = '';
setTimeout(() => {
mediaStart();
}, 500);
};
/**
* 重置
*/
const handleReset = async () => {
channelApi.mediaStop(props.data.deviceId, props.data.channelId);
};
/**
* 点击控制按钮
* @param type 控制类型
*/
const handleMouseDown = (type: string) => {
channelApi.ptzTool(props.data.deviceId, props.data.channelId, type, speed.value);
};
const handleMouseUp = () => {
channelApi.ptzStop(props.data.deviceId, props.data.channelId);
};
/**
* 分享视频
*/
const onShare = () => {
visible.value = true;
};
const onRefresh = () => {
emit('refresh')
}
watch(
() => _vis.value,
(val: boolean) => {
if (val) {
mediaStart();
getIsRecord();
} else {
// url,
url.value = '';
}
},
{
immediate: true
}
);
</script>
<style lang="less" scoped>
@import './index.less';
:deep(.live-player-stretch-btn) {
display: none;
}
:deep(.vjs-icon-spinner) {
display: none;
}
.center {
display: flex;
flex-direction: column;
align-items: center;
}
</style>

View File

@ -1,16 +1,17 @@
<template> <template>
<Live :visible="true" type="share" :data="playData" /> <ShareLive :visible="true" type="share" :data="playData"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { LocalStore } from '@/utils/comm'; import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable'; import { TOKEN_KEY } from '@/utils/variable';
import Live from '../Live/index.vue'; import ShareLive from '../Live/shareLive.vue';
const playData = ref({ const playData = ref({
deviceId: '', deviceId: '',
channelId: '', channelId: '',
type: '' type: '',
id:''
}); });
// url // url
@ -21,7 +22,8 @@ watchEffect(() => {
playData.value = { playData.value = {
deviceId: obj?.deviceId || '', deviceId: obj?.deviceId || '',
channelId: obj?.channelId || '', channelId: obj?.channelId || '',
type: obj?.type type: obj?.type,
id:obj.id || ''
}; };
if(obj?.[TOKEN_KEY]){ if(obj?.[TOKEN_KEY]){
LocalStore.set(TOKEN_KEY, obj?.[TOKEN_KEY]); LocalStore.set(TOKEN_KEY, obj?.[TOKEN_KEY]);