Commit 07b7c6ee by 宋珺琪

视频功能、阴影

parent 468e2e07
File added
<!DOCTYPE html>
<html>
<html style="height: 100%">
<head>
<meta charset="utf-8">
<title>考试</title>
</head>
<body>
<body style="height: 100%">
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
......
......@@ -46,6 +46,7 @@
"less-loader": "5.0.0",
"lib-flexible": "^0.3.2",
"node-notifier": "^5.1.2",
"node-sass": "^4.14.1",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
......@@ -54,6 +55,8 @@
"postcss-px2rem": "^0.3.0",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"sass": "^1.34.1",
"sass-loader": "^7.3.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
......
......@@ -24,5 +24,6 @@ a {
#app {
font-family: "Microsoft YaHei", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif;
background-color: #eee;
height: 100%;
}
</style>
......@@ -2,14 +2,14 @@
<template>
<div id="left">
<el-menu
active-text-color="#dd5862"
text-color="#000"
active-text-color="#dd5862"
text-color="#000"
:default-active="this.$route.path"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
:collapse="flag"
background-color="#124280"
background-color="#124280"
menu-trigger="click" router>
<el-submenu v-for="(item,index) in menu" :index='item.index' :key="index">
<template slot="title">
......@@ -22,6 +22,7 @@
<el-menu-item @click="handleTitle(item.index)" :index="list.path" v-if="list.item1 != null">{{list.item1}}</el-menu-item>
<el-menu-item @click="handleTitle(item.index)" :index="list.path" v-if="list.item2 != null">{{list.item2}}</el-menu-item>
<el-menu-item @click="handleTitle(item.index)" :index="list.path" v-if="list.item3 != null">{{list.item3}}</el-menu-item>
<el-menu-item @click="handleTitle(item.index)" :index="list.path" v-if="list.item4 != null">{{list.item4}}</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
......@@ -34,7 +35,7 @@ export default {
name: "mainLeft",
data() {
return {
}
},
computed: mapState(["flag","menu"]),
......
<template>
<div class="sctp">
<div class="oneClass">
<!--查询-->
<div class="tabs">
<el-form ref="searchForm" :model="searchForm">
<el-row>
<el-col :span="6">
<el-form-item label="文件名称 :" style="display: flex">
<el-input v-model="searchForm.videoNmae" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="9">
<el-form-item label="创建时间">
<el-date-picker
v-model="searchForm.uploadTime"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-button
class="toEmptyButton"
@click="toEmpty">
<span class="skyp icon-sousuo"></span>
清空
</el-button>
<el-button
class="searchListButton"
@click="searchList()"
>
<span class="skyp icon-sousuo"></span>
查询
</el-button>
<!--上传视频-->
<el-upload
action=""
:multiple="false"
:auto-upload="false"
accept="video/*"
:on-change="handlePhotoChange"
:limit="1"
:show-file-list="false">
<el-button slot="trigger" type="primary" style="margin-left: 10px;" title="只能上传视频文件">上传视频</el-button>
</el-upload>
<el-button class="searchListButton" type="success" style="margin-left: 10px">
当前视频总数{{this.videoLists.length}}
</el-button>
</el-col>
</el-row>
</el-form>
</div>
</div>
<div class="twoClass">
<div class="twoClassRow">
<el-row>
<el-col :span="8" v-for="(o, index) in videoLists" :key="o.videoId">
<el-card :body-style="{ padding: '0px' }">
<video controls width="100%">
<source :src=o.video type="video/mp4">
</video>
<div style="padding: 14px;">
<span>{{o.videoName}}</span>
<el-button class="searchListButton" type="success" style="margin-left: 10px" @click="open(o)">
编辑
</el-button>
<el-button class="searchListButton" type="success" style="margin-left: 10px" @click="deleteVideo(o.videoId)">
删除
</el-button>
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</div>
</template>
<script>
export default {
name: "addVideo",
data() {
return {
searchForm: {
videoNmae: '',
uploadTime: [],
},
video: '', //视频
videoName: "", //名称
fileList: [], //上传的list
videoLists: [],//视频列表
};
},
methods: {
// 弹框
open(video) {
this.$prompt('请输入修改的视频名称', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputErrorMessage: '邮箱格式不正确'
}).then(({ value }) => {
var name = value+'.'+video.videoName.split('.').pop()
this.compileVideo(video.videoId,name)
}).catch(() => {
this.$message({
type: 'info',
message: '取消编辑'
});
});
},
//编辑接口
compileVideo(id,name){
let params = new FormData();
params.append("id",id)
params.append("name",name)
this.$axios({
url: '/api/compileVideo',
method: 'post',
data: params
}).then(res => {
if (res.data.code == 200) {
this.$message.success('编辑成功');
this.videoList()
}
})
},
//删除
deleteVideo(videoId){
let params = new FormData();
params.append("id",videoId)
this.$axios({
url: '/api/deleteVideo',
method: 'post',
data: params
}).then(res => {
if (res.data.code == 200) {
this.$message.success('删除成功');
this.videoList()
}
})
},
//清空
toEmpty() {
this.searchForm = {
videoNmae: '',
uploadTime: [],
}
this.videoList()
},
//添加照片
handlePhotoChange(file) {
console.log("添加照片")
this.video = file.raw
this.videoName = file.name
this.fileList.push(file.raw);
this.submitForm();
},
//移除
handleRemove(file) {
this.video = ''
this.fileList.length = 0;
},
//提交
submitForm() {
console.log(this.video)
if (!this.video) {
return;
console.log(111111111111111)
}
const isLt10M = this.video.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$message.error('上传视频文件不能超过10MB!');
}
console.log(isLt10M);
let date = new Date()
let params = new FormData();
params.append("videoFile", this.video);
params.append("videoName", this.videoName);
params.append("uploadTime", date);
this.$axios({
url: '/api/uploadVideo',
method: 'post',
data: params
}).then(res => {
if (res.data.code == 200) {
this.fileList = []
this.video = ''
this.$message.success('添加成功');
this.videoList()
}
})
},
//列表
videoList() {
this.$axios({
url: '/api/selectVideo',
method: 'post',
}).then(res => {
if (res.data.code == 200) {
this.videoLists = res.data.data
}
})
},
//模糊查询
searchList() {
console.log(this.searchForm.uploadTime)
let params = new FormData();
params.append("name",this.searchForm.videoNmae)
if (this.searchForm.uploadTime[0]){
params.append("startTime",this.searchForm.uploadTime[0])
}
if (this.searchForm.uploadTime[1]){
params.append("endTime",this.searchForm.uploadTime[1])
}
this.$axios({
url: '/api/selectVideoByNameOrTime',
method: 'post',
data: params
}).then(res => {
if (res.data.code == 200) {
this.videoLists = res.data.data
this.$message.success('模糊查询成功');
}
})
}
},
created() {
this.videoList()
}
}
</script>
<style lang="scss" scoped>
.el-col-6 {
display: flex;
}
.sctp {
/*margin-left: 40px;*/
}
.scspButton {
margin-top: 30px;
}
.oneClass {
height: 30%;
margin-left: 40px;
/*background-color: yellow;*/
}
.twoClass {
height: 70%;
margin-left: 20px;
margin-right: 20px;
/*max-height: 70%; !* 设置固定高度,根据需要调整 *!*/
/*overflow: auto; !* 添加滚动条 *!*/
/*background-color: red;*/
}
.twoClassRow{
height: 700px;
overflow: auto; /* 添加滚动条 */
}
</style>
<template>
<div class="qun-video">
<VueMiniPlayer ref="vueMiniPlayer" :mutex="false" :video="video" @fullscreen="fullscreen" />
</div>
</template>
<script>
import VueMiniPlayer from "./packages/vue-mini-player/src/VuePlayer";
export default {
name: 'QunVideo',
components: {
VueMiniPlayer
},
data() {
return {
video: {
url: this.url,
loop: true,
autoplay: false,
muted: false,
playsinline: true,
logo: '',
// cover: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png'
},
};
},
props: {
url: String,
},
watch: {},
computed: {
$video() {
return this.$refs.vueMiniPlayer.$video;
},
},
filters: {},
methods: {
fullscreen(data) {
console.log('====================================');
console.log(data);
console.log('====================================');
},
},
created() {},
mounted() {},
updated() {},
beforeDestroy() {},
destroyed() {},
};
</script>
<style scoped>
.qun-video {
max-width: 500px;
width: 100%;
min-width: 400px;
height: 15em;
margin: 0 auto;
}
</style>
import VueMiniPlayer from './vue-mini-player';
const version = "0.2.1";
const components = [VueMiniPlayer];
const install = function(Vue) {
if (install.installed) return;
components.map(component => Vue.component(component.name, component));
};
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default {
// 导出的对象必须具有 install,才能被 Vue.use() 方法安装
install,
version,
// 以下是具体的组件列表
VueMiniPlayer
};
import VuePlayer from './src/VuePlayer.vue';
VuePlayer.install = function(Vue) {
Vue.component(VuePlayer.name, VuePlayer);
};
export default VuePlayer;
<template>
<div class="qun-base-controls">
<Volume :isMuted.sync="isMuted" />
<Progress @paused="$emit('paused')" />
<Fullscreen :isFullscreen.sync="isFullscreen" />
</div>
</template>
<script>
import Volume from './controls/Volume.vue';
import Progress from './controls/Progress.vue';
import Fullscreen from './controls/Fullscreen.vue';
export default {
name: 'BaseControls',
components: {
Volume,
Progress,
Fullscreen
},
data() {
return {
isMuted: false,
isFullscreen: false
};
},
watch: {
isFullscreen(newData, oldData) {
this.$emit('fullscreen', newData);
}
},
computed: {
$parentComponent() {
return this.$parent;
},
$video() {
return this.$parentComponent.$video;
}
},
filters: {},
methods: {
initVideoEvents() {
this.$video.addEventListener('volumechange', function(e) {
this.isMuted = e.target.muted;
});
}
},
created() {
this.$nextTick(() => {
const { volume } = this.$video
this.isMuted = this.$video.muted || volume === 0
this.initVideoEvents();
});
},
updated() {},
beforeDestroy() {},
destroyed() {}
};
</script>
<style lang="scss" scoped>
.qun-base-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 2147483647;
width: 100%;
height: 3em;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-sizing: border-box;
transition: all 0.3s ease;
user-select: none;
}
</style>
<template>
<div class="_play-btn"
@click.stop="handleClick">
<svg v-if="isPlaying"
viewBox="0 0 64 64"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd">
<g>
<path d="M64,32 C64,49.6733333 49.6733333,64 32,64 C14.3266667,64 0,49.6733333 0,32 C0,14.3266667 14.3266667,0 32,0 C49.6733333,0 64,14.3266667 64,32"
fill-opacity="0.5"
fill="#231F20"></path>
<rect fill="#FFFFFF"
fill-rule="nonzero"
x="23"
y="19"
width="5"
height="26"
rx="2.5"></rect>
<rect fill="#FFFFFF"
fill-rule="nonzero"
x="36"
y="19"
width="5"
height="26"
rx="2.5"></rect>
</g>
</g>
</svg>
<svg v-else
viewBox="0 0 96 96"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd">
<g>
<path d="M96,48 C96,74.51 74.51,96 48,96 C21.49,96 0,74.51 0,48 C0,21.49 21.49,0 48,0 C74.51,0 96,21.49 96,48"
fill-opacity="0.5"
fill="#231F20"></path>
<path d="M68.332,49.1162 L36.209,67.4722 C35.352,67.9622 34.286,67.3432 34.286,66.3552 L34.286,29.6442 C34.286,28.6572 35.352,28.0382 36.209,28.5272 L68.332,46.8842 C69.196,47.3772 69.196,48.6232 68.332,49.1162"
fill="#FFFFFF"></path>
</g>
</g>
</svg>
</div>
</template>
<script>
export default {
props: {
isPlaying: {
default: false,
type: Boolean
}
},
methods: {
handleClick() {
this.$emit('update:isPlaying', !this.isPlaying);
}
}
};
</script>
<style lang="scss" scoped>
._play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 3.5em;
height: 3.5em;
user-select: none;
z-index: 2147483647;
cursor: pointer;
svg{
width: 100%;
height: 100%;
}
}
</style>
<template>
<div class="vm-player"
ref="container"
@click.stop="handleClickVideo">
<!-- logo -->
<div class="_logo" v-if="options.logo" :style="logoStyle">
{{options.logo}}
</div>
<!--模拟poster -->
<div class="_poster"
:style="{backgroundImage:`url(${options.cover})`}"
v-show="!isPlaying&&isStart && options.cover">
</div>
<template v-show="isPlaying">
<video class="_video-ref"
ref="video"
:muted="options.muted"
:loop="options.loop"
:preload="options.preload"
:poster="options.cover"
:autoplay="options.autoplay"
>
<source v-for="(item, index) in vUrl"
:key="index"
:src="item"
:type="`${getUrlType(item)}`">
Your browser does not support the video element.
</video>
</template>
<transition name="fade">
<PlayBtn :isPlaying.sync="isPlaying"
v-show="!isClearMode" />
</transition>
<transition name="fade">
<BaseControls @paused="handlePaused"
@fullscreen="$emit('fullscreen',$event)"
v-show="!isClearMode" />
</transition>
</div>
</template>
<script>
import BaseControls from './BaseControls.vue';
import PlayBtn from './PlayBtn.vue';
const VERSION = "0.2.1";
export default {
name: 'VueMiniPlayer',
components: {
BaseControls,
PlayBtn
},
props: {
video: {
type: Object,
default: function() {
return {};
}
},
mutex: {
type: Boolean,
default: false
}
},
data() {
return {
baseVideo: {
url: '',
cover: '',
muted: true,
loop: false,
preload: 'auto',
poster: '',
volume: 1,
autoplay: false
},
$video: null,
$container: null,
clearModeTimer: null,
isStart: true,
isPlaying: false,
isClearMode: false
};
},
watch: {
isPlaying() {
this.isStart = false;
this.play();
this.setClearModeTimer();
}
},
computed: {
vUrl() {
let url = this.video.url || [];
if (typeof url === 'string') {
url = [url];
} else if (Object.prototype.toString.call(url) === '[object Object]') {
console.warn(new Error('视频URL只接受String或者Array'));
return [];
}
return url;
},
// 合并默认和用户自定义属性配置
options() {
return Object.assign({}, this.baseVideo, this.video);
},
playsinline() {
return this.options.playsinline
},
crossOrigin() {
return this.options.crossOrigin
},
autoplay() {
return this.options.autoplay
},
isMobile() {
return navigator.userAgent.toLowerCase().match(/(ipod|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null;
},
logoStyle() {
return this.options.logoStyle || {}
}
},
methods: {
getUrlType(url) {
// let u = url.split('?')[0] + '?v=1';
// return u.match(/[^\.]+(?=\?)/) || 'mp4';
const regex = /:(.*?);/;
const matches = url.match(regex);
return matches ? matches[1] : 'mp4';
},
init() {
this.$video = this.$refs.video;
this.$container = this.$refs.container;
this.$video.load();
this.initPlayer();
this.$emit('ready');
},
initPlayer() {
this.$video.volume = this.options.volume;
if (this.playsinline) {
this.$refs.video.setAttribute('playsinline', this.playsinline)
this.$refs.video.setAttribute('webkit-playsinline', this.playsinline)
this.$refs.video.setAttribute('x5-playsinline', this.playsinline)
this.$refs.video.setAttribute('x5-video-player-type', 'h5')
this.$refs.video.setAttribute('x-webkit-airplay', 'allow')
this.$refs.video.setAttribute('x5-video-player-fullscreen', false)
}
// cross origin
if (this.crossOrigin) {
this.$refs.video.crossOrigin = this.crossOrigin
this.$refs.video.setAttribute('crossOrigin', this.crossOrigin)
}
if (this.autoplay && this.isMobile) {
this.$video.muted = true
// 兼容微信自动播放
document.addEventListener('WeixinJSBridgeReady', () => this.$video.play(), false);
}
if (this.autoplay) {
this.isPlaying = true;
}
},
setClearModeTimer() {
if (this.clearModeTimer) {
clearTimeout(this.clearModeTimer);
}
this.clearModeTimer = setTimeout(() => {
this.isClearMode = true;
this.clearModeTimer = null
this.$emit('clearMode');
}, 3000);
},
pauseAllVideo() {
if (this.mutex) {
const videos = document.querySelectorAll('video');
videos.forEach(v => {
v.pause && v.pause();
});
}
},
play() {
if (this.isPlaying) {
this.$video.play();
} else {
this.pauseAllVideo();
this.$video.pause();
}
this.$emit('videoPlay', this.isPlaying);
},
handlePaused() {
this.isPlaying = false;
},
handleClickVideo() {
if (this.isClearMode) {
this.isClearMode = false;
this.setClearModeTimer()
} else {
this.isClearMode = true;
}
}
},
created() {
this.$emit('created');
this.$nextTick(() => {
this.init();
});
},
mounted() {
this.$emit('mounted');
console.log(
'\n' + ' %c vue-mini-player v' + VERSION + ' %c https://github.com/webweifeng/vue-mini-player ' + '\n' + '\n',
'color: #fadfa3; background: #030307; padding:5px 0;',
'background: #fadfa3; padding:5px 0;'
);
},
updated() {},
beforeDestroy() {
this.$emit('beforeDestroy');
},
destroyed() {
this.$emit('destroyed');
}
};
</script>
<style lang="scss" scoped>
.vm-player {
width: 100%;
height: 100%;
min-height: 10em;
position: relative;
background: #000;
overflow: hidden;
&:fullscreen,
&:-webkit-full-screen,
&:-moz-full-screen,
&:-ms-fullscreen {
width: 100%;
height: 100%;
position: fixed;
z-index: 100000;
left: 0;
top: 0;
margin: 0;
padding: 0;
transform: translate(0, 0);
}
._poster {
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
._video-ref {
background: #000;
width: 100%;
height: 100%;
/* object-fit: cover; */
&::-webkit-media-controls,
&::-webkit-media-controls-enclosure {
display: none !important;
}
}
._logo{
position: absolute;
z-index: 2147483647;
right: 20px;
top: 20px;
font-weight: bold;
font-family:cursive;
text-shadow:6px 2px 2px #666;
color:#fff;
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>
<template>
<div class="_fullscreen"
@click.stop="handleClickToggle">
<svg viewBox="0 0 40 40"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round">
<g transform="translate(-670.000000, -354.000000)"
stroke="#FFFFFF"
stroke-width="5">
<g transform="translate(670.000000, 354.000000)">
<path d="M35,20 L35,34 C35,34.5522847 34.5522847,35 34,35 L20,35 M5,20 L5,6 C5,5.44771525 5.44771525,5 6,5 L6,5 L20,5"></path>
</g>
</g>
</g>
</svg>
</div>
</template>
<script>
export default {
props: {
isFullscreen: {
type: Boolean,
default: false
}
},
data() {
return {};
},
computed: {
$video() {
return this.$parent.$parent.$video;
},
$container() {
return this.$parent.$parent.$container;
}
},
watch: {},
methods: {
handleClickToggle() {
if (this.isFullscreen) {
this.resetFullScreen();
} else {
this.reqFullScreen(this.$container);
}
},
setFullscreenState() {
const fullEle =
document.fullscreenElement || document.mozFullScreenElement || document.msFullscreenElement || document.webkitFullscreenElement;
this.$emit('update:isFullscreen', !!fullEle);
},
reqFullScreen(element) {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if (element.webkitEnterFullscreen) {
element.webkitEnterFullscreen();
} else if (this.$video.webkitEnterFullscreen) {
// Safari for iOS
this.$video.webkitEnterFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
} else {
console.log('进入全屏失败');
}
},
resetFullScreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.msCancelFullScreen) {
document.msCancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
}
},
docfullscreenchange() {
this.setFullscreenState();
},
fullscreenchange() {
this.setFullscreenState();
},
unbindChangeEvent() {
if (/Firefox/.test(navigator.userAgent)) {
document.removeEventListener('mozfullscreenchange', this.docfullscreenchange);
document.removeEventListener('fullscreenchange', this.docfullscreenchange);
} else {
this.$container.removeEventListener('fullscreenchange', this.fullscreenchange);
this.$container.removeEventListener('webkitfullscreenchange', this.fullscreenchange);
document.removeEventListener('msfullscreenchange', this.docfullscreenchange);
document.removeEventListener('MSFullscreenChange', this.docfullscreenchange);
}
},
bindChangeEvent() {
if (/Firefox/.test(navigator.userAgent)) {
document.addEventListener('mozfullscreenchange', this.docfullscreenchange);
document.addEventListener('fullscreenchange', this.docfullscreenchange);
} else {
this.$container.addEventListener('fullscreenchange', this.fullscreenchange);
this.$container.addEventListener('webkitfullscreenchange', this.fullscreenchange);
document.addEventListener('msfullscreenchange', this.docfullscreenchange);
document.addEventListener('MSFullscreenChange', this.docfullscreenchange);
}
}
},
mounted() {
this.$nextTick(() => {
this.bindChangeEvent();
});
},
beforeDestroy() {
if (this.$container) {
this.unbindChangeEvent();
}
}
};
</script>
<style lang="scss" scoped>
._fullscreen {
width: 1.5em;
width: 1.5em;
cursor: pointer;
svg {
width: 100%;
height: 100%;
}
}
</style>
<template>
<div class="_progress" @click.stop>
<span class="_time-current">{{ textCurrentTime }}</span>
<div class="_slider" ref="_sliderRef" @click.stop="clickSlider">
<div class="_slider-cur" :style="{ width: offsetLeft }"></div>
<i class="_slider-btn" @click.stop :style="{ left: offsetLeft }" ref="_sliderBtnRef"></i>
</div>
<span class="_time-amount">{{ textTotalTime }}</span>
</div>
</template>
<script>
const isMobile = /mobile/i.test(window.navigator.userAgent);
export default {
props: {},
data() {
return {
textTotalTime: '00:00',
textCurrentTime: '00:00',
offsetLeft: 0,
sliderInfo: {
isMove: false,
startX: 0,
oldTime: 0,
oldOffsetLeft: 0
}
};
},
computed: {
playerRef() {
return this.$parent.$parent;
},
$video() {
return this.$parent.$parent.$video;
},
sliderRef() {
return this.$refs._sliderRef;
}
},
watch: {},
methods: {
secondToTime(time = 0) {
time = time > 0 ? time : 0;
const add0 = num => (num < 10 ? `0${num}` : num);
const h = ~~(time / 3600);
const m = ~~((time % 3600) / 60);
const s = ~~(time % 60);
return (h > 0 ? [h, m, s] : [m, s]).map(add0).join(':');
},
updateSlider(ratio) {
if (ratio >= 0 && ratio <= 100) {
this.offsetLeft = `${ratio}%`;
}
},
updateTextTime(second) {
this.textCurrentTime = this.secondToTime(second);
},
updateVideoTime(second) {
if (second) {
this.$video.currentTime = second;
}
},
clickSlider($event) {
const sliderW = this.sliderRef.offsetWidth;
const curOffestLeft = $event.clientX - $event.target.getBoundingClientRect().left;
this.offsetLeft = `${~~((curOffestLeft / sliderW) * 100)}%`;
this.updateVideoState();
},
updateVideoState() {
const newSecond = ~~(this.$video.duration * (parseFloat(this.offsetLeft) / 100));
this.updateVideoTime(newSecond);
this.updateTextTime(newSecond);
this.playerRef.isPlaying = true;
},
initVideoEvents() {
const events = ['pause', 'play', 'waiting', 'timeupdate', 'durationchange', 'loadeddata'];
events.forEach(e => {
this.$video.addEventListener(e, this[`handle${e.toLowerCase().replace(/^./, f => f.toUpperCase())}`], false);
});
},
initSliderBtnEvents() {
const dragEventMap = {
DragStart: isMobile ? 'touchstart' : 'mousedown',
DragMove: isMobile ? 'touchmove' : 'mousemove',
DragEnd: isMobile ? 'touchend' : 'mouseup'
};
Object.keys(dragEventMap).forEach(key => {
const bindRef = key === 'DragStart' ? this.$refs._sliderBtnRef : this.playerRef.$container;
bindRef.addEventListener(dragEventMap[key], this[`handle${key}`], false);
});
},
handleLoadeddata() {
const totalTime = this.$video.duration;
this.textTotalTime = this.secondToTime(totalTime);
},
handleDurationchange() {
// 视频读取完成拿到视频长度
const totalTime = this.$video.duration;
this.textTotalTime = this.secondToTime(totalTime);
},
handleTimeupdate() {
// 视频播放时间变化执行
const currentTime = this.$video.currentTime;
const ratio = (currentTime / this.$video.duration) * 100;
this.updateTextTime(currentTime);
this.updateSlider(ratio);
},
handlePause() {
// 视频暂停
const totalTime = this.$video.duration;
const currentTime = this.$video.currentTime;
if (currentTime === totalTime) this.playerRef.isPlaying = false;
this.$emit('paused');
},
handlePlay() {
// 视频播放
},
handleWaiting() {
// 视频因点击下一帧等待
},
computeMoveInfo(moveX) {
const sliderW = this.sliderRef.offsetWidth;
let offset = moveX - this.sliderInfo.startX;
if (Math.abs(offset) >= sliderW) {
offset = offset > 0 ? sliderW : -sliderW;
}
const ratio = (offset / sliderW) * 100;
const second = this.$video.duration * (ratio / 100);
return { ratio, second };
},
handleDragStart($event) {
$event.stopPropagation();
// 滑块拖拽开始
this.sliderInfo.startX = $event.clientX || $event.changedTouches[0].clientX;
this.sliderInfo.isMove = true;
this.sliderInfo.oldTime = this.$video.currentTime;
this.sliderInfo.oldOffsetLeft = parseFloat(this.offsetLeft);
},
handleDragMove($event) {
const ClienX = $event.clientX || $event.changedTouches[0].clientX;
// 滑块拖拽移动中
if (this.sliderInfo.isMove) {
const { ratio, second } = this.computeMoveInfo(ClienX);
// this.updateVideoTime(second);
this.updateTextTime(second + this.sliderInfo.oldTime);
this.updateSlider(ratio + this.sliderInfo.oldOffsetLeft);
}
},
handleDragEnd($event) {
const ClienX = $event.clientX || $event.changedTouches[0].clientX;
// 滑块拖拽结束
if (this.sliderInfo.isMove) {
const { ratio, second } = this.computeMoveInfo(ClienX);
this.updateVideoTime(second + this.sliderInfo.oldTime);
this.updateTextTime(second + this.sliderInfo.oldTime);
this.updateSlider(ratio + parseFloat(this.offsetLeft));
this.sliderInfo.isMove = false;
// this.playerRef.isPlaying = true;
}
}
},
mounted() {
this.$nextTick(() => {
this.initVideoEvents();
this.initSliderBtnEvents();
});
}
};
</script>
<style lang="scss" scoped>
._progress {
color: #fff;
width: 100%;
height: 2em;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.5em;
box-sizing: border-box;
._time-current,
._time-amount {
font-size: 1em;
}
._slider {
width: 100%;
height: 0.4em;
background: rgba(255, 255, 255, 0.4);
border-radius: 4px;
position: relative;
margin: 0 1em;
._slider-cur {
width: 0;
height: 100%;
border-radius: 4px;
background-color: #fff;
position: absolute;
top: 0;
left: 0;
transition: all 3ms;
}
._slider-btn {
width: 1em;
height: 1em;
display: inline-block;
background-color: #fff;
border-radius: 50%;
position: absolute;
top: 50%;
left: 0;
transform: translate(-50%, -50%);
transition: all 3ms;
cursor: pointer;
}
}
}
</style>
<template>
<div class="_vol"
@click.stop="handleClick">
<!-- 非静音 -->
<svg v-if="!isMuted"
viewBox="0 0 40 40"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd">
<g transform="translate(-40.000000, -281.000000)">
<g transform="translate(40.000000, 281.000000)">
<g transform="translate(0.000000, 4.000000)">
<path d="M10.314666,7.29159253 L10.314666,23.8296452 L4.05278755,23.8296452 C2.32789564,23.8296452 0.927090038,22.5856319 0.927090038,21.1011808 L0.927090038,10.0200629 C0.927090038,8.50947627 2.31222825,7.29159253 4.05278755,7.29159253 L10.314666,7.29159253 L10.314666,7.29159253 Z"
fill="#FFFFFF"
fill-rule="nonzero"></path>
<path d="M18.0191834,0.674274796 C19.237061,-0.240440841 20.2249539,0.277026428 20.2249539,1.78238658 L20.2249539,29.3335821 C20.2249539,30.8598483 19.2475171,31.361636 18.0191834,30.4416938 L9.19610116,23.8296087 L9.19610116,7.29155607 L18.0191834,0.674244409 L18.0191834,0.674274796 Z"
fill="#FFFFFF"
fill-rule="nonzero"></path>
<path d="M29.5357411,25.6333387 C32.0371112,23.0341574 33.5750798,19.5011972 33.5750798,15.6090251 C33.5750798,11.7410714 32.0561911,8.2278731 29.582322,5.63333866"
stroke="#FFFFFF"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"></path>
</g>
</g>
</g>
</g>
</svg>
<!-- 静音 -->
<svg v-else
viewBox="0 0 40 40"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd">
<g transform="translate(-40.000000, -354.000000)"
fill="#FFFFFF"
fill-rule="nonzero">
<g transform="translate(40.000000, 354.000000)">
<g transform="translate(0.000000, 5.000000)">
<path d="M10.314666,7.29159253 L10.314666,23.8296452 L4.05278755,23.8296452 C2.32789564,23.8296452 0.927090038,22.5856319 0.927090038,21.1011808 L0.927090038,10.0200629 C0.927090038,8.50947627 2.31222825,7.29159253 4.05278755,7.29159253 L10.314666,7.29159253 L10.314666,7.29159253 Z"></path>
<path d="M18.0191834,0.674274796 C19.237061,-0.240440841 20.2249539,0.277026428 20.2249539,1.78238658 L20.2249539,29.3335821 C20.2249539,30.8598483 19.2475171,31.361636 18.0191834,30.4416938 L9.19610116,23.8296087 L9.19610116,7.29155607 L18.0191834,0.674244409 L18.0191834,0.674274796 Z"></path>
<path d="M23.9256893,11.143836 C23.1573282,10.3754749 23.1573282,9.12100861 23.9256893,8.35265052 L23.9570512,8.32128901 C24.7254123,7.55292788 25.9798786,7.55292788 26.7482367,8.32128901 L38.4461438,20.0191961 C39.2145049,20.7875573 39.2145049,22.0420236 38.4461438,22.8103817 L38.4147823,22.8417435 C37.6411947,23.6153311 36.3919549,23.6153311 35.6235968,22.8417435 L23.9256893,11.143836 Z"></path>
<path d="M23.9517611,19.9930508 L35.6496683,8.29514369 C36.4180294,7.52678255 37.6724957,7.52678255 38.4408538,8.29514369 L38.4722156,8.32650519 C39.2405767,9.09486633 39.2405767,10.3493326 38.4722156,11.1176907 L26.7743085,22.8208244 C26.0059473,23.5891855 24.751481,23.5891855 23.9831229,22.8208244 L23.9517614,22.7894625 C23.1781738,22.0158749 23.1781738,20.7666351 23.9517614,19.9930505 L23.9517611,19.9930508 Z"></path>
</g>
</g>
</g>
</g>
</svg>
</div>
</template>
<script>
export default {
props: {
isMuted: {
type: Boolean,
default: false
}
},
data() {
return {};
},
computed: {
$video() {
return this.$parent.$parent.$video;
}
},
methods: {
handleClick() {
const isMuted = !this.isMuted;
this.$emit('update:isMuted', isMuted);
this.$video && (this.$video.muted = isMuted);
}
}
};
</script>
<style lang="scss" scoped>
._vol {
width: 1.2em;
height: 1.2em;
cursor: pointer;
svg {
width: 100%;
height: 100%;
}
}
</style>
<template>
<div id="videoList">
<div class="top">
<ul class="item">
<div style="display: flex;">
<img class="jinhuiClass" src="@/assets/img/jinhui.png" alt="" />
<li style=" font-family:'youshe';color: black;font-size: 45px">计算机-计算机</li>
</div>
</ul>
</div>
<div>
<section class="main" v-for="(o, index) in videoLists" :key="o.videoId">
<div class="videoBox">
<p class="videoTitle">{{o.videoName}}</p>
<QunVideo :url=o.video />
</div>
</section>
</div>
</div>
</template>
<script>
import QunVideo from "./QunVideo";
export default {
name: "videoList",
components: {
QunVideo,
},
data() {
return {
videoLists: [],//视频列表
};
},
methods: {
toDesk() {
let self = this;
self.$router.push("/deskTop");
},
//列表
videoList() {
this.$axios({
url: '/api/selectVideo',
method: 'post',
}).then(res => {
if (res.data.code == 200) {
this.videoLists = res.data.data
}
})
}
},
created() {
this.videoList()
}
};
</script>
<style lang="scss" scoped>
.top{
background-color: rgb(39, 118, 223);
}
.item {
display: flex;
padding: 20px;
font-size: 26px;
letter-spacing: 1px;
}
html,
body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
background-color: #f1f1f1;
}
#videoList {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
height: 100%;
background: url("../../assets/img/beijing111.jpg") no-repeat;
background-size: 100% 100%;
max-height: 100%; /* 设置固定高度,根据需要调整 */
overflow: auto; /* 添加滚动条 */
.header {
padding-top: 20px;
overflow: hidden;
width: 100%;
height: 150px;
color: #fff;
background-color: #159957;
background-image: linear-gradient(120deg, #1b9fd4, #159957);
}
.github-corner {
position: absolute;
top: 0;
border: 0;
right: 0;
z-index: 1;
}
.main {
display: inline-flex;
padding: 20px;
.videoBox {
height: 250px;
padding: 2px;
border-radius: 10px;
background: url("../../assets/img/videoImg/itemBg.png") no-repeat;
background-size: 100% 100%;
margin-left:15px;
margin-right:15px;
margin-top: 15px;
.videoTitle {
color: #fff;
font-weight: 600;
line-height: 30px;
font-size: 16px;
margin: 10px 20px;
}
}
.qun-video {
margin: 0;
min-width:350px
}
}
}
</style>
......@@ -15,6 +15,11 @@ export default new Router({
component: () => import('@/components/common/jumpLogin')
},
{
path: "/videoList",
name: "videoList",
component: () => import('@/components/videoList/videoList')
},
{
path: '/index', //教师主页
component: () => import('@/components/admin/index'),
children: [
......@@ -63,6 +68,10 @@ export default new Router({
component: () => import('@/components/teacher/addAnswer')
},
{
path: '/addVideo', //上传视频主界面
component: () => import('@/components/teacher/addVideo')
},
{
path: '/addAnswerChildren', //点击试卷跳转到添加题库页面
component: () => import('@/components/teacher/addAnswerChildren')
},
......
......@@ -17,7 +17,8 @@ const state = {
index: '2',
title: '题库管理',
icon: 'icon-tiku',
content:[{item1:'功能介绍',path:'/answerDescription'},{item2:'所有题库',path:'/selectAnswer'},{item3:'增加题库',path:'/addAnswer'},{path: '/addAnswerChildren'}],
content:[{item1:'功能介绍',path:'/answerDescription'},{item2:'所有题库',path:'/selectAnswer'},{item3:'增加题库',path:'/addAnswer'},{item4:'上传视频',path:'/addVideo'},{path: '/addAnswerChildren'}],
// content:[{item1:'功能介绍',path:'/answerDescription'},{item2:'所有题库',path:'/selectAnswer'},{item3:'增加题库',path:'/addAnswer'},{path: '/addAnswerChildren'}],
},
{
index: '3',
......@@ -57,7 +58,7 @@ const mutations = {
}
}
const getters = {
}
const actions = {
getUserInfo(context,info) {
......
// function resolve(dir) {
// return path.join(__dirname, dir);
// }
module.exports = {
assetsDir: "static",
lintOnSave: false, //关闭eslint
productionSourceMap: false, //关闭生产映射
css: {
sourceMap: process.env.NODE_ENV === "development" ? true : false, // 在开发环境下开启 CSS sourcemaps
loaderOptions: {
css: {},
postcss: {
plugins: [
require('postcss-px2rem')({
// 以设计稿750为例, 750 / 10 = 75
remUnit: 192
}),
]
}
}
assetsDir: "static",
lintOnSave: false, //关闭eslint
productionSourceMap: false, //关闭生产映射
entry: ['entry.js'],
output: {
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.scss$/,
use: [
'vue-style-loader',
"css-loader",
"sass-loader"
]
}
]
},
css: {
sourceMap: process.env.NODE_ENV === "development" ? true : false, // 在开发环境下开启 CSS sourcemaps
loaderOptions: {
css: {},
postcss: {
plugins: [
require('postcss-px2rem')({
// 以设计稿750为例, 750 / 10 = 75
remUnit: 192
}),
]
}
}
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment