浏览代码

#3379 活动系统--增加教师作品上传,视频文件格式支持情况及预览判断
#3373 活动系统--活动专属页面,增加报名参赛页面上传作品入口
#3370 活动系统--提交作品为附件无预览或下载入口
#3369 活动系统--评审专家分配任务包含已退赛教师作品

XW 1 年之前
父节点
当前提交
75b954e97d

+ 19 - 15
TEAMModelContest/contest.client/public/index.html

@@ -1,18 +1,22 @@
 <!DOCTYPE html>
 <html lang="">
-  <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-		<link id="theme" type="text/css" rel="stylesheet" href="<%= BASE_URL %>reset.css" />
-    <title>赛课系统</title>
-  </head>
-  <body>
-    <noscript>
-      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
-    </noscript>
-    <div id="app"></div>
-    <!-- built files will be auto injected -->
-  </body>
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width,initial-scale=1.0">
+        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+        <link id="theme" type="text/css" rel="stylesheet" href="<%= BASE_URL %>reset.css" />
+        <title>赛课系统</title>
+    </head>
+    <body>
+        <noscript>
+            <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+        </noscript>
+        <div id="app"></div>
+        <!-- built files will be auto injected -->
+    </body>
+    <script>
+        let blobHost = "https://teammodelos.blob.core.chinacloudapi.cn"
+        document.write('<script id="mediaInfo"  src="' + blobHost + '/0-public/js/checkMedia.js"><\/script>')
+    </script>
 </html>

+ 65 - 0
TEAMModelContest/contest.client/src/utils/common.js

@@ -57,6 +57,71 @@ export default {
             }
         })
     },
+	
+	/* 检查音视频格式是否符合格式要求 */
+	checkMediaFile(file) {
+		return new Promise((r, j) => {
+			const getSize = () => file.size
+			const readChunk = (chunkSize, offset) => new Promise((resolve, reject) => {
+				const reader = new FileReader()
+				reader.onload = (event) => {
+					if (event.target.error) {
+						reject(event.target.error)
+					}
+					resolve(new Uint8Array(event.target.result))
+				}
+				reader.readAsArrayBuffer(file.slice(offset, offset + chunkSize))
+			})
+			window.MediaInfo().then((media) => {
+				media.analyzeData(getSize, readChunk).then((result) => {
+					console.log(result)
+					if (result['media']) {
+						let tracks = result['media']['track']
+						// 判断是否是视频
+						let videoTrack = tracks.find(track => track['@type'] === 'Video')
+						let audioTrack = tracks.find(track => track['@type'] === 'Audio')
+						// 如果视频文件满足MP4(H264+AAC),WAV('vp8','vp9') 则代表是可以正常播放的视频文件,返回视频的编码级别
+						if (videoTrack && audioTrack) {
+							let videoFormat = videoTrack.Format.toLowerCase()
+							let audioFormat = audioTrack.Format.toLowerCase()
+							if ((videoFormat === 'avc' && audioFormat === 'aac') || (['vp8', 'vp9'].includes(videoFormat) && audioFormat === 'vorbis')) {
+								r(videoTrack.Format_Profile || true)
+							} else {
+								r(false)
+							}
+						}
+						// 如果是只有视频轨没有音频轨(PPT直接转视频)
+						else if (videoTrack && !audioTrack) {
+							let videoFormat = videoTrack.Format.toLowerCase()
+							if (videoFormat === 'avc') {
+								Modal.info({
+									title: "温馨提示",
+									content: "视频文件未检测到音频轨,建议您调整后重新上传"
+								})
+								r(true)
+							} else {
+								r(false)
+							}
+						}
+						// 如果是音频文件则需要满足 mp3(MPEG Audio),ogg(Vorbis),wav(PCM) 任意一种编码格式
+						else if (!videoTrack && audioTrack && ['mpeg audio', 'vorbis', 'pcm'].includes(audioTrack.Format.toLowerCase())) {
+							r(true)
+						} else {
+							r(false)
+						}
+					} else {
+						r(false)
+					}
+				}).catch((error) => {
+					console.log('error', error);
+					j(error)
+				})
+			}).catch((error) => {
+				console.log('error', error);
+				j(error)
+			})
+		})
+	},
 	/* 将二进制流字符串转换成Uint8Array */
 	stringToUint8Array(str) {
 		var arr = [];

+ 1 - 0
TEAMModelContest/contest.client/src/view/Home.vue

@@ -303,6 +303,7 @@ watch([ticket, authToken], ([newValue1, newValue2], [oldValue1, oldValue2]) => {
         left: 50%;
         top: 50%;
         transform: translate(-50%, -50%);
+        font-size: 1.5rem;
     }
     .el-header {
         position: relative;

+ 12 - 0
TEAMModelContest/contest.client/src/view/activitylist/ActivityInfo.vue

@@ -70,6 +70,9 @@
                                             </template>
                                         </el-step>
                                     </el-steps>
+                                    <div v-if="registered === 1 && contestStep === 1" style="text-align: center; margin-top: 10px;">
+                                        <el-button type="primary" @click="toUpload()">去上传作品</el-button>
+                                    </div>
                                     <!-- <div v-if="contestScores.length && contest.score.scoreStatus" class="score-box">
                                         <div v-for="(item, index) in contestScores" :key="index">
                                             <div class="score-lable" v-show="contest.score.showType">
@@ -580,6 +583,15 @@ function deleteRule(type) {
     enrollForm.value.clearValidate(type)
 }
 
+function toUpload() {
+    router.push({
+        name: 'myActivity',
+        query: {
+            id: actInfo.value.id
+        }
+    })
+}
+
 watch(() => enrollData.cipher, (newdata) => {
     codeSearch.value = true
     teamMembers.value = []

+ 31 - 18
TEAMModelContest/contest.client/src/view/myactivity/MyActivity.vue

@@ -377,11 +377,12 @@ import { Search, Delete, UserFilled, Clock, UploadFilled, CaretBottom, CaretRigh
 import { ElMessageBox, ElMessage, FormRules, ElLoading } from 'element-plus'
 import { getCurrentInstance, nextTick, onMounted, reactive, ref, toRaw, watch } from 'vue'
 import { useStore } from "@/pinia/common"
-import { useRouter, onBeforeRouteLeave } from 'vue-router'
+import { useRouter, onBeforeRouteLeave, useRoute } from 'vue-router'
 import BlobTool from '@/utils/blobTool.js'
 
 let { proxy } = getCurrentInstance()
 let store = useStore()
+let route = useRoute()
 let router = useRouter()
 
 let actIndex = ref(0)
@@ -452,7 +453,20 @@ function getActList() {
             })
             actListShow.value = actList.value
             if(actListShow.value.length) {
-                getListInfo(actListShow.value[0], 0)
+                if(route.query.id) {
+                    let index = actListShow.value.findIndex(item => item.id === route.query.id)
+                    if(index === -1) {
+                        ElMessage({
+                            type: 'warning',
+                            message: '未找到相关活动'
+                        })
+                        getListInfo(actListShow.value[0], 0)
+                    } else {
+                        getListInfo(actListShow.value[index], index)
+                    }
+                } else {
+                    getListInfo(actListShow.value[0], 0)
+                }
             }
         }
     }).finally(() => {
@@ -651,7 +665,7 @@ function exitAct(index, info) {
             message: proxy.$t('elMessage.handoverExit')
         })
     } else {
-        ElMessageBox.confirm(proxy.$t('elMessage.exitAct')).then(() => {
+        ElMessageBox.confirm(scoreData.value.allotStatus === 1 ? '您的作品已被分配,退出后将清空所有记录,是否确认退出?' : proxy.$t('elMessage.exitAct')).then(() => {
             let params = {
                 grant_type: 'cancel-enroll',
                 activityId: actInfo.value.id
@@ -690,21 +704,7 @@ function handleRemove(file, files) {
     fileList.value = fileList.value.filter(item => item.name != file.name)
 }
 
-function handleChange(file, files) {
-    let info = {
-        name: file.name,
-        url: '',
-        size: file.size,
-        createTime: '',
-        extension: '',
-        type: '',
-        blob: '',
-        hash: '',
-        duration: 0,
-        cnt: '',
-        tmdid: store.userInfo.sub,
-        tag: []
-    }
+async function handleChange(file, files) {
     if(fileList.value.find(item => item.name === file.name)) {
         ElMessage({
             type: 'warning',
@@ -729,6 +729,19 @@ function handleChange(file, files) {
         files.splice(fileList.value.length, 1)
         return
     }
+    let nameType = file.name.split('.')[file.name.split('.').length - 1]
+    if (['mp4', 'mp3', 'ogg', 'wav', 'webm'].includes(nameType.toLowerCase())) {
+        /* 检查上传媒体文件编码信息是否符合要求 */
+        let checkMediaFile = await proxy.$tools.checkMediaFile(file.raw)
+        if (!checkMediaFile) {
+            ElMessage({
+                type: 'warning',
+                message: '多媒体文件编码信息不正确,无法播放,请重新上传'
+            })
+            files.splice(fileList.value.length, 1)
+            return
+        }
+    }
     file.progress = 0
     fileList.value.push(file)
 }

+ 6 - 2
TEAMModelContest/contest.client/src/view/myactivity/MyReview.vue

@@ -86,9 +86,9 @@
                                                         <span @click="onPreview(fileInfo)" v-if="fileInfo.type !== 'other'" style="font-weight: bold; color: #009d18; cursor: pointer;">
                                                             {{ $t('elMessage.jumpPreview') }}
                                                         </span>
-                                                        <!-- <p @click="onDownload(fileInfo)" v-if="item.type !== 'link'">
+                                                        <span @click="onDownload(fileInfo)" v-if="fileInfo.type === 'other'" style="font-weight: bold; color: #009d18; cursor: pointer;">
                                                             下载
-                                                        </p> -->
+                                                        </span>
                                                     </div>
                                                 </div>
                                             </div>
@@ -500,6 +500,10 @@ async function onPreview(item) {
     }
 }
 
+function onDownload(item) {
+    proxy.$tools.doDownloadByUrl(item.urlShow, item.name)
+}
+
 function changeFile(info, index) {
     if(fileIndex.value != index) {
         fileIndex.value = index

+ 2 - 1
TEAMModelOS/ClientApp/src/view/signupActivity/createActivity.vue

@@ -497,7 +497,8 @@ export default {
                     value: 'video',
                     label: this.$t('ability.points.video'),
                     isCheck: false,
-                    format: ['mp4', 'webm']
+                    // format: ['mp4', 'webm']
+                    format: ['mp4']
                 },
                 {
                     value: 'img',