Просмотр исходного кода

局域网——教师端展示试卷内容

XW 3 месяцев назад
Родитель
Сommit
a66f47a344
17 измененных файлов с 1592 добавлено и 96 удалено
  1. 1 0
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/package.json
  2. 13 0
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/api/index.js
  3. 1 0
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/icon/no_data.svg
  4. 45 45
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/demo_index.html
  5. 11 11
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.css
  6. 1 1
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.js
  7. 14 14
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.json
  8. BIN
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.ttf
  9. BIN
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.woff
  10. BIN
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.woff2
  11. 4 0
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/main.js
  12. 847 0
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/utils/evTools.js
  13. 4 20
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/utils/public.js
  14. 63 4
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/admin/ActivityManage.vue
  15. 264 0
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/admin/TestPaper.less
  16. 315 0
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/admin/TestPaper.vue
  17. 9 1
      TEAMModelOS.Extension/IES.Exam/IES.ExamViews/vue.config.js

+ 1 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/package.json

@@ -18,6 +18,7 @@
     "element-ui": "^2.15.14",
     "js-audio-recorder": "^1.0.7",
     "jwt-decode": "^4.0.0",
+    "lodash": "^4.17.21",
     "qrcodejs2": "^0.0.2",
     "vue": "^2.6.14",
     "vue-router": "^3.6.5",

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

@@ -82,6 +82,7 @@ export default {
      * @param {String} evaluationId - 评测id
      * @param {String} openCode - 开卷码
      * @param {String} shortCode - 提取码
+     * @param {String} settingId - 轮次设置id
      */
     getExamRoundInfo: function (data) {
         return post('/manage/load-evaluation-round', data)
@@ -101,6 +102,18 @@ export default {
     setExamRoundInfo: function (data) {
         return post('/manage/setting-evaluation-round', data)
     },
+    /**
+     * 当前评测的开考设置列表信息
+     * @param {String} evaluationId - 评测id
+     * @param {String} openCode - 开卷码
+     * @param {String} shortCode - 提取码
+     */
+    getRoundList: function(data) {
+        return post('/manage/setting-evaluation-round', data)
+    },
 
     // 学生页面
+    stuGetEvaluation: function(data) {
+        return post('/manage/get-activate-evaluation', data)
+    },
 }

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/icon/no_data.svg


+ 45 - 45
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/demo_index.html

@@ -54,6 +54,18 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon element-icons">&#xe615;</span>
+                <div class="name">07_箭头_向下</div>
+                <div class="code-name">&amp;#xe615;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon element-icons">&#xee1a;</span>
+                <div class="name">07_箭头_向上</div>
+                <div class="code-name">&amp;#xee1a;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon element-icons">&#xe614;</span>
                 <div class="name">管理中心</div>
@@ -72,18 +84,6 @@
                 <div class="code-name">&amp;#xe9d9;</div>
               </li>
           
-            <li class="dib">
-              <span class="icon element-icons">&#xe6b4;</span>
-                <div class="name">电脑</div>
-                <div class="code-name">&amp;#xe6b4;</div>
-              </li>
-          
-            <li class="dib">
-              <span class="icon element-icons">&#xe604;</span>
-                <div class="name">电脑</div>
-                <div class="code-name">&amp;#xe604;</div>
-              </li>
-          
             <li class="dib">
               <span class="icon element-icons">&#xe608;</span>
                 <div class="name">iconfont_disconnected</div>
@@ -330,9 +330,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'element-icons';
-  src: url('iconfont.woff2?t=1739527479205') format('woff2'),
-       url('iconfont.woff?t=1739527479205') format('woff'),
-       url('iconfont.ttf?t=1739527479205') format('truetype');
+  src: url('iconfont.woff2?t=1740130849005') format('woff2'),
+       url('iconfont.woff?t=1740130849005') format('woff'),
+       url('iconfont.ttf?t=1740130849005') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -359,47 +359,47 @@
         <ul class="icon_lists dib-box">
           
           <li class="dib">
-            <span class="icon element-icons el-icon-guanlizhongxin"></span>
+            <span class="icon element-icons el-icon-jiantouxiangxia"></span>
             <div class="name">
-              管理中心
+              07_箭头_向下
             </div>
-            <div class="code-name">.el-icon-guanlizhongxin
+            <div class="code-name">.el-icon-jiantouxiangxia
             </div>
           </li>
           
           <li class="dib">
-            <span class="icon element-icons el-icon-a-ziyuan491"></span>
+            <span class="icon element-icons el-icon-jiantouxiangshang"></span>
             <div class="name">
-              工作台电脑
+              07_箭头_向上
             </div>
-            <div class="code-name">.el-icon-a-ziyuan491
+            <div class="code-name">.el-icon-jiantouxiangshang
             </div>
           </li>
           
           <li class="dib">
-            <span class="icon element-icons el-icon-mti-diannao"></span>
+            <span class="icon element-icons el-icon-guanlizhongxin"></span>
             <div class="name">
-              mti-电脑
+              管理中心
             </div>
-            <div class="code-name">.el-icon-mti-diannao
+            <div class="code-name">.el-icon-guanlizhongxin
             </div>
           </li>
           
           <li class="dib">
-            <span class="icon element-icons el-icon-diannao2"></span>
+            <span class="icon element-icons el-icon-a-ziyuan491"></span>
             <div class="name">
-              电脑
+              工作台电脑
             </div>
-            <div class="code-name">.el-icon-diannao2
+            <div class="code-name">.el-icon-a-ziyuan491
             </div>
           </li>
           
           <li class="dib">
-            <span class="icon element-icons el-icon-diannao1"></span>
+            <span class="icon element-icons el-icon-mti-diannao"></span>
             <div class="name">
-              电脑
+              mti-电脑
             </div>
-            <div class="code-name">.el-icon-diannao1
+            <div class="code-name">.el-icon-mti-diannao
             </div>
           </li>
           
@@ -774,42 +774,42 @@
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
-                  <use xlink:href="#el-icon-guanlizhongxin"></use>
+                  <use xlink:href="#el-icon-jiantouxiangxia"></use>
                 </svg>
-                <div class="name">管理中心</div>
-                <div class="code-name">#el-icon-guanlizhongxin</div>
+                <div class="name">07_箭头_向下</div>
+                <div class="code-name">#el-icon-jiantouxiangxia</div>
             </li>
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
-                  <use xlink:href="#el-icon-a-ziyuan491"></use>
+                  <use xlink:href="#el-icon-jiantouxiangshang"></use>
                 </svg>
-                <div class="name">工作台电脑</div>
-                <div class="code-name">#el-icon-a-ziyuan491</div>
+                <div class="name">07_箭头_向上</div>
+                <div class="code-name">#el-icon-jiantouxiangshang</div>
             </li>
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
-                  <use xlink:href="#el-icon-mti-diannao"></use>
+                  <use xlink:href="#el-icon-guanlizhongxin"></use>
                 </svg>
-                <div class="name">mti-电脑</div>
-                <div class="code-name">#el-icon-mti-diannao</div>
+                <div class="name">管理中心</div>
+                <div class="code-name">#el-icon-guanlizhongxin</div>
             </li>
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
-                  <use xlink:href="#el-icon-diannao2"></use>
+                  <use xlink:href="#el-icon-a-ziyuan491"></use>
                 </svg>
-                <div class="name">电脑</div>
-                <div class="code-name">#el-icon-diannao2</div>
+                <div class="name">工作台电脑</div>
+                <div class="code-name">#el-icon-a-ziyuan491</div>
             </li>
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
-                  <use xlink:href="#el-icon-diannao1"></use>
+                  <use xlink:href="#el-icon-mti-diannao"></use>
                 </svg>
-                <div class="name">电脑</div>
-                <div class="code-name">#el-icon-diannao1</div>
+                <div class="name">mti-电脑</div>
+                <div class="code-name">#el-icon-mti-diannao</div>
             </li>
           
             <li class="dib">

+ 11 - 11
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "element-icons"; /* Project id 4795944 */
-  src: url('iconfont.woff2?t=1739527479205') format('woff2'),
-       url('iconfont.woff?t=1739527479205') format('woff'),
-       url('iconfont.ttf?t=1739527479205') format('truetype');
+  src: url('iconfont.woff2?t=1740130849005') format('woff2'),
+       url('iconfont.woff?t=1740130849005') format('woff'),
+       url('iconfont.ttf?t=1740130849005') format('truetype');
 }
 
 .element-icons {
@@ -13,6 +13,14 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.el-icon-jiantouxiangxia:before {
+  content: "\e615";
+}
+
+.el-icon-jiantouxiangshang:before {
+  content: "\ee1a";
+}
+
 .el-icon-guanlizhongxin:before {
   content: "\e614";
 }
@@ -25,14 +33,6 @@
   content: "\e9d9";
 }
 
-.el-icon-diannao2:before {
-  content: "\e6b4";
-}
-
-.el-icon-diannao1:before {
-  content: "\e604";
-}
-
 .el-icon-iconfont_disconnected:before {
   content: "\e608";
 }

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.js


+ 14 - 14
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.json

@@ -5,6 +5,20 @@
   "css_prefix_text": "el-icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "609292",
+      "name": "07_箭头_向下",
+      "font_class": "jiantouxiangxia",
+      "unicode": "e615",
+      "unicode_decimal": 58901
+    },
+    {
+      "icon_id": "43421025",
+      "name": "07_箭头_向上",
+      "font_class": "jiantouxiangshang",
+      "unicode": "ee1a",
+      "unicode_decimal": 60954
+    },
     {
       "icon_id": "26126920",
       "name": "管理中心",
@@ -26,20 +40,6 @@
       "unicode": "e9d9",
       "unicode_decimal": 59865
     },
-    {
-      "icon_id": "15659133",
-      "name": "电脑",
-      "font_class": "diannao2",
-      "unicode": "e6b4",
-      "unicode_decimal": 59060
-    },
-    {
-      "icon_id": "6850406",
-      "name": "电脑",
-      "font_class": "diannao1",
-      "unicode": "e604",
-      "unicode_decimal": 58884
-    },
     {
       "icon_id": "9232602",
       "name": "iconfont_disconnected",

BIN
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.ttf


BIN
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.woff


BIN
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/assets/iconfont/iconfont.woff2


+ 4 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/main.js

@@ -4,12 +4,14 @@ import router from './router/router'
 import apiTools from '@/api'
 import { fetch, post } from '@/api/http'
 import tools from '@/utils/public.js'
+import evTools from '@/utils/evTools.js'
 
 import ElementUI from 'element-ui'
 import 'element-ui/lib/theme-chalk/index.css'
 import axios from 'axios'
 import '@/assets/reset.css'
 import "@/assets/iconfont/iconfont.css"
+import _ from 'lodash'
 
 import vuescroll from 'vuescroll/dist/vuescroll-native'
 
@@ -20,8 +22,10 @@ Vue.config.productionTip = false
 Vue.prototype.$api = apiTools
 Vue.prototype.$axios = axios
 Vue.prototype.$tools = tools
+Vue.prototype.$evTools = evTools
 Vue.prototype.$post = post
 Vue.prototype.$get = fetch
+Vue.prototype._ = _
 
 // 定义vuescroll全局滚动条组件
 Vue.component('vuescroll', vuescroll)

+ 847 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/utils/evTools.js

@@ -0,0 +1,847 @@
+import $tools from './public.js'
+import { app } from '@/main.js'
+
+
+export default {
+	/* 根据登录后的用户信息获取blobHOST域名 */
+	getBlobHost(url) {
+		let s = url || store.state.user.userProfile.blob_uri || store.state.user.studentProfile.blob_uri
+		let pattern = /[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/
+		return s.split('//')[0] + '//' + s.match(pattern)[0]
+	},
+	getAbilityDetailById(abilityId) {
+		return new Promise((r, j) => {
+			$api.ability.FindAbilityById({
+				"scope": "school",
+				"schoolCode": store.state.userInfo.schoolCode,
+				"abilityId": abilityId,
+				"standard": sessionStorage.getItem('standard')
+			}).then(res => {
+				if (!res.error) {
+					r(res.ability)
+				} else {
+					j(res.error)
+				}
+			}).catch(e => {
+				j(e)
+			})
+		})
+	},
+	/* 获取试题保存在Blob的JSON文件 */
+	/* 创建Blob试题格式 */
+	createBlobItem(item) {
+		return new Promise((r, j) => {
+			let itemJson = {
+				id: item.id,
+				pid: item.pid || null,
+				exercise: {
+					answer: item.answer,
+					explain: item.explain,
+					type: item.type,
+					answerType: item.answerType || 'text',
+					useAutoScore: item.useAutoScore || false,
+					answerLang: item.answerLang || 'en-US',
+					objective: this.getItemType(item.type),
+					opts: item.option ? item.option.length : 0,
+					knowledge: item.knowledge,
+					field: item.field,
+					level: item.level,
+					periodId: item.periodId,
+					gradeIds: item.gradeIds,
+					subjectId: item.subjectId,
+					children: item.children || [],
+					// scope:item.scope,
+					score: item.score || 0,
+					source: item.source || 0,
+					blankCount: item.blankCount || 1,
+					repair: item.repair,
+					createTime: new Date().getTime(),
+					creator: store.state.userInfo.TEAMModelId || 'null'
+				},
+				item: [{
+					type: 'Html',
+					uid: item.id,
+					question: item.question,
+					option: item.option
+				}]
+			}
+			r(itemJson)
+		})
+	},
+	/* 获取保存在COSMOS里面的试题格式 */
+	createCosmosItem(item, scope, code) {
+		return new Promise((r, j) => {
+			let cosmosItem = {
+				id: item.id,
+				pid: item.pid || null,
+				code: code || item.code,
+				scope: scope || item.scope,
+				score: item.score || 0,
+				source: item.source || 0,
+				type: item.type,
+				answer: item.answer || [],
+				answerType: item.answerType || 'text',
+				useAutoScore: item.useAutoScore || false,
+				answerLang: item.answerLang || 'en-US',
+				objective: this.getItemType(item.type),
+				question: this.getSimpleText(item.question),
+				knowledge: item.knowledge,
+				field: item.field,
+				level: item.level,
+				periodId: item.periodId,
+				gradeIds: item.gradeIds,
+				subjectId: item.subjectId,
+				repair: item.repair,
+				blankCount: item.blankCount || 1,
+				blob: item.blob,
+				createTime: new Date().getTime(),
+				creator: store.state.userInfo.TEAMModelId || 'null',
+				tags: item.tags || []
+			}
+			r(cosmosItem)
+		})
+	},
+	/* 生成试卷的index.json文件格式 */
+	createBlobPaper(paper, slides) {
+		return new Promise((r, j) => {
+			let paperItem = {
+				id: paper.id,
+				name: paper.name,
+				// code:paper.code,
+				// scope:paper.scope,
+				blob: paper.blob || '',
+				itemSort: paper.itemSort || 0,
+				isNumOption: paper.isNumOption || 0,
+				qamode: paper.qamode || 0,
+				multipleRule: paper.multipleRule,
+				attachments: paper.attachments || [],
+				tags: paper.tags || [],
+				slides: slides,
+				points: paper.points,
+				periodId: paper.periodId,
+				gradeIds: paper.gradeIds,
+				subjectId: paper.subjectId,
+				subjectName: paper.subjectName,
+				score: paper.score,
+				sheet: paper.sheet || null,
+				typeSummaryInfo: paper.typeSummaryInfo || null,
+				orderTemp: paper.orderTemp || 0,
+				secret: paper.secret || 0,
+				markModel: paper.markModel || 0,
+				creatorId: paper.creatorId || store.state.userInfo.TEAMModelId
+			}
+			r(paperItem)
+		})
+	},
+	/* 生成试卷保存在cosmos的数据结构 */
+	createCosmosPaper(paper) {
+		return new Promise((r, j) => {
+			let paperItem = {
+				id: paper.id,
+				name: paper.name,
+				code: paper.code,
+				blob: paper.blob,
+				tags: paper.tags || [],
+				qamode: paper.qamode || 0,
+				attachments: paper.attachments || [],
+				sheet: paper.sheet || null,
+				itemSort: paper.itemSort || 0,
+				isNumOption: paper.isNumOption || 0,
+				scope: paper.scope,
+				scoring: paper.scoring,
+				points: paper.points,
+				periodId: paper.periodId,
+				gradeIds: paper.gradeIds,
+				subjectId: paper.subjectId,
+				subjectName: paper.subjectName,
+				score: paper.score,
+				multipleRule: paper.multipleRule,
+				secret: paper.secret || 0,
+				markModel: paper.markModel || 0,
+				creatorId: paper.creatorId || store.state.userInfo.TEAMModelId
+			}
+			r(paperItem)
+		})
+	},
+	/* 根据醍摩豆ID获取对应用户Blob内部的完整试题 */
+	getFullItemByTmdId(tmdId, blob, inSyllabus) {
+		return new Promise(async (r, j) => {
+			let privateSas = await this.getBlobPrivateSas(tmdId)
+			let fullPath = this.getBlobHost() + '/' + tmdId + blob + privateSas
+			let jsonData = JSON.parse(await $tools.getFile(fullPath))
+			// 如果是综合题 那就拿到children里面的小题id集合 去换取小题的blobJSON文件 然后替换children的内容
+			if (jsonData.exercise.children.length && jsonData.exercise.type === 'compose') {
+				let childrenUrls;
+				if (inSyllabus) {
+					let syllabusPrefix = '/syllabus/' + blob.split('/')[2] + '/' + jsonData.id
+					childrenUrls = jsonData.exercise.children.map(i => this.getBlobHost() + '/' + tmdId + syllabusPrefix + '/' + i.id + '.json' + privateSas)
+				} else {
+					childrenUrls = jsonData.exercise.children.map(i => this.getBlobHost() + '/' + tmdId + '/item/' + i + '/' + i + '.json' + privateSas)
+				}
+				console.log(childrenUrls);
+				jsonData.exercise.children = await this.getFullChildren(childrenUrls, tmdId, inSyllabus)
+			}
+
+			// 调整渲染试题数据结构
+			jsonData.exercise.question = jsonData.item[0].question
+			jsonData.exercise.blob = fullPath
+			jsonData.exercise.code = tmdId
+			jsonData.exercise.option = jsonData.item[0].option
+			jsonData.exercise.id = jsonData.id
+			jsonData.exercise.scope = 'private'
+			jsonData.exercise.pid = jsonData.pid
+			jsonData.exercise = await this.doAddHost(jsonData.exercise, null, tmdId, inSyllabus)
+			r(jsonData.exercise)
+		})
+	},
+	/* 根据醍摩豆ID获取对应BLOB个人容器授权信息 */
+	getBlobPrivateSas(tmdId) {
+		return new Promise((r, j) => {
+			$api.blob.blobSasR({
+				name: tmdId,
+				role: 'teacher'
+			}).then(res => {
+				if (!res.error) {
+					r('?' + res.sas)
+				}
+			})
+		})
+	},
+
+	/* 根据醍摩豆ID获取对应BLOB个人容器授权信息 */
+	getBlobPrivateSasObj(tmdId) {
+		return new Promise((r, j) => {
+			$api.blob.blobSasR({
+				name: tmdId,
+				role: 'teacher'
+			}).then(res => {
+				if (!res.error) {
+					res.sas = '?' + res.sas
+					r(res)
+				}
+			})
+		})
+	},
+
+	/* 根据醍摩豆ID获取对应BLOB个人容器授权信息 */
+	getBlobSchoolSas(schoolCode) {
+		return new Promise((r, j) => {
+			$api.blob.blobSasR({
+				name: schoolCode,
+				role: 'school'
+			}).then(res => {
+				if (!res.error) {
+					r('?' + res.sas)
+				}
+			})
+		})
+	},
+	/* 获取完整的试题数据 */
+	getFullItem(list, examScope, inSyllabus) {
+		console.log('接受到的examScope', examScope)
+		return new Promise(async (resolve, reject) => {
+			if (list.length === 0) return
+			let promiseArr = []
+			console.log('getFullITEM接收到的list')
+			console.log(list)
+			for (let i = 0; i < list.length; i++) {
+				promiseArr.push(new Promise(async (r, j) => {
+					if (list[i].blob) {
+						let curScope = list[i].scope
+						const blobHost = curScope === 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+						// 根据试题的Blob地址 去读取JSON文件
+						let sasString = curScope === 'school' ? await $tools.getSchoolSas() : await $tools.getPrivateSas()
+						try {
+							let jsonInfo = list[i].blob.includes('https://') ? await $tools.getFile(list[i].blob + sasString.sas) : await $tools.getFile(blobHost + list[i].blob + sasString.sas)
+							let jsonData = JSON.parse(jsonInfo)
+							// 如果是综合题 那就拿到children里面的小题id集合 去换取小题的blobJSON文件 然后替换children的内容
+							if (jsonData.exercise.children.length && jsonData.exercise.type === 'compose') {
+								let childrenUrls;
+								if (inSyllabus) {
+									let syllabusPrefix = '/syllabus/' + list[i].blob.split('/')[2] + '/' + list[i].id
+									childrenUrls = jsonData.exercise.children.map(i => blobHost + syllabusPrefix + '/' + i.id + '.json' + sasString.sas)
+								} else {
+									childrenUrls = jsonData.exercise.children.map(i => blobHost + '/item/' + i + '/' + i + '.json' + sasString.sas)
+								}
+								jsonData.exercise.children = await this.getFullChildren(childrenUrls, list[i].code, list[i].scope, inSyllabus)
+							}
+							// 调整渲染试题数据结构
+							jsonData.id = list[i].id
+							jsonData.exercise.question = jsonData.item[0].question
+							jsonData.exercise.createTime = list[i].createTime || 0
+							jsonData.exercise.blob = list[i].blob
+							jsonData.exercise.code = list[i].code
+							jsonData.exercise.scope = list[i].scope
+							jsonData.exercise.option = jsonData.item[0].option
+							jsonData.exercise.id = list[i].id
+							jsonData.exercise.pid = jsonData.pid
+							jsonData.exercise.tags = list[i]?.tags || []
+							if(inSyllabus && list[i].nodeId) jsonData.exercise.nodeId = list[i].nodeId
+							jsonData.exercise = await this.doAddHost(jsonData.exercise, null, null, inSyllabus)
+							r(jsonData.exercise)
+						} catch (e) {
+							console.log(e)
+							j(e)
+							// this.$Message.error(e)
+						}
+
+					} else {
+						r(null)
+					}
+				}))
+			}
+			Promise.allSettled(promiseArr).then(result => {
+				console.log('从Blob获取来的试题', result.filter(i => i.status === 'fulfilled').map(j => j.value))
+				resolve(result.filter(i => i.status === 'fulfilled').map(j => j.value))
+			}).catch(err => {
+				Message.error(app.$t('utils.fileReadFail'))
+				reject(err)
+			})
+		})
+	},
+	/* 保存综合题小题 */
+	saveChildren(children, containerClient) {
+		return new Promise((resolve, reject) => {
+			let promiseArr = []
+			let itemJsonFiles = []
+			children.forEach(exerciseItem => {
+				promiseArr.push(new Promise(async (r, j) => {
+					// 将当前的试题数据转化为BLOB内部的试题JSON格式
+					const itemJsonFile = await this.createBlobItem(exerciseItem)
+					const cosmosItem = await this.createCosmosItem(exerciseItem)
+					// 首先保存新题目的JSON文件到Blob 然后返回URL链接
+					let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+					try {
+						// 等待上传blob的返回结果
+						let blobFile = await containerClient.upload(file, { path: 'item/' + exerciseItem.id })
+						if (blobFile.blob) {
+							// 保存试题JSON文件到试卷文件夹需要
+							itemJsonFiles.push(file)
+							// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+							cosmosItem.blob = blobFile.blob
+							// 保存当前试题到数据库
+							that.saveExercise(cosmosItem).then(res => {
+								r(res.itemInfo)
+							})
+						} else {
+							j(500)
+						}
+					} catch (e) {
+						j(500)
+					}
+				}))
+			})
+			Promise.all(promiseArr).then(result => {
+				if (result.length) {
+					resolve(itemJsonFiles)
+				} else {
+					resolve([])
+				}
+			})
+
+		})
+
+	},
+	/* 获取综合题子题的Blob数据 */
+	getFullChildren(urls, code, scope, inSyllabus) {
+		return new Promise((resolve, reject) => {
+			let promiseArr = []
+			urls.forEach(url => {
+				promiseArr.push(new Promise(async (r, j) => {
+					try {
+						let jsonData = JSON.parse(await $tools.getFile(url))
+						// 调整渲染试题数据结构
+						jsonData.exercise.question = jsonData.item[0].question
+						jsonData.exercise.blob = url
+						jsonData.exercise.code = code
+						jsonData.exercise.option = jsonData.item[0].option
+						jsonData.exercise.id = jsonData.id
+						jsonData.exercise.pid = jsonData.pid
+						jsonData.exercise.scope = scope || 'private'
+						jsonData.exercise = await this.doAddHost(jsonData.exercise, null, null, inSyllabus)
+						r(jsonData.exercise)
+					} catch (e) {
+						j(e)
+					}
+				}))
+			})
+
+			Promise.allSettled(promiseArr).then(result => {
+				if (result.length) {
+					// resolve(result)
+					resolve(result.filter(i => i.status === 'fulfilled').map(j => j.value))
+				} else {
+					resolve([])
+				}
+			})
+		})
+	},
+	/* 根据醍摩豆ID获取对应用户Blob内部的完整试卷 */
+	getFullPaperByTmdId(tmdId, blob, nodeId) {
+		return new Promise(async (r, j) => {
+			console.log('根据ID获取试题')
+			let privateSas = await this.getBlobPrivateSas(tmdId)
+			let fullPath = this.getBlobHost() + '/' + tmdId + blob + '/index.json' + privateSas
+			try {
+				let jsonInfo = await $tools.getFile(fullPath)
+				let jsonData = JSON.parse(jsonInfo)
+				jsonData.scope = 'private'
+				jsonData.code = tmdId
+				// 获取试卷包含的试题数据并包装好
+				if (jsonData.slides && jsonData.slides.length) {
+					let promiseArr = []
+					let allItems = []
+					jsonData.item = []
+					const path = this.getBlobHost() + '/' + tmdId + blob
+					jsonData.slides.forEach(async (item, index) => {
+						promiseArr.push(new Promise(async (resolve, reject) => {
+							try {
+								// 获取题目JSON并且包装成完整试题对象
+								let itemJson = JSON.parse(await $tools.getFile(path + '/' + item.url + privateSas))
+								itemJson.exercise.question = itemJson.item[0].question
+								itemJson.exercise.option = itemJson.item[0].option
+								itemJson.exercise.id = itemJson.id
+								itemJson.exercise.pid = itemJson.pid
+								itemJson.exercise.blob = path + '/' + item.url // 添加blob是方便在保存试卷是 refresh 与导入的试题区分开
+								itemJson.exercise.scope = 'private'
+								itemJson.exercise.score = item.scoring ? item.scoring.score : 0
+								try {
+									itemJson.exercise = await this.doAddHost(itemJson.exercise, { name: jsonData.name }, nodeId ? 'syllabus' : tmdId, nodeId) // 检测试题中的富文本 为有src为相对路径的音视频文件添加blob的HOST地址
+									resolve(itemJson.exercise)
+								} catch (e) {
+									reject(e)
+								}
+							} catch (e) {
+								reject(e)
+							}
+						}))
+					})
+
+					Promise.all(promiseArr).then(res => {
+						res.forEach((resItem, resIndex) => {
+							resItem.children = []
+							if (resItem.pid) {
+								let pItem = res.filter(i => i.id === resItem.pid)[0]
+								pItem.children.push(resItem)
+								pItem.score = pItem.score + resItem.score
+							}
+						})
+						jsonData.item = res.filter(i => !i.pid)
+						r(jsonData)
+					}).catch(e => {
+						// Message.error('试卷文件读取失败')
+						j(e)
+					})
+				}
+			} catch (e) {
+				console.log(e)
+				j(e)
+			}
+		})
+	},
+	/* 获取完整的试卷数据 */
+	getFullPaper(paper, examScope, nodeId) {
+		console.log(paper)
+		console.log(examScope)
+		console.log(nodeId)
+		let curScope = examScope || paper.examScope || paper.scope
+		return new Promise(async (r, j) => {
+			let blobHost = curScope === 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : 
+			JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+			let sasString = curScope === 'school' ? await $tools.getSchoolSas() : await $tools.getPrivateSas()
+			let privateSas = sasString.sas
+			// 如果是活動版sas拿法不同
+			if(paper.blob.indexOf("jointexam") != -1){	
+				if(paper.creatorId){
+					privateSas = await this.getBlobPrivateSas(paper.creatorId)
+				}else{
+					privateSas = await this.getBlobPrivateSas(paper.examId)
+				}		
+				
+				blobHost ="https://teammodel.blob.core.windows.net/"+paper.creatorId
+			}
+			
+			// 根据试卷的Blob地址 去读取JSON文件			
+			try {
+				let paperBlob = paper.blob 				
+				let jsonInfo = await $tools.getFile(blobHost + paperBlob + '/index.json' + privateSas)
+				let jsonData = JSON.parse(jsonInfo)
+				jsonData.scope = paper.scope
+				jsonData.code = paper.code
+				jsonData.sheet = paper.sheet || null
+				// 获取试卷包含的试题数据并包装好
+				if (jsonData.slides && jsonData.slides.length) {
+					let promiseArr = []
+					let allItems = []
+					jsonData.item = []
+					const path = blobHost + paper.blob
+					jsonData.slides.forEach(async (item, index) => {
+						promiseArr.push(new Promise(async (resolve, reject) => {
+							try {
+								// 获取题目JSON并且包装成完整试题对象
+								let itemJson = JSON.parse(await $tools.getFile(path + '/' + item.url + privateSas))
+								itemJson.exercise.question = itemJson.item[0].question
+								itemJson.exercise.option = itemJson.item[0].option
+								itemJson.exercise.id = itemJson.id
+								itemJson.exercise.pid = itemJson.pid
+								itemJson.exercise.scope = paper.scope
+								itemJson.exercise.blob = path + '/' + item.url // 添加blob是方便在保存试卷是 refresh 与导入的试题区分开
+								itemJson.exercise.score = item.scoring ? item.scoring.score : 0
+								try {
+									let p = nodeId ? { name: paper.name } : paper
+									itemJson.exercise = await this.doAddHost(itemJson.exercise, p, nodeId ? 'syllabus' : null, nodeId) // 检测试题中的富文本 为有src为相对路径的音视频文件添加blob的HOST地址
+									resolve(itemJson.exercise)
+								} catch (e) {
+									reject(e)
+								}
+							} catch (e) {
+								reject(e)
+							}
+						}))
+					})
+
+					Promise.allSettled(promiseArr).then(res => {
+						res = res.filter(i => i.status === 'fulfilled').map(j => j.value)
+						res.forEach((resItem, resIndex) => {
+							resItem.children = []
+							if (resItem.pid) {
+								let pItem = res.filter(i => i.id === resItem.pid)[0]
+								pItem.children.push(resItem)
+								pItem.score = pItem.score + resItem.score
+							}
+						})
+						jsonData.item = res.filter(i => !i.pid)
+						r(jsonData)
+					}).catch(e => {
+						// Message.error('试卷文件读取失败')
+						j(e)
+					})
+				}
+			} catch (e) {
+				j(e)
+			}
+		})
+	},
+	/* 获取完整的试卷数据 */
+	getStuPaper(paper, examScope) {
+		let curScope = examScope || paper.scope
+		console.log(...arguments);
+		return new Promise(async (r, j) => {
+			// let blobHost = this.getBlobHost()
+			// 根据试卷的Blob地址 去读取JSON文件
+			let cntr = paper.code
+			let sas = await $tools.getBlobSas(cntr)
+			let sasString = "?" + sas.sas
+			let blobHost = sas.url
+			let paperBlobPath = blobHost + '/' + cntr + paper.blob
+			let fullPath = paperBlobPath + '/index.json' + sasString
+			console.log(fullPath);
+			try {
+				let jsonInfo = await $tools.getFile(fullPath)
+				let jsonData = JSON.parse(jsonInfo)
+				jsonData.scope = curScope
+				jsonData.code = paper.code
+				jsonData.sheet = paper.sheet || null
+				paper.tags = paper.tags || jsonData.tags
+				// 获取试卷包含的试题数据并包装好
+				if (jsonData.slides && jsonData.slides.length) {
+					jsonData.item = []
+					let promiseArr = []
+					jsonData.slides.forEach((item, index) => {
+						promiseArr.push(new Promise(async (resolve, reject) => {
+							// 获取题目JSON并且包装成完整试题对象
+							let itemFullPath = paperBlobPath + '/' + item.url + sasString
+							let itemJson = JSON.parse(await $tools.getFile(itemFullPath))
+							itemJson.exercise.question = itemJson.item[0].question
+							itemJson.exercise.option = itemJson.item[0].option
+							itemJson.exercise.id = itemJson.id
+							itemJson.exercise.pid = itemJson.pid
+							itemJson.exercise.scope = curScope
+							itemJson.exercise.score = item.scoring ? item.scoring.score : 0
+							// jsonData.item.push(itemJson.exercise)
+							try {
+								itemJson.exercise = await this.doAddHost(itemJson.exercise, paper, paper.code, null, sasString)
+								resolve(itemJson.exercise)
+							} catch (e) {
+								reject(e)
+							}
+						}))
+					})
+					Promise.all(promiseArr).then(res => {
+						res.forEach((resItem, resIndex) => {
+							resItem.children = []
+							if (resItem.pid) {
+								let pItem = res.filter(i => i.id === resItem.pid)[0]
+								pItem.children.push(resItem)
+								pItem.score = pItem.score + resItem.score
+							}
+						})
+						jsonData.item = res.filter(i => !i.pid)
+						r(jsonData)
+					}).catch(e => {
+						j(e)
+					})
+				}
+			} catch (e) {
+				j(e)
+			}
+		})
+	},
+	// 艺术测评专用获取试卷方案
+	getStuPaperForArt(paper, examScope, sas) {
+		let curScope = examScope || paper.scope
+		console.log(...arguments);
+		return new Promise(async (r, j) => {
+			// let blobHost = this.getBlobHost()
+			// 根据试卷的Blob地址 去读取JSON文件
+			let cntr = paper.code
+			let sasString = "?" + sas.sas
+			let blobHost = sas.url
+			let paperBlobPath = blobHost + '/' + cntr + paper.blob
+			let fullPath = paperBlobPath + '/index.json' + sasString
+			console.log(fullPath);
+			try {
+				let jsonInfo = await $tools.getFile(fullPath)
+				let jsonData = JSON.parse(jsonInfo)
+				jsonData.scope = curScope
+				jsonData.code = paper.code
+				jsonData.sheet = paper.sheet || null
+				paper.tags = paper.tags || jsonData.tags
+				r(jsonData)
+				// 获取试卷包含的试题数据并包装好
+				// if (jsonData.slides && jsonData.slides.length) {
+				// 	jsonData.item = []
+				// 	let promiseArr = []
+				// 	jsonData.slides.forEach((item, index) => {
+				// 		promiseArr.push(new Promise(async (resolve, reject) => {
+				// 			// 获取题目JSON并且包装成完整试题对象
+				// 			let itemFullPath = paperBlobPath + '/' + item.url + sasString
+				// 			let itemJson = JSON.parse(await $tools.getFile(itemFullPath))
+				// 			itemJson.exercise.question = itemJson.item[0].question
+				// 			itemJson.exercise.option = itemJson.item[0].option
+				// 			itemJson.exercise.id = itemJson.id
+				// 			itemJson.exercise.pid = itemJson.pid
+				// 			itemJson.exercise.scope = curScope
+				// 			itemJson.exercise.score = item.scoring ? item.scoring.score : 0
+				// 			// jsonData.item.push(itemJson.exercise)
+				// 			try {
+				// 				itemJson.exercise = await this.doAddHost(itemJson.exercise, paper, paper.code, null, sasString)
+				// 				resolve(itemJson.exercise)
+				// 			} catch (e) {
+				// 				reject(e)
+				// 			}
+				// 		}))
+				// 	})
+				// 	Promise.all(promiseArr).then(res => {
+				// 		res.forEach((resItem, resIndex) => {
+				// 			resItem.children = []
+				// 			if (resItem.pid) {
+				// 				let pItem = res.filter(i => i.id === resItem.pid)[0]
+				// 				pItem.children.push(resItem)
+				// 				pItem.score = pItem.score + resItem.score
+				// 			}
+				// 		})
+				// 		jsonData.item = res.filter(i => !i.pid)
+				// 		r(jsonData)
+				// 	}).catch(e => {
+				// 		j(e)
+				// 	})
+				// }
+			} catch (e) {
+				j(e)
+			}
+		})
+	},
+
+	/* 获取完整的试卷数据 */
+	getComposeItem(paper) {
+		return new Promise(async (r, j) => {
+			console.log(paper);
+			// 根据试卷的Blob地址 去读取JSON文件
+			let cntr = paper.code
+			let sas = await $tools.getBlobSas(cntr)
+			let sasString = sas.sas
+			let fullPath = paper.blob + "?" + sasString
+			try {
+				let jsonInfo = await $tools.getFile(fullPath)
+				let jsonData = JSON.parse(jsonInfo)
+				// 获取试卷包含的试题数据并包装好
+				if (jsonData.length) {
+					r(jsonData)
+				}
+			} catch (e) {
+				j(e)
+			}
+		})
+	},
+	/* 提取富文本内容中的文本 */
+	getSimpleText(html) {
+		var r = /<\/?(img)[^>]*>/gi;
+		return html.replace(r, "");
+	},
+	/* 判断是否为客观题 */
+	getItemType(type) {
+		const objective = ['single', 'multiple', 'judge']
+		return objective.includes(type)
+	},
+	/* 获取img标签内的src */
+	getImgSrc(richtext) {
+		let imgList = [];
+		richtext.replace(/<video [^>]*src=['"]([^'"]+)[^>]*>/g, (match, capture) => {
+			imgList.push(capture);
+		});
+		return imgList;
+	},
+
+
+
+
+
+
+	getPaperInfo(examId, paperId) {
+		return new Promise(async (resolve, reject) => {
+			let url = `/package/${examId}/papers/${paperId}`
+            let indexUrl = url + '/index.json'
+            try {
+                let jsonInfo = await $tools.getFile(indexUrl)
+                let jsonData = JSON.parse(jsonInfo)
+                // 获取试卷包含的试题数据并包装好
+                if (jsonData.slides && jsonData.slides.length) {
+                    jsonData.item = []
+                    let promiseArr = []
+                    jsonData.slides.forEach((item, index) => {
+                        promiseArr.push(new Promise(async (resolve, reject) => {
+                            // 获取题目JSON并且包装成完整试题对象
+                            let itemFullPath = url + '/' + item.url
+                            let itemJson = JSON.parse(await $tools.getFile(itemFullPath))
+                            itemJson.exercise.question = itemJson.item[0].question
+                            itemJson.exercise.option = itemJson.item[0].option
+                            itemJson.exercise.id = itemJson.id
+                            itemJson.exercise.pid = itemJson.pid
+                            // itemJson.exercise.scope = curScope
+                            itemJson.exercise.score = item.scoring ? item.scoring.score : 0
+                            try {
+                                console.log('多媒体链接', await this.doAddHost(itemJson.exercise, url));
+                                // this.processNum++
+                                resolve(itemJson.exercise)
+                            } catch (e) {
+                                reject(e)
+                            }
+                        }))
+                    })
+                    Promise.all(promiseArr).then(res => {
+                        res.forEach((resItem, resIndex) => {
+                            resItem.children = []
+                            if (resItem.pid) {
+                                let pItem = res.filter(i => i.id === resItem.pid)[0]
+                                pItem.children.push(resItem)
+                                pItem.score = pItem.score + resItem.score
+                            }
+                        })
+                        jsonData.item = res.filter(i => !i.pid)
+                        console.log('题目详细内容', jsonData);
+                        /* this.paperList.push(jsonData)
+                        this.paperInfo = jsonData
+                        this.isShowPaper = true
+                        this.isLoading.close() */
+						resolve(jsonData)
+                    }).catch(e => {
+                        console.error('22222222222', e);
+						reject(undefined)
+                    })
+                }
+            } catch (error) {
+                console.error('33333333333333', error);
+				reject(undefined)
+                /* this.$message({
+                    message: '打开试卷失败',
+                    type: 'error'
+                }); */
+            }
+		})
+	},
+	/* 给富文本添加 cntr是防止读取的是其他用户的BLOB */
+	async doAddHost(exerciseItem, url) {
+		// console.log(exerciseItem, paperItem, cntr, nodeId, sasString)
+		if (exerciseItem.source && exerciseItem.source === 3) {
+			return exerciseItem
+		}
+		/* 如果操作的是试卷内的试题 则需要拿试卷的code来作为containerName */
+		let isSubjective = exerciseItem.type === 'complete' || exerciseItem.type === 'subjective' || exerciseItem.type === 'compose'
+		let richTextObj = {
+			question: exerciseItem.question,
+			answer: Array.isArray(exerciseItem.answer) && exerciseItem.answer.length ? exerciseItem.answer[0] : exerciseItem.answer,
+			explain: exerciseItem.explain,
+		}
+		isSubjective && delete richTextObj.answer
+		return new Promise((resolve, reject) => {
+			let promiseArr = []
+			// 遍历题目的所有富文本内容
+			for (let key in richTextObj) {
+				promiseArr.push(new Promise(async (r, j) => {
+					let videoSrcList = this.getRichTextSrc(richTextObj[key], 'video')
+					let audioSrcList = this.getRichTextSrc(richTextObj[key], 'audio')
+					let srcList = videoSrcList.concat(audioSrcList)
+					if (srcList.length) {
+						console.log('要添加list', srcList)
+						for (let i = 0; i < srcList.length; i++) {
+							let src = decodeURI(srcList[i])
+							let showSrc = src
+							let spStr = src.split('.')
+							if(spStr[spStr.length - 1] === 'MP4' || spStr[spStr.length - 1] === 'MP3') {
+								showSrc = src.split('.').slice(0, -1).join('.')
+								showSrc = showSrc + '_1.' + spStr[spStr.length - 1]
+							}
+							let blobUrl = url + '/' + showSrc
+							try {
+								richTextObj[key] = richTextObj[key].replaceAll(`src="${src}"`, `src="${blobUrl}"`);
+							} catch (e) {
+								j(500)
+							}
+						}
+						if (key === 'answer' && Array.isArray(exerciseItem.answer) && exerciseItem.answer.length) {
+							exerciseItem.answer[0] = richTextObj[key]
+						} else {
+							exerciseItem[key] = richTextObj[key]
+						}
+						r(200)
+					} else {
+						r(200)
+					}
+				}))
+			}
+			Promise.all(promiseArr).then(result => {
+				//console.log('添加HOST之后的',exerciseItem)
+				resolve(exerciseItem)
+			}).catch(e => {
+				reject(e)
+			})
+		})
+	},
+	/* 获取富文本的资源src数据 */
+	getRichTextSrc(richText, type) {
+		if (!richText) {
+			return []
+		}
+		var videoReg = /<video.*?(?:>|\/>)/gi;
+		var imgReg = /<img.*?(?:>|\/>)/gi;
+		var audioReg = /<audio.*?(?:>|\/>)/gi;
+		//匹配src属性
+		var srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;
+		var arr = String(richText).match(type === 'img' ? imgReg : type === 'video' ? videoReg : audioReg);
+		var result = []
+		if (!arr || !arr.length) {
+			return []
+		} else {
+			for (var i = 0; i < arr.length; i++) {
+				var src = arr[i].match(srcReg);
+				//获取图片地址
+				if (src[1]) {
+					result.push(src[1])
+				}
+			}
+			return result
+		}
+
+	}
+}

+ 4 - 20
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/utils/public.js

@@ -710,23 +710,7 @@ export default {
 	/* 数字与中文转换 */
 	getChineseByNum(num) {
 		num = Number(num)
-		var upperCaseNumber = [
-			app.$t('learnActivity.score.zero'),
-			app.$t('learnActivity.score.one'),
-			app.$t('learnActivity.score.two'),
-			app.$t('learnActivity.score.three'),
-			app.$t('learnActivity.score.four'),
-			app.$t('learnActivity.score.five'),
-			app.$t('learnActivity.score.six'),
-			app.$t('learnActivity.score.seven'),
-			app.$t('learnActivity.score.eight'),
-			app.$t('learnActivity.score.nine'),
-			app.$t('learnActivity.score.ten'),
-			app.$t('learnActivity.score.hundred'),
-			app.$t('learnActivity.score.thousand'),
-			app.$t('learnActivity.score.tenThd'),
-			app.$t('learnActivity.score.hMillion')
-		]
+		var upperCaseNumber = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '百', '千', '万', '亿']
 		var length = String(num).length
 		if (length === 1) {
 			return upperCaseNumber[num]
@@ -734,10 +718,10 @@ export default {
 			if (num === 10) {
 				return upperCaseNumber[num]
 			} else if (num > 10 && num < 20) {
-				return app.$t('learnActivity.score.ten') + upperCaseNumber[String(num).charAt(1)]
+				return '十' + upperCaseNumber[String(num).charAt(1)]
 			} else {
-				return upperCaseNumber[String(num).charAt(0)] + app.$t('learnActivity.score.ten') + upperCaseNumber[
-					String(num).charAt(1)].replace(app.$t('learnActivity.score.zero'), '')
+				return upperCaseNumber[String(num).charAt(0)] + '十' + upperCaseNumber[
+					String(num).charAt(1)].replace('零', '')
 			}
 		}
 	},

+ 63 - 4
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/admin/ActivityManage.vue

@@ -92,7 +92,7 @@
                                     <div v-for="(item, index) in evaluationClient.subjects" :key="index">
                                         <p class="subject-name">{{ item.subjectName }}</p>
                                         <div>
-                                            <el-tag type="warning" v-for="(papers, pIndex) in item.papers" :key="pIndex">{{ papers.paperName }}(/package/评测id/paper/试卷id)</el-tag>
+                                            <el-tag type="warning" v-for="(papers, pIndex) in item.papers" :key="pIndex" @click="openPaper(index, pIndex)">{{ papers.paperName }}</el-tag>
                                         </div>
                                     </div>
                                 </div>
@@ -237,14 +237,19 @@
                 <span><i class="el-icon-close"></i></span>
             </div> -->
         </div>
+        <el-dialog title="试卷详情" width="50%" :visible.sync="isShowPaper" class="test-paper">
+            <TestPaper v-if="isShowPaper" :paperInfo="paperInfo"></TestPaper>
+        </el-dialog>
     </el-container>
 </template>
 
 <script>
 import {jwtDecode} from 'jwt-decode'
 import { Loading } from 'element-ui'
+import TestPaper from './TestPaper.vue'
 
 export default {
+    components: { TestPaper },
     data() {
         return {
             isLoading: undefined,
@@ -277,6 +282,9 @@ export default {
             deviceId: '',
             openErrorMsgs: [],
             showErrorMsgs: false,
+            paperList: [],
+            paperInfo: undefined,
+            isShowPaper: false,
         }
     },
     created() {
@@ -311,7 +319,7 @@ export default {
                     return
                 }
                 if(!this.evaluationClient.openCode) this.isInputOpen = true
-                else this.getExamRoundInfo()
+                // else this.getExamRoundInfo()
             }
         },
         getNowTime() {
@@ -767,9 +775,13 @@ export default {
                 if(openCode && this.openCode === openCode) {
                     this.evaluationClient.openCode = openCode
                     this.examList[this.curIndex].openCode = openCode
-                    this.getExamRoundInfo()
+                    // this.getExamRoundInfo()
                     this.openCode = ''
                     this.isInputOpen = false
+                    this.$message({
+                        message: '检测成功',
+                        type: 'success'
+                    });
                 } else {
                     let params = {
                         deviceId: this.deviceId,
@@ -781,7 +793,7 @@ export default {
                         if(res.code === 200) {
                             this.evaluationClient.openCode = this.openCode
                             this.examList[this.curIndex].openCode = this.openCode
-                            this.getExamRoundInfo()
+                            // this.getExamRoundInfo()
                             sessionStorage.setItem(this.evaluationClient.id, this.openCode)
                             this.openCode = ''
                         } else if(res.code === 1) {
@@ -894,11 +906,13 @@ export default {
                 this.isLoading.close()
             })
         },
+        // 先调用 getRoundList if(settings === []) 不调用getExamRoundInfo else 获取 settings中activate === 1 的数据,若有,则调用getExamRoundInfo 否则不调用
         getExamRoundInfo() {
             let params = {
                 evaluationId: this.evaluationClient.id,
                 openCode: this.evaluationClient.openCode,
                 shortCode: this.evaluationClient.shortCode,
+                settingId: '',
             }
             this.$api.getExamRoundInfo(params).then(res => {
                 if(res.code === 200) {
@@ -924,6 +938,42 @@ export default {
         delInfo(index) {
             this.openErrorMsgs.splice(index, 1)
         },
+        async openPaper(index, paperIndex) {
+            if(this.needUpdate.status === 1) {
+                this.$message({
+                    message: '活动需更新,请先下载',
+                    type: 'warning'
+                });
+                return
+            }
+            if(!this.evaluationClient.openCode) {
+                this.isInputOpen = true
+                return
+            }
+            this.isLoading = Loading.service({
+                lock: true,
+                text: '获取试卷中...',
+                background: 'rgba(0, 0, 0, 0.7)'
+            })
+            this.paperInfo = this.paperList.find(item => item.id === this.evaluationClient.subjects[index].papers[paperIndex].paperId) || undefined
+            if(this.paperList.find(item => item.id === this.evaluationClient.subjects[index].papers[paperIndex].paperId)) {
+                this.isShowPaper = true
+                this.isLoading.close()
+                return
+            }
+            let paperInfo = await this.$evTools.getPaperInfo(this.evaluationClient.id, this.evaluationClient.subjects[index].papers[paperIndex].paperId)
+            if(!paperInfo) {
+                this.$message({
+                    message: '打开试卷失败',
+                    type: 'error'
+                });
+            } else {
+                this.paperList.push(paperInfo)
+                this.paperInfo = paperInfo
+                this.isShowPaper = true
+            }
+            this.isLoading.close()
+        },
         loginOut() {
             localStorage.removeItem('auth_token')
 			localStorage.removeItem('expires_in')
@@ -979,4 +1029,13 @@ export default {
 .el-tab-pane {
     margin: 0 15px 15px;
 }
+
+.test-paper {
+    height: 100%;
+
+    .el-dialog,
+    .el-dialog__body {
+        height: calc(100% - 120px);
+    }
+}
 </style>

+ 264 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/admin/TestPaper.less

@@ -0,0 +1,264 @@
+.paper-body {
+    width: 98%;
+    height: 88%;
+    padding-left: 20px;
+    position: relative;
+
+    .back-to-top {
+        position: absolute;
+        right: 10px;
+        bottom: 10px;
+        height: 40px;
+        width: 40px;
+        background: #979797;
+        z-index: 99999;
+        border-radius: 50%;
+        cursor: pointer;
+        text-align: center;
+        line-height: 40px;
+
+        &:hover {
+            background: rgb(128, 128, 128);
+        }
+
+        .el-icon-arrow-up {
+            font-size: 22px;
+            color: white;
+        }
+    }
+
+    .paper-base-info {
+        position: sticky;
+        top: -2px;
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        background-color: #fff;
+        border-bottom: 1px dashed #cfcfcf;
+        z-index: 99;
+        padding: 10px 0 20px 0;
+
+        .base-info-btn:not(:last-child) {
+            margin-right: 10px;
+        }
+
+        .analysis-info {
+            font-weight: bold;
+            color: #69baec;
+            margin: 0 5px;
+        }
+
+        .base-info-item {
+            margin-right: 15px;
+        }
+    }
+
+
+    .components-el-container {
+        .paper-header {
+            margin-top: 20px;
+        }
+
+        /*向垂直水平居中*/
+        .flex-col-center {
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+        }
+
+        .paper-title {
+            font-size: 30px;
+            margin: 30px 0;
+            font-weight: bold;
+            vertical-align: middle;
+            text-align: center;
+            cursor: pointer;
+        }
+
+        .no-data-text {
+            width: 100%;
+            padding: 30px;
+            background: #fff;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            margin-top: 10px;
+            font-size: 16px;
+        }
+
+
+        .content-wrap {
+            position: relative;
+            width: 100%;
+            height: auto;
+            display: flex;
+            flex-direction: column;
+
+            .type-name {
+                font-size: 18px;
+                font-weight: bold;
+                margin-top: 20px;
+            }
+
+            .exercise-item {
+                position: relative;
+                width: calc(100% - 45px);
+                height: auto;
+                padding: 10px 20px 10px 20px;
+                margin-bottom: 10px;
+                margin-top: 30px;
+                font-size: 14px;
+                background: #fff;
+                border: 2px solid transparent;
+                cursor: pointer;
+
+                p {
+                    width: 92%;
+                }
+
+                &:hover {
+                    border: 2px solid #01b4ef;
+                    box-shadow: none !important;
+
+                    .item-tools-bind {
+                        display: unset;
+                    }
+                }
+
+                table,
+                td {
+                    border: 1px solid rgb(128, 128, 128);
+                    border-collapse: collapse;
+                    text-align: center;
+                    padding: 5px 10px;
+                }
+
+                .item-question {
+                    position: relative;
+                    cursor: pointer;
+
+                    .item-question-order {
+                        display: inline-block;
+                        vertical-align: top;
+                        width: 32px;
+                    }
+
+                    .item-question-text {
+                        display: inline-block;
+                        width: calc(90% - 30px);
+                    }
+                }
+
+                .item-options {
+                    margin-top: 10px;
+
+                    .item-option-content {
+                        margin: 7px 0;
+                    }
+
+                    .item-option-order {
+                        display: inline-block;
+                        vertical-align: top;
+                        width: 30px;
+                    }
+
+                    .item-option-text {
+                        display: inline-block;
+                        width: calc(100% - 30px);
+                    }
+                }
+
+                .item-btn-toggle {
+                    position: absolute;
+                    right: 10px;
+                    top: 8px;
+                    width: 15%;
+                    display: flex;
+                    justify-content: center;
+                    align-items: center;
+                }
+
+                .toggle-area {
+                    border-top: 1px #c3c3c34d dashed;
+                    padding-top: 10px;
+                    margin-top: 10px;
+                }
+
+                .item-explain {
+                    margin-top: 10px;
+                    cursor: pointer;
+                    font-size: 14px;
+
+                    .explain-title {
+                        width: 12%;
+                        max-width: 100px;
+                        display: inline-block;
+                        color: rgb(16, 171, 231);
+                    }
+
+                    .item-explain-details {
+                        vertical-align: top;
+                        display: inline-block;
+                        width: calc(100% - 100px);
+
+                        // width: 90%;
+                        .item-point-tag {
+                            padding: 0 10px;
+                            border: 1px solid #d6d6d6;
+                            margin-left: 10px;
+                            border-radius: 4px;
+
+                            &:first-child {
+                                margin-left: 0;
+                            }
+                        }
+
+                        .repair-item {
+                            display: inline-flex;
+                            margin-right: 20px;
+                            color: #21b1ff;
+                            text-decoration: underline;
+                            align-items: flex-end;
+
+                            &-link {
+                                margin-left: 5px;
+                            }
+
+                        }
+                    }
+                }
+
+                .item-answer,
+                .item-explain {
+                    line-height: 26px;
+                }
+
+                img {
+                    vertical-align: middle;
+                    max-width: 100%;
+                }
+
+                video {
+                    max-width: 100%;
+                }
+
+                .item-answer-item {
+                    margin-left: 25px;
+                    padding: 0 25px;
+                    border-bottom: 2px solid rgb(128, 128, 128);
+
+                    p {
+                        display: inline-block;
+                    }
+
+                    &:first-child {
+                        margin-left: 0;
+                    }
+                }
+            }
+        }
+    }
+}

+ 315 - 0
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/src/view/admin/TestPaper.vue

@@ -0,0 +1,315 @@
+<template>
+    <div class="paper-body">
+        <div class="back-to-top flex-col-center" title="返回顶部" v-if="isShowBackToTop" @click="handleBackToTop">
+            <i class="el-icon-arrow-up" />
+        </div>
+        <vuescroll ref="paperRef" @handle-scroll="handleScroll">
+            <div class="paper-base-info">
+                <div style="display: flex;">
+                    <span class="base-info-item">总分:<span class="analysis-info" style="cursor: pointer;">{{ paperInfo.score }}</span>分</span>
+                    <!-- <span class="base-info-item" @click="onShowScoreTable" style="cursor: pointer;">已配:<span class="analysis-info">{{ allocatedScore || 0 }}</span>分</span> -->
+                    <span class="base-info-item">题量:<span class="analysis-info">{{ paperInfo.item ? paperInfo.item.length : 0 }}</span></span>
+                    <span class="base-info-item" style="display: flex;">难度:
+                        <el-rate v-model="paperInfo.item.length ? +paperDiff : 0" allow-half disabled></el-rate>
+                    </span>
+                </div>
+                <div>
+                    <el-button class="base-info-btn" type="primary" size="small" @click="onHandleToggle()" v-show="paperInfo.item.length">{{ isAllOpen ? '全部折叠' : '全部展开' }}</el-button>
+                    <el-button class="base-info-btn" type="primary" size="small" @click="onViewModelChange()" v-show="paperInfo.item.length">{{ viewModel === 'type' ? '题号排序' : '题型排序' }}</el-button>
+                </div>
+            </div>
+            <div class="components-el-container">
+                <div class="paper-header flex-col-center">
+                    <p class="paper-title">{{ paperInfo.name }}</p>
+                </div>
+                <div v-if="!exerciseList.length" class="no-data-text">
+                    <img src="@/assets/icon/no_data.svg" width="120" />
+                    <span style="margin-top: 15px; color: #808080">暂无数据</span>
+                </div>
+                <div class="content-wrap" ref="mathJaxContainer" v-else>
+                    <div class="list-view" :key="typeIndex" v-for="(typeItem, typeIndex) in listData">
+                        <p v-show="viewModel === 'type' && typeItem.list.length" class="type-name">
+                            {{ $tools.getChineseByNum(getLatestTypeIndex(typeItem.type) + 1) }}: {{ exersicesType[typeItem.type] }}
+                            <span style="font-size: 14px; font-weight: 600">(共{{ typeItem.list.length }}题,总计{{ typeItem.score || 0 }}分)</span>
+                        </p>
+                        <div v-for="(item, index) of typeItem.list" :key="index" class="exercise-item" :data-id="item.id">
+                            <div @click="onQuestionToggle(exerciseList.indexOf(item), item.id, $event, typeItem.list)" style="max-width: 85%">
+                                <!-- 题干部分 -->
+                                <div class="item-question">
+                                    <div v-if="viewModel === 'list'">
+                                        <div class="item-question-order">{{ index + 1 }} :</div>
+                                        <div class="item-question-text" v-html="item.question"></div>
+                                    </div>
+                                    <div v-else>
+                                        <div class="item-question-order">{{ exerciseList.indexOf(item) + 1 }} :</div>
+                                        <div class="item-question-text" v-html="item.question"></div>
+                                    </div>
+                                </div>
+                                <!-- 选项部分 -->
+                                <div v-for="(option, optionIndex) in item.option" :key="optionIndex" class="item-options">
+                                    <div class="item-option-content">
+                                        <div class="item-option-order">{{ String.fromCharCode(64 + parseInt(optionIndex + 1)) }} :</div>
+                                        <div class="item-option-text" v-html="option.value"></div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="exercise-item-children" v-if="item.children.length">
+                                <!-- <BaseChild :children="item.children" :totalScore="item.score" :isChangePaper="isChangePaper" :isShowScore="!isExamPaper" :canFix="canFix" @onEditChildFinish="onEditChildFinish"> </BaseChild> -->
+                            </div>
+                            <div class="item-btn-toggle">
+                                <span style="margin-right: 10px; font-size: 16px">
+                                    <span style="font-weight: bold; color: #0086e6">{{ item.score }}</span> 分
+                                </span>
+                                <i @click.stop="onQuestionToggle(exerciseList.indexOf(item), item.id, $event, typeItem.list)" :class="collapseList.indexOf(exerciseList.indexOf(item)) > -1 ? 'el-icon-jiantouxiangshang' : 'el-icon-jiantouxiangxia'" style="font-size: 20px;" v-if="item.type !== 'compose'" />
+                            </div>
+                            <!-- 答案以及解析 -->
+                            <transition name="slide" v-if="item.type !== 'compose'">
+                                <!-- <div v-show="collapseList.indexOf(exerciseList.indexOf(item)) > -1" class="toggle-area"> -->
+                                <div v-show="collapseList.indexOf(exerciseList.indexOf(item)) > -1" class="toggle-area">
+                                    <div>
+                                        <!-- 答案展示部分 -->
+                                        <div class="item-explain">
+                                            <span class="explain-title">【题型】</span>
+                                            <div class="item-explain-details">{{ exersicesType[item.type] }}</div>
+                                        </div>
+                                        <div class="item-explain">
+                                            <span class="explain-title">【答案】</span>
+                                            <div class="item-explain-details">
+                                                <!-- 问答题答案 -->
+                                                <div v-if="item.type === 'subjective' || item.type === 'complete' || item.type === 'connector' || item.type === 'correct'">
+                                                    <span v-for="(answer, index) in item.answer" :key="index" v-html="item.answer.length ? answer : '暂无'"></span>
+                                                </div>
+                                                <!-- 问答题答案 -->
+                                                <div v-else-if="item.type === 'judge'">
+                                                    <span>{{ item.answer.length ? (item.answer[0] === "A" ? '正确' : '错误') : '答案' }}</span>
+                                                </div>
+                                                <!-- 其余题型答案 -->
+                                                <div v-else>
+                                                    <span :class="[item.type === 'complete' ? 'item-answer-item' : '']" v-for="(answer, index) in item.answer" :key="index">{{ answer }}</span>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <!-- 解析部分 -->
+                                        <div class="item-explain">
+                                            <span class="explain-title">【解析】</span>
+                                            <div class="item-explain-details">
+                                                <span v-html="item.explain || '暂无'"></span>
+                                            </div>
+                                        </div>
+                                        <!-- 知识点部分 -->
+                                        <div class="item-explain">
+                                            <span class="explain-title">【知识点】</span>
+                                            <div class="item-explain-details">
+                                                <span v-if="!item.knowledge || !_.compact(item.knowledge).length">暂无</span>
+                                                <div v-else>
+                                                    <span v-for="(point, index) in item.knowledge" :key="index" class="item-point-tag">
+                                                        {{ point }}
+                                                    </span>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <!-- 认知层次部分 -->
+                                        <div class="item-explain">
+                                            <span class="explain-title">【认知层次】</span>
+                                            <div class="item-explain-details">
+                                                {{ item.field ? exersicesField[item.field - 1] : exersicesField[0] }}
+                                            </div>
+                                        </div>
+                                        <!-- 补救资源部分 -->
+                                        <div class="item-explain">
+                                            <span class="explain-title">【补救资源】</span>
+                                            <div class="item-explain-details">
+                                                <div v-if="item.repair && item.repair.length" style="display: flex; flex-wrap: wrap">
+                                                    <div class="repair-item" v-for="(link, index) in item.repair" :key="index">
+                                                        <img :src="$tools.getFileThum(link.type, link.name)" width="20" />
+                                                        <span class="repair-item-link" @click.stop="onRepairLinkClick(link)">{{ link.name }}</span>
+                                                    </div>
+                                                </div>
+                                                <span v-else>暂无</span>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </transition>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </vuescroll>
+    </div>
+</template>
+
+<script>
+export default {
+    props: {
+        paperInfo: {
+            type: Object,
+            default: undefined
+        }
+    },
+    data() {
+        return {
+            isShowBackToTop: false,
+            typeList: ["single", "multiple", "judge", "complete", "subjective", "connector", "correct", "compose"],
+            exersicesType: {
+                single: '单选题',
+                multiple: '多选题',
+                judge: '判断题',
+                complete: '填空题',
+                subjective: '问答题',
+                connector: '连线题',
+                correct: '改错题',
+                compose: '综合题'
+            },
+            exersicesField: ['记忆', '理解', '应用', '分析', '评价', '创造'],
+            paperDiff: 0,
+            exerciseList: [],
+            groupList: [],
+            orderList: [],
+            isAllOpen: false,
+            viewModel: 'type',
+            collapseList: [],
+        }
+    },
+    mounted() {
+        this.viewModel = this.paperInfo.itemSort === 1 ? 'list' : 'type'
+        this.paperDiff = this.paperInfo.item ? this.handleDiffCalc(this.paperInfo.item) : 0
+
+        if (this.paperInfo.item && this.paperInfo.item.length) {
+            let that = this;
+            this.groupList = []; // 题型排序的数据
+            this.orderList = []; //顺序排列的数据
+            this.exerciseList = [];
+            that.typeSummaryInfo = this.paperInfo.typeSummaryInfo || that.typeSummaryInfo;
+            this.paperInfo = this.paperInfo;
+            this.multipleRule = this.paperInfo.multipleRule || 1; // 配分规则
+            if (this.paperInfo.item.length) {
+                this.paperInfo.item.forEach((i) => {
+                    if (!i.score) i.score = 0;
+                    // 如果有综合题 则将小题的分数进行累加作为综合题的分数
+                    if (i.type === "compose" && i.children.length) {
+                        i.score = i.children.reduce((a, b) => a + b.score, 0);
+                    }
+                });
+                // 给顺序题目排序
+                this.orderList.push({
+                    list: this.paperInfo.item
+                });
+                /* 处理试卷内题目按照题型排序 */
+                this.typeList.forEach((item) => {
+                    this._.mapKeys(this._.groupBy(this.paperInfo.item, "type"), function (value, key) {
+                        if (key === item) {
+                            /* 按照题型排序,并且计算每种题型的总分 */
+                            let typeInfo = {
+                                type: key,
+                                list: value,
+                                score: value.reduce((p, e) => p + e.score, 0)
+                            };
+                            that.groupList.push(typeInfo);
+                            that.exerciseList = that.exerciseList.concat(value);
+                        }
+                    });
+                });
+            }
+            // 重新赋值
+            // this.originData = this.exerciseList;
+            
+            /* window.MathJax.startup.promise.then(() => {
+                window.MathJax.typesetPromise([this.$refs.mathJaxContainer])
+            }) */
+        }
+    },
+    computed: {
+        listData() {
+            return this.viewModel === "type" ? this.groupList : this.orderList;
+        },
+    },
+    methods: {
+        /**
+         * 计算试卷题目平均难度
+         * @param arr 试题集合
+         */
+        handleDiffCalc(arr) {
+            let levelArr = arr.map(i => i.level)
+            return this._.meanBy(levelArr).toFixed(1)
+        },
+        /**
+         * 全部展开与全部折叠
+         */
+        onHandleToggle() {
+            if (this.isAllOpen) {
+                this.collapseList = [];
+            } else {
+                this.collapseList = [...this.exerciseList.keys()];
+            }
+            this.isAllOpen = !this.isAllOpen
+        },
+        onViewModelChange() {
+            this.viewModel = this.viewModel === 'type' ? 'list' : 'type'
+        },
+        /* 获取当前题型的说明文本 */
+        getTypeSummary(typeItem) {
+            let that = this;
+            return (typeItem) => {
+                if (that.typeSummaryInfo && that.typeSummaryInfo[typeItem.type]) {
+                    return that.typeSummaryInfo[typeItem.type];
+                } else {
+                    return `共 ${typeItem.list.length}题,总计${typeItem.score || 0}分`;
+                }
+            };
+        },
+        /**
+         * 题干展开与收缩
+         * @param index
+         * @param id
+         */
+        onQuestionToggle(index, id, e) {
+            e.stopPropagation();
+            this.curEditItemId = null;
+            let listIndex = this.collapseList.indexOf(index);
+            if (listIndex > -1) {
+                this.collapseList.splice(listIndex, 1);
+            } else {
+                /** 如果是首次展开 则需要获取详细数据 否则直接展开 */
+                if (!this.exerciseList[index].answer.length) {
+                    this.collapseList.push(index);
+                } else {
+                    this.collapseList.push(index);
+                }
+            }
+        },
+        /* 补救资源点击事件 */
+        onRepairLinkClick(link) {
+            window.open(/^(http:|https:)/i.test(link.blobUrl) ? link.blobUrl : "http://" + link.blobUrl);
+        },
+        getLatestTypeIndex(type) {
+            let arr = [];
+            this.groupList.forEach((i) => {
+                if (i.list.length) {
+                    arr.push(i.type);
+                }
+            });
+            return arr.indexOf(type);
+        },
+        // 判断容器滚动距离
+        handleScroll(vertical, horizontal, nativeEvent) {
+            this.isShowBackToTop = vertical.scrollTop > 400
+        },
+        handleBackToTop() {
+            console.log(this)
+            this.$nextTick(() => {
+                if (this.$refs.paperRef) {
+                    this.$refs['paperRef'].scrollTo({
+                        y: '0'
+                    },
+                    500
+                )}
+            })
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+@import './TestPaper.less';
+</style>

+ 9 - 1
TEAMModelOS.Extension/IES.Exam/IES.ExamViews/vue.config.js

@@ -58,7 +58,15 @@ module.exports = defineConfig({
                 pathRewrite: {
                     '^/api': ''
                 }
-            }
+            },
+            '/package': {
+                target: 'https://localhost:6001', //后端接口
+                secure: false,
+                changeOrigin: false,
+                pathRewrite: {
+                    '^/package': '/package'
+                }
+            },
         },
         historyApiFallback: true,
     },