浏览代码

Merge branch 'develop' of http://163.228.141.122:3000/TEAMMODEL/TEAMModelOS into develop

黄贺彬 3 月之前
父节点
当前提交
53ba7cf0e7

+ 4 - 4
TEAMModelOS.Extension/IES.Exam/IES.ExamServer/Controllers/ManageController.cs

@@ -158,7 +158,7 @@ namespace IES.ExamServer.Controllers
                                 client.DefaultRequestHeaders.Remove(Constant._X_Auth_AuthToken);
                             }
                             client.DefaultRequestHeaders.Add(Constant._X_Auth_AuthToken, teacher.x_auth_token);
-                            HttpResponseMessage message = await client.PostAsJsonAsync($"{CenterUrl}/blob/sas-read", new { containerName = $"{evaluationClient.ownerId}" });
+                            HttpResponseMessage message = await client.PostAsJsonAsync($"{CenterUrl}/blob/sas-read", new { containerName = $"{dataInfo.evaluationCloud.ownerId}" });
                             string sas = string.Empty;
                             string url = string.Empty;
                             string cnt = string.Empty;
@@ -176,7 +176,7 @@ namespace IES.ExamServer.Controllers
                             }
                             var httpClient = _httpClientFactory.CreateClient();
                             string packagePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "package");
-                            string evaluationPath = Path.Combine(packagePath, evaluationClient.id!);
+                            string evaluationPath = Path.Combine(packagePath, dataInfo.evaluationCloud.id!);
                             //删除文件夹
                             FileHelper.DeleteFolder(evaluationPath);
                             string evaluationDataPath = Path.Combine(evaluationPath, "data");
@@ -304,8 +304,8 @@ namespace IES.ExamServer.Controllers
                             {
                                 Directory.CreateDirectory(zipPath);
                             }
-                            string zipFilePath = Path.Combine(zipPath, $"{evaluationClient.id}-{evaluationClient.blobHash}.zip");
-                            var zipInfo = ZipHelper.CreatePasswordProtectedZip(evaluationPath, zipFilePath, evaluationClient.openCode!);
+                            string zipFilePath = Path.Combine(zipPath, $"{dataInfo.evaluationCloud.id}-{dataInfo.evaluationCloud.blobHash}.zip");
+                            var zipInfo = ZipHelper.CreatePasswordProtectedZip(evaluationPath, zipFilePath, dataInfo.evaluationCloud.openCode!);
                           
                             if (zipInfo.res)
                             {

+ 5 - 3
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/api/http.js

@@ -258,11 +258,13 @@ function refreshToken() {
 
 // 超时退出重新登录
 function loginOut() {
-    localStorage.clear()
-    sessionStorage.clear()
     console.log('超时退出');
     // router.push({path: '/home/homePage'})
-    window.location.href = window.location.origin + '/login/admin'
+    let identity = sessionStorage.getItem('identity')
+    window.location.href = window.location.origin + '/login'
+    localStorage.clear()
+    sessionStorage.clear()
+    sessionStorage.setItem('identity', identity)
 }
 
 /**

+ 3 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/api/index.js

@@ -129,4 +129,7 @@ export default {
     stuGetResult: function(data) {
         return post('/student/load-evaluation-result', data)
     },
+    saveStuExamPaper: function(data) {
+        return post('/student/submit-subject-result', data)
+    },
 }

+ 3 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/router/router.js

@@ -3,6 +3,8 @@ import VueRouter from 'vue-router'
 
 Vue.use(VueRouter)
 
+let identity = sessionStorage.getItem('identity')
+
 const routes = [
     {
         path: '/',
@@ -10,6 +12,7 @@ const routes = [
     },
     {
         path: '/login',
+        redirect: identity === 'student' ? '/login/student' : '/login/admin',
         component: () => import("@/view/login/Index.vue"),
         children: [
             {

+ 15 - 2
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/utils/evTools.js

@@ -842,6 +842,19 @@ export default {
 			}
 			return result
 		}
-
-	}
+	},
+	/* 获取富文本中音视频相对路径 */
+	getRelativeSrcPath(richText) {
+		let videoSrcList = this.getRichTextSrc(richText, 'video')
+		let audioSrcList = this.getRichTextSrc(richText, 'audio')
+		let srcList = videoSrcList.concat(audioSrcList)
+		if (srcList.length) {
+			srcList.forEach(src => {
+				let withSas = src.split('/')[src.split('/').length - 1]
+				let j = withSas.lastIndexOf('?')
+				richText = richText.replaceAll(src, decodeURI(withSas.slice(0, j)));
+			})
+		}
+		return richText
+	},
 }

+ 1 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/login/Admin.vue

@@ -168,6 +168,7 @@ export default {
     mounted() {
         this.getSchoolList()
         this.viewNetworkInfo()
+        sessionStorage.setItem('identity', 'teacher')
     },
     methods: {
         async viewNetworkInfo() {

+ 4 - 4
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/login/Student.vue

@@ -51,6 +51,7 @@ export default {
     },
     created() {
         this.viewNetworkInfo()
+        sessionStorage.setItem('identity', 'student')
     },
     methods: {
         async viewNetworkInfo() {
@@ -71,10 +72,10 @@ export default {
                     item.showHZ = item.hz ? (item.hz / 1000) : 0
                 });
                 if(!res.data.server.school) this.isBindSchool = true
-                localStorage.setItem('deviceId', res.data.device)
-                localStorage.setItem('schoolInfo', JSON.stringify(res.data.server.school))
                 this.deviceInfo = res.data
                 this.hybridType = res.data?.hybrid */
+                localStorage.setItem('deviceId', res.data.device)
+                localStorage.setItem('schoolInfo', JSON.stringify(res.data.server.school))
 
                 this.stuGetEvaluation()
             })
@@ -93,7 +94,7 @@ export default {
             })
         },
         toLogin() {
-            if(this.loginForm.name || this.loginForm.id) return
+            if(!this.loginForm.name || !this.loginForm.id) return
             let params = {
                 studentId: this.loginForm.id,
                 studentName: this.loginForm.name,
@@ -103,7 +104,6 @@ export default {
             this.$api.stuLogin(params).then(res => {
                 if(res.code === 200) {
                     localStorage.setItem('stu_auth_token', res.x_auth_token)
-                    sessionStorage.setItem('identity', 'student')
                     this.$router.push({path: '/info'})
                     this.$message({
                         message: '登陆成功',

+ 2 - 1
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/student/ActivityAnswer.less

@@ -15,7 +15,8 @@
         z-index: 1000;
 
         #loadingBox {
-            height: 200px;
+            height: 240px;
+            width: 260px;
             display: flex;
             flex-direction: column;
             justify-content: center;

+ 216 - 154
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/student/ActivityAnswer.vue

@@ -1,17 +1,23 @@
 <template>
-    <el-container>
+    <el-container id="full">
         <div class="loading-container" style="background: rgba(0, 0, 0, 0.7)" v-show="isLoadQues">
             <div id="loadingBox" style="margin: auto;">
                 <el-progress type="circle" :stroke-width="8" :width="150" define-back-color="#fff" text-color="#07ac88" color="#07ac88" :percentage="quesProcess"></el-progress>
-                <div style="color: #00ad6c; margin: 30px 0px 15px; font-size: 20px;">试题载入中,请耐心等待</div>
-                <span style="font-size: 12px; color: #666 !important;">若长时间卡住,请 <a href="#" @click.prevent="handleLink()" style="color: #1487ff;">刷新页面</a> 后重试 </span>
+                <div v-show="quesProcess != 100">
+                    <div style="color: #00ad6c; margin: 30px 0px 15px; font-size: 20px;">试题载入中,请耐心等待</div>
+                    <span style="font-size: 12px; color: #666 !important;">若长时间卡住,请 <a href="#" @click.prevent="handleLink()" style="color: #1487ff;">刷新页面</a> 后重试 </span>
+                </div>
+                <div v-show="quesProcess === 100" style="width: 200px; text-align: center;">
+                    <div style="color: #00ad6c; margin: 30px 0px 25px; font-size: 20px;">试题加载完成</div>
+                    <el-button type="success" size="medium" round style="width: 100%;" @click="startExam()">开始考试</el-button>
+                </div>
             </div>
         </div>
         <el-header>
             <div style="width: 100%;">
                 <i class="el-icon-tuichu2 logout-icon" @click="openWarmMessage(1)"></i>
                 <span class="test-title-text">{{ evaluationInfo.activityName }}</span>
-                <span class="count-down" v-if="needCountDown && showExam.length">
+                <span class="count-down" v-if="needCountDown && showExam.length && isStartExam">
                     <span>作答时间&nbsp;</span>
                     <span :style="{'color': diffSeconds < 60 ? 'red' : ''}">{{ surplus }}</span>
                 </span>
@@ -19,7 +25,7 @@
             <el-progress class="top-progress" :stroke-width="8" :percentage="completeRate" :color="completeRate === 100 ? '#5cb85c' : '#2db7f5'"></el-progress>
         </el-header>
         <el-main>
-            <div v-if="!showExam.length" style="text-align: center; margin-top: 25%; font-size: 20px;">暂没有试题</div>
+            <div v-if="!showExam.length || !isStartExam" style="text-align: center; margin-top: 10%; font-size: 36px; width: 100%;">{{ !isStartExam ? '暂未开始考试' : '暂没有试题'}}</div>
             <template v-else>
                 <!-- 附件展示 -->
                 <div v-if="instantPaper">
@@ -116,7 +122,7 @@
                                             </div>
                                             <!--问答题-->
                                             <div class="compose-content" v-else-if="item.type === 'subjective'">
-                                                <Compose v-if="item.answerType === 'text' || item.answerType === 'text_Image'" ref="compose" :itemInfo="item" :close="!closeTest" :textData="checkers[index]" :index="index" @dataGet="getComposeAns"></Compose>
+                                                <Compose v-if="item.answerType === 'text' || item.answerType === 'text_Image'" ref="compose" :itemInfo="item" :textData="checkers[index]" :index="index" @dataGet="getComposeAns"></Compose>
                                                 <template v-else-if="item.answerType === 'audio'">
                                                     <AudioRecorder :textData="checkers[index]" :index="index" @dataGet="getComposeAns" />
                                                 </template>
@@ -152,11 +158,11 @@
                                                     </div>
                                                 </template>
                                                 <div class="compose-content" v-else>
-                                                    <Compose ref="compose" :itemInfo="item" :close="!closeTest" :textData="checkers[index]" :index="index" @dataGet="getComposeAns"></Compose>
+                                                    <Compose ref="compose" :itemInfo="item" :textData="checkers[index]" :index="index" @dataGet="getComposeAns"></Compose>
                                                 </div>
                                             </div>
                                             <div class="compose-content" v-else>
-                                                <Compose ref="compose" :itemInfo="item" :close="!closeTest" :textData="checkers[index]" :index="index" @dataGet="getComposeAns"></Compose>
+                                                <Compose ref="compose" :itemInfo="item" :textData="checkers[index]" :index="index" @dataGet="getComposeAns"></Compose>
                                             </div>
                                         </div>
                                     </div>
@@ -235,7 +241,7 @@
                                         </div>
                                         <!--问答题-->
                                         <div class="compose-content" v-else-if="showQuesInfo.type === 'subjective'">
-                                            <Compose v-if="showQuesInfo.answerType === 'text' || showQuesInfo.answerType === 'text_Image'" ref="compose" :itemInfo="showQuesInfo" :close="!closeTest" :textData="checkers[queNo]" :index="queNo" @dataGet="getComposeAns"></Compose>
+                                            <Compose v-if="showQuesInfo.answerType === 'text' || showQuesInfo.answerType === 'text_Image'" ref="compose" :itemInfo="showQuesInfo" :textData="checkers[queNo]" :index="queNo" @dataGet="getComposeAns"></Compose>
                                             <template v-else-if="showQuesInfo.answerType === 'audio'">
                                                 <AudioRecorder :textData="checkers[queNo]" :index="queNo" @dataGet="getComposeAns" />
                                             </template>
@@ -271,11 +277,11 @@
                                                 </div>
                                             </template>
                                             <div class="compose-content" v-else>
-                                                <Compose ref="compose" :itemInfo="showQuesInfo" :close="!closeTest" :textData="checkers[queNo]" :index="queNo" @dataGet="getComposeAns"></Compose>
+                                                <Compose ref="compose" :itemInfo="showQuesInfo" :textData="checkers[queNo]" :index="queNo" @dataGet="getComposeAns"></Compose>
                                             </div>
                                         </div>
                                         <div class="compose-content" v-else>
-                                            <Compose ref="compose" :itemInfo="showQuesInfo" :close="!closeTest" :textData="checkers[queNo]" :index="queNo" @dataGet="getComposeAns"></Compose>
+                                            <Compose ref="compose" :itemInfo="showQuesInfo" :textData="checkers[queNo]" :index="queNo" @dataGet="getComposeAns"></Compose>
                                         </div>
                                     </div>
                                 </div>
@@ -321,6 +327,16 @@
         <div class="top-icon" @click="gotoTop" v-if="instantPaper || entireExam">
             <i class="el-icon-arrow-up" style="font-size: 30px; color: #249e35;"></i>
         </div>
+        <el-dialog :title="messageType === 1 || messageType === 4 ? '离开测验提示' : '交卷提示'" width="30%" :visible.sync="isEditExam" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
+            <div v-show="messageType === 1">系统检测您尚未「交卷」,如您选择「确定」,{{ needCountDown ? '系统将默认交卷,且无法再次作答' : '则目前作答将不保存,下次需重新测验' }}</div>
+            <div v-show="messageType === 2">{{ !undo ? '系统检测目前您已全数作答完成,确定交卷吗?' : `目前您有${undo}题 未作答 (详查答案卡),请先完成题目作答!` }}</div>
+            <div v-show="messageType === 3">该评量的作答时间已结束,请交卷(<span style="color: red;">{{ modalSecond }}</span>)</div>
+            <div v-show="messageType === 4">暂无试题信息,请重新获取试卷</div>
+            <span slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="messageType === 4 ?  quitTest() : submitAns()">{{ messageType === 1 || messageType === 4 ? '确定' : '确定交卷' }}</el-button>
+                <el-button @click="closeWarmMessage()" v-show="messageType === 1 || messageType === 2">{{ messageType === 1 ? '取消' : undo ? '继续作答' : '否,需再检查' }}</el-button>
+            </span>
+        </el-dialog>
     </el-container>
 </template>
 
@@ -336,11 +352,13 @@ export default {
             isLoadQues: false,
             processNum: 0, //试题加载中的数量
             artQuesTotal: 0, //本次评测的总试题数量
+            realExam: [],
             showExam: [], //试卷题目信息
             evaluationInfo: undefined,
             setting: undefined,
             surplus: '', //倒计时的字符
             diffSeconds: 0, //秒数
+            costTime: 0, //作答使用时间
             interval: undefined,
             modalSecond: 10, //弹出框秒数
             modalInterval: undefined, //弹出框的倒计时(10s)
@@ -348,7 +366,6 @@ export default {
             imgList: [],
             entireExam: false, //是否整卷作答
             queNo: 0, // 当前第几题
-            closeTest: true,
             testType: {
                 single: '单选题',
                 multiple: '多选题',
@@ -364,6 +381,10 @@ export default {
             loading: false,
             widthLimit: true,
             isSubmit: false,
+            isFullSceen: true, //全屏
+            isEditExam: false,
+            messageType: 0,
+            isStartExam: false,
         }
     },
     async created() {
@@ -371,34 +392,26 @@ export default {
         this.evaluationInfo = JSON.parse(decodeURIComponent(localStorage.getItem('evaluationInfo')))
         this.setting = JSON.parse(decodeURIComponent(localStorage.getItem('setting')))
         await this.formPaper()
-        this.isLoadQues = false
     },
     async mounted () {
-        if(this.needCountDown) {
-            if(this.setting.countdownType === 1) {
-                // 需根据结束时间来判断还剩多少
-                let nowtime = await this.getNowTime() || (new Date()).getTime()
-                let reSecond = this.setting.deadline - nowtime
-                this.diffSeconds = reSecond > 0 ? reSecond / 1000 : 0
-            } else if(this.setting.countdownType === 2) {
-                if(JSON.parse(localStorage.getItem("time"))) this.diffSeconds = JSON.parse(localStorage.getItem("time"))
-                else this.diffSeconds = this.setting.countdown / 1000
-                this.diffSeconds = 10
-            }
-            this.interval = setInterval(() => {
-                this.diffSeconds -= 1
-            }, 1000)
-        }
+        let that = this
         window.onbeforeunload = function (e) {
             e = e || window.event;
             // 兼容IE8和Firefox 4之前的版本
             if (e) {
                 e.returnValue = "关闭提示";
+                that.storageCheck(that.checkers)
             }
             // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
             return "关闭提示";
         }
         this.widthLimit = window.innerWidth > 577
+        // 模拟用户交互
+        const button = document.createElement('button');
+        button.style.display = 'none';
+        document.body.appendChild(button);
+        button.click();
+        document.body.removeChild(button);
     },
     methods: {
         getNowTime() {
@@ -413,29 +426,45 @@ export default {
         formPaper() {
             let that = this
             return new Promise(async (resolve, reject) => {
-                let exam = await that.getStuPaper()
-                
-                for (let i = 0; i < exam.length; i++) {
-                    let obj = {star: false, open: false, stuAns: [], mark: [], getScore: 0, paperIndex: i + 1, parent: undefined, parentInfo: undefined}
-                    Object.assign(exam[i], obj)
-                    if (exam[i].type === 'compose') {
-                        let k = 1
-                        if (exam[i].children.length) {
-                            // 综合题有子题,k+1,并把子题放入paper 中作为单独的一题
-                            for (let c = 0; c < exam[i].children.length; c++) {
-                                let objChild = {parent: i, paperIndex: (i + 1) + '-' + k, stuAns: [], mark: [], star: false, open: false, getScore: 0, parentInfo: Object.assign({}, exam[i])}
-                                Object.assign(exam[i].children[c], objChild)
-                                exam[i].children[c].parentInfo.children = []
-                                
-                                k++
+                try {
+                    let exam = await that.getStuPaper()
+                    for (let i = 0; i < exam.length; i++) {
+                        let obj = {star: false, open: false, stuAns: [], mark: [], getScore: 0, paperIndex: i + 1, parent: undefined, parentInfo: undefined}
+                        Object.assign(exam[i], obj)
+                        if (exam[i].type === 'compose') {
+                            let k = 1
+                            if (exam[i].children.length) {
+                                // 综合题有子题,k+1,并把子题放入paper 中作为单独的一题
+                                for (let c = 0; c < exam[i].children.length; c++) {
+                                    let objChild = {parent: i, paperIndex: (i + 1) + '-' + k, stuAns: [], mark: [], star: false, open: false, getScore: 0, parentInfo: Object.assign({}, exam[i])}
+                                    Object.assign(exam[i].children[c], objChild)
+                                    exam[i].children[c].parentInfo.children = []
+                                    
+                                    k++
+                                }
                             }
                         }
                     }
+                    that.realExam = that._.cloneDeep(exam)
+                    // 是否乱序作答
+                    let artExamIsOrder = JSON.parse(localStorage.getItem("artExamIsOrder"))
+                    if(artExamIsOrder) {
+                        let newExam = exam.sort((a, b) => {
+                            return Math.random() > 0.5 ? -1 : 1
+                        })
+                        that.showExam = newExam
+                    } else that.showExam = exam
+                    if(!that.showExam.length) {
+                        that.openWarmMessage(4)
+                        this.isLoadQues = false
+                    }
+                    resolve(true)
+                } catch (error) {
+                    that.openWarmMessage(4)
+                    that.isLoadQues = false
+                    resolve(false)
                 }
-                that.showExam = exam
-                resolve(true)
             })
-            
         },
         getStuPaper() {
             let that = this
@@ -466,7 +495,7 @@ export default {
                                     itemJson.exercise.score = item.scoring ? item.scoring.score : 0
                                     // jsonData.item.push(itemJson.exercise)
                                     try {
-                                        console.log('多媒体链接', await that.$evTools.doAddHost(itemJson.exercise, url));
+                                        await that.$evTools.doAddHost(itemJson.exercise, url)
                                         that.processNum++
                                         resolve(itemJson.exercise)
                                     } catch (e) {
@@ -495,6 +524,8 @@ export default {
                                         that.checkers.push([])
                                     }
                                 }
+                                that.diffSeconds = JSON.parse(localStorage.getItem("time")) || 10
+                                that.costTime = JSON.parse(localStorage.getItem("costTime")) || 0
                                 // that.showExam = jsonData.item
                                 r(jsonData.item)
                             }).catch(e => {
@@ -502,10 +533,10 @@ export default {
                             })
                         }
                     } catch (e) {
-                        j(undefined)
+                        j([])
                     }
                 } catch(e) {
-                    j(undefined)
+                    j([])
                 }
             })
         },
@@ -522,29 +553,30 @@ export default {
                 }
             }
         },
-
-
-
         storageCheck(answer) {
             localStorage.setItem("answer", JSON.stringify(answer))
             localStorage.setItem("time", JSON.stringify(this.diffSeconds))
+            localStorage.setItem("costTime", JSON.stringify(this.costTime))
         },
         //提交作答记录
-        closetest() {
+        submitAns() {
             if(this.modalInterval) {
                 clearInterval(this.modalInterval)
             }
-            console.log('000000000000000000');
-            this.isSubmit = true
-            this.$router.push({
-                path: "/info"
-            });
-            return
+            if(this.interval) {
+                clearInterval(this.interval)
+            }
             if (this.checkers.length) {
-                this.isLoading = true;
+                this.loading = this.$loading({
+                    lock: true,
+                    text: 'Loading',
+                    spinner: 'el-icon-loading',
+                    background: 'rgba(0, 0, 0, 0.8)'
+                })
                 let answer = []
-                let artExam = JSON.parse(localStorage.getItem("artExam"))
-                if(artExam) {
+                // 是否乱序作答
+                let artExamIsOrder = JSON.parse(localStorage.getItem("artExamIsOrder"))
+                if(artExamIsOrder) {
                     answer = new Array(this.checkers.length).fill([])
                     // 打乱顺序后的答案要对比到标准试卷答案
                     this.showExam.forEach((item, index) => {
@@ -554,50 +586,45 @@ export default {
                     // let answer = []
                     this.checkers.forEach(item => {
                         if(item.length) {
-                            item[0] = this.$editorTools.getRelativeSrcPath(item[0])
+                            item[0] = this.$evTools.getRelativeSrcPath(item[0])
                         }
                         answer.push(item)
                     })
                 }
-                // 选了个人课程:scope == 'private'
-                // 选了标准课程:scope == 'school'
-                let codes = this.getItemTitle.scope == 'school' ? this.getItemTitle.school : this.getItemTitle.creatorId
-                let req = {
-                    id: this.getItemTitle.id,
+                let params = {
                     answer,
-                    studentId: this.getStuUserInfo.studentId,
-                    classIds: this.getItemTitle.classIds,
-                    subjectId: this.getExamInfo.subject.id,
-                    multipleRule: this.getExamInfo.multipleRule, //阅卷规则
-                    paperId: this.getExamInfo.id, //试卷id
-                    code: codes,
-                    scode: this.getItemTitle.scode,
-                    paperAns: this.getExamInfo.answers,
-                    point: this.getExamInfo.point
-                }
-                if(this.getItemTitle.type === "Art") {
-                    req.scode = `Exam-${codes}`
-                    req.id = this.getExamInfo.acId
-                    req.artId = this.getItemTitle.id
-                    req.quotaId = localStorage.getItem("quotaId")
+                    evaluationId: this.evaluationInfo.activityId,
+                    examId: this.evaluationInfo.examId,
+                    subjectId: this.evaluationInfo.subjectId,
+                    paperId: this.evaluationInfo.paperId, //试卷id
+                    costTime: this.costTime * 1000, //作答所用时间,毫秒
+                    settingId: this.setting.id,
                 }
-                this.$api.studentWeb.SaveStuExamPaper(req).then(res => {
+                this.$api.saveStuExamPaper(params).then(res => {
                     if (res.classResult) {
-                        this.$Message.success(this.$t('studentWeb.exam.submitSuccess'))
+                        this.$message({
+                            message: '作答信息提交成功!',
+                            type: 'success'
+                        });
+                        this.isSubmit = true
                         this.$router.push({
-                            path: '/studentWeb/examView',
-                            query: {aId: this.getItemTitle.id, subIds: this.getItemTitle.subjects.map(sub => sub.id)}
-                        })
-                        // this.$router.go(-1)
+                            path: "/info"
+                        });
                         this.removeLocal()
                     } else {
-                        this.isLoading = false
-                        this.$Message.warning(res.msg)
+                        this.loading.close()
+                        this.$message({
+                            message: res.msg,
+                            type: 'warning'
+                        });
                     }
                 }, err => {
-                    this.$Message.warning(this.$t('studentWeb.exam.submitFail'))
+                    this.$message({
+                        message: '作答信息保存失败!',
+                        type: 'warning'
+                    });
                     setTimeout(() => {
-                        this.isLoading = false
+                        this.loading.close()
                     }, 1000)
                 })
             } 
@@ -605,45 +632,28 @@ export default {
         removeLocal() {
             localStorage.removeItem("answer")
             localStorage.removeItem("time")
-            localStorage.removeItem("artExam")
+            localStorage.removeItem("artExamIsOrder")
+            localStorage.removeItem("costTime")
+        },
+        //退出测试
+        quitTest() {
+            this.$router.push({
+                path: "/info"
+            });
         },
         //打开提示信息
         openWarmMessage(type) {
-            if (type === 1) { //退出
-                this.$confirm(`系统检测您尚未「交卷」,如您选择「确定」,${this.needCountDown ? '系统将默认交卷,且无法再次作答' : '则目前作答将不保存,下次需重新测验'}`, '离开测验提示', {
-                    type: 'warning',
-                    confirmButtonText: '确定',
-                    cancelButtonText: '取消',
-                }).then(() => {
-                    console.log('111111111111111111');
-                    this.closetest()
-                }).catch(() => {
-                });
-            } else if (type === 2) { //交卷
-                this.$confirm(`${!this.undo ? '系统检测目前您已全数作答完成,确定交卷吗?' : `目前您有${this.undo}`}题 未作答 (详查答案卡)`, '交卷提示', {
-                    type: 'warning',
-                    confirmButtonText: '确定交卷',
-                    cancelButtonText: this.undo ? '继续作答' : '否,需再检查',
-                }).then(() => {
-                    console.log('2222222222222222');
-                    this.closetest()
-                }).catch(() => {
-                });
-            } else if(type === 3) { //限制作答时间结束
-                this.$confirm('该评量的作答时间已结束,请交卷', '交卷提示', {
-                    type: 'warning',
-                    confirmButtonText: '确定交卷',
-                    showCancelButton: false,
-                    closeOnClickModal: false,
-                    closeOnPressEscape: false,
-                    closeOnHashChange: false,
-                    showClose: false,
-                }).then(() => {
-                    console.log('33333333333333333333');
-                    this.closetest()
-                }).catch(() => {
-                });
-            } else return
+            this.isEditExam = true
+            this.messageType = type
+        },
+        //关闭提示信息
+        closeWarmMessage() {
+            if(this.evaluationInfo.type === 'Art' && !this.$tools.isMobile()) {
+                const full = document.getElementById("full")
+                full.requestFullscreen()
+            }
+            this.isEditExam = false
+            this.messageType = 0
         },
         // 获得选择题——选中的答案
         getAns(index, optionIndex) {
@@ -735,6 +745,49 @@ export default {
                 return false
             })
         },
+        async startExam(type) {
+            let that = this
+            // 2023.12.11 艺术评测不论乱序,全部全屏
+            if(this.evaluationInfo.type === 'Art' && !this.$tools.isMobile() && !type) {
+                let screen = document.getElementById("full")
+                screen.requestFullscreen()
+                if(screen.requestFullscreen) {
+                    try {
+                        document.addEventListener("fullscreenchange", () => {
+                            if((document['fullscreenElement'] || document['mozFullScreenElement'] || document['msFullScreenElement'] || document['webkitFullScreenElement'] || null) === null) {
+                                console.log('exit');
+                                that.isFullSceen = false
+                            } else {
+                                console.log('full');
+                                that.isFullSceen = true
+                            }
+                        })
+                    } catch (error) {
+                        console.log('11111111111111', error);
+                    }
+                }
+            }
+            if(this.needCountDown && this.showExam.length) {
+                if(this.setting.countdownType === 1) {
+                    // 需根据结束时间来判断还剩多少
+                    let nowtime = await this.getNowTime() || (new Date()).getTime()
+                    let reSecond = this.setting.deadline - nowtime
+                    this.diffSeconds = reSecond > 0 ? reSecond / 1000 : 0
+                } else if(this.setting.countdownType === 2) {
+                    this.diffSeconds = JSON.parse(localStorage.getItem("time")) || this.setting.countdown / 1000
+                }
+                this.costTime = JSON.parse(localStorage.getItem("costTime")) || 0
+                this.interval = setInterval(() => {
+                    this.diffSeconds -= 1
+                    this.costTime++
+                }, 1000)
+            }
+            if(type) {
+                this.surplus = '00:00:00'
+            }
+            this.isStartExam = true
+            this.isLoadQues = false
+        },
     },
     computed: {
         quesProcess() {
@@ -785,11 +838,14 @@ export default {
                     clearInterval(this.interval)
                     // 弹出框倒计时10s,到时间直接交卷
                     this.openWarmMessage(3)
+                    if(this.isLoadQues) {
+                        this.startExam(true)
+                    }
                     this.modalInterval = setInterval(() => {
                         if(this.modalSecond > 1) {
                             this.modalSecond -= 1
                         } else {
-                            this.closetest()
+                            this.submitAns()
                         }
                     }, 1000)
                     this.$forceUpdate()
@@ -807,36 +863,42 @@ export default {
                 }
             }
         },
+        isFullSceen: {
+            handler(newV, o) {
+                if(!newV) {
+                    this.openWarmMessage(1)
+                }
+            }
+        },
     },
     // 导航守卫监听
     beforeRouteLeave(to, from, next) {
-        if(!this.isSubmit) {
-            if(!this.setting.countdownType && this.diffSeconds != -1) {
-                this.$confirm('离开页面,系统将默认交卷,且无法再次作答,是否退出?', '离开测验提示', {
-                    type: 'warning'
-                }).then(() => {
-                    this.closetest()
-                    next(true)
-                }).catch(() => {
-                    next(false)
-                });
-            } else {
-                this.$confirm('离开页面,您已作答的数据不会保存,是否确认退出?', '离开测验提示', {
-                    type: 'warning'
-                }).then(() => {
-                    next(true)
-                }).catch(() => {
-                    next(false)
-                });
-            }
+        if(!this.isSubmit && !this.setting.countdownType && this.diffSeconds != -1) {
+            this.$confirm('离开页面,系统将默认交卷,且无法再次作答,是否退出?', '离开测验提示', {
+                type: 'warning'
+            }).then(() => {
+                this.submitAns()
+                next(true)
+            }).catch(() => {
+                next(false)
+            });
+        } else if(!this.messageType) {
+            this.$confirm('离开页面,您已作答的数据不会保存,是否确认退出?', '离开测验提示', {
+                type: 'warning'
+            }).then(() => {
+                next(true)
+            }).catch(() => {
+                next(false)
+            });
+        } else {
+            next(true)
         }
-        next(true)
         this.removeLocal()
     },
-        destroyed () {
-            // 取消onbeforeunload事件
-            window.onbeforeunload = null;
-        }
+    destroyed () {
+        // 取消onbeforeunload事件
+        window.onbeforeunload = null;
+    }
 }
 </script>
 

+ 11 - 11
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/student/ActivityInfo.vue

@@ -165,6 +165,12 @@ export default {
         }
     },
     async created() {
+        this.loading = this.$loading({
+            lock: true,
+            text: '加载中...',
+            spinner: 'el-icon-loading',
+            background: 'rgba(0, 0, 0, 0.8)'
+        })
         this.nowtime = await this.getNowTime()
         this.userInfo = jwtDecode(localStorage.getItem('stu_auth_token'))
         let schoolInfo = JSON.parse(localStorage.getItem('schoolInfo'))
@@ -191,6 +197,7 @@ export default {
                     this.setting = res.setting
                     this.getResult()
                 } else {
+                    this.loading.close()
                     this.$message({
                         message: '当前无评测可以作答',
                         type: 'warning'
@@ -219,25 +226,18 @@ export default {
                         return item
                     })
                 }
+            }).finally(() => {
+                this.loading.close()
             })
         },
-        startLoading() {
-            this.loading = this.$loading({
-                lock: true,
-                text: 'Loading',
-                spinner: 'el-icon-loading',
-                background: 'rgba(0, 0, 0, 0.8)'
-            })
-        },
-        closeLoading() {
-            this.loading.close()
-        },
         async onLoadQues(subjectInfo) {
             let evaluationInfo = {
                 activityId: this.activityInfo.id,
                 activityName: this.activityInfo.name,
+                type: this.activityInfo.type,
                 subjectId: subjectInfo.subjectId,
                 paperId: subjectInfo.paperId,
+                examId: subjectInfo.examId,
                 stuId: this.userInfo.sub
             }
             localStorage.setItem("evaluationInfo", encodeURIComponent(JSON.stringify(evaluationInfo)))

+ 8 - 2
TEAMModelOS/ClientApp/src/components/coursemgt/StudentList.vue

@@ -287,14 +287,20 @@ export default {
             this.readExcel(file, data => {
                 if (data.results.length) {
                     excelResult = this._.cloneDeep(data.results)
-                    this.tableShowData = stuData.filter(item => {
-                        let eIndex = excelResult.findIndex(results => results.id.toString() === item.id && results.name === item.name)
+                    this.tableFilterData = stuData.filter(item => {
+                        let eIndex = excelResult.findIndex(results => results?.id && results?.id.toString() === item.id && results?.name === item.name)
                         if(eIndex != -1) {
                             excelResult.splice(eIndex, 1)
                             return true
                         }
                     })
+                    this.pointNum = this.basicCount
+                    this.tableShowData = this.tableFilterData.slice(0, this.basicCount)
+                    
                     this.notFoundlist = excelResult
+                    this.$nextTick(() => {
+                        this.$refs.stuSelection.selectAll(true);
+                    });
                 }
             })
             return false