|
@@ -0,0 +1,404 @@
|
|
|
+<script setup>
|
|
|
+import MD5 from 'js-md5'
|
|
|
+import {getOSSSign,bannerupload} from '@/api/api.js'
|
|
|
+import {VideoInterface} from '@/api/modules/trainingApi'
|
|
|
+import AddTags from './components/addTags.vue'
|
|
|
+import {useVideo} from './hooks/use-video'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { ref, reactive } from 'vue'
|
|
|
+import { useRoute, useRouter } from "vue-router"
|
|
|
+import _ from 'lodash'
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+const router = useRouter()
|
|
|
+const {
|
|
|
+ classifyList,getClassifyList,
|
|
|
+ tagList,getTagList,
|
|
|
+ getDataClassify,
|
|
|
+ previewPop,previewVideo,previewVideoUrl,previewPopTitle,
|
|
|
+ handlePreviewVideo,endingPreview
|
|
|
+} = useVideo()
|
|
|
+getClassifyList()
|
|
|
+
|
|
|
+let form = reactive({
|
|
|
+ Title: '',
|
|
|
+ Introduce: '',
|
|
|
+ ClassifyId: '',
|
|
|
+ TagIds: [],
|
|
|
+ CoverImg: '',
|
|
|
+ VideoUrl: ''
|
|
|
+})
|
|
|
+const formRef = ref(null)
|
|
|
+const rules = reactive({
|
|
|
+ ClassifyId:[{required:true,message:'请选择所属分类'}],
|
|
|
+ Title:[{required:true,message:'请输入视频名称'}],
|
|
|
+ CoverImg:[{required:true,message:'请选择视频封面'}],
|
|
|
+ VideoUrl:[{required:true,message:'请上传视频'}],
|
|
|
+ TagIds:[{required:true,validator:(rule,value,callback)=>{
|
|
|
+ if(!value.length){
|
|
|
+ return callback(new Error("请至少选择一个标签"))
|
|
|
+ }else{
|
|
|
+ return callback()
|
|
|
+ }
|
|
|
+ }}]
|
|
|
+})
|
|
|
+
|
|
|
+let tagIdKey = ref(0)
|
|
|
+let isModifyDialogShow = ref(false)
|
|
|
+let isImageUploading = ref(false)
|
|
|
+//检查图片是否合法
|
|
|
+function handleUploadImg(file){
|
|
|
+ isImageUploading.value = true;
|
|
|
+ //图片格式限制
|
|
|
+ const { type } = file.file;
|
|
|
+ if (!['image/png', 'image/jpeg'].includes(type)) {
|
|
|
+ ElMessage.warning('仅支持png、jpg格式的图片');
|
|
|
+ isImageUploading.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ uploadImg(file);
|
|
|
+}
|
|
|
+//上传图片
|
|
|
+function uploadImg(file) {
|
|
|
+ let formData = new FormData();
|
|
|
+ formData.append('file', file.file);
|
|
|
+ bannerupload(formData).then(res => {
|
|
|
+ isImageUploading.value = false;
|
|
|
+ if (res.Ret !== 200) return;
|
|
|
+ form.CoverImg = res.Data.ResourceUrl;
|
|
|
+ });
|
|
|
+}
|
|
|
+let isVideoUploading = ref(false)
|
|
|
+let percentage = ref(0)
|
|
|
+let timeDuration = ref('')
|
|
|
+//检查视频是否合法,并获取视频时长
|
|
|
+async function handleUploadVideo(file) {
|
|
|
+ if(file.file.type!='video/mp4'){
|
|
|
+ ElMessage.warning('上传失败,上传视频格式不正确');
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const duration=await handleGetDuration(file.file);
|
|
|
+ timeDuration.value = `${String(parseInt(duration/60)).padStart(2,'0')}:${String(parseInt(duration%60)).padStart(2,'0')}`;
|
|
|
+ uploadVideo(file.file);
|
|
|
+ isVideoUploading.value = true;
|
|
|
+}
|
|
|
+//获取视频时长的promise
|
|
|
+async function handleGetDuration(file){
|
|
|
+ return new Promise((resolve,reject)=>{
|
|
|
+ const fileUrl=URL.createObjectURL(file);
|
|
|
+ const audioEl=new Audio(fileUrl);
|
|
|
+ audioEl.addEventListener('loadedmetadata',(e)=>{
|
|
|
+ const t=e.composedPath()[0].duration;
|
|
|
+ resolve(t);
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+//上传视频
|
|
|
+async function uploadVideo(file) {
|
|
|
+ const res = await getOSSSign();
|
|
|
+ if(res.Ret===200){
|
|
|
+ handleUploadToOSS(file,res.Data);
|
|
|
+ }
|
|
|
+}
|
|
|
+//上传到阿里云
|
|
|
+let ALOSSINS=null //阿里云上传实例
|
|
|
+let ALOSSAbortCheckpoint=null //阿里云上传实例中断点
|
|
|
+async function handleUploadToOSS(file,{AccessKeyId,AccessKeySecret,SecurityToken}){
|
|
|
+ ALOSSINS=new OSS({
|
|
|
+ // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
|
|
+ region: "oss-cn-shanghai",
|
|
|
+ // 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
|
|
+ accessKeyId: AccessKeyId,
|
|
|
+ accessKeySecret: AccessKeySecret,
|
|
|
+ // 从STS服务获取的安全令牌(SecurityToken)。
|
|
|
+ stsToken: SecurityToken,
|
|
|
+ // 填写Bucket名称,例如examplebucket。
|
|
|
+ bucket: "hzchart",
|
|
|
+ endpoint:'hzstatic.hzinsights.com',
|
|
|
+ cname:true,
|
|
|
+ timeout:600000000000
|
|
|
+ });
|
|
|
+ // 生成文件名
|
|
|
+ const t=new Date().getTime().toString();
|
|
|
+ const temName=`static/yb/video/${MD5(t)}.${file.type.split('/')[1]}`;
|
|
|
+ const options = {
|
|
|
+ // 获取分片上传进度、断点和返回值。
|
|
|
+ progress: (p, cpt, res) => {
|
|
|
+ ALOSSAbortCheckpoint=cpt;
|
|
|
+ percentage.value=parseInt(p*100);
|
|
|
+ },
|
|
|
+ // 设置并发上传的分片数量。
|
|
|
+ parallel: 10,
|
|
|
+ // 设置分片大小。默认值为1 MB,最小值为100 KB。
|
|
|
+ partSize: 1024 * 1024 * 10, // 10MB
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ const res=await ALOSSINS.multipartUpload(temName,file,{...options});
|
|
|
+ console.log('上传结果',res);
|
|
|
+ if(res.res.status===200){
|
|
|
+ form.VideoUrl='https://hzstatic.hzinsights.com/'+res.name;
|
|
|
+ percentage.value=0;
|
|
|
+ ALOSSAbortCheckpoint=null;
|
|
|
+ isVideoUploading.value = false;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log('上传到阿里云失败',error);
|
|
|
+ if(error.name!=="cancel"){//不是取消上传的则给错误提示
|
|
|
+ ElMessage.warning('上传失败,请刷新重试');
|
|
|
+ }
|
|
|
+ percentage.value=0;
|
|
|
+ ALOSSAbortCheckpoint=null;
|
|
|
+ isVideoUploading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+//预览视频
|
|
|
+function handleOpenPreviewDialog(){
|
|
|
+ if(isVideoUploading.value||!form.VideoUrl) return
|
|
|
+ const data = {
|
|
|
+ Title:form.Title||'暂无标题',
|
|
|
+ VideoUrl:form.VideoUrl
|
|
|
+ }
|
|
|
+ handlePreviewVideo(data)
|
|
|
+
|
|
|
+}
|
|
|
+//删除所选标签
|
|
|
+function removeTag(tag) {
|
|
|
+ const index = form.TagIds.findIndex(i => i.TagId === tag.TagId);
|
|
|
+ index !== -1 && (form.TagIds.splice(index, 1));
|
|
|
+ tagIdKey++;
|
|
|
+}
|
|
|
+//改变所选标签
|
|
|
+function modifyTags(tags) {
|
|
|
+ form.TagIds = _.cloneDeep(tags);
|
|
|
+ isModifyDialogShow.value = false;
|
|
|
+}
|
|
|
+//编辑视频-获取视频信息
|
|
|
+function getVideoDetail() {
|
|
|
+ const { VideoId } = route.query;
|
|
|
+ if (!Number(VideoId)) return;
|
|
|
+ VideoInterface.getVideoDetail({
|
|
|
+ VideoId: Number(VideoId)
|
|
|
+ }).then(res => {
|
|
|
+ if (res.Ret !== 200)
|
|
|
+ return;
|
|
|
+ Object.assign(form,res.Data||{})
|
|
|
+ if (form.Classify) {
|
|
|
+ const { Classify } = form;
|
|
|
+ const classifyArr = getDataClassify(Classify,[],'ClassifyId');
|
|
|
+ form.ClassifyId = classifyArr[classifyArr.length - 1];
|
|
|
+ delete form.Classify;
|
|
|
+ }
|
|
|
+ if (form.Tags) {
|
|
|
+ form.TagIds = form.Tags;
|
|
|
+ delete form.Tags;
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+getVideoDetail()
|
|
|
+//添加/编辑视频
|
|
|
+async function modifyVideo(type='modify') {
|
|
|
+ try{
|
|
|
+ await formRef.value?.validate()
|
|
|
+ }catch(e){
|
|
|
+ console.log(e)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let res = null;
|
|
|
+ let params = { ...form, ...{ TagIds: form.TagIds.map(t => t.TagId) } };
|
|
|
+ if (!form.VideoId) {
|
|
|
+ res = await VideoInterface.addVideo(params);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ res = await VideoInterface.editVideo(params);
|
|
|
+ }
|
|
|
+ if (res.Ret !== 200)
|
|
|
+ return;
|
|
|
+ type!=='publish'&&ElMessage.success(`${form.VideoId ? '编辑' : '添加'}成功`);
|
|
|
+ type!=='publish'&&changeRoute();
|
|
|
+ !form.VideoId&&(form.VideoId = res.Data?res.Data:'');
|
|
|
+}
|
|
|
+//发布视频
|
|
|
+async function publishVideo(){
|
|
|
+ let res = {}
|
|
|
+ await modifyVideo('publish');
|
|
|
+ if(form.VideoId){
|
|
|
+ res = await VideoInterface.publishVideo({VideoId:Number(form.VideoId),PublishState:1})
|
|
|
+ if(res.Ret!==200) return;
|
|
|
+ ElMessage.success("发布成功");
|
|
|
+ }
|
|
|
+ changeRoute();
|
|
|
+}
|
|
|
+function changeRoute(){
|
|
|
+ if(ALOSSAbortCheckpoint){
|
|
|
+ console.log('终止上传');
|
|
|
+ ALOSSINS.abortMultipartUpload(ALOSSAbortCheckpoint.name,ALOSSAbortCheckpoint.uploadId)
|
|
|
+ }
|
|
|
+ router.push('/trainingVideo');
|
|
|
+}
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <!-- 新增编辑视频 -->
|
|
|
+ <div class="modify-video-page-wrap">
|
|
|
+ <el-form :model="form" :rules="rules" ref="formRef">
|
|
|
+ <el-form-item label="所属分类" prop="ClassifyId">
|
|
|
+ <el-cascader placeholder="选择所属分类"
|
|
|
+ v-model="form.ClassifyId"
|
|
|
+ :options="classifyList"
|
|
|
+ clearable
|
|
|
+ :show-all-levels="false"
|
|
|
+ :props="{emitPath:false,
|
|
|
+ label:'ClassifyName',
|
|
|
+ value:'ClassifyId',
|
|
|
+ children:'Children'}">
|
|
|
+ </el-cascader>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="视频名称" prop="Title">
|
|
|
+ <el-input v-model="form.Title" placeholder="请输入视频名称"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="视频简介" prop="Introduce">
|
|
|
+ <el-input v-model="form.Introduce"
|
|
|
+ placeholder="请输入视频简介"
|
|
|
+ type="textarea" maxlength="50" show-word-limit :rows="5"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="上传封面" prop="CoverImg">
|
|
|
+ <el-upload action="" accept="image/*"
|
|
|
+ :http-request="handleUploadImg" :show-file-list="false" :disabled="isImageUploading">
|
|
|
+ <el-button type="primary" :loading="isImageUploading">点击上传</el-button>
|
|
|
+ <span style="color:#999999;margin-left: 5px;" @click.stop>建议尺寸比例3:2,支持png、jpg、gif、jpeg格式</span>
|
|
|
+ </el-upload>
|
|
|
+ <div class="img-box">
|
|
|
+ <img :src="form.CoverImg" v-if="form.CoverImg">
|
|
|
+ <span v-else style="color:#999999;line-height: 100px;">请上传封面图</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="上传视频" prop="VideoUrl">
|
|
|
+ <el-upload action="" accept=".mp4"
|
|
|
+ :http-request="handleUploadVideo" :show-file-list="false" :disabled="isVideoUploading">
|
|
|
+ <el-button type="primary" :loading="isVideoUploading">点击上传</el-button>
|
|
|
+ <span style="color:#999999;margin-left: 5px;" @click.stop>仅支持mp4格式</span>
|
|
|
+ </el-upload>
|
|
|
+
|
|
|
+ <div class="img-box">
|
|
|
+ <el-progress type="circle" :percentage="percentage" :width="40" v-if="isVideoUploading"></el-progress>
|
|
|
+ <span v-if="form.VideoUrl&&!form.VideoId" class="duration">{{timeDuration}}</span>
|
|
|
+ <img :src="form.CoverImg" v-if="form.VideoUrl" @click="handleOpenPreviewDialog">
|
|
|
+ <span v-else style="color:#999999;line-height: 100px;">请上传视频</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="视频标签" prop="TagIds">
|
|
|
+ <el-link :underline="false" type="primary" @click="isModifyDialogShow = true">选择标签</el-link>
|
|
|
+ <div class="tag-list" :key="tagIdKey">
|
|
|
+ <span class="tag-item" v-for="tag in form.TagIds" :key="tag.TagId">
|
|
|
+ <span>{{tag.TagName}}</span>
|
|
|
+ <span @click.stop="removeTag(tag)"><i class="el-icon-close"></i></span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div class="btn-box">
|
|
|
+ <el-button type="primary" plain @click="changeRoute">取消</el-button>
|
|
|
+ <el-button type="primary" @click="modifyVideo" :loading="isImageUploading||isVideoUploading">保存</el-button>
|
|
|
+ <el-button type="primary" @click="publishVideo" :loading="isImageUploading||isVideoUploading">发布</el-button>
|
|
|
+ </div>
|
|
|
+ <!-- 选择标签弹窗 -->
|
|
|
+ <AddTags
|
|
|
+ :isModifyDialogShow="isModifyDialogShow"
|
|
|
+ :Tags="form.TagIds"
|
|
|
+ :tagList="tagList"
|
|
|
+ :getTagList="getTagList"
|
|
|
+ @modify="modifyTags"
|
|
|
+ @close="isModifyDialogShow=false"
|
|
|
+ />
|
|
|
+ <!-- 预览视频弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="previewPop"
|
|
|
+ :modal-append-to-body='false'
|
|
|
+ width="60vw"
|
|
|
+ :title="previewPopTitle"
|
|
|
+ @close="endingPreview"
|
|
|
+ >
|
|
|
+ <video style="width: 100%;height: 100%;max-height: 70vh;outline: none;"
|
|
|
+ controls :src="previewVideoUrl" autoplay ref="previewVideo">
|
|
|
+ 您的浏览器暂不支持,请更换浏览器
|
|
|
+ </video>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.modify-video-page-wrap{
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding:40px;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ .el-form{
|
|
|
+ .el-input,.el-select,.el-textarea{
|
|
|
+ width:500px;
|
|
|
+ }
|
|
|
+ .img-box{
|
|
|
+ /* background-color: #D9D9D9; */
|
|
|
+ border:1px dashed #d9d9d9;
|
|
|
+ width:150px;
|
|
|
+ height:100px;
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 10px;
|
|
|
+ position: relative;
|
|
|
+ line-height: normal;
|
|
|
+ img{
|
|
|
+ width:100%;
|
|
|
+ height:100%;
|
|
|
+ }
|
|
|
+ .duration{
|
|
|
+ position:absolute;
|
|
|
+ right:0;
|
|
|
+ bottom:0;
|
|
|
+ background-color: black;
|
|
|
+ color:#fff;
|
|
|
+ padding:4px;
|
|
|
+ }
|
|
|
+ .el-progress{
|
|
|
+ position: absolute;
|
|
|
+ left:50%;
|
|
|
+ top:50%;
|
|
|
+ transform: translate(-50%,-50%);
|
|
|
+ background-color: white;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .tag-list{
|
|
|
+ display: flex;
|
|
|
+ gap:10px;
|
|
|
+ line-height:0;
|
|
|
+ .tag-item{
|
|
|
+ cursor: pointer;
|
|
|
+ text-align: center;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding:12px;
|
|
|
+ min-width:78px;
|
|
|
+ color: #409EFF;
|
|
|
+ background-color: #EAF3FE;
|
|
|
+ border:1px solid #409EFF;
|
|
|
+ border-radius: 2px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ :deep(.el-textarea__inner){
|
|
|
+ resize: none;
|
|
|
+ }
|
|
|
+ .el-form-item{
|
|
|
+ :deep(.el-form-item__content){
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items:flex-start;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .btn-box{
|
|
|
+ text-align: center;
|
|
|
+ .el-button{
|
|
|
+ margin-right: 50px;
|
|
|
+ width:120px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|