视频预览
This commit is contained in:
parent
bb7fb86c3b
commit
155f5f230b
@ -9,6 +9,10 @@
|
||||
<title>幼儿阅读教学服务平台</title>
|
||||
<!-- 阿里云IMM -->
|
||||
<script src="https://g.alicdn.com/IMM/office-js/1.1.19/aliyun-web-office-sdk.min.js"></script>
|
||||
<link rel="stylesheet"
|
||||
href="https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/skins/default/aliplayer-min.css" />
|
||||
<script charset="utf-8" type="text/javascript"
|
||||
src="https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/aliplayer-min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@ -26,6 +26,7 @@ declare module 'vue' {
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AImage: typeof import('ant-design-vue/es')['Image']
|
||||
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
@ -75,6 +76,7 @@ declare module 'vue' {
|
||||
LessonConfigPanel: typeof import('./components/course/LessonConfigPanel.vue')['default']
|
||||
LessonStepsEditor: typeof import('./components/course/LessonStepsEditor.vue')['default']
|
||||
NotificationBell: typeof import('./components/NotificationBell.vue')['default']
|
||||
PressDrag: typeof import('./components/PressDrag.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Step1BasicInfo: typeof import('./components/course-edit/Step1BasicInfo.vue')['default']
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
<template>
|
||||
<a-modal v-model:open="visible" :title="title" :width="modalWidth" :footer="null" centered @cancel="handleClose">
|
||||
<!-- PDF 预览 -->
|
||||
<div v-if="fileType === 'pdf'" class="preview-container pdf-container">
|
||||
<iframe v-if="previewUrl" :src="previewUrl" class="pdf-iframe" frameborder="0"></iframe>
|
||||
<div v-else class="preview-error">
|
||||
<div v-if="fileType === 'pdf'" class="preview-container ppt-container">
|
||||
<div class="ppt-notice">
|
||||
<FileTextOutlined style="font-size: 48px; color: #999;" />
|
||||
<p>无法预览此PDF文件</p>
|
||||
<a-button type="primary" @click="downloadFile">下载查看</a-button>
|
||||
<h3>PDF 文件预览</h3>
|
||||
<p>使用 WebOffice 在新页面中预览</p>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="openInWebOffice">
|
||||
<EyeOutlined /> PDF 预览
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -26,10 +30,11 @@
|
||||
</div>
|
||||
|
||||
<!-- 视频播放器 -->
|
||||
<div v-else-if="fileType === 'video'" class="preview-container video-container">
|
||||
<video ref="videoRef" :src="previewUrl" controls class="video-element" @error="handleMediaError">
|
||||
<div v-else-if="fileType === 'video'" class="preview-container video-container relative h-600px">
|
||||
<!-- <video ref="videoRef" :src="previewUrl" controls class="video-element" @error="handleMediaError">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</video> -->
|
||||
<player v-if="visible" :url="previewUrl" :noPage="true" />
|
||||
</div>
|
||||
|
||||
<!-- 图片预览 -->
|
||||
@ -107,7 +112,7 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { openWebOffice } from '@/views/office/webOffice';
|
||||
|
||||
import Player from '@/views/office/player.vue';
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
fileUrl: string;
|
||||
@ -169,8 +174,6 @@ const title = computed(() => {
|
||||
// 模态框宽度
|
||||
const modalWidth = computed(() => {
|
||||
switch (fileType.value) {
|
||||
case 'pdf':
|
||||
return '90%';
|
||||
case 'video':
|
||||
return 900;
|
||||
case 'audio':
|
||||
|
||||
141
reading-platform-frontend/src/components/PressDrag.vue
Normal file
141
reading-platform-frontend/src/components/PressDrag.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div ref="" id="draggableElementView" class="draggable-box cursor-grab" :style="elementStyle"
|
||||
@mousedown.passive="handleStart" @touchstart.passive="handleStart" @click="handleClick">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, onUnmounted } from 'vue';
|
||||
import { addResizeTimeOut, removeResizeTimeOut } from '@/utils';
|
||||
export default defineComponent({
|
||||
name: 'PressDragComponent',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
storageKey: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const view = {
|
||||
offsetWidth: 50,
|
||||
offsetHeight: 50,
|
||||
innerWidth: 750,
|
||||
innerHeight: 1280,
|
||||
};
|
||||
const storageKeyVal = `_tem_drag_${props.storageKey}`;
|
||||
|
||||
const initil = () => {
|
||||
const node = document.querySelector('#draggableElementView') as HTMLElement;
|
||||
view.offsetWidth = node.offsetWidth;
|
||||
view.offsetHeight = node.offsetHeight;
|
||||
|
||||
view.innerWidth =
|
||||
window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
view.innerHeight =
|
||||
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
|
||||
};
|
||||
addResizeTimeOut(initil);
|
||||
const isDragging = ref(false);
|
||||
const pressTimer = ref<number | null>(null);
|
||||
const startTime = ref(0);
|
||||
const elementStyle = ref<any>({
|
||||
userSelect: 'none',
|
||||
touchAction: 'none',
|
||||
});
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
initil();
|
||||
// if (props.storageKey && props.storageKey.length > 0) {
|
||||
// let storageVal: any = localStorage.getItem(storageKeyVal);
|
||||
// if (storageVal && storageVal.length > 0) {
|
||||
// storageVal = storageVal.split(',');
|
||||
// setStyle(storageVal[0], storageVal[1]);
|
||||
// }
|
||||
// }
|
||||
});
|
||||
});
|
||||
const handleStart = (e: MouseEvent | TouchEvent) => {
|
||||
e.preventDefault();
|
||||
startTime.value = Date.now();
|
||||
pressTimer.value = window.setTimeout(() => {
|
||||
isDragging.value = true;
|
||||
document.addEventListener('mousemove', handleDrag);
|
||||
document.addEventListener('touchmove', handleDrag, { passive: false });
|
||||
document.addEventListener('mouseup', handleEnd);
|
||||
document.addEventListener('touchend', handleEnd);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const handleDrag = (e: MouseEvent | TouchEvent) => {
|
||||
if (!isDragging.value) return;
|
||||
e.preventDefault();
|
||||
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
||||
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
||||
let left = clientX - view.offsetWidth / 2;
|
||||
let top = clientY - view.offsetHeight / 2;
|
||||
|
||||
left = left > view.innerWidth - view.offsetWidth ? view.innerWidth - view.offsetWidth : left;
|
||||
top = top > view.innerHeight - view.offsetHeight ? view.innerHeight - view.offsetHeight : top;
|
||||
setStyle(left, top);
|
||||
};
|
||||
const setStyle = (left: number, top: number) => {
|
||||
if (props.storageKey && props.storageKey.length > 0) {
|
||||
localStorage.setItem(storageKeyVal, [left, top].join(','));
|
||||
}
|
||||
|
||||
// if (left > innerWidth - 90) {
|
||||
// left = innerWidth - 90;
|
||||
// } else if (left < 30) {
|
||||
// left = 30;
|
||||
// }
|
||||
// if (top > innerHeight - 90) {
|
||||
// top = innerHeight - 90;
|
||||
// } else if (top < 30) {
|
||||
// top = 30;
|
||||
// }
|
||||
|
||||
left = (left / view.innerWidth) * 100;
|
||||
left = left < 0 ? 0 : left;
|
||||
|
||||
top = (top / view.innerHeight) * 100;
|
||||
top = top < 0 ? 0 : top;
|
||||
elementStyle.value = {
|
||||
...elementStyle.value,
|
||||
left: `${left}%`,
|
||||
top: `${top}%`,
|
||||
};
|
||||
};
|
||||
const handleEnd = () => {
|
||||
clearTimeout(pressTimer.value!);
|
||||
document.removeEventListener('mousemove', handleDrag);
|
||||
document.removeEventListener('touchmove', handleDrag);
|
||||
document.removeEventListener('mouseup', handleEnd);
|
||||
document.removeEventListener('touchend', handleEnd);
|
||||
isDragging.value = false;
|
||||
};
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (Date.now() - startTime.value > 200) return;
|
||||
emit('click', e);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
handleEnd();
|
||||
|
||||
removeResizeTimeOut(initil);
|
||||
});
|
||||
return { elementStyle, handleStart, handleClick };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* .draggable-box {
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.1s, box-shadow 0.3s;
|
||||
} */
|
||||
|
||||
.draggable-box:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
</style>
|
||||
51
reading-platform-frontend/src/utils/index.ts
Normal file
51
reading-platform-frontend/src/utils/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 延迟
|
||||
* @param ms
|
||||
* @returns
|
||||
*/
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
/***
|
||||
* 窗口resize事件封装
|
||||
*/
|
||||
const resizeObjs = reactive<{
|
||||
time: any;
|
||||
callbackList: Function[];
|
||||
}>({
|
||||
time: -1,
|
||||
callbackList: [],
|
||||
});
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
() => {
|
||||
if (resizeObjs.time >= 0) {
|
||||
clearTimeout(resizeObjs.time);
|
||||
}
|
||||
resizeObjs.time = setTimeout(() => {
|
||||
resizeObjs.time = -1;
|
||||
try {
|
||||
resizeObjs.callbackList.forEach((callback: Function) => {
|
||||
callback();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, 600);
|
||||
},
|
||||
false,
|
||||
);
|
||||
/**
|
||||
* @param callback 添加窗口变化事件
|
||||
*/
|
||||
export function addResizeTimeOut(callback: Function) {
|
||||
resizeObjs.callbackList.push(callback);
|
||||
}
|
||||
/**
|
||||
* @param callback 移除窗口变化事件
|
||||
*/
|
||||
export function removeResizeTimeOut(callback: Function) {
|
||||
resizeObjs.callbackList = resizeObjs.callbackList.filter((_callback) => {
|
||||
return callback != _callback;
|
||||
});
|
||||
}
|
||||
123
reading-platform-frontend/src/views/office/player.vue
Normal file
123
reading-platform-frontend/src/views/office/player.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="w-full h-full bg-#000 z-50 top-0 left-0" :class="noPage ? 'absolute' : 'fixed '">
|
||||
<!-- <PressDrag @click="toBreak"
|
||||
class="pos-absolute bg-#00000033 cursor-pointer w-30px h-30px flex-center z-99999 p-10px top-20px right-20px rounded-50%">
|
||||
<CloseCircleOutlined class="text-30px color-#ffffff" />
|
||||
</PressDrag> -->
|
||||
<div class="w-full h-full z-1" id="playerView"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CloseCircleOutlined } from '@ant-design/icons-vue';
|
||||
import PressDrag from '@/components/PressDrag.vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getTemItem, TemObj } from './temObjs';
|
||||
// @ts-ignore
|
||||
const player = ref<typeof Aliplayer>(null); //播放器
|
||||
const route = useRoute();
|
||||
const props = defineProps<{
|
||||
url: string;
|
||||
cover?: string;
|
||||
noPage?: boolean;
|
||||
}>();
|
||||
const _temObj = ref<TemObj>({
|
||||
id: '',
|
||||
name: '',
|
||||
isEdit: false,
|
||||
url: '',
|
||||
});
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (!props.noPage) {
|
||||
|
||||
const temObj = getTemItem(route.query._t as string);
|
||||
if (!temObj) {
|
||||
return;
|
||||
}
|
||||
_temObj.value = temObj;
|
||||
createPlayer(_temObj.value.url, '/long/long.svg');
|
||||
} else if (!!props.url) {
|
||||
createPlayer(props.url, props.cover || '/long/long.svg');
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
watch(() => props.url, (newVal) => {
|
||||
if (!!newVal) {
|
||||
createPlayer(newVal, props.cover || '/long/long.svg');
|
||||
}
|
||||
});
|
||||
const createPlayer = (source?: string, cover?: string) => {
|
||||
// 更多使用方法请参考接入文档:https://help.aliyun.com/zh/vod/developer-reference/integration
|
||||
|
||||
// @ts-ignore
|
||||
player.value = new Aliplayer(
|
||||
{
|
||||
id: 'playerView',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
source: source, // 如果是私有加密播放请传入 vid/playauth/encryptType
|
||||
cover: cover,
|
||||
skinLayout: [
|
||||
{ name: 'bigPlayButton', align: 'blabs', x: 30, y: 80 },
|
||||
{
|
||||
name: 'H5Loading',
|
||||
align: 'cc',
|
||||
},
|
||||
{
|
||||
name: 'controlBar',
|
||||
align: 'blabs',
|
||||
x: 0,
|
||||
y: 0,
|
||||
children: [
|
||||
{ name: 'progress', align: 'tlabs', x: 0, y: 0 },
|
||||
{ name: 'playButton', align: 'tl', x: 15, y: 10 },
|
||||
{ name: 'timeDisplay', align: 'tl', x: 10, y: 2 },
|
||||
{ name: 'fullScreenButton', align: 'tr', x: 20, y: 12 },
|
||||
{ name: 'setting', align: 'tr', x: 20, y: 11 },
|
||||
{ name: 'volume', align: 'tr', x: 20, y: 10 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// @ts-ignore
|
||||
(player: typeof Aliplayer) => {
|
||||
//播放下一个视频
|
||||
player.on('ended', () => {
|
||||
// update(videoList[index + 1]);
|
||||
});
|
||||
},
|
||||
);
|
||||
console.log('player', player.value);
|
||||
};
|
||||
|
||||
function toBreak() {
|
||||
|
||||
}
|
||||
//点击右侧列表视频切换
|
||||
// const update = (video: PlayInfo) => {
|
||||
// // playObj.value = video;
|
||||
// player.value.dispose(); //销毁
|
||||
// createPlayer(video.Source, video.CoverURL); //创建
|
||||
// };
|
||||
function dispose() {
|
||||
if (player.value) {
|
||||
player.value.dispose(); //销毁
|
||||
}
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
try {
|
||||
dispose();
|
||||
} catch (error) { }
|
||||
});
|
||||
// // 存储当前播放时间
|
||||
// const saveTime = function (memoryVideo: string, currentTime: string) {
|
||||
// localStorage.setItem(memoryVideo, currentTime);
|
||||
// };
|
||||
// // 获取此视频上次播放时间
|
||||
// const getTime = function (memoryVideo: string): string | null {
|
||||
// return localStorage.getItem(memoryVideo);
|
||||
// };
|
||||
</script>
|
||||
<style scoped></style>
|
||||
13
reading-platform-frontend/typed-router.d.ts
vendored
13
reading-platform-frontend/typed-router.d.ts
vendored
@ -191,6 +191,13 @@ declare module 'vue-router/auto-routes' {
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/office/player': RouteRecordInfo<
|
||||
'/office/player',
|
||||
'/office/player',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/office/WebOffice': RouteRecordInfo<
|
||||
'/office/WebOffice',
|
||||
'/office/WebOffice',
|
||||
@ -852,6 +859,12 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/views/office/player.vue': {
|
||||
routes:
|
||||
| '/office/player'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/views/office/WebOffice.vue': {
|
||||
routes:
|
||||
| '/office/WebOffice'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user