运用组件
<template>
<div class="addvideo" v-loading.fullscreen.lock="isloading">
<div class="main">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="标题:" prop="title">
<el-input v-model.trim="ruleForm.title" class="wid700" placeholder="请填写标题(限制3-50个字)"></el-input>
</el-form-item>
<el-form-item label="年级:" prop="grades">
<el-checkbox-group v-model="ruleForm.grades">
<el-checkbox v-for="item of classarr" :key="item.id" :label="item.id" name="type">{{ item.name }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="主题:" prop="themeId">
<el-select v-model="ruleForm.themeId" placeholder="请选择主题">
<el-option v-for="item of themList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="类型:" prop="type" v-if="!isptyy">
<el-radio-group v-model="ruleForm.type">
<el-radio :label="1">自制</el-radio>
<el-radio :label="2">转载</el-radio>
</el-radio-group>
<el-input v-if="ruleForm.type == 2" v-model="ruleForm.source" class="wid700" placeholder="网络资源转载请标明视频来源(例如:转自http://www.xxxx.com/yyy)" style="display:block"></el-input>
</el-form-item>
<el-form-item label="视频简介:" prop="remark">
<el-input type="textarea" v-model="ruleForm.remark" rows="9" class="wid700" maxlength="1000" show-word-limit></el-input>
</el-form-item>
<el-form-item>
<div class="uploadvideo" id="uploadvideo">
<video class="videos" controls ref="videoRef" v-show="videoForm.videoUrl" :src="videoForm.videoUrl" id="myvideo"></video>
<div class="progress" :style="{ position: videoForm.videoUrl ? 'absolute' : 'relative' }" v-if="percent != 0"><el-progress color="#f5b051" class="progressself" type="circle" :percentage="percent"></el-progress></div>
<!-- :http-request="options => chunkedUpload(options, 0)" v-if="percent != 0"-->
<el-upload :headers="{ Authorization: `Bearer ${$store.getters.token}` }" :on-progress="uploadprogress" :on-exceed="onExceed" :on-remove="file => onRemove(file, upload)" :before-remove="file => beforeRemove(file, upload)" :before-upload="file => beforeUpload(file, upload)" :on-success="(response, file, fileList) => onSuccess(response, file, fileList, upload)" ref="upload" :action="upload.uploadUrl" :data="upload.uploadData" :on-error="onError" name="files" :on-change="changeAvatarUpload" :file-list="fileList" :show-file-list="false" :disabled="percent != 0">
<el-button slot="trigger" size="mini" type="warning" round icon="el-icon-upload2" plain :disabled="percent != 0">{{ fileList.length > 0 ? '重新上传' : '点击上传' }}</el-button>
</el-upload>
<!-- <el-button size="large" class="button">
选择视频文件
<input id="video-file" type="file" accept="video/*" @change="fileChange" />
</el-button> -->
<div class="tip">
视频格式:MP4<br />
视频时长:时长20分钟以内 <br />
视频分辨率:推荐上传720P及以上视频 <br />
视频大小:文件不能大于2GB,若超过请<span class="down" @click="herf">点击这里</span>下载压缩工具,并按操作手册压缩后重新上传
</div>
<div class="cover" v-if="ruleForm.avatarImg" v-loading="percent != 0" element-loading-background="rgba(0, 0, 0, 0.8)" element-loading-text="解析中">
<div class="left">
<!-- <canvas ref="canvasRef" :width="279" :height="157"></canvas> -->
<img :src="ossImgPath + ruleForm.avatarImg" :width="279" :height="157" />
</div>
<div class="right">
<div class="line1">
<div class="line1-title">支持从视频中截取或上传</div>
<div class="line1-nav">默认使用视频第一帧作为封面,清晰美观的封面有利于观看</div>
</div>
<div class="line2">
<el-button slot="trigger" size="mini" type="warning" class="change-cover" @click="openmodal">
<div class="btn-value"><img src="@/assets/img/img-icon.png" alt="" class="img-icon" />修改封面</div>
</el-button>
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')" :disabled="percent != 0">保存</el-button>
<el-button @click="resetForm('ruleForm')" :disabled="percent != 0">取消</el-button>
</el-form-item>
</el-form>
</div>
<videoCover ref="videoCover" :file="videoForm.file" :is-show="videoForm.comIsShow" @closeDialog="close" @confirmImg="confirmImg"></videoCover>
</div>
</template>
<script>
// import chunkedUpload from './chunkedUpload'
import { mapGetters } from 'vuex'
import mixins from './getossid'
import ak from '@/utils/common.js'
import axios from 'axios'
import videoCover from './captur.vue'
import { videoCourses } from '@/api/api.js'
import request from '@/utils/request.js'
export default {
components: {
videoCover
},
mixins: [mixins],
created() {
if (this.$route.query.id) {
this.getData()
}
this.select()
},
computed: {
...mapGetters(['userName']),
isptyy() {
//判断是否事运营角色
return (JSON.parse(window.localStorage.getItem('sysRoles')) || []).some(item => {
return item.code == 'qcr_ptyyjs'
})
}
},
data() {
const validatortype = (rule, value, callback) => {
if (value && parseInt(value) == 2 && !this.ruleForm.source) {
callback(new Error('请填写转载来源'))
} else {
this.rulePhone = true
callback()
}
}
const validatortitle = (rule, value, callback) => {
if (value.length < 3 || value.length > 50) {
callback(new Error('标题限制3~50个字符内'))
} else {
this.rulePhone = true
callback()
}
}
return {
ossImgPath: process.env.BASE_API + '/ykb-oss/oss/view?ossId=',
isloading: false, //loading
progress: false,
percent: 0,
progViode: 0,
isuploaded: false,
loading: null,
fileList: [],
upload: {
name: '上传资源',
ext: ['mp3', 'MP3', 'mp4', 'MP4'],
extText: 'mp3、mp4',
maxSize: 1024,
maxSizeText: '1GB',
required: true,
uploadData: {
param: JSON.stringify({
appid: 'oss7wqc3k',
appsecret: 'ok601gkg',
project: 'lms'
})
},
chunked: true,
uploadUrl: process.env.BASE_API + '/ykb-oss/oss/uploadFdfs'
},
ruleForm: {
title: '',
themeId: '',
source: '',
grades: [],
type: '',
remark: '',
avatarImg: '',
ossId: ''
},
// 分片上传自定义方法
// chunkedUpload: chunkedUpload,
videoForm: {
videoUrl: '',
file: {},
comIsShow: false,
duration: 0
},
classarr: [],
themList: [], //主题列表
rules: {
title: [
{ required: true, message: '请输入标题', trigger: 'blur' },
{ validator: validatortitle, trigger: 'change' }
],
themeId: [{ required: true, message: '请选择主题', trigger: 'change' }],
date1: [{ type: 'date', required: true, message: '请选择日期', trigger: 'change' }],
date2: [{ type: 'date', required: true, message: '请选择时间', trigger: 'change' }],
grades: [{ type: 'array', required: true, message: '请至少选择一个年级', trigger: 'change' }],
type: [
{ required: true, message: '请选择类型', trigger: 'change' },
{ validator: validatortype, trigger: 'change' }
]
// remark: [{ required: true, message: '请填写视频简介', trigger: 'blur' }]
}
}
},
methods: {
async getData() {
//获取详情
const res = await request(videoCourses.QcrClassieApiget, { id: this.$route.query.id })
this.ruleForm = { ...res.data, grades: res.data.gradeIds || [] }
let url = await this.getUrlByOssid(this.ruleForm.ossId)
this.videoForm.videoUrl = process.env.STORE_BASE_API + '/' + url.storePath
this.fileList = [{ ...url, name: url.fileName, status: 'success' }]
},
herf() {
location.href = 'http://119.23.255.35:8888/compress/compress.rar'
},
//获取主题,班级
async select() {
const res = await request(videoCourses.themeList)
this.themList = res
if (this.isptyy) {
//运营人员
let classe = await request(videoCourses.yyclass, {}, 'get')
this.classarr = classe.data
} else {
// 排课人员
let classe = await request(videoCourses.selectManageUserGradeRange, { userName: this.userName })
this.classarr = classe.data
}
},
submitForm(formName) {
this.$refs[formName].validate(async valid => {
if (valid) {
// alert('submit!')
if (!this.ruleForm.ossId) {
ak.error('请先上传视频!')
return false
}
// let sysRoles = (JSON.parse(window.localStorage.getItem('sysRoles')) || []).map(item => {
// if (item.qcr_admin) return item.code == 'qcr_ptyyjs'
// })
let code = (JSON.parse(window.localStorage.getItem('sysRoles')) || []).map(item => {
return item.code
})
//:管理人员 >运营角色>排课人员
let sysRoles = code.indexOf('qcr_ptyyjs') != -1 ? 'qcr_ptyyjs' : 'qcr_teacher'
let data = { ...this.ruleForm, duration: this.ruleForm.duration || this.videoForm.duration, manageType: sysRoles, type: sysRoles == 'qcr_ptyyjs' ? 1 : this.ruleForm.type }
const res = this.$route.query.id ? await request(videoCourses.QcrClassieApiupdata, data) : await request(videoCourses.QcrClassieApisave, data)
if (res.code == 0) {
ak.success('操作成功!')
this.$router.push({ path: '/videoCourses/schedule/video' })
}
} else {
console.log('error submit!!')
return false
}
})
},
resetForm(formName) {
this.$refs[formName].resetFields()
this.$router.push({ path: '/videoCourses/schedule/video' })
},
changeAvatarUpload(e) {},
// 文件上传前的验证
beforeUpload(file, event, scope) {
this.percent = 0
const tmp = file.name.split('.')
const ext = tmp[tmp.length - 1]
console.log('beforeUpload', file)
const suffix = file.type === 'video/mp4' || file.type === 'video/MP4'
// const isLt1M = file.size / 1024 / 1024 < 1
if (!suffix) {
this.$message.error('只能上传视频!')
return false
}
// const sizeIsRight = file.size / 1024 / 1024 <= event.maxSize
// if (!sizeIsRight) {
// ak.warn('文件大小不能超过' + event.maxSizeText)
// return false
// }
const uploadFiles = this.$refs.upload.uploadFiles
// 将文件格式,添加到$refs中
uploadFiles.forEach(item => {
if (item.uid === file.uid) {
item.ext = ext
}
})
this.isuploaded = false
// 如果上传的视频文件,则获取视频时长添加到$refs中
if (tmp[tmp.length - 1] === 'mp4' || tmp[tmp.length - 1] === 'MP4') {
return new Promise((resolve, reject) => {
var video = document.createElement('video') // 生成一个video元素
video.src = URL.createObjectURL(file) // 视频的路径
video.addEventListener('durationchange', () => {
uploadFiles.forEach(item => {
if (item.uid === file.uid) {
item.duration = parseInt(video.duration)
}
})
resolve()
})
setTimeout(() => {
if (Number.isNaN(video.duration)) {
ak.warn('该文件已损坏,无法上传,如有疑问,请联系管理员')
}
reject()
}, 2000)
})
}
this.loading = this.$loading({
target: this.$refs.cover,
text: '系统处理中',
background: 'transparent'
})
},
// 文件上传成功后
onSuccess(response, file, fileList, event, scope) {
// 将ossid添加到$refs中
this.fileList = [fileList[fileList.length - 1]]
const that = this
const uploadFiles = this.$refs.upload.uploadFiles
const exit = file.name.split('.')
const ext = exit[exit.length - 1]
uploadFiles.forEach(item => {
if (item.uid === file.uid) {
item.ossid = response.data[0].id
}
})
const tmp = fileList.filter(item => {
return item.status !== 'success'
})
if (file.raw) {
that.ruleForm.ossId = response.data[0].id
that.videoForm.videoUrl = URL.createObjectURL(file.raw)
that.videoForm.file = file.raw
that.videoForm.duration = fileList[fileList.length - 1].duration || 0
that.$nextTick(() => {
that.handleVideo()
})
}
console.log('fileList', fileList)
this.percent = 0
if (tmp.length === 0) {
// this.$refs.upload.clearValidate(event.keyName)
}
},
onError(err, file, fileList) {
this.$store.getters.chunkedUploadXhr.forEach(item => {
item.abort()
})
this.$alert('文件上传失败,请重试', '错误', {
confirmButtonText: '确定'
})
},
// 文件超出个数限制时的钩子
onExceed(files, fileList) {
// ak.warn('此处只能上传1个文件')
},
uploadprogress(e, file) {
this.percent = parseInt(e.percent)
},
// 文件删除前
beforeRemove(file, item, scope) {
// 如果正在分片上传,则取消分片上传
if (file.percentage !== 100 && item.chunked) {
this.$store.getters.chunkedUploadXhr.forEach(item => {
item.abort()
})
}
},
// 文件删除后,将文件id放到删除的数组中
onRemove(file, item, scope) {
this.ruleForm.ossId = ''
this.videoForm.videoUrl = ''
this.videoForm.file = {}
this.videoForm.comIsShow = false
this.ruleForm.avatarImg = ''
},
//自动获取第一帧的封面
handleVideo() {
const video = this.$refs.videoRef
let canvas = document.createElement('canvas')
video.currentTime = 1
video.addEventListener('loadeddata', () => {
// 在视频加载完成后执行以下操作
canvas.width = 279
canvas.height = 157
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
const dataURL = canvas.toDataURL('image/jpeg')
// 使用dataURL作为封面
this.getossid(dataURL).then(res => {
this.ruleForm.avatarImg = res
})
})
},
//打开弹窗
openmodal() {
this.videoForm.comIsShow = true
if (Object.keys(this.videoForm.file).length != 0) {
this.$refs.videoCover.changeFile(this.videoForm.file, this.videoForm.duration, this.videoForm.videoUrl)
}
},
//关闭组件回调
close() {
this.videoForm.comIsShow = false
},
//确认封面回调 data返回值
confirmImg(data) {
this.ruleForm.avatarImg = data.ossid
}
}
}
</script>
<style lang="scss" scoped>
.addvideo {
background: white;
min-height: calc(100vh - 70px);
.main {
width: 1200px;
margin: auto;
padding: 40px 0px;
.wid700 {
width: 700px;
}
/deep/ input.el-input__inner {
height: 34px;
line-height: 34px;
}
.uploadvideo {
.progress {
background: rgba(121, 114, 114, 0.7);
width: 700px;
height: 395px;
position: absolute;
top: 1px;
display: flex;
justify-content: center;
flex-direction: column;
.progressself {
text-align: center;
/deep/ .el-progress__text {
color: white;
}
}
/deep/ .el-progress-circle {
position: relative;
right: 45%;
left: 41%;
}
}
.button {
position: relative;
margin-bottom: 22px;
}
.button input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.videos {
width: 700px;
height: 395px;
display: block;
margin-bottom: 30px;
}
.tip {
line-height: 21px;
color: #c0c4cc;
font-size: 14px;
.down {
color: #3f9dfd;
cursor: pointer;
}
}
.cover {
padding: 20px;
background: #f5f7faff;
display: flex;
flex-direction: row;
margin-top: 10px;
.right {
display: flex;
flex-direction: column;
justify-content: space-between;
.line1 {
&-title {
color: #1c1f21ff;
font-size: 14px;
font-weight: 600;
line-height: 28px;
}
&-nav {
color: #585f66ff;
font-size: 12px;
line-height: 20px;
}
}
.line2 {
.change-cover {
height: 34px;
background: #f5b051;
border-radius: 4px 4px 4px 4px;
opacity: 1;
&:hover {
opacity: 0.7;
}
}
.btn-value {
display: flex;
line-height: 20px;
}
.img-icon {
width: 20px;
height: 20px;
}
}
}
.left {
width: 279px;
height: 157px;
margin-right: 20px;
}
}
}
}
/deep/ i.el-icon-document::before {
display: block;
content: ' ';
width: 16px;
height: 16px;
background: url(../../../../assets/img/coursr/mp4.png) no-repeat;
background-size: 100% 100%;
}
}
</style>
组件
<template>
<div>
<el-dialog :visible.sync="isShow" title="设置封面" :close-on-click-modal="false" width="750px" class="dialog-dfl" :before-close="beforeClose">
<div>
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="封面截取" name="one">
<div class=" nochose" v-if="!file">
暂不支持预览选帧
<div>重新上传本地视频,再截选一帧;或直接<span @click="uploadimg">上传图片</span></div>
</div>
<div class="conts" v-else style="height: 403px" v-loading="loading" element-loading-background="#fff" element-loading-text="解析中">
<div v-show="!loading">
<div class="look_img">
<img :src="cover.ossImgPath + imgForm.ossid" alt="" :key="imgForm.ossid" />
</div>
<div class="imgs_list_box">
<div class="imgs_list">
<div v-for="(item, index) in imgForm.img_list" :key="index" class="imgs_item">
<img :src="item" alt="" />
</div>
<div class="slider-dfl">
<span style="color:transparent">{{ imgForm.videoTime }} {{ sliderVal }}</span>
<el-slider v-model="sliderVal" :step="0.01" :min="0" :max="imgForm.videoTime" placement="bottom" :format-tooltip="formatTooltip" @change="sliderChange" />
</div>
</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="本地上传" name="two">
<div v-loading="false" element-loading-background="#fff" element-loading-text="解析中" style="height: 403px" class="cover-upload">
<div v-if="true" class="conts_right">
<img class="detailBg" :src="cover.ossImgPath + coverImg.ossid" alt="" v-if="coverImg.ossid" />
<el-upload :action="cover.coverUploadUrl" class="upload-img" :headers="{ Authorization: `Bearer ${$store.getters.token}` }" :show-file-list="false" :data="cover.uploadData" name="files" ref="coverImg" key="coverImg" :before-upload="beforeAvatarUpload" :on-success="(response, file, fileList) => handlecoverAvatarSuccess(response, file, fileList)">
<div class="noimg-upload" v-if="!coverImg.ossid">
<img src="@/assets/img/upload.png" alt="" class="upload-icon" />
<el-button class="fot-btn upload-btn">
上传封面
</el-button>
</div>
<div v-else class="re-upload"><img src="@/assets/img/reupload.png" alt="" class="reupload-icon" />重新上传</div>
</el-upload>
<!-- <div class="el-upload__tip">图片上传尺寸建议370*370,文件小于400K,仅支持JPG,PNG</div> -->
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<div class="dialog-footer">
<el-button class="calcle-btn fot-btn" @click="beforeClose">
取消
</el-button>
<el-button class="submit-btn fot-btn" @click="confirmCover">
确认
</el-button>
<!-- <div v-show="activeName == 'two'" class="bottom_btn">
<div class="r">
<el-button color="#fe3355" @click="confirmCoverTwo">
确认封面
</el-button>
</div>
</div> -->
</div>
</template>
</el-dialog>
</div>
</template>
<script>
// import { VueCropper } from 'vue-cropper'
import mixins from './getossid'
export default {
// components: { VueCropper },
props: {
isShow: {
type: Boolean,
default: () => true
}
//传过来的视频文件
// file: {
// type: Object,
// default: () => ({})
// }
},
mixins: [mixins],
data() {
return {
loading: true,
file: null,
activeName: 'one',
sliderVal: 0,
sliderVal2: 20,
cropperRef: {},
coverImg: {}, //上传封面
imgForm: {
url: '', //封面预览地址
urlTwo: '', //封面预览地址
blob: {}, //封面blob对象
img_list: [], //底部预览条图片数组
videoTime: 0, //视频时长
ossid: '',
oldVideoFile: {} //旧的视频文件
},
cover: {
coverUploadUrl: process.env.BASE_API + '/ykb-oss/oss/uploadFdfs',
ossImgPath: process.env.BASE_API + '/ykb-oss/oss/view?ossId=',
uploadData: {
param: JSON.stringify({
appid: 'oss7wqc3k',
appsecret: 'ok601gkg',
project: 'lms',
opDocType: ''
})
}
},
option: {
img: '',
outputSize: 1, // 裁剪生成图片的质量
outputType: 'jpeg', // 裁剪生成图片的格式 jpeg, png, webp
info: true, // 裁剪框的大小信息
canScale: true, // 图片是否允许滚轮缩放
autoCrop: true, // 是否默认生成截图框
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: false, // 固定截图框大小 不允许改变
fixed: false, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例 [ 宽度 , 高度 ]
canMove: true, // 上传图片是否可以移动
canMoveBox: true, // 截图框能否拖动
original: false, // 上传图片按照原始比例渲染
centerBox: false, // 截图框是否被限制在图片里面
infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
full: false, // 是否输出原图比例的截图
enlarge: '1', // 图片根据截图框输出比例倍数
mode: 'contain' // 图片默认渲染方式 contain , cover, 100px, 100% auto,
},
duration: '',
url: '',
cropperForm: {
imgLookUrl: '', //裁剪实时预览
bgColor: '#fff', //裁剪图片底色
loadings: true
}
}
},
watch: {
file(newVal, oldVal) {
// if (this.isShow == true && this.file.type) {
// if (this.file.type.includes('video')) {
// //通过验证
// if (newVal.name != this.imgForm.oldVideoFile.name && newVal.size != this.imgForm.oldVideoFile.size) {
// //是否已经选择了这个视频文件 选择了相同的文件就不用初始化了 如果不同就初始化
// this.loading = true
// this.cropperForm.loadings = true
// this.activeName = 'one'
// this.init()
// }
// } else {
// //未通过验证
// this.$message({
// message: '请选择视频格式的文件',
// grouping: true,
// type: 'error'
// })
// this.$emit('closeDialog', false)
// }
// } else if (this.isShow == true) {
// //未通过验证
// this.$message({
// message: '请选择视频格式的文件',
// grouping: true,
// type: 'error'
// })
// this.$emit('closeDialog', false)
// }
},
isShow(newVal, oldVal) {
this.sliderVal = 0.1
this.coverImg = {}
console.log(this.file)
// this.sliderChange(0.1)
// if (this.isShow == true && this.file.type) {
// if (this.file.type.includes('video')) {
// //通过验证
// if (newVal[0].name != this.imgForm.oldVideoFile.name && newVal[0].size != this.imgForm.oldVideoFile.size) {
// //是否已经选择了这个视频文件 选择了相同的文件就不用初始化了 如果不同就初始化
// this.loading = true
// this.cropperForm.loadings = true
// this.activeName = 'one'
// this.init()
// }
// } else {
// //未通过验证
// this.$message({
// message: '请选择视频格式的文件',
// grouping: true,
// type: 'error'
// })
// this.$emit('closeDialog', false)
// }
// } else if (this.isShow == true) {
// //未通过验证
// this.$message({
// message: '请选择视频格式的文件',
// grouping: true,
// type: 'error'
// })
// this.$emit('closeDialog', false)
// }
}
},
created() {},
methods: {
uploadimg() {
this.activeName = 'two'
this.$refs['coverImg'].$refs['upload-inner'].handleClick()
},
changeVideo() {
//关闭弹窗并且触发上传本地视频
this.$emit('closeDialog', false)
},
changeFile(newVal, duration, url) {
this.file = newVal
this.duration = duration
this.url = url
// {
// uid: 1698891870169,
// lastModified: 1692350189838,
// name: 'b.mp4',
// size: 15250255,
// type: 'video/mp4',
// webkitRelativePath: ''
// }
console.log('changeFile', newVal, duration, url)
if (this.file.type) {
if (this.file.type.includes('video')) {
//通过验证
if (newVal.name != this.imgForm.oldVideoFile.name && newVal.size != this.imgForm.oldVideoFile.size) {
//是否已经选择了这个视频文件 选择了相同的文件就不用初始化了 如果不同就初始化
this.loading = true
this.cropperForm.loadings = true
this.activeName = 'one'
this.init()
}
} else {
//未通过验证
this.$message({
message: '请选择视频格式的文件',
grouping: true,
type: 'error'
})
this.$emit('closeDialog', false)
}
} else if (this.isShow == true) {
//未通过验证
this.$message({
message: '请选择视频格式的文件',
grouping: true,
type: 'error'
})
this.$emit('closeDialog', false)
}
},
init() {
this.imgForm.url = ''
this.imgForm.ossid = ''
this.imgForm.blob = {}
this.imgForm.img_list = []
this.imgForm.videoTime = 0
this.imgForm.oldVideoFile = this.file
let reader = new FileReader()
let that = this
//获取视频时长
reader.onload = function(e) {
let video = document.createElement('video')
// @ts-ignore
video.src = e.target.result || that.url
video.addEventListener('loadedmetadata', async function() {
// 这里先看900宽度能放几张图片
const img_src = await that.captureFrame(that.file, Math.floor(video.duration || that.duration))
var img_load = document.createElement('img')
img_load.setAttribute('src', img_src.url)
img_load.onload = function() {
var aspectRatio = img_load.naturalWidth / img_load.naturalHeight
// option.fixedNumber[0] =
// parseFloat((img_load.width / img_load.height).toFixed(2)) - 0.2;
that.option.fixedNumber[0] = img_load.width / img_load.height
var width = 50 * aspectRatio
let count = Math.floor(540 / width) // 总宽度为960 看能放几张图片
let duration = Math.floor(video.duration) //取整
that.imgForm.videoTime = duration
var step = Math.floor(duration / (count - 1)) // 步长
var result = [] // 存储结果的数组
for (var i = 0; i < count; i++) {
result.push(i * step)
}
if (result[0] == 0) {
result[0] = 0.1
}
result.forEach(async (item, index) => {
const res = await that.captureFrame(that.file, item)
if (index == 0) {
that.imgForm.url = res.url
that.getImageBase64(res.blob).then(res => {
that.getossid(res).then(resid => {
that.imgForm.ossid = resid
})
})
that.imgForm.blob = res.blob
}
that.imgForm.img_list.push(res.url)
})
that.sliderChange(0.1)
that.$nextTick(() => {
setTimeout(() => {
that.loading = false
}, 2000)
})
}
})
}
// @ts-ignore
reader.readAsDataURL(this.file)
},
//滑块位置改变 更滑上方主封面图
async sliderChange(val) {
let that = this
const res = await this.captureFrame(this.file, val)
this.imgForm.url = res.url
that.getImageBase64(res.blob).then(res1 => {
that.getossid(res1).then(resid => {
that.$set(that.imgForm, 'ossid', resid)
})
})
this.imgForm.blob = res.blob
},
// 格式化提示时间
formatTooltip(val) {
var timeString = this.convertSeconds(val)
return timeString
},
//关闭模态弹窗
beforeClose() {
this.$emit('closeDialog', false)
},
//确认封面选择封面
confirmCover() {
this.$emit('closeDialog', false)
if (this.activeName == 'one') {
this.$emit('confirmImg', {
url: this.imgForm.url,
blob: this.imgForm.blob,
ossid: this.imgForm.ossid
})
} else {
this.$emit('confirmImg', {
url: this.cover.ossImgPath + this.coverImg.ossid,
ossid: this.coverImg.ossid
// blob: this.imgForm.blob
})
}
},
// 获取视频帧的封面
captureFrame(videoFile, time = 0) {
return new Promise(succeed => {
const video = document.createElement('video')
video.currentTime = time
video.muted = true
video.autoplay = true
video.oncanplay = async () => {
const res = await this.drawVideo(video)
succeed(res)
}
video.src = URL.createObjectURL(videoFile)
})
},
// 画视频
drawVideo(video) {
return new Promise(res => {
const cvs = document.createElement('canvas')
const ctx = cvs.getContext('2d')
cvs.width = video.videoWidth
cvs.height = video.videoHeight
ctx.drawImage(video, 0, 0, cvs.width, cvs.height)
cvs.toBlob(blob => {
res({
blob,
url: URL.createObjectURL(blob)
})
})
})
},
// 秒数换算时间
convertSeconds(seconds) {
var hours = parseInt(seconds / 3600)
var minutes = parseInt(seconds / 60)
var remainingSeconds = parseInt(seconds % 60) //秒
var millisecond = Math.floor((seconds % 60) * 10) //毫秒
var timeString = ''
if (hours > 0) {
timeString += hours + ':'
}
timeString += minutes + ':' + remainingSeconds
return timeString
},
//封面上传
beforeAvatarUpload(file) {
const suffix = file.type === 'image/jpg' || file.type === 'image/png' || file.type === 'image/jpeg'
const isLt1M = file.size / 1024 / 1024 < 1
if (!suffix) {
this.$message.error('只能上传图片!')
return false
}
if (!isLt1M) {
this.$message.error('上传图片大小不能超过 1MB!')
return false
}
},
//封面上传
handlecoverAvatarSuccess(response, file, fileList, index, scope) {
// console.log(this.$refs['coverImg' + index].uploadFiles, response, file, fileList)
let data = {
id: file.id || null,
fileName: file.name,
ossid: response.data[0].id,
fileSize: file.size,
fileExt: file.ext,
type: 6,
duration: file.duration || 0
}
this.$set(this, 'coverImg', data)
},
//本地封面确定事件
confirmCoverTwo() {
// @ts-ignore
this.$refs.cropperRef.getCropData(data => {
const image = new Image()
image.src = data
image.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = image.width
canvas.height = image.height
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
canvas.toBlob(blob => {
this.$emit('closeDialog', false)
this.$emit('confirmImg', {
url: URL.createObjectURL(blob),
blob
})
})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.conts {
height: 403px;
box-sizing: border-box;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: center;
.look_img {
display: flex;
justify-content: center;
margin-bottom: 25px;
img {
width: 540px !important;
height: 309px !important;
border-radius: 4px;
}
}
.imgs_list_box {
display: flex;
align-items: center;
justify-content: center;
}
.imgs_list {
position: relative;
width: auto !important;
height: 91px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
.imgs_item {
img {
width: auto !important;
height: 60px !important;
}
}
}
}
.nochose {
text-align: center;
height: 403px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
div {
margin-top: 10px;
}
span {
color: #00aeec;
cursor: pointer;
}
}
.slider-dfl {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
.cover-upload {
display: flex;
// align-items: center;
justify-content: center;
}
.conts_right {
height: 309px;
width: 540px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
background: #f7f7f7ff;
border: 1px dashed #d9d9d9;
position: relative;
.l {
flex: 1;
height: 100%;
border: 1px solid #e4e7ed;
margin-right: 20px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.r {
flex: 1;
height: 100%;
border: 1px solid #e4e7ed;
}
.upload-icon {
width: 40px;
height: 40px;
margin-bottom: 24px;
}
.noimg-upload {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.upload-btn {
background: #f5b051ff;
color: #ffffffff;
border: 1px solid #f5b051ff;
&:hover {
opacity: 0.7;
}
}
.detailBg {
width: 100%;
height: 100%;
}
.re-upload {
position: absolute;
bottom: -30px;
width: 200px;
color: #f03e40ff;
text-align: left;
right: calc(50% - 20px);
left: calc(50% - 20px);
.reupload-icon {
width: 13px;
height: 13px;
position: relative;
top: 1px;
margin-right: 4px;
}
}
}
.bottom_btn {
display: flex;
align-items: center;
justify-content: space-between;
.l {
display: flex;
align-items: center;
}
}
#img-file {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.dialog-footer {
text-align: center;
}
.fot-btn {
width: 110px;
}
.calcle-btn {
color: #848a90ff;
border: 1px solid #dcdfe6ff;
&:hover {
opacity: 0.7;
background-color: #f5f7faff;
}
}
.submit-btn {
color: #ffffffff;
background: #f03e40ff;
border: 1px solid #f03e40ff;
&:hover {
opacity: 0.7;
}
}
/deep/ .el-dialog__headerbtn .el-dialog__close {
color: #1c1f21ff;
font-weight: 600;
}
/deep/ button.el-dialog__headerbtn {
background: #f5f7faff;
padding: 4px 2px 2px 2px;
}
/deep/ span.el-dialog__title {
color: #1c1f21ff;
font-size: 20px;
font-weight: 400;
}
/deep/ .el-dialog__header {
border-bottom: 1px solid #f3f5f6ff;
}
/deep/ .dialog-dfl .el-dialog__body {
padding-top: 24px;
padding-bottom: 0px;
}
/deep/ .dialog-dfl .el-tabs__nav-wrap.is-top {
text-align: center;
}
/deep/ .dialog-dfl .el-tabs__nav-scroll {
display: inline-block;
}
/deep/ .dialog-dfl .el-tabs__item.is-top.is-active {
color: #f03e40ff;
margin-bottom: 7px;
}
/deep/ .dialog-dfl .el-tabs__item.is-top {
font-size: 18px;
height: 21px;
line-height: 21px;
color: #1c1f21;
}
/deep/ .dialog-dfl .el-slider__button {
position: relative !important;
width: 18px !important;
height: 65px !important;
border: 2px solid #fe3355;
border-radius: 4px;
transform: translateY(15%);
}
/deep/ .dialog-dfl .el-tabs__nav-wrap::after {
background: transparent;
}
/deep/ .dialog-dfl .el-slider__button::after {
position: absolute;
left: 5px;
top: 50%;
transform: translateY(-50%);
content: '';
background-color: #ebebeb;
border-radius: 1.5px;
height: 34px;
width: 3px;
}
/deep/ .dialog-dfl .el-slider__button::before {
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
content: '';
background-color: #ebebeb;
border-radius: 1.5px;
height: 34px;
width: 3px;
}
/deep/ .dialog-dfl .el-slider__runway {
background-color: transparent !important;
height: 66px !important;
// top: -21px;
margin: 0px;
width: 99%;
}
/deep/ .el-slider__bar {
height: 66px !important;
// transform: translateY(-48%);
background-color: transparent;
}
/deep/ .dialog-dfl .el-slider {
height: 100% !important;
}
/deep/ .dialog-dfl .el-loading-spinner .path {
stroke: #fe3355 !important;
}
/deep/ .dialog-dfl .el-loading-spinner .el-loading-text {
color: #c1c1c1 !important;
margin-top: 10px;
}
/deep/ .el-slider__button {
height: 74px;
}
</style>
js,64转为id自定义上传事件,
import axios from 'axios'
import ak from '@/utils/common.js'
export default {
data() {
return {}
},
methods: {
async getossid(urlData, fileName = new Date().getTime() + 'cover.jpg') {
//获取提图片的ossid
let arr = urlData.split(',')
let mime = arr[0].match(/:(.*?);/)[1]
let bytes = atob(arr[1]) // 解码base64
let n = bytes.length
let ia = new Uint8Array(n)
while (n--) {
ia[n] = bytes.charCodeAt(n)
}
let file = new File([ia], fileName, { type: mime })
const url = process.env.BASE_API + '/ykb-oss/oss/uploadFdfs'
let formData = new FormData()
formData.append('files', new File([ia], fileName, { type: mime }))
formData.append('param', JSON.stringify({ appid: 'oss7wqc3k', appsecret: 'ok601gkg', project: 'lms' }))
const fileInfo = await axios({
method: 'post',
url,
// url: '/api/ykb-oss/oss/mergeChunkFile',
headers: {
Authorization: `Bearer ${this.$store.getters.token}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: formData
})
// this.firstImage = fileInfo.data.data[0].id
return fileInfo.data.data[0].id
// return new File([ia], fileName, { type: mime })
},
getImageBase64(blob) {
//blob转换为base64
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onload = () => {
const base64 = reader.result
resolve(base64)
}
reader.onerror = error => reject(error)
})
},
// 根据ossid获取文件实际的路径
async getUrlByOssid(ossId) {
const res = await axios({
method: 'post',
headers: {
Authorization: 'Bearer ' + localStorage.getItem('token')
},
url: process.env.BASE_API + '/ykb-oss/oss/getStoragePath',
data: { ossId: ossId, type: 'S00000002-200040' }
})
if (res.data.code !== 8200) {
if (res.data.message) {
ak.error(res.data.message)
return Promise.reject(res.data.message)
} else {
ak.error('通讯错误,请联系管理员')
return Promise.reject('通讯错误,请联系管理员')
}
} else {
return res.data.data
}
}
}
}
效果: