juesechacun

This commit is contained in:
shilixia 2021-07-08 11:10:15 +08:00
parent 3168a1e913
commit 74f8b80204
10 changed files with 927 additions and 2 deletions

View File

@ -23,6 +23,7 @@
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"vod-js-sdk-v6": "^1.4.12",
"vue": "2.6.10",
"vue-router": "3.0.6",
"vuex": "3.1.0",

44
client/src/api/video.js Normal file
View File

@ -0,0 +1,44 @@
import request from '@/utils/request'
export function getVideoList(query) {
return request({
url: '/vod/video/',
method: 'get',
params: query
})
}
export function getsignature() {
return request({
url: '/vod/signature/',
method: 'get'
})
}
export function createVideo(data) {
return request({
url: '/vod/video/',
method: 'post',
data
})
}
export function deletevideo(id, data) {
return request({
url: `/vod/video/${id}/`,
method: 'delete',
data
})
}
export function updatevideo(id, data) {
return request({
url: `/vod/video/${id}/`,
method: 'put',
data
})
}
export function getVideo(id) {
return request({
url: `/vod/video/${id}/`,
method: 'get'
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -7,7 +7,6 @@ import 'element-ui/lib/theme-chalk/index.css'
// import locale from 'element-ui/lib/locale/lang/en' // lang i18n
import '@/styles/index.scss' // global css
import App from './App'
import store from './store'
import router from './router'

View File

@ -202,6 +202,35 @@ export const asyncRoutes = [
},
]
},
{
path: '/test',
component: Layout,
redirect: '/test/',
name: 'TestVideo',
meta: { title: '视频培训', icon: 'guide', perms: ['testvideo'] },
alwaysShow: true,
children: [
{
path: 'videolist',
name: 'videolist',
component: () => import('@/views/testvideo/videolist.vue'),
meta: { title: '视频列表', perms: ['videolist'] }
},
{
path: 'upload',
name: 'upload',
component: () => import('@/views/testvideo/upload.vue'),
meta: { title: '上传视频', perms: ['uploadvideo'] }
},
{
path: 'index/:id',
name: 'Index',
component: () => import('@/views/testvideo/index.vue'),
meta: { title: '视频播放', perms: ['uploadvideo'] },
hidden: true
},
]
},
{
path: '/pt',
component: Layout,

View File

@ -136,7 +136,6 @@ export default {
upUrl: upUrl(),
fileList:[],
Content: defaultContent,
typeOptions: [],
listLoading: true,
dialogVisible: false,

View File

@ -41,6 +41,21 @@
:label="item.display_name"
:value="item.key"
/>
</el-select>
<el-select
v-model="listQuery.roles"
placeholder="角色"
clearable
style="width: 90px"
class="filter-item"
@change="handleFilter"
>
<el-option
v-for="item in roles"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="listQuery.search"

View File

@ -0,0 +1,160 @@
<template>
<el-container>
<el-header style="height: 80px;padding: 0">
<el-row class="biaotou">
<el-col :span="20" style="text-align:center;color:seashell;font-size:32px;line-height: 70px;" >
检验管理部培训视频
</el-col>
</el-row>
</el-header>
<el-main style="margin-top: 70px;">
<div style="margin:1% 12.5%;">
<div class="content" >
<video :id="tcPlayerId" width="1000" height="500" class="tencent-player" preload="auto" playsinline webkit-playsinline></video>
</div>
<div style="margin-top: 70px;">
<el-row :gutter="20">
<el-col >
<div style="margin-bottom: 6px">
<span class="term">视频名称</span>
<span class="desc"> {{ video.name }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col >
<div style="margin-bottom: 6px">
<span class="term">视频描述</span>
<span class="desc"> {{ video.description }}</span>
</div>
</el-col>
</el-row>
</div>
</div>
</el-main>
</el-container>
</template>
<script>
import {getVideo} from "@/api/video";
function loadTcScript(cb) {
loadScript(cb, {
id: 'tcPlayerScriptId',
url: 'https://imgcache.qq.com/open/qcloud/video/tcplayer/tcplayer.min.js',
});
}
function loadScript(cb, obj) {
if (document.getElementById(obj.id)) {
cb();
} else {
const script = document.createElement('script');
script.async = true;
script.src = obj.url;
script.id = obj.id;
script.onload = () => {
cb();
};
document.body.appendChild(script);
}
}
export default {
name: 'TencentPlayer',
props: {
options: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
tcPlayerId : 'tcPlayer' + Date.now(),
player: null,
video:{id: 0},
description:'',
name:'',
};
},
watch: {
options: {
handler(newV, oldV) {
this.$nextTick(() => {
this.loadJS();
});
},
immediate: true,
deep: true,
}
},
created() {
this.id = this.$route.params.id;
this.fileid = this.$route.params.fileid;
this.getVideo();
},
methods: {
getVideo(){
getVideo(this.id).then((response) => {
if (response.data) {
this.video= response.data;
}
this.listLoading = false;
});
},
loadJS() {
if (window.TCPlayer) {
this.initVideo();
} else {
loadTcScript(() => {
this.initVideo();
});
}
},
initVideo() {
const playerParm = {
fileID: this.fileid,
appID: '1253484223',
};
setTimeout(() => {
if (!this.player) {
this.player = TCPlayer(this.tcPlayerId, playerParm);
} else {
this.player.loadVideoByID(playerParm);
}
});
}
,
},
};
</script>
<style lang="scss" scoped>
@import url("https://imgcache.qq.com/open/qcloud/video/tcplayer/tcplayer.css");
.el-header, .el-footer {
color: #333;
text-align: center;
line-height: 40px;
position: fixed;
width: 100%;
z-index: 1010;
}
.biaotou{
background: url(../../assets/year-100-bg.png) no-repeat 50%,linear-gradient(90deg,#e95353 50%,#bf2221 0);
box-shadow: none;
height: 72px;
}
</style>

View File

@ -0,0 +1,453 @@
<template>
<div class="app-container">
<el-card>
<el-button type="primary" icon="el-icon-plus" @click="handleAddVideo"
>新增</el-button
>
</el-card>
<el-card style="margin-top: 10px">
<el-table
v-loading="listLoading"
:data="videoList.results.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase())|| data.category_name.toLowerCase().includes(search.toLowerCase()))"
border
fit
stripe
highlight-current-row
max-height="800"
>
<el-table-column type="index" width="50" />
<el-table-column label="名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="描述">
<template slot-scope="scope">{{ scope.row.description }}</template>
</el-table-column>
<el-table-column label="视频类别">
<template slot-scope="scope">{{ scope.row.category_name }}</template>
</el-table-column>
<el-table-column label="创建时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
</el-table-column>
<el-table-column label="视频地址">
<template slot-scope="scope">{{ scope.row.mediaurl }}</template>
</el-table-column>
<el-table-column label="封面地址">
<template slot-scope="scope">{{ scope.row.coverurl }}</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
width="200px"
fixed="right"
>
<template slot="header" slot-scope="scope">
<el-input
v-model="search"
size="mini"
placeholder="输入关键字搜索"/>
</template>
<template slot-scope="scope">
<el-button
type="primary"
size="small"
icon="el-icon-edit"
@click="handleEdit(scope)"
/>
<el-button
type="danger"
size="small"
icon="el-icon-delete"
@click="handleDelete(scope)"
/>
</template>
</el-table-column>
</el-table>
<pagination
v-show="videoList.count > 0"
:total="videoList.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.page_size"
@pagination="getVideoList"
/>
</el-card>
<el-dialog
:visible.sync="dialogVisible"
:title="dialogType === 'edit' ? '编辑视频信息' : '新增视频信息'"
>
<el-form
ref="Forms"
:model="VideoContent"
label-width="80px"
label-position="right"
:rules="rule1"
>
<el-form-item label="视频名称" prop="name">
<el-input v-model="VideoContent.name" placeholder="名称" />
</el-form-item>
<el-form-item label="视频描述" prop="description">
<el-input v-model="VideoContent.description" placeholder="名称" />
</el-form-item>
<el-form-item label="视频类型" prop="category">
<el-cascader
v-model="VideoContent.category"
:options="typeOptions"
:props="{ emitPath: false }"
clearable
style="width: 100%"
></el-cascader>
</el-form-item>
<form ref="vcExample">
<input type="file" style="display:none;" ref="vcExampleVideo" @change="setVcExampleVideoName()" />
<input type="file" style="display:none;" ref="vcExampleCover" @change="setVcExampleCoverName()" />
</form>
<div class="row" style="padding:10px;">
<el-button type="primary" @click="vcExampleAddVideo">{{vcExampleVideoName || '添加视频'}}</el-button>
<el-button type="primary" @click="vcExampleAddCover">{{vcExampleCoverName || '添加封面'}}</el-button>
<el-button type="success" @click="vcExampleAddUpload">上传视频和封面</el-button>
</div>
<div class="row" id="resultBox">
<!-- 上传信息组件 -->
<div class="uploaderMsgBox" v-for="uploaderInfo in uploaderInfos">
<div v-if="uploaderInfo.videoInfo" >
视频名称{{uploaderInfo.videoInfo.name + '.' + uploaderInfo.videoInfo.type}}
上传进度
<el-progress :text-inside="true" :stroke-width="24" :percentage="Math.floor(uploaderInfo.progress * 100)" status="success">
</el-progress>
上传结果{{uploaderInfo.isVideoUploadCancel ? '已取消' : uploaderInfo.isVideoUploadSuccess ? '上传成功' : '上传中'}}
<br>
地址{{uploaderInfo.videoUrl}}
<a href="javascript:void(0);" class="cancel-upload" v-if="!uploaderInfo.isVideoUploadSuccess && !uploaderInfo.isVideoUploadCancel" @click="uploaderInfo.cancel()">取消上传</a><br>
</div>
<div v-if="uploaderInfo.coverInfo">
封面名称{{uploaderInfo.coverInfo.name}}
上传进度
<el-progress :text-inside="true" :stroke-width="24" :percentage="Math.floor(uploaderInfo.coverProgress * 100)" status="success">
</el-progress>
上传结果{{uploaderInfo.isCoverUploadSuccess ? '上传成功' : '上传中'}}
<br>
地址{{uploaderInfo.coverUrl}}
<br>
<video :src="uploaderInfo.videoUrl" style="width:400px;height:300px" id="video" autoplay></video>
</div>
</div>
</div>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirm('Forms')">创建信息</el-button>
</div>
</el-dialog>
</div>
</template>
<script src="https://cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>
<script type="text/javascript">
import { updatevideo,getsignature,createVideo,getVideoList,deletevideo} from "@/api/video";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
import { getDictList, getDictTypeList } from "@/api/dict";
import TcVod from "vod-js-sdk-v6";
import { genTree } from "@/utils";
const defaultVideo = {
name:"",
description:"",
category:"",
mediaurl:'',
}
export default {
components: {Pagination },
data() {
return {
dialogVisible:false,
signature: "",
treeLoding: false,
videoList:"",
typeOptions:[],
listQuery: {
page: 1,
page_size: 20,
},
search: '',
VideoContent:defaultVideo,
uploaderInfos: [],
vcExampleVideoName: '',
listLoading:false,
vcExampleCoverName: '',
rule1: {
name: [{ required: true, message: "请输入名称", trigger: "blur" }],
category: [{ required: true, message: "请选择视频分类", trigger: 'change' }],
},
};
},
computed: {},
created() {
this.getList();
this.getTypeAll();
},
methods: {
//列表加载
getList() {
this.listLoading = true;
getVideoList().then((response) => {
if (response.data) {
this.videoList = response.data;
}
this.listLoading = false;
});
},
//视频类型
getTypeAll() {
getDictList({ type__code: "data_video" }).then((res) => {
this.typeOptions = genTree(res.data);
});
},
/*弹出弹窗 */
handleAddVideo() {
this.VideoContent = Object.assign({},defaultVideo);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Forms"].clearValidate();
});
},
//删除视频
handleDelete(scope) {
this.$confirm("确认删除?", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error",
})
.then(async () => {
await deletevideo(scope.row.id);
this.getList();
this.$message.success("成功");
})
.catch((err) => {
console.error(err);
});
},
//编辑视频
handleEdit(scope) {
this.VideoContent = Object.assign({}, scope.row); // copy obj
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Forms"].clearValidate();
});
},
setVcExampleVideoName: function () {
this.vcExampleVideoName = this.$refs.vcExampleVideo.files[0].name;
},
setVcExampleCoverName: function () {
this.vcExampleCoverName = this.$refs.vcExampleCover.files[0].name;
},
/*
vcExample添加视频
*/
vcExampleAddVideo: function () {
this.$refs.vcExampleVideo.click()
},
/*
vcExample添加封面
*/
vcExampleAddCover: function () {
this.$refs.vcExampleCover.click()
},
/*
vcExample上传过程
*/
vcExampleAddUpload: function () {
var self = this;
var mediaFile = this.$refs.vcExampleVideo.files[0];
var coverFile = this.$refs.vcExampleCover.files[0];
// 获取签名 这里必须定义为函数tcVod才可识别
const getSignature = async function() {
const { data } = await getsignature()
return data
}
// 前文中所述的获取上传签名的函数
const tcVod = new TcVod({ getSignature })
var uploader = tcVod.upload({
mediaFile: mediaFile,
coverFile: coverFile,
})
uploader.on('media_progress', function(info) {
uploaderInfo.progress = info.percent;
})
uploader.on('media_upload', function(info) {
uploaderInfo.isVideoUploadSuccess = true;
})
uploader.on('cover_progress', function(info) {
uploaderInfo.coverProgress = info.percent;
})
uploader.on('cover_upload', function(info) {
uploaderInfo.isCoverUploadSuccess = true;
})
var uploaderInfo = {
videoInfo: uploader.videoInfo,
coverInfo: uploader.coverInfo,
isVideoUploadSuccess: false,
isVideoUploadCancel: false,
isCoverUploadSuccess: false,
progress: 0,
coverProgress: 0,
fileId: '',
videoUrl: '',
coverUrl: '',
cancel: function () {
uploaderInfo.isVideoUploadCancel = true;
uploader.cancel()
},
}
this.uploaderInfos.push(uploaderInfo)
// console.log('uploaderInfos',this.uploaderInfos);
uploader.done().then(function (doneResult) {
// console.log('doneResult', doneResult)
uploaderInfo.fileId = doneResult.fileId;
uploaderInfo.coverUrl = doneResult.cover.url;
uploaderInfo.videoUrl = doneResult.video.url;
self.$refs.vcExample.reset();
self.vcExampleVideoName = ''
self.vcExampleCoverName = ''
})
},
async confirm(form) {
this.$refs[form].validate((valid) => {
if (valid) {
const isEdit = this.dialogType === "edit";
if (isEdit) {
for (var i = 0; i < this.uploaderInfos.length; i++) {
this.VideoContent.fileid= this.uploaderInfos[i].fileId ;
this.VideoContent.coverurl= this.uploaderInfos[i].coverUrl ;
this.VideoContent.mediaurl= this.uploaderInfos[i].videoUrl;
}
// console.log(this.VideoContent)
updatevideo(this.VideoContent.id, this.VideoContent).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
} else {
for (var i = 0; i < this.uploaderInfos.length; i++) {
this.VideoContent.fileid= this.uploaderInfos[i].fileId ;
this.VideoContent.coverurl= this.uploaderInfos[i].coverUrl ;
this.VideoContent.mediaurl= this.uploaderInfos[i].videoUrl;
}
createVideo(this.VideoContent).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
}
} else {
return false;
}
});
},
},
};
</script>
<style type="text/css">
.text-danger {
color: red;
}
.control-label {
text-align: left !important;
}
#resultBox {
width: 100%;
height: 300px;
border: 1px solid #888;
padding: 5px;
overflow: auto;
margin-bottom: 20px;
}
.uploaderMsgBox {
border-bottom: 1px solid #888;
background: #1a7ce0;
color: white;
font-size: medium;
}
.cancel-upload {
text-decoration: none;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,225 @@
<template>
<el-container>
<el-header style="height: 80px;padding: 0">
<el-row class="biaotou">
<el-col :span="20" style="text-align:center;color:seashell;font-size:32px;line-height: 70px;" >
检验管理部培训视频
</el-col>
</el-row>
</el-header>
<el-main>
<el-row style="background-color: #f5f7fa;
position: fixed;width: 83%;z-index:1000;margin-top: -8px;
">
<el-col style="width:120px;margin-top: 12px;">
<el-link :underline="false" @click="getList"><el-span class="mune">全部</el-span></el-link>
</el-col>
<el-col style="width:120px;margin-top: 12px;" v-for="o in typeOptions" :key="o">
<el-link :underline="false" @click="getListtype(o)"><el-span class="mune">{{o.name}}</el-span></el-link>
</el-col>
</el-row>
<el-row style="margin-top: 50px;">
<el-col style="margin:0.5%;width: 24%;" v-for="o in videoList.results" :key="o">
<el-card :body-style="{ padding: '0px' }" shadow="always" class="cardDiv" >
<a @click="clickImg(o)">
<el-image :src="o.coverurl" style="width:100%;height: 300px;" ></el-image>
<div class="des"> <span>{{o.description}}</span></div>
<div style="padding: 14px;">
<span style="font-family: 'Helvetica Neue'">{{o.name}}</span>
<div class="bottom clearfix">
<time class="time">{{ o.create_time.substring(0,10) }}</time>
</div>
</div>
</a>
</el-card>
</el-col>
</el-row>
</el-main>
</el-container>
</template>
<script type="text/javascript">
import { getsignature,createVideo,getVideoList} from "@/api/video";
import { getDictList, getDictTypeList } from "@/api/dict";
import { genTree } from "@/utils";
export default {
components: { },
data() {
return {
videoList:"",
typeOptions:[],
};
},
computed: {},
created() {
this.getList();
this.getTypeAll();
},
methods: {
//列表加载
getList() {
this.listLoading = true;
getVideoList().then((response) => {
if (response.data) {
this.videoList = response.data;
}
this.listLoading = false;
});
},
getListtype(a){
getVideoList({category:a.id}).then((response) => {
if (response.data) {
this.videoList = response.data;
}
this.listLoading = false;
});
},
//视频类型
getTypeAll() {
getDictList({ type__code: "data_video" }).then((res) => {
this.typeOptions = genTree(res.data);
});
},
//跳转播放视频界面
clickImg(a){
this.$router.push({name: "Index", params: { fileid: a.fileid,id:a.id} })
}
},
};
</script>
<style type="text/css">
.time {
font-size: 13px;
color: #999;
float:right;
}
.el-col-offset-0{
margin-left: 50px;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.biaotou{
background: url(../../assets/year-100-bg.png) no-repeat 50%,linear-gradient(90deg,#e95353 50%,#bf2221 0);
box-shadow: none;
height: 72px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.el-header, .el-footer {
color: #333;
text-align: center;
line-height: 40px;
position: fixed;
width: 100%;
z-index: 1010;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
text-align: center;
line-height: 200px;
}
.navbar[data-v-d16d6306]{
display: none;
}
.el-main {
background-color: #ffffff;
color: #333;
text-align: center;
margin-top: 60px;
}
.des{
height: 100px; margin-top: -100px;z-index:9999;position: relative;background-color:rgba(220,38,38,0.2);color: #f5f4f4;
opacity: 0;
}
.cardDiv:hover .des{
opacity: 1;
}
body > .el-container {
margin-bottom: 40px;
}
.el-link{
font-size: 20px;
font-family: auto;
font-weight: 700;
}
.mune{
font-family: PingFang SC,DFPKingGothicGB-Medium,sans-serif;
font-size: 16px;
font-weight: 500;
line-height: 24px;
max-width: 112px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.el-link.el-link--default {
color: seashell;
background: #fff;
border: 1px solid rgba(0,0,0,.1);
border-radius: 100px;
color: rgba(47,48,53,.9);
cursor: pointer;
display: flex;
border: 1px solid rgba(0,0,0,.1);
justify-content: center;
min-width: 72px;
padding: 5px 16px;
margin-bottom: 12px;
margin-right: 16px;
}
</style>