Преглед изворни кода

Merge branch 'develop5.0-tmd' of http://106.12.23.251:10080/TEAMMODEL/TEAMModelOS into develop5.0-tmd

zhouj1203@hotmail.com пре 4 година
родитељ
комит
9764e61de6
46 измењених фајлова са 2576 додато и 313 уклоњено
  1. 1 1
      TEAMModelOS.SDK/Models/Cosmos/Common/Bloblog.cs
  2. 6 6
      TEAMModelOS.SDK/Models/Cosmos/Common/Snode.cs
  3. 2 0
      TEAMModelOS/ClientApp/src/api/index.js
  4. 6 0
      TEAMModelOS/ClientApp/src/api/mark.js
  5. 25 3
      TEAMModelOS/ClientApp/src/api/openMgmt.js
  6. BIN
      TEAMModelOS/ClientApp/src/assets/source/audio.png
  7. BIN
      TEAMModelOS/ClientApp/src/assets/source/excel.png
  8. BIN
      TEAMModelOS/ClientApp/src/assets/source/folder.png
  9. BIN
      TEAMModelOS/ClientApp/src/assets/source/image.png
  10. BIN
      TEAMModelOS/ClientApp/src/assets/source/item.png
  11. BIN
      TEAMModelOS/ClientApp/src/assets/source/link.png
  12. BIN
      TEAMModelOS/ClientApp/src/assets/source/pdf.png
  13. BIN
      TEAMModelOS/ClientApp/src/assets/source/ppt.png
  14. BIN
      TEAMModelOS/ClientApp/src/assets/source/unknow.png
  15. BIN
      TEAMModelOS/ClientApp/src/assets/source/video.png
  16. BIN
      TEAMModelOS/ClientApp/src/assets/source/word.png
  17. BIN
      TEAMModelOS/ClientApp/src/assets/source/zip.png
  18. 21 17
      TEAMModelOS/ClientApp/src/common/UploadModal.vue
  19. 3 2
      TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.less
  20. 30 6
      TEAMModelOS/ClientApp/src/components/selflearn/ExerciseList.vue
  21. 8 12
      TEAMModelOS/ClientApp/src/components/selflearn/NewChooseContent.vue
  22. 6 6
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReport.vue
  23. 2 2
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperTest.vue
  24. 19 2
      TEAMModelOS/ClientApp/src/components/syllabus/DragTree.less
  25. 11 6
      TEAMModelOS/ClientApp/src/components/syllabus/DragTree.vue
  26. 1 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/settings.js
  27. 7 0
      TEAMModelOS/ClientApp/src/router/routes.js
  28. 5 0
      TEAMModelOS/ClientApp/src/utils/blobTool.js
  29. 18 10
      TEAMModelOS/ClientApp/src/utils/public.js
  30. 3 3
      TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue
  31. 6 4
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue
  32. 13 2
      TEAMModelOS/ClientApp/src/view/learnactivity/CreateSchoolEva.vue
  33. 0 2
      TEAMModelOS/ClientApp/src/view/learnactivity/PaperScore.vue
  34. 695 0
      TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkCanvas.vue
  35. 370 84
      TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkView.vue
  36. 123 69
      TEAMModelOS/ClientApp/src/view/settings/OpenMgmt2.vue
  37. 9 3
      TEAMModelOS/ClientApp/src/view/student-account/ImportStudent.vue
  38. 93 2
      TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.less
  39. 233 17
      TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue
  40. 35 14
      TEAMModelOS/ClientApp/src/view/task/index.vue
  41. 0 0
      TEAMModelOS/ClientApp/src/view/task/mark/ByQu.vue
  42. 718 0
      TEAMModelOS/ClientApp/src/view/task/mark/ByStu.vue
  43. 3 3
      TEAMModelOS/Controllers/Common/ExamController.cs
  44. 97 29
      TEAMModelOS/Controllers/Core/BlobController.cs
  45. 7 7
      TEAMModelOS/Controllers/Core/OpenApiController.cs
  46. 0 1
      TEAMModelOS/Services/Common/SyllabusService.cs

+ 1 - 1
TEAMModelOS.SDK/Models/Cosmos/Common/Bloblog.cs

@@ -15,7 +15,7 @@ namespace TEAMModelOS.SDK.Models
         public long  size { get; set; }
         public string period { get; set; }
         /// <summary>
-        /// 
+        /// audio 音频,video 视频 ,doc文档,image图片,other 其他,res教材,thum缩略图
         /// </summary>
         public string type { get; set; }
     }

+ 6 - 6
TEAMModelOS.SDK/Models/Cosmos/Common/Snode.cs

@@ -10,11 +10,7 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
     /// </summary>
     public  class Snode
     {
-        /// <summary>
-        ///0 分支节点,1资源节点,2 试题 ,3试卷,4,5外部链接(只有link,和name),
-        ///tree分支节点  rnode资源节点  item 试题 paper试卷 link外部链接 audio 音频,video 视频 ,doc文档,image图片,other 其他,res教材,thum缩略图
-        /// </summary>
-        public string type { get; set; } 
+ 
         public string title { get; set; }
 
     }
@@ -47,7 +43,11 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
     /// 资源节点
     /// </summary>
     public class Rnode : Snode
-    {
+    {       
+        /// <summary>
+        /// item 试题 paper试卷 link外部链接 audio 音频,video 视频 ,doc文档,image图片,other 其他,res教材,thum缩略图
+        /// </summary>
+        public string type { get; set; }
         public string id { get; set; }
         public string code { get; set; }
         public string scope { get; set; }

+ 2 - 0
TEAMModelOS/ClientApp/src/api/index.js

@@ -28,6 +28,7 @@ import serviceDriveAuth from './serviceDriveAuth'
 import spaceAuth from './spaceAuth'
 import room from './room'
 import mark from './mark'
+import openMgmt from './openMgmt';
 
 export default {
     accessToken,
@@ -57,6 +58,7 @@ export default {
     spaceAuth,
     room,
     mark,
+    openMgmt,
 
     // 获取登录跳转链接
     getLoginLink: function (data) {

+ 6 - 0
TEAMModelOS/ClientApp/src/api/mark.js

@@ -29,6 +29,12 @@ export default {
      */
      FindNextStu: function (data) {
         return post('/common/exam/review', data)
+    },
+    /**
+     * 阅卷打分
+     */
+    saveScore: function(data){
+        return post('/common/exam/sub-result', data)
     }
 
 }

+ 25 - 3
TEAMModelOS/ClientApp/src/api/openMgmt.js

@@ -1,11 +1,33 @@
-import { } from '@/api/http';
+import { post } from '@/api/http';
 
 export default{
     /**
      * 开放平台列表
-     * @param {any} data
+     * @param {code: "学校编码"} data
      */
     getOpenList: function (data) {
-        return post('', data)
+        return post('/open-api/find-app', data)
+    },
+    /**
+     * 获取token
+     * @param {id: "应用id", code: "学校编码"} data
+     */
+    getToken: function (data) {
+        return post('/open-api/create-token', data)
+    },
+    /**
+     * 获取api
+     * @param {} data
+     */
+    getApiList: function (data) {
+        return post('/open-api/get', data)
+    },
+    /**
+     * 新增/编辑
+     * @param {} data  新增
+     * @param {id: "应用id"} data  编辑
+     */
+    editOpenInfo: function (data) {
+        return post('/open-api/upsert-app', data)
     }
 }

BIN
TEAMModelOS/ClientApp/src/assets/source/audio.png


BIN
TEAMModelOS/ClientApp/src/assets/source/excel.png


BIN
TEAMModelOS/ClientApp/src/assets/source/folder.png


BIN
TEAMModelOS/ClientApp/src/assets/source/image.png


BIN
TEAMModelOS/ClientApp/src/assets/source/item.png


BIN
TEAMModelOS/ClientApp/src/assets/source/link.png


BIN
TEAMModelOS/ClientApp/src/assets/source/pdf.png


BIN
TEAMModelOS/ClientApp/src/assets/source/ppt.png


BIN
TEAMModelOS/ClientApp/src/assets/source/unknow.png


BIN
TEAMModelOS/ClientApp/src/assets/source/video.png


BIN
TEAMModelOS/ClientApp/src/assets/source/word.png


BIN
TEAMModelOS/ClientApp/src/assets/source/zip.png


+ 21 - 17
TEAMModelOS/ClientApp/src/common/UploadModal.vue

@@ -11,9 +11,7 @@
                 </p>
                 <p class="upload-text" style="font-size:12px;">{{$t('updModal.tips1')}}</p>
                 <p class="upload-text" :style="{fontSize:'12px',marginBottom: uploadedList.length ? '25px':'50px'}">
-                    <!-- {{$t('updModal.tips2')}}
-                    <Icon custom="iconfont icon-convert" size="12" class="is-parse-htex" /> -->
-                    * {{$t('updModal.tips3')}}
+                    {{$t('updModal.tips3')}}
                 </p>
             </Upload>
             <div class="upload-file-box">
@@ -86,13 +84,6 @@ export default {
         }
     },
     props: {
-        //默认文件列表
-        defaultFileList: {
-            default: () => {
-                return []
-            },
-            type: Array
-        },
         //文件路径
         path: {
             default: '',
@@ -133,6 +124,11 @@ export default {
         pdId: {
             default: '',
             type: String
+        },
+        //上传到容器位置 school/private
+        scope: {
+            default: '',
+            type: String
         }
     },
     methods: {
@@ -279,7 +275,8 @@ export default {
                 this.uploadedList.forEach((item, index) => {
                     if (item.loadedBytes < item.size) {
                         this.loadingCount++
-                        this.confirmUpload(item.file, item.type, index)
+                        //默认使用props接受的路径,没有则根据文件类型上传
+                        this.confirmUpload(item.file, this.path || item.type, index)
                     }
                 })
 
@@ -287,7 +284,6 @@ export default {
         },
         // 确认上传文件
         confirmUpload(file, fileType, index) {
-            console.log(fileType)
             let extension = this.uploadedList[index].extension
             let _this = this
             this.containerClient.upload(file, fileType, {
@@ -404,11 +400,19 @@ export default {
     },
     created() {
         let route = this.$route
-        if (route.name == 'personalcontent') {
-            this.routerScope = 'private'
-        } else {
-            this.routerScope = 'school'
+        //没有接收 scope,则通过内容模块逻辑,路由判断
+        if (!this.scope) {
+            if (route.name == 'personalcontent') {
+                this.routerScope = 'private'
+            } else {
+                this.routerScope = 'school'
+            }
         }
+        // 使用组件接收的值
+        else{
+            this.routerScope = this.scope
+        }
+
     },
     watch: {
         urlString: {
@@ -458,7 +462,7 @@ export default {
 
 </script>
 <style scoped>
-.upd-to-pd{
+.upd-to-pd {
     margin-bottom: 10px;
 }
 .upload-text {

+ 3 - 2
TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.less

@@ -122,7 +122,7 @@
     height: auto;
     padding: 10px 20px 10px 20px;
     margin-top: 10px;
-    font-size: 16px;
+    font-size: 14px;
     background: #fff;
     border: 2px solid transparent;
     cursor: pointer;
@@ -145,6 +145,7 @@
   border-bottom: 2px solid rgb(128, 128, 128);
 }
 .cp-exercise-item {
+	color: #565656;
   .item-question {
     position: relative;
     cursor: pointer;
@@ -251,7 +252,7 @@
       border-radius: 5px;
     }
     .explain-title {
-      width: 10%;
+      width: 12%;
       max-width: 85px;
       display: inline-block;
       color: rgb(16, 171, 231);

+ 30 - 6
TEAMModelOS/ClientApp/src/components/selflearn/ExerciseList.vue

@@ -19,7 +19,7 @@
                 <span class="filter-title">年级:</span>
                 <CheckboxGroup v-model="filterGrade" border @on-change="filterGradeChange">
                     <Checkbox lable="all">全部</Checkbox>
-                    <Checkbox v-for="(item, index) in gradeList" :key="index" :label="item.id">{{ item.name }}</Checkbox>
+                    <Checkbox v-for="(item, index) in gradeList" :key="index" :label="index">{{ item }}</Checkbox>
                 </CheckboxGroup>
             </div>
             <div class="filter-item" v-show="filterOrigin == schoolCode">
@@ -76,12 +76,12 @@
         </div>
         <!-- 筛选部分结束 -->
         <!-- 题目列表部分 -->
+		<Loading :top="100" v-show="dataLoading" hideMask></Loading>
         <div v-if="exerciseList.length === 0" class="no-data-text">
             <img src="@/assets/icon/no_data_evaluation.png" width="120" />
             <span style="margin-top: 15px; color: #808080">暂无数据</span>
         </div>
         <div class="content-wrap" ref="mathJaxContainer" v-else>
-            <Loading :top="100" v-show="dataLoading" hideMask></Loading>
             <div class="exercise-item" v-for="(item, index) of exerciseList" :key="index" @click="onQuestionToggle(index, item.id, $event)">
                 <!-- 题干部分 -->
                 <div class="item-question">
@@ -158,8 +158,8 @@
                     <span class="item-tools-info">难度:{{ exersicesDiff[item.level - 1] }}</span>
                     <span class="item-tools-info">使用次数:{{ item.usageCount || 0 }} 次</span>
 
-                    <Button type="text" @click.stop="chooseExercise(item)" icon="md-add" style="margin-right: 10px">
-                        选题
+                    <Button type="text" @click.stop="chooseExercise(item)" :icon="ids.indexOf(item.id) > -1 ? 'md-remove' : 'md-add'" style="margin-right: 10px">
+                        {{ids.indexOf(item.id) > -1 ? '移除' : '选题'}}
                     </Button>
                 </div>
             </div>
@@ -209,12 +209,27 @@ export default {
             curAudioName: "",
             curVideoSrc: "",
             curVideoName: "",
+            selectItems: []
         };
     },
     created() {
         this.getSchoolInfo()
     },
     methods: {
+        /**挑选试题 */
+        chooseExercise(item) {
+            if(this.ids.indexOf(item.id) == -1){
+                this.selectItems.push(item) //添加题目
+            }else{
+                for(let i = 0; i < this.selectItems.length; i++){
+                    if(this.selectItems[i].id == item.id){
+                        this.selectItems.splice(i,1) //移除
+                        break
+                    }
+                }
+            }
+            this.$emit('chooseQuChange',this.selectItems)
+        },
         /* 音频弹窗切换事件 */
         onAudioModalChange(val) {
             if (!val) {
@@ -307,7 +322,7 @@ export default {
                 "@DESC": this.filterSort,
                 code: this.filterOrigin,
                 periodId: this.filterOrigin == this.schoolCode ? [this.periodList[this.filterPeriod].id] : [],
-                "gradeIds[*]": this.filterOrigin == this.schoolCode ? this.deleteFalse(this.filterGrade) : [],
+                "gradeIds[*]": this.filterOrigin == this.schoolCode ? this.deleteFalse(this.filterGrade).map(i => i + '') : [],
                 subjectId: this.filterOrigin == this.schoolCode ? this.deleteFalse(this.filterSubject) : [],
                 level: this.deleteFalse(this.filterDiff),
                 type: this.deleteFalse(this.filterType),
@@ -511,7 +526,7 @@ export default {
         deleteFalse(arr) {
             let list = JSON.parse(JSON.stringify(arr));
             list.forEach((item, index) => {
-                if (!item || item === "all") list.splice(index, 1);
+                if ((!item || item === "all") && item !== 0 ) list.splice(index, 1);
             });
             return list;
         },
@@ -569,6 +584,12 @@ export default {
         hasSchool() {
             return this.$store.state.userInfo.hasSchool;
         },
+        //已选题目的id
+        ids() {
+            return this.selectItems.map(item => {
+                return item.id
+            })
+        }
     },
 };
 </script>
@@ -598,4 +619,7 @@ export default {
     transform: translateY(10px);
     opacity: 0;
 }
+.filter-item .ivu-checkbox{
+    display: none;
+}
 </style>

+ 8 - 12
TEAMModelOS/ClientApp/src/components/selflearn/NewChooseContent.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="choose-content">
-        <Tabs type="card" name="chooseContent" @on-click="clickTab">
+        <Tabs :value="tabName" type="card" name="chooseContent" @on-click="clickTab">
             <!-- 选择课纲内容 -->
             <TabPane label="课纲" name="syllabus" v-if="showSyllabus" tab="chooseContent">
                 <div class="tab-wrap">
@@ -104,12 +104,11 @@
                     </div>
                 </div>
             </TabPane>
-
             <!-- 选择题库 -->
             <TabPane label="题目" name="question" v-if="showQuestion" tab="chooseContent">
                 <div class="tab-wrap">
                     <vuescroll>
-                        <ExerciseList></ExerciseList>
+                        <ExerciseList @chooseQuChange="chooseQuChange"></ExerciseList>
                     </vuescroll>
                 </div>
             </TabPane>
@@ -160,6 +159,7 @@ export default {
     },
     data() {
         return {
+			tabName:'content',
             questionList: [],
             pageNum: 1,
             pageSize: 20,
@@ -270,6 +270,10 @@ export default {
         }
     },
     methods: {
+        //选择题目
+        chooseQuChange(qus){
+            this.$emit('quChange',qus)
+        },
         //初始化数据
         initData() {
             this.questionFilter.code = this.$store.state.userInfo.TEAMModelId
@@ -383,17 +387,9 @@ export default {
                 }, 500)
             })
         },
-        /**
-         * 查询当前页题目
-         */
-        getCurrentPageData(pageNum) {
-            this.queryQuestionByPage()
-        },
-        selectQuestion(data) {
-            this.$emit('on-select-question', data)
-        },
         //Tab切换事件
         clickTab(name) {
+			this.tabName = name
             switch (name) {
                 case 'content':
                     this.getFileList()

+ 6 - 6
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReport.vue

@@ -170,7 +170,7 @@
                                 <!-- 作答结果 -->
                                 <div class="TitleRec1"><span style="margin:5px;color:#1472c7">{{$t("studentWeb.exam.report.ansRes")}}:</span></div>
                                 <br />
-                                <div v-if="ansData[index]" style="margin-left:10px" v-html="ansData[index].length > 0 ? ansData[index][0] : $t('studentWeb.exam.report.noAns')"></div>
+                                <div v-if="ansData[index]" style="margin-left:10px" v-html="ansData[index].length > 0 ? ansData[index].join(' ') : $t('studentWeb.exam.report.noAns')"></div>
                             </div>
                         </div>
                         <!-- 参考答案、解析 -->
@@ -507,11 +507,11 @@
                             paper.push(exam[i])
                         }
                     }
-               }
-               this.paperData = [...paper]
-               if (this.paperData.length) {
-                   this.ansData = await this.getItem(this.examInfo.stuAns[0])
-               }
+                }
+                this.paperData = [...paper]
+                if (this.paperData.length) {
+                    this.ansData= await this.getItem(this.examInfo.stuAns[0])
+                }
             },
             closeDetail() {
                 this.closeAnsDetail = !this.closeAnsDetail;

+ 2 - 2
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperTest.vue

@@ -474,7 +474,7 @@
                     let req = {
                         id: this.getItemTitle.id,
                         answer: this.checkers,
-                        studentId: this.userInfo.sub,
+                        studentId: this.getUserInfo.studentId,
                         classId: this.getExamInfo.allClass,
                         subjectId: this.getExamInfo.subject.id,
                         multipleRule: this.getExamInfo.multipleRule,
@@ -589,7 +589,7 @@
                 "getPaperInfo",
                 "getCurrentSubject",
                 "getExamInfo",
-                "userInfo"
+                "getUserInfo"
             ]),
             completeRate() {
                 if (this.examInfo.length) {

+ 19 - 2
TEAMModelOS/ClientApp/src/components/syllabus/DragTree.less

@@ -8,6 +8,10 @@
     padding-top: 10px;
     padding-bottom: 100px;
   }
+  
+  .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{
+	  background-color: #313131 !important;
+  }
   .el-tree-node__content {
     height: 45px;
     padding-left: 0 !important;
@@ -73,9 +77,22 @@
     overflow: hidden;
     color: #fff;
   }
+  
+  .content-wrap{
+	  height: 600px !important;
+	  overflow: auto;
+	  padding-bottom: 200px;
+  }
+  
+  .ivu-modal-footer{
+	  border: none;
+	  display: flex;
+	  justify-content: center;
+  }
   .ivu-modal-body {
-    height: 400px;
+	max-height: 650px;
     padding: 20px;
+	overflow: hidden;
   }
   .ivu-modal-header {
     border-bottom: none;
@@ -110,7 +127,7 @@
     margin-top: 30px;
   }
   .choose-content {
-    height: 85%;
+    // height: 85%;
   }
 }
 /*修改iview Tab标签页样式重写*/

+ 11 - 6
TEAMModelOS/ClientApp/src/components/syllabus/DragTree.vue

@@ -1,12 +1,12 @@
 <template>
 	<div class="syllabus-tree-main">
 		<vuescroll>
-			<el-tree :data="treeDatas" :props="defaultProps" class="tree" node-key="id" default-expand-all
+			<el-tree :data="treeDatas" :props="defaultProps" class="tree" node-key="id"  default-expand-all highlight-current
 				@node-drop="handleDrop" @node-click="onNodeClick" :draggable="editable" :expand-on-click-node="false">
 				<span class="custom-tree-node" slot-scope="{ node, data }">
 					<span class="tree-node-lable">
 						{{data.title}}
-						<Icon type="md-cube" title="有关联资源" v-if="data.rnodes.length"/>
+						<Icon type="md-cube" title="有关联资源" v-if="data.rnodes && data.rnodes.length"/>
 					</span>
 					<span class="custom-tree-tools" v-if="editable">
 						<Icon type="md-create" size="16" title="编辑" @click="onEditItem(node,data,$event)" />
@@ -26,7 +26,7 @@
 				<p class="node-title">节点名称</p>
 				<Input v-model="nodeInfo.title" placeholder="请输入节点名称..." style="width: 100%" />
 			</div>
-			<Button @click="onSubmitNode" class="modal-btn">确认</Button>
+			<Button @click="onSubmitNode" class="modal-btn" style="width: 88%;margin-left: 6%;margin-bottom: 20px;">确认</Button>
 		</Modal>
 	</div>
 </template>
@@ -36,13 +36,11 @@
 	import BaseResource from '@/view/syllabus/newSyllabus/operation/BaseResource'
 	import BaseKnowledge from '@/view/syllabus/newSyllabus/operation/BaseKnowledge'
 	import BaseQuestionList from '@/common/BaseQuestionList'
-	import ChooseContent from '@/components/selflearn/NewChooseContent'
 	export default {
 		props: ['volume', 'treeData', 'editable'],
 		components: {
 			BaseResource,
 			BaseKnowledge,
-			ChooseContent,
 			BaseQuestionList
 		},
 		data() {
@@ -66,13 +64,14 @@
 				currentParentData: null,
 				currentResources: [],
 				currentItems: [],
+				curNode:null,
 				nodeInfo: {
 					id: null,
 					title: '',
 					expand: true,
 					editable: true,
 					version: '',
-					type: 1,
+					type: '1',
 					children: [],
 					remark: '',
 					nodeKey: '',
@@ -86,6 +85,8 @@
 		methods: {
 			onNodeClick(data, node) {
 				console.log(data, node)
+				this.curNode = data
+				this.$emit('onNodeClick',data)
 			},
 			// 拖拽完成回调
 			handleDrop(draggingNode, dropNode, dropType) {
@@ -286,6 +287,10 @@
 					// volumeParent.children = n
 					// defaultTree.push(volumeParent)
 					this.treeDatas = n
+					this.$nextTick().then(() =>{
+						const firstNode = document.querySelector('.el-tree-node')
+						firstNode.click();
+					  })
 				},
 				immediate: true
 			},

+ 1 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/settings.js

@@ -55,6 +55,7 @@ export default {
 	apiName: '接口名称',
 	apiAddress: '接口地址',
 	apiMethod: '请求方法',
+	apiParams: '参数示例',
 	openKeep: '保存平台',
 	unedit: '取消编辑',
 }

+ 7 - 0
TEAMModelOS/ClientApp/src/router/routes.js

@@ -237,11 +237,18 @@ export const routes = [
 			}
 			]
 		},
+		//废弃 阅卷和批注代码混合,不方便维护
 		{
 			path: 'MarkView',
 			name:'MarkView',
 			component: resolve => require(['@/view/learnactivity/markpaper/MarkView.vue'], resolve)
 		},
+		// 按人阅卷
+		{
+			path: 'ByStu',
+			name:'ByStu',
+			component: resolve => require(['@/view/task/mark/ByStu.vue'], resolve)
+		},
 		//校园基础数据管理
 		{
 			name: 'system',

+ 5 - 0
TEAMModelOS/ClientApp/src/utils/blobTool.js

@@ -1,6 +1,7 @@
 import { GLOBAL } from '@/static/Global.js';
 import JsFn from '@/utils/js-fn.js';
 import API from '@/api/index.js';
+const blobPath = ['audio', 'doc', 'exam','image', 'item', 'notice', 'other', 'paper', 'res', 'student', 'survey', 'temp', 'thum', 'video', 'vote']
 const { BlobServiceClient, BlobClient } = require("@azure/storage-blob")
 //获取文件后缀和类型
 function getExAndType(fileName) {
@@ -193,6 +194,10 @@ export default class BlobTool {
      * @returns {object} {url, name,size,createTime,extension,type}
      */
     upload(file, path, option, checkSize = true, handleSize = true) {
+        console.log(blobPath.includes(path))
+        if (!blobPath.includes(path)) {
+            throw new Error('上传路径不合法,请检查上传路径:' + path)
+        }
         return new Promise(async (r, j) => {
             //检查容器空间大小
             if (checkSize) {

+ 18 - 10
TEAMModelOS/ClientApp/src/utils/public.js

@@ -351,22 +351,24 @@ export default {
 			}
 		})
 	},
-
+	
+	getBlobHost(){
+		let s = store.state.user.userProfile.blob_uri || store.state.user.studentProfile.blob_uri || 'https://teammodelstorage.blob.core.chinacloudapi.cn/hbcn'
+		return s.split(s.substring(s.lastIndexOf('/')))[0]
+	},
 
 	/**
 	 * 获取个人容器授权
 	 */
 	getPrivateSas(code) {
 		return new Promise((r, j) => {
-			if (!store.state.privateSas || checkSas(store.state.privateSas.timeout) || store.state.privateSas
-				.name !== store.state.userInfo.TEAMModelId) {
+			if (!store.state.user.schoolProfile.blob_sas) {
 				$api.blob.blobSasRCW({
 					name: code || store.state.userInfo.TEAMModelId,
-					role: 'teacher'
+					role: 'school'
 				}).then(
 					(res) => {
 						if (res.error == null) {
-							store.commit('setPrivateSas', res)
 							res.sas = '?' + res.sas
 							r(res)
 						} else {
@@ -378,7 +380,11 @@ export default {
 					}
 				)
 			} else {
-				r(store.state.privateSas)
+				r({
+					sas:'?' + store.state.user.userProfile.blob_sas,
+					name:code || store.state.userInfo.TEAMModelId,
+					url:this.getBlobHost()
+				})
 			}
 		})
 	},
@@ -387,15 +393,13 @@ export default {
 	 */
 	getSchoolSas(code) {
 		return new Promise((r, j) => {
-			if (!store.state.schoolSas || checkSas(store.state.schoolSas.timeout) || store.state.schoolSas
-				.name !== store.state.userInfo.schoolCode) {
+			if (!store.state.user.schoolProfile.blob_sas) {
 				$api.blob.blobSasRCW({
 					name: code || store.state.userInfo.schoolCode,
 					role: 'school'
 				}).then(
 					(res) => {
 						if (res.error == null) {
-							store.commit('setSchoolSas', res)
 							res.sas = '?' + res.sas
 							r(res)
 						} else {
@@ -407,7 +411,11 @@ export default {
 					}
 				)
 			} else {
-				r(store.state.schoolSas)
+				r({
+					sas:'?' + store.state.user.schoolProfile.blob_sas,
+					name:code || store.state.userInfo.schoolCode,
+					url:this.getBlobHost()
+				})
 			}
 		})
 	},

+ 3 - 3
TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue

@@ -318,7 +318,7 @@
 				this.$api.newEvaluation.FindExerciseList(data).then(async (res) => {
 					if(res.items.length){
 						let list = res.items;
-						if(!this.flag){
+						if((!this.flag && this.isShowSchoolBank) || (!this.flag && !this.isShowSchoolBank && this.filterOrigin === this.schoolCode)){
 							this.periodCountArr = this.getPeriodCount(res.items)
 							list = res.items.filter(i => i.periodId === this.periodList[this.filterPeriod].id)
 							this.flag = true
@@ -354,13 +354,13 @@
 				if(filterKey !== 'grade'){
 					let gradeIdArr = this.gradeList.map((i,index) => index)
 					this.gradeCountArr = gradeIdArr.map(i => {
-						return items.filter(j => j.gradeIds.includes(i+'')).length
+						return items.length ? items.filter(j => j.gradeIds && j.gradeIds.includes(i+'')).length : 0
 					})
 				}
 				if(filterKey !== 'subject'){
 					let subjectIdArr = this.subjectList.map(i => i.id)
 					this.subjectCountArr = subjectIdArr.map(i => {
-						return items.filter(j => j.subjectId === i).length
+						return items.length ? items.filter(j => j.subjectId === i).length : 0
 					})
 				}
 			},

+ 6 - 4
TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue

@@ -142,12 +142,14 @@
 					gradeCountArr:[],
 					subjectCountArr:[],
 					periodCountArr:[]
-				}
+				},
+				isShowSchoolBank:false
 			}
 		},
 		created() {
 			// this.getPaperList()
 			// this.doFilter()
+			this.isShowSchoolBank = this.$route.name === "schoolBank";
 
 		},
 		methods: {
@@ -236,7 +238,7 @@
 				let that = this
 				this.$api.learnActivity.FindExamPaper(params).then(async res => {
 					let list = res.papers
-					if(!this.flag && this.periodList.length){
+					if((!this.flag && this.periodList.length && this.filterParams.code === this.$store.state.userInfo.schoolCode) || (this.flag && !this.isShowSchoolBank && this.filterParams.code === this.$store.state.userInfo.schoolCode)){
 						this.filterCounts.periodCountArr = this.getPeriodCount(res.papers)
 						list = res.papers.filter(i => i.periodId === this.periodList[0].id)
 						this.flag = true
@@ -272,7 +274,7 @@
 				if(filterKey !== 'grade'){
 					let gradeIdArr = this.$refs.baseFilter.gradeList.map((i,index) => index)
 					this.filterCounts.gradeCountArr = gradeIdArr.map(i => {
-						return papers.filter(j => j.gradeIds.includes(i + '')).length
+						return papers.filter(j => j.gradeIds && j.gradeIds.includes(i + '')).length
 					})
 				}
 				if(filterKey !== 'subject'){
@@ -433,7 +435,7 @@
 			 * @param code
 			 */
 			getPeriodName(code) {
-				return this.$store.state.user.schoolProfile.school_base ? this.$store.state.user.schoolProfile.school_base
+				return code && this.$store.state.user.schoolProfile.school_base ? this.$store.state.user.schoolProfile.school_base
 					.period.filter(i => i.id === code)[0].name : this.$t('evaluation.noData')
 			},
 

+ 13 - 2
TEAMModelOS/ClientApp/src/view/learnactivity/CreateSchoolEva.vue

@@ -263,7 +263,13 @@ export default {
                 return item[0]
             }))
             graIds = Array.from(graIds)
-            this.evaluationInfo.grades = graIds //
+
+            this.evaluationInfo.grades = graIds.map(item => {
+                return {
+                    id: item + '',
+                    name: this.curGrades[item]
+                }
+            })
             // this.evaluationInfo.grades = this.curGrades.filter(item => {
             //     return graIds.indexOf(item.id) >= 0
             // })
@@ -534,7 +540,12 @@ export default {
 
                         //发布成功需要备份试卷数据
                         let examId = res.exam.id
-                        let privateSas = this.$store.state.privateSas
+                        // let privateSas = this.$store.state.privateSas
+                        let privateSas = {
+                            sas: '?' + this.$store.state.user.userProfile.blob_sas,
+                            url: this.$store.state.user.userProfile.blob_uri.slice(0, this.$store.state.user.userProfile.blob_uri.lastIndexOf(this.$store.state.userInfo.TEAMModelId) - 1),
+                            name: this.$store.state.userInfo.TEAMModelId
+                        }
                         let schoolSas = {
                             sas: '?' + this.$store.state.user.schoolProfile.blob_sas,
                             url: this.$store.state.user.schoolProfile.blob_uri.slice(0, this.$store.state.user.schoolProfile.blob_uri.lastIndexOf(this.$store.state.userInfo.schoolCode) - 1),

+ 0 - 2
TEAMModelOS/ClientApp/src/view/learnactivity/PaperScore.vue

@@ -600,7 +600,6 @@ export default {
         },
         paperInfo: {
             handler(newPaper) {
-                console.log('试卷数据', newPaper)
                 if (newPaper && newPaper.item) {
                     this.dataLoading = true
                     let that = this
@@ -634,7 +633,6 @@ export default {
                                 }
                             })
                         })
-                        console.log('题型顺序', this.groupList)
                     }
                     this.dataLoading = false
                 }

+ 695 - 0
TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkCanvas.vue

@@ -0,0 +1,695 @@
+<template>
+    <!-- 未做多语言 -->
+    <div class="mark-stage">
+        <vuescroll ref="canvasScroll">
+            <!-- canvas部分 -->
+            <div class="container-box">
+                <div id="container" @mousemove="canvasMouseMove" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp"></div>
+            </div>
+        </vuescroll>
+    </div>
+</template>
+
+<script>
+import Konva from 'konva'
+export default {
+    name: 'MarkCanvas',
+    props: {
+        // 当前鼠标操作类型
+        mouseStatus: {
+            type: String,
+            default: 'move',
+            required: true
+        },
+        //canvas主图(eg:学生作答数据)
+        bgImg: {
+            type: [String, Object],
+            default: ''
+        },
+        //canvas绘制图片的数据(eg:添加表情包)
+        drawImgData: {
+            type: [String, Object],
+            default: ''
+        }
+    },
+    data() {
+        return {
+            stage: undefined,
+            orgLayer: undefined,
+            markLayer: undefined,
+            tr: undefined,
+            scaleDefault: 1,
+            maxScale: 5,
+            minScale: 0.2,
+            scaleStep: 0.2,
+            imgWidth: 0,
+            imgHeight: 0,
+            isMouseDown: false,
+            clickPoint: {
+                x: 0,
+                y: 0
+            },
+            // mouseStatus: 'move', //move:移动  rect:画矩形 resize:调整图形
+            resizeBefore: '',
+            rect: undefined,
+            oval: undefined,
+            arrow: undefined,
+            text: undefined,
+            line: undefined,
+            img: undefined,
+            isDltText: false,
+        }
+    },
+    methods: {
+        uuid() {
+            function S4() {
+                return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
+            }
+            return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4())
+        },
+        move(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'move'
+        },
+        drawImg(imgIndex) {
+            // this.activeIcon = imgIndex
+            // this.curImg = new Image()
+            // this.curImg.src = this.imgs[imgIndex]
+            // this.curImg.onload = () => {
+            //     this.mouseStatus = 'img'
+            //     this.startImg({ x: 100, y: 100 })
+            // }
+        },
+        startImg(current) {
+            let _this = this
+            this.img = new Konva.Image({
+                id: this.uuid(),
+                draggable: false,
+                strokeScaleEnabled: false,
+                image: _this.curImg,
+                width: 80,
+                height: 80,
+                x: current.x,
+                y: current.y
+            })
+            // 监听点击事件设置选中状态
+            this.img.on('click', (evt) => {
+                _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                _this.mouseStatus = 'resize'
+                evt.target.draggable(true)
+                evt.cancelBubble = true //阻止事件冒泡 
+                _this.tr.nodes([evt.target])
+                _this.markLayer.add(_this.tr)
+            })
+            // 优化一些绘制逻辑
+            this.img.on('mouseout', (evt) => {
+                evt.cancelBubble = true //阻止事件冒泡
+                evt.target.draggable(false)
+                let nodes = _this.tr.nodes()
+                if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+            })
+            // this.img.zIndex(1)
+            this.markLayer.add(this.img)
+            this.stage.add(this.markLayer)
+            this.img = null
+        },
+        drawText(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'text'
+        },
+        startText(current) {
+            let _this = this
+            let nodes = this.tr.nodes()
+            if (nodes.length > 0) return
+            if (!this.text) {
+                this.text = new Konva.Text({
+                    id: this.uuid(),
+                    fontSize: 16,
+                    text: '',
+                    fill: 'red',
+                    lineHeight: 1.5,
+                    width: 180,
+                    height: 30,
+                    x: _this.clickPoint.x / _this.scaleDefault,
+                    y: _this.clickPoint.y / _this.scaleDefault,
+                    draggable: false
+                })
+                // 监听点击事件设置选中状态
+                this.text.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                this.text.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+                this.text.on('dblclick dbltap', (evt) => {
+                    let nodes = _this.tr.nodes()
+                    if (nodes && nodes.length > 0) {
+                        let text = nodes[0]
+                        let textPosition = text.getAbsolutePosition()
+                        let stageBox = _this.stage.container().getBoundingClientRect()
+                        let areaPosition = {
+                            x: stageBox.left + textPosition.x,
+                            y: stageBox.top + textPosition.y,
+                        }
+                        let textarea = document.createElement('textarea');
+                        document.body.appendChild(textarea)
+
+                        textarea.value = text.text()
+                        textarea.style.position = 'absolute'
+                        textarea.style.top = areaPosition.y + 'px'
+                        textarea.style.left = areaPosition.x + 'px'
+                        textarea.style.background = 'transparent'
+                        textarea.style.color = 'red'
+                        textarea.style.fontSize = '16px'
+                        textarea.style.borderColor = 'red'
+                        textarea.style.boxShadow = 'none'
+                        textarea.style.height = text.height() + 'px'
+                        textarea.className = 'textarea'
+                        textarea.style.width = text.width()
+
+                        textarea.focus()
+                        _this.isDltText = true //设置删除按键的状态,文本框状态应该是删除文字不能删除图形
+                        text.hide()
+                        _this.tr.hide()
+                        textarea.addEventListener('input', () => {
+                            textarea.style.height = 'auto'
+                            textarea.style.height = textarea.scrollHeight + 'px'
+                            _this.isDltText = true
+                        })
+                        textarea.addEventListener('blur', function (e) {
+                            if (textarea.value) {
+                                text.text(textarea.value)
+                                text.width(textarea.clientWidth)
+                                text.height(textarea.scrollHeight)
+                                text.show()
+
+                                _this.markLayer.draw()
+                            } else {
+                                text.remove()
+                            }
+                            _this.tr.nodes([])
+                            _this.tr.show()
+                            document.body.removeChild(textarea)
+                            _this.isDltText = false
+                        })
+                    }
+                })
+            } else {
+                this.text.x(current.x)
+                this.text.y(current.y)
+            }
+            // this.text.zIndex(100)
+            this.tr.nodes([this.text])
+            this.markLayer.add(this.tr)
+            this.markLayer.add(this.text)
+            this.stage.add(this.markLayer)
+        },
+        drawLine(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'line'
+        },
+        startLine(current) {
+            let _this = this
+            if (!this.line) {
+                this.line = new Konva.Line({
+                    id: this.uuid(),
+                    stroke: 'red',
+                    strokeWidth: 3,
+                    draggable: false,
+                    tension: 1,
+                    points: [_this.clickPoint.x / _this.scaleDefault, _this.clickPoint.y / _this.scaleDefault]
+                })
+                // 监听点击事件设置选中状态
+                this.line.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.line.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.line.points(this.line.points().concat([current.x / _this.scaleDefault, current.y / _this.scaleDefault]))
+            }
+            // this.line.zIndex(1)
+            this.markLayer.add(this.line)
+            this.stage.add(this.markLayer)
+        },
+        drawArrow(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'arrow'
+        },
+        // 绘制箭头
+        startArrow(current) {
+            let _this = this
+            if (!this.arrow) {
+                this.arrow = new Konva.Arrow({
+                    id: this.uuid(),
+                    fill: 'transparent',
+                    stroke: 'red',
+                    strokeWidth: 4,
+                    pointerLength: 10,
+                    pointerWidth: 8,
+                    draggable: false,
+                    fill: 'red',
+                    lineCap: 'round',
+                    strokeScaleEnabled: false,
+                    points: [_this.clickPoint.x / _this.scaleDefault, _this.clickPoint.y / _this.scaleDefault, (current.x) / _this.scaleDefault, (current.y) / _this.scaleDefault]
+                })
+                // 监听点击事件设置选中状态
+                this.arrow.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.arrow.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.arrow.points([_this.clickPoint.x / _this.scaleDefault, _this.clickPoint.y / _this.scaleDefault, (current.x) / _this.scaleDefault, (current.y) / _this.scaleDefault])
+            }
+            // this.arrow.zIndex(1)
+            this.markLayer.add(this.arrow)
+            this.stage.add(this.markLayer)
+        },
+        drawOval(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'oval'
+        },
+        startOval(current) {
+            console.log('scale', this.scaleDefault)
+            console.log('point', this.clickPoint)
+            let _this = this
+            //判断rect是否初始化
+            let rx = (current.x - _this.clickPoint.x) / _this.scaleDefault * 0.5
+            let ry = (_this.clickPoint.y - current.y) / _this.scaleDefault * 0.5
+            if (!this.oval) {
+                this.oval = new Konva.Ellipse({
+                    id: this.uuid(),
+                    // x: _this.clickPoint.x / _this.scaleDefault,
+                    // y: _this.clickPoint.y / _this.scaleDefault,
+                    x: _this.clickPoint.x,
+                    y: _this.clickPoint.y,
+                    radiusX: rx > 0 ? rx : -rx,
+                    radiusY: ry,
+                    fill: 'transparent',
+                    stroke: 'red',
+                    strokeWidth: 2,
+                    draggable: false,
+                    rotation: rx > 0 ? 0 : 180,
+                    strokeScaleEnabled: false
+                })
+                // 监听点击事件设置选中状态
+                this.oval.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.oval.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.oval.x(_this.clickPoint.x / _this.scaleDefault + rx)
+                this.oval.y(_this.clickPoint.y / _this.scaleDefault - ry)
+                this.oval.radiusX(rx > 0 ? rx : -rx)
+                this.oval.radiusY(ry)
+                this.oval.rotation(rx > 0 ? 0 : 180)
+            }
+            this.oval.zIndex(1)
+            this.markLayer.add(this.oval)
+            this.stage.add(this.markLayer)
+        },
+        drawIcon(index) {
+            this.activeIcon = index
+        },
+        review(index) {
+            this.activeIcon = index
+        },
+        cancelFull(index) {
+            this.activeIcon = index
+        },
+        //绘制矩形
+        drawRect(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'rect'
+        },
+        startRect(current) {
+            let _this = this
+            //判断rect是否初始化
+            if (!this.rect) {
+                this.rect = new Konva.Rect({
+                    id: this.uuid(),
+                    x: _this.clickPoint.x / _this.scaleDefault,
+                    y: _this.clickPoint.y / _this.scaleDefault,
+                    width: (current.x - _this.clickPoint.x) / _this.scaleDefault,
+                    height: (current.y - _this.clickPoint.y) / _this.scaleDefault,
+                    fill: 'transparent',
+                    stroke: 'red',
+                    strokeWidth: 2,
+                    draggable: false,
+                    strokeScaleEnabled: false
+                })
+                // 监听点击事件设置选中状态
+                this.rect.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡
+                    //Transformer   
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.rect.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.rect.x(_this.clickPoint.x / _this.scaleDefault)
+                this.rect.y(_this.clickPoint.y / _this.scaleDefault)
+                this.rect.width((current.x - _this.clickPoint.x) / _this.scaleDefault)
+                this.rect.height((current.y - _this.clickPoint.y) / _this.scaleDefault)
+            }
+            this.rect.zIndex(1)
+            this.markLayer.add(this.rect)
+            this.stage.add(this.markLayer)
+        },
+        //防抖函数
+        debounce(func, delay) {
+            return function () {
+                window.clearTimeout(window.timeout)
+                window.timeout = setTimeout(() => {
+                    clearTimeout(window.timeout)
+                    func.apply(this, arguments)
+                }, delay)
+            }
+        },
+        //还原比例
+        restore() {
+            this.scaleDefault = 1
+            this.myScale()
+        },
+        // 缩小
+        smaller() {
+            console.log(this.stage)
+            this.scaleDefault -= this.scaleStep
+            if (this.scaleDefault < this.minScale) {
+                this.scaleDefault += this.scaleStep
+                //函数防抖,优化重复提示
+                this.debounce(() => {
+                    this.$Message.warning('已经缩小到最小比例')
+                }, 400)()
+            } else {
+                this.myScale()
+            }
+        },
+        //放大
+        larger() {
+            console.log(this.stage)
+            this.scaleDefault += this.scaleStep
+            if (this.scaleDefault > this.maxScale) {
+                this.scaleDefault -= this.scaleStep
+                //函数防抖,优化重复提示
+                this.debounce(() => {
+                    this.$Message.warning('已经放大到最大比例')
+                }, 400)()
+            } else {
+                this.myScale()
+            }
+        },
+        myScale() {
+            this.stage.children.forEach(item => {
+                item.scale({
+                    x: this.scaleDefault,
+                    y: this.scaleDefault
+                })
+            })
+            this.stage.width(this.imgWidth * this.scaleDefault)
+            this.stage.height(this.imgHeight * this.scaleDefault)
+        },
+        //绑定鼠标滚轮事件,操作缩放功能
+        bundleScoll() {
+            let stage = document.getElementById('container')
+            let _this = this
+            stage.addEventListener('mousewheel', function (e) {
+                if (e.preventDefault) {
+                    e.preventDefault()
+                } else {
+                    window.event.returnValue == false
+                }
+                _this.scrollFunc(e)
+            }, false) || stage.addEventListener('DOMMouseScroll', function (e) {
+                if (e.preventDefault) {
+                    e.preventDefault()
+                } else {
+                    window.event.returnValue == false
+                }
+                _this.scrollFunc(e)
+            }, false)
+        },
+        //判断鼠标放大还是缩小
+        scrollFunc(e) {
+            e = e || window.event
+            if (e.wheelDelta) { // 判断浏览器IE,谷歌滑轮事件
+                if (e.wheelDelta > 0) { // 当滑轮向上滚动时
+                    this.larger()
+                }
+                if (e.wheelDelta < 0) { // 当滑轮向下滚动时
+                    this.smaller()
+                }
+            } else if (e.detail) { // Firefox滑轮事件
+                if (e.detail > 0) { // 当滑轮向上滚动时
+                    this.larger()
+                }
+                if (e.detail < 0) { // 当滑轮向下滚动时
+                    this.smaller()
+                }
+            }
+        },
+        //获取在canvas上的坐标
+        getCanvasPoint(x, y) {
+            // let canvas = document.getElementById('container')
+            let canvas = document.getElementsByClassName('konvajs-content')
+            let cs = canvas.length ? canvas[0] : null
+            if (cs) {
+                let canvasRect = cs.getBoundingClientRect()
+                return {
+                    x: x - canvasRect.left,
+                    y: y - canvasRect.top
+                }
+            } else {
+                return {
+                    x, y
+                }
+            }
+
+        },
+        //按下鼠标事件
+        canvasMouseDown(e) {
+            this.isMouseDown = true
+            this.clickPoint = this.getCanvasPoint(e.clientX, e.clientY)
+        },
+        //鼠标弹起事件
+        canvasMouseUp() {
+            this.isMouseDown = false
+            switch (this.mouseStatus) {
+                case 'move':
+                    break
+                case 'rect':
+                    this.rect = null
+                    break
+                case 'oval':
+                    this.oval = null
+                    break
+                case 'arrow':
+                    this.arrow = null
+                    break
+                case 'text':
+                    this.text = null
+                    break
+                case 'line':
+                    this.line = null
+                    break
+                // case 'img':
+                //     this.img = null
+                //     break
+                default:
+                    break
+            }
+        },
+        //鼠标移动事件
+        canvasMouseMove(e) {
+            if (this.isMouseDown) {
+                let current = this.getCanvasPoint(e.clientX, e.clientY)
+                switch (this.mouseStatus) {
+                    case 'move':
+                        this.$refs["canvasScroll"].scrollBy({
+                            dx: this.clickPoint.x - current.x,
+                            dy: this.clickPoint.y - current.y
+                        }, 0)
+                        break
+                    case 'rect':
+                        this.startRect(current)
+                        break
+                    case 'oval':
+                        this.startOval(current)
+                        break
+                    case 'arrow':
+                        this.startArrow(current)
+                        break
+                    case 'text':
+                        this.startText(current)
+                        break
+                    case 'line':
+                        this.startLine(current)
+                        break
+                    default:
+                        break
+                }
+            }
+        },
+
+        //清除所有批注
+        clear(index) {
+            this.activeIcon = index
+            this.markLayer.removeChildren()
+            this.stage.add(this.markLayer)
+        }
+    },
+    mounted() {
+        let _this = this
+
+        //创建画布
+        _this.stage = new Konva.Stage({
+            container: 'container',
+            id: 'canvas',
+            width: 600,
+            height: 240
+        })
+        _this.orgLayer = new Konva.Layer()
+        _this.markLayer = new Konva.Layer()
+        _this.stage.add(_this.orgLayer)
+        _this.stage.add(_this.markLayer)
+        _this.orgLayer.batchDraw()
+        _this.bundleScoll()
+        _this.stage.on('click', (evt) => {
+            _this.tr.nodes([])
+        })
+        _this.stage.on('mouseover', (evt) => {
+            let nodes = _this.tr.nodes()
+            if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+
+        })
+
+        this.tr = new Konva.Transformer({
+            rotateEnabled: false,
+            anchorStroke: 'red',
+            enabledAnchors: ['top-center', 'top-left', 'top-right', 'middle-left', 'middle-right', 'bottom-left', 'bottom-center', 'bottom-right'],
+            anchorSize: 8,
+            borderStroke: 'red'
+        })
+        this.tr.borderEnabled(true)
+        this.tr.anchorCornerRadius(10)
+
+    },
+    created() {
+        //监听删除按键,删除图形
+        document.onkeydown = e => {
+            if (e.keyCode == 8 && !this.isDltText) {
+                let shape = this.tr ? this.tr.nodes() : []
+                if (shape.length) {
+                    shape[0].remove()
+                    this.tr.nodes([])
+                }
+            }
+        }
+    },
+    watch: {
+        bgImg: {
+            handler(n, o) {
+                this.$nextTick(() => {
+                    this.orgLayer.removeChildren()
+                    this.markLayer.removeChildren()
+                    let imageObj = new Image()
+                    let _this = this
+                    imageObj.onload = function () {
+                        //创建画布
+                        _this.imgWidth = imageObj.width
+                        _this.imgHeight = imageObj.height
+                        _this.stage.width(imageObj.width)
+                        _this.stage.height(imageObj.height)
+                        let img = new Konva.Image({
+                            x: 0,
+                            y: 0,
+                            image: imageObj,
+                            width: imageObj.width,
+                            height: imageObj.height
+                        })
+                        _this.orgLayer.add(img)
+                        _this.orgLayer.batchDraw()
+                    }
+                    imageObj.src = this.bgImg
+                    this.stage.add(this.orgLayer)
+                })
+            },
+            immediate: true,
+            deep: true
+        }
+    }
+}
+</script>
+<style scoped lang="less">
+.mark-stage {
+    flex: 1;
+    overflow: hidden;
+    background: white;
+    text-align: center;
+    position: relative;
+    height: 100%;
+}
+.container-box {
+    display: flex;
+    justify-content: center;
+    width: 100%;
+    padding: 10px;
+}
+</style>
+<style>
+.textarea:focus {
+    box-shadow: none;
+    overflow: hidden;
+    outline: none !important;
+    line-height: 1.5;
+}
+</style>

+ 370 - 84
TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkView.vue

@@ -4,24 +4,23 @@
         <!-- 头部基础信息 -->
         <div class="mark-header">
             <span class="quit-marking-text">
-                <!-- <Icon custom="iconfont icon-quit2" class="quit-marking-icon" title="退出阅卷" @click="test"/> -->
                 <Icon type="ios-arrow-back" class="quit-marking-icon" title="退出阅卷" @click="quit" />
-                <!-- 退出阅卷 -->
             </span>
             <span class="info-label">考试名称:</span>
-            <span class="info-value">一年级数学期末考试</span>
+            <span class="info-value">{{taskInfo.name}}</span>
             <span class="info-label">阅卷方式:</span>
             <span class="info-value">{{mode == 0 ? '按题阅卷' : '按人阅卷'}}</span>
             <span class="info-label">学生id:</span>
-            <span class="info-value">基金会</span>
+            <span class="info-value stu-id-info">{{stuId}}</span>
             <span class="info-label" v-show="mode == 1">分数:</span>
-            <span class="info-value" v-show="mode == 1">82</span>
+            <span class="info-value score-info" v-show="mode == 1">{{totalScore}}</span>
             <span class="info-label">当前题号:</span>
-            <span class="info-value">{{quIndex}}</span>
-            <span class="info-label">阅卷进度:</span>
-            <span class="info-value">{{`${12}/${41}`}}</span>
+            <span class="info-value cur-qu-index" v-if="childIndex > -1">{{`${quIndex + 1}-${childIndex + 1}`}}</span>
+            <span class="info-value cur-qu-index" v-else>{{quIndex + 1}}</span>
+            <!-- <span class="info-label">阅卷进度:</span>
+            <span class="info-value">{{`${12}/${41}`}}</span> -->
             <div class="btn-wrap">
-                <span class="action-btn">
+                <span class="action-btn" @click="toggleStatus = !toggleStatus">
                     <Icon type="md-shuffle" class="action-btn-icon" />
                     切换
                 </span>
@@ -65,21 +64,24 @@
                 <div class="qu-index-box" v-show="mode == 1">
                     <div class="qu-tips-box">
                         <span class="qu-tips-tag">
-                            满分
-                        </span>
-                        <span class="qu-tips-tag">
-                            半对
-                        </span>
-                        <span class="qu-tips-tag">
-                            错误
+                            已阅
                         </span>
                         <span class="qu-tips-tag">
                             未阅
                         </span>
                     </div>
                     <div>
-                        <span v-for="(item,index) in qus" :key="index" :class="['qu-index',index < 5 ? 'right-qu' : index < 8 ? 'half-qu' : index < 15 ? 'err-qu' : '']" @click="quIndex = index + 1">
-                            {{index + 1}}
+                        <span v-for="(item,index) in paperData.item" :key="index">
+                            <!-- 综合题 -->
+                            <span v-if="item.children.length" :key="index">
+                                <span @click="toQu(index,childIndex)" v-for="(childItem,childIndex) in item.children" :key="childIndex" :class="['qu-index',stuScore[getScoreIndex(index,childIndex)] > -1 ? 'right-qu' : '']">
+                                    {{(index + 1) + '-' + (childIndex + 1)}}
+                                </span>
+                            </span>
+                            <!-- 其他题 -->
+                            <span v-else @click="toQu(index)" :class="['qu-index',stuScore[getScoreIndex(index)] > -1 ? 'right-qu' : '']">
+                                {{index + 1}}
+                            </span>
                         </span>
                     </div>
                 </div>
@@ -88,37 +90,55 @@
             <div class="score-wrap">
                 <div class="quick-score-box score-input-box">
                     <span>分数:</span>
-                    <InputNumber style="flex:1" :max="10" :min="1" v-model="score"></InputNumber>
+                    <InputNumber style="flex:1" :max="10" :min="1" v-model="stuScore[getScoreIndex(quIndex,childIndex)]"></InputNumber>
                 </div>
                 <div class="quick-score-box">
                     <Button size="small" type="info" style="margin-right:8px" ghost @click="score = 10">满分</Button>
                     <Button size="small" type="error" ghost @click="score = 0">零分</Button>
                     <Icon :type="isShowNum ? 'md-eye-off' : 'md-eye'" class="toggle-num-status" @click="isShowNum = !isShowNum" />
                     <div :class="['score-key-box', isShowNum ? '':'hind-key-box']">
-                        <span v-for="(item,index) in scores" :key="index" :class="['score-key', score == index ? 'score-key-active':'']" @click="score = index">
+                        <span v-for="(item,index) in quScoreArr" :key="index" :class="['score-key', stuScore[getScoreIndex(quIndex,childIndex)] == index ? 'score-key-active':'']" @click="setScore(index)">
                             {{item}}
                         </span>
                     </div>
                 </div>
                 <Button type="success" class="submit-score" @click="submit()">提交分数</Button>
+                <div class="score-setting-wrap">
+                    <div class="score-setting-item">
+                        <span>提交分数自动切换题目</span>
+                        <i-switch v-model="autoQu" size="small"/>
+                    </div>
+                    <div class="score-setting-item">
+                        <span>完成阅卷自动获取新学生</span>
+                        <i-switch v-model="autoStu" @on-change="change" size="small"/>
+                    </div>
+                </div>
             </div>
         </div>
+        <!-- 用来单独渲染学生作答数据,提高tocanvas 的效率 -->
+        <iframe id="markIframe" :srcdoc="curAnswer" v-if="curAnswer"></iframe>
+        <Modal v-model="toggleStatus" title="切换学生">
+            进行中
+        </Modal>
     </div>
 </template>
 
 <script>
 import Konva from 'konva'
+import html2canvas from 'html2canvas';
 export default {
     name: 'Home',
     data() {
         return {
+            autoQu: true,//自动切换下一题
+            autoStu:true,//自动获取下一学生
+            toggleStatus: false,
             activeIcon: -1,
             score: 0,
             isShowNum: true,
-            scores: [],
-            qus: [],
             mode: '0',//阅卷模式 1:按人阅卷 0:按题阅卷
-            quIndex: 1,
+            quIndex: 0,
+            childIndex: undefined,
             stage: undefined,
             orgLayer: undefined,
             markLayer: undefined,
@@ -147,28 +167,50 @@ export default {
             curImg: undefined,
             counter: 0,
             isFull: false, //是否为全屏模式
-            examData:{
-                name:'期末考试',
+            examData: {
+                name: '期末考试',
             },
-            stuData:{
-                name:'',
-                id:'',
-            }
+            stuData: {
+                name: '',
+                id: '',
+            },
+            paperData: {
+                item: []
+            },
+            stuAnswer: [],
+            stuScore: [],
+            taskInfo: {},
+            stuId: ''
         }
     },
     methods: {
-        togglefull() {
-            this.$router.push({
-                name: this.isFull ? 'MarkView' : 'FullMarkView'
-            })
-        },
-        //提交分数
-        submit() {
-            this.counter++
-            this.nextQu()
+        /**将答案绘制到canvas上 */
+        ansToImg() {
+            let answerIframe = document.getElementById('markIframe')
+            answerIframe.onload = () => {
+                answerIframe.style.width = '850px'
+                answerIframe.contentWindow.document.body.style.margin = '0px 20px'
+                answerIframe.contentWindow.document.body.style.padding = '10px'
+                answerIframe.contentWindow.document.body.style.minWidth = '600px'
+                answerIframe.contentWindow.document.body.style.minHeight = '240px'
+                answerIframe.contentWindow.document.body.style.height = 'fit-content'
+                answerIframe.contentWindow.document.body.style.width = 'fit-content'
+                let bodyWidth = answerIframe.contentWindow.document.body.clientWidth
+                answerIframe.style.width = (bodyWidth + 20) + 'px'
+                answerIframe.contentWindow.document.body.style.backgroundColor = '#f5f5f5'
+                console.log('markIframe', answerIframe)
+                html2canvas(answerIframe.contentWindow.document.body, {}).then((canvas) => {
+                    console.log('canvas', canvas)
+                    canvas.id = "canvas" + this.getScoreIndex(this.quIndex, this.childIndex)
+                    let markBg = canvas.toDataURL()
+                    // 将转出来的答案绘制到canvas上
+                    this.imgToCanvas(markBg)
+                    console.log(markBg)
+                })
+            }
         },
-        //加载下一道题目
-        nextQu() {
+        //将图片(答案)绘制到canvas
+        imgToCanvas(img) {
             this.orgLayer.removeChildren()
             this.markLayer.removeChildren()
             let imageObj = new Image()
@@ -189,9 +231,157 @@ export default {
                 _this.orgLayer.add(img)
                 _this.orgLayer.batchDraw()
             }
-            imageObj.src = require('@/assets/mark/img' + this.counter % 3 + '.jpg')
+            imageObj.src = img
             this.stage.add(this.orgLayer)
         },
+        /** 打分 */
+        setScore(score) {
+            this.$set(this.stuScore, this.getScoreIndex(this.quIndex, this.childIndex), score)
+        },
+        toQu(index, childIndex) {
+            this.quIndex = index
+            this.childIndex = childIndex
+
+        },
+        /**
+         * index 题目index 必传
+         * childIndex 小题index 非必传 
+         */
+        getScoreIndex(index, childIndex) {
+            let realIndex = index
+            this.paperData.item.forEach((item, itemIndex) => {
+                if (itemIndex < index && item.children.length) {
+                    realIndex += item.children.length
+                } else if (itemIndex == index && item.children.length) {
+                    realIndex += childIndex
+                }
+            })
+            return realIndex
+        },
+        togglefull() {
+            this.$router.push({
+                name: this.isFull ? 'MarkView' : 'FullMarkView'
+            })
+        },
+        //提交分数
+        submit() {
+            let requstData = {
+                id: this.taskInfo.id,
+                stuId: this.stuId,
+                subjectId: this.taskInfo.subject,
+                tmdId: this.$store.state.userInfo.TEAMModelId,
+                score: this.stuScore,
+                count: this.taskInfo.count,
+                code: this.taskInfo.ecode.replace('Exam-', ''),
+                mark: ''
+            }
+            this.$api.mark.saveScore(requstData).then(
+                res => {
+                    this.$Message.success('保存成功')
+                    // 按人阅卷自动跳转下一题
+                    if (this.mode == 1 && this.autoQu) {
+                        this.nextQuestion()
+                        this.ansToImg()
+                    }
+                    //按题阅卷自动加载下一人
+                    else {
+
+                    }
+                },
+                err => {
+                    this.$Message.error('保存失败')
+                }
+            )
+
+        },
+        nextQuestion() {
+            // 当前不是最后一题
+            if (this.quIndex < this.paperData.item.length - 1) {
+                //当前题目是综合题
+                if (this.paperData.item[this.quIndex].children.length) {
+                    //当前小题不是最后一个
+                    if (this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                        this.childIndex++
+                    }
+                    //当前小题是最后一个,需要判断下一个题目类型
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+                //当前题目不是综合题
+                else {
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+            }
+            //如果是最后一题
+            else {
+                //当前题目是综合题,并且不是小题最后一题
+                if (this.paperData.item[this.quIndex].children.length && this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                    this.childIndex++
+                }
+                //当前目不是综合题 则代表题号已经到最后一个
+                else {
+                    //检查所有题目是否完成评测
+                    if (this.stuScore.includes(-1)) {
+                        let qu = ''
+                        let quIndex = 0
+                        let childIndex = -1
+                        let realIndex = 0
+                        // 检测具体未评分的题目
+                        for (let i = 0; i < this.paperData.item.length; i++) {
+                            if (this.paperData.item[i].children.length) {
+                                let flag = false
+                                for (let j = 0; j < this.paperData.item[i].children.length; j++) {
+                                    if (this.stuScore[realIndex++] == -1) {
+                                        quIndex = i
+                                        childIndex = j
+                                        flag = true
+                                        break
+                                    }
+                                }
+                                if (flag) {
+                                    break
+                                }
+                            } else {
+                                if (this.stuScore[realIndex++] == -1) {
+                                    quIndex = i
+                                    break
+                                }
+                            }
+                        }
+                        qu = childIndex > -1 ? `${(quIndex + 1)}-${(childIndex + 1)}` : quIndex
+                        this.$Modal.confirm({
+                            title: '未阅题目',
+                            content: `${qu}题目尚未评分,是否跳转到对应题目继续评分?`,
+                            onOk: () => {
+                                this.quIndex = quIndex
+                                this.childIndex = childIndex
+                            }
+                        })
+                    } else {
+                        this.$Message.success('已阅完')
+                    }
+
+                }
+            }
+        },
+
         quit() {
             // 返回页面
             this.$router.push({
@@ -726,44 +916,65 @@ export default {
     },
     mounted() {
         let _this = this
-        let imageObj = new Image()
-        imageObj.onload = function () {
-            //创建画布
-            _this.imgWidth = imageObj.width
-            _this.imgHeight = imageObj.height
-            _this.stage = new Konva.Stage({
-                container: 'container',
-                id: 'canvas',
-                width: imageObj.width,
-                height: imageObj.height
-            })
-            _this.orgLayer = new Konva.Layer({
-            })
-            _this.markLayer = new Konva.Layer()
-            _this.stage.add(_this.orgLayer)
-            _this.stage.add(_this.markLayer)
-            let img = new Konva.Image({
-                x: 0,
-                y: 0,
-                image: imageObj,
-                width: imageObj.width,
-                height: imageObj.height
-            })
-            _this.orgLayer.add(img)
-            _this.orgLayer.batchDraw()
-            _this.bundleScoll()
-            _this.stage.on('click', (evt) => {
-                _this.tr.nodes([])
-            })
-            _this.stage.on('mouseover', (evt) => {
-                let nodes = _this.tr.nodes()
-                if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+        // let imageObj = new Image()
+        // imageObj.onload = function () {
+        //     //创建画布
+        //     _this.imgWidth = imageObj.width
+        //     _this.imgHeight = imageObj.height
+        //     _this.stage = new Konva.Stage({
+        //         container: 'container',
+        //         id: 'canvas',
+        //         width: imageObj.width,
+        //         height: imageObj.height
+        //     })
+        //     _this.orgLayer = new Konva.Layer({
+        //     })
+        //     _this.markLayer = new Konva.Layer()
+        //     _this.stage.add(_this.orgLayer)
+        //     _this.stage.add(_this.markLayer)
+        //     let img = new Konva.Image({
+        //         x: 0,
+        //         y: 0,
+        //         image: imageObj,
+        //         width: imageObj.width,
+        //         height: imageObj.height
+        //     })
+        //     _this.orgLayer.add(img)
+        //     _this.orgLayer.batchDraw()
+        //     _this.bundleScoll()
+        //     _this.stage.on('click', (evt) => {
+        //         _this.tr.nodes([])
+        //     })
+        //     _this.stage.on('mouseover', (evt) => {
+        //         let nodes = _this.tr.nodes()
+        //         if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
 
-            })
+        //     })
 
-        }
-        imageObj.src = require('@/assets/mark/img' + this.counter % 3 + '.jpg')
-        
+        // }
+        // imageObj.src = require('@/assets/mark/img' + this.counter % 3 + '.jpg')
+
+        //创建画布
+        _this.stage = new Konva.Stage({
+            container: 'container',
+            id: 'canvas',
+            width: 600,
+            height: 240
+        })
+        _this.orgLayer = new Konva.Layer()
+        _this.markLayer = new Konva.Layer()
+        _this.stage.add(_this.orgLayer)
+        _this.stage.add(_this.markLayer)
+        _this.orgLayer.batchDraw()
+        _this.bundleScoll()
+        _this.stage.on('click', (evt) => {
+            _this.tr.nodes([])
+        })
+        _this.stage.on('mouseover', (evt) => {
+            let nodes = _this.tr.nodes()
+            if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+
+        })
 
         this.tr = new Konva.Transformer({
             rotateEnabled: false,
@@ -774,15 +985,25 @@ export default {
         })
         this.tr.borderEnabled(true)
         this.tr.anchorCornerRadius(10)
+        this.ansToImg()
 
     },
     created() {
-        this.scores = Array.from(new Array(10).keys())
-        this.qus = Array.from(new Array(40).keys())
-
-        //模拟按题阅卷和整体阅卷
-        let routeData = this.$route
-        this.mode = routeData.params.type
+        //按题阅卷和整体阅卷
+        let routeData = this.$route.params
+        this.paperData = routeData.fullPaper
+        this.stuAnswer = routeData.answer
+        this.stuScore = routeData.score
+        this.mode = routeData.type
+        this.taskInfo = routeData.task
+        this.stuId = routeData.stuId
+        //初始化题目index
+        if (this.paperData && this.paperData.item.length) {
+            this.quIndex = 0
+            if (this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children.length) {
+                this.childIndex = 0
+            }
+        }
         if (routeData.name == 'FullMarkView') this.isFull = true
         // this.mode = 1
 
@@ -793,17 +1014,82 @@ export default {
                 if (shape.length) {
                     shape[0].remove()
                     this.tr.nodes([])
-                    // this.tr.zIndex(1000)
                 }
             }
         }
+        //默认表情包
         for (let i = 0; i < 9; i++) {
             this.imgs.push(require('@/assets/mark/' + i + '.jpg'))
         }
+    },
+    computed: {
+        /**当期题目分数数组 */
+        quScoreArr() {
+            let score = 0
+            if (this.childIndex > -1 && this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children) {
+                score = this.paperData.item[this.quIndex].children[this.childIndex].score
+            } else {
+                score = this.paperData.item[this.quIndex].score
+            }
+            return Array.from(new Array(score + 1).keys())
+        },
+        /**当前题目作答数据 */
+        curAnswer() {
+            let index = this.getScoreIndex(this.quIndex, this.childIndex)
+            return this.stuAnswer[index]
+        },
+        totalScore() {
+            if (this.mode == 1) {
+                return this.stuScore.reduce((a, b) => {
+                    return a + b
+                }, 0)
+            } else {
+                return 0
+            }
+        }
     }
 }
 </script>
 <style scoped lang="less">
+.score-setting-wrap{
+    margin-top: 50px;
+    padding: 0px 5px;
+
+}
+.score-setting-item{
+    margin-top: 15px;
+    display: flex;
+    justify-content: space-between;
+    span{
+        // font-size: 16px;
+    }
+}
+.stu-id-info {
+    color: black;
+    font-size: 16px;
+}
+.score-info {
+    background: #19be6b;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+.cur-qu-index {
+    background: #2db7f5;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+#markIframe {
+    position: fixed;
+    top: 9990px;
+    width: 850px;
+    background: #f5f5f5;
+}
 .quit-marking-icon {
     display: inline-block;
     font-size: 18px;

+ 123 - 69
TEAMModelOS/ClientApp/src/view/settings/OpenMgmt2.vue

@@ -67,16 +67,16 @@
                                 ></Input>
                             </FormItem>
                             <FormItem :label="$t('settings.des')">
-                                <Input v-model="openMgInfo.des"
+                                <Input v-model="openMgInfo.descr"
                                        type="textarea"
                                        :autosize="{ minRows: 2, maxRows: 10 }"
                                        :class="isEdit ? '' : 'open-info-disabled'"
                                        :readonly="isEdit ? false : true"></Input>
                             </FormItem>
                             <FormItem :label="$t('settings.openStatus')">
-                                <RadioGroup v-model="openMgInfo.type">
-                                    <Radio label="1" :disabled="isEdit ? false : true">{{ $t("settings.enable") }}</Radio>
-                                    <Radio label="0" :disabled="isEdit ? false : true">{{ $t("settings.disable") }}</Radio>
+                                <RadioGroup v-model="openMgInfo.status">
+                                    <Radio :label="1" :disabled="isEdit ? false : true">{{ $t("settings.enable") }}</Radio>
+                                    <Radio :label="0" :disabled="isEdit ? false : true">{{ $t("settings.disable") }}</Radio>
                                 </RadioGroup>
                             </FormItem>
                         </Form>
@@ -87,7 +87,11 @@
                     <!-- <div class="open-api-header">
                         <span>Api列表</span>
                     </div> -->
-                    <Table ref="selection" :columns="apiListCol" :data="apiList"></Table>
+                    <Table ref="selection"
+                           :columns="apiListCol"
+                           :data="isEdit ? apiList : apiListNow"
+                           @on-select="select"
+                    ></Table>
                     <Page :total="pageTotal" :page-size-opts="pageSizeOpts" size="small" show-sizer show-total />
                 </div>
             </div>
@@ -97,31 +101,19 @@
 
 <script>
 export default {
+    name: "OpenMgmt",
     data() {
         return {
             // 开放平台列表
-            openList: [
-                {
-                    name: "fvrhrthtyjhtyhregr",
-                },
-                {
-                    name: "mfghgjyj",
-                }
-            ],
+            openList: [],
             // 平台信息
-            openMgInfo:{
-                school: "XXXXXXXXXX学校",
-                name: "哈哈哈哈哈哈哈哈哈",
-                des: "屏幕看了上面的几个wfegrh逆回购将",
-                type: "1",
-                token: "zcnjkdfjoiwfjergerghwfhefvsojfoergjorhbnfbkdnvjeroiguitrghbfdvneavnesvbnrbnkbnbrgeirgre"
-            },
+            openMgInfo: {},
             // api表头
             apiListCol: [
                 {
                     type: 'selection',
                     width: 80,
-                    align: 'center'
+                    align: 'center',
                 },
                 {
                     title: this.$t("settings.apiName"),
@@ -129,98 +121,157 @@ export default {
                 },
                 {
                     title: this.$t("settings.apiAddress"),
-                    key: "address",
+                    key: "url",
                 },
                 {
                     title: this.$t("settings.apiMethod"),
                     key: "method",
                 },
                 {
-                    title: this.$t("settings.des"),
-                    key: "des",
+                    title: this.$t("settings.apiParams"),
+                    key: "descr",
                 },
             ],
             // api列表
-            apiList: [
-                {
-                    name: "vdfvd",
-                    address: "fvrhtyjuyjgjg",
-                    method: "get",
-                    des: "侧福晋而破格饿哦通过方能听你提过rfretrhjrtzo南非方恒包括v你的恐怖活动飞机二十分你大概v哪些不能代表的版本太高i不耐烦"
-                },
-                {
-                    name: "vdfvd",
-                    address: "fvrhtyjuyjgjg",
-                    method: "get",
-                    des: "侧福晋而破格饿哦通过方能听你提过"
-                },
-                {
-                    name: "vdfvd",
-                    address: "fvrhtyjuyjgjg",
-                    method: "get",
-                    des: "侧福晋而破格饿哦通过方能听你提过"
-                },
-                {
-                    name: "vdfvd",
-                    address: "fvrhtyjuyjgjg",
-                    method: "get",
-                    des: "侧福晋而破格饿哦通过方能听你提过rfretrhjrtzo南非方恒包括v你的恐怖活动飞机二十分你大概v哪些不能代表的版本太高i不耐烦"
-                },
-                {
-                    name: "vdfvd",
-                    address: "fvrhtyjuyjgjg",
-                    method: "get",
-                    des: "侧福晋而破格饿哦通过方能听你提过"
-                },
-                {
-                    name: "vdfvd",
-                    address: "fvrhtyjuyjgjg",
-                    method: "get",
-                    des: "侧福晋而破格饿哦通过方能听你提过"
-                }
-            ],
+            apiList: [],
+            // 当前应用已有的api 列表
+            apiListNow: [],
             pageTotal: 0,
             pageSizeOpts:[5, 10, 20, 30, 40],
             // 当前下标
             nowIndex: 0,
             isEdit: false, //编辑状态
+            isAdd: false, // 编辑/新增
         }
     },
     mounted () {
-        // this.getOpenList()  
+        // this.getApiList()
+        this.getOpenList()
     },
     methods: {
+        // 获取应用列表
         getOpenList() {
-            let req = {}
-            this.$api.openMgmt.getOpenList(req).then()
+            let list = []
+            let req = {code: this.$store.state.userInfo.schoolCode}
+            this.$api.openMgmt.getOpenList(req).then(
+                res => {
+                    if (res) {
+                        list = res.apps
+                        this.openList = list
+                        this.list(0)
+                    }
+                }, err => {
+                    this.$Message.warning("api获取失败")
+                    setTimeout(() => {
+                        this.isLoading = false
+                    }, 1000)
+            })
+        },
+        // 获取api 列表
+        getApiList() {
+            let reqs = {}
+            this.$api.openMgmt.getApiList(reqs).then(res => {
+                    if (res) {
+                        let list = res.apis
+                        if(list.length > 0) {
+                            list.sort((a, b) => {
+                                return a.auth - b.auth
+                            })
+                        }
+                        this.apiList = list
+                    }
+                }, err => {
+                    this.$Message.warning("api获取失败")
+                    setTimeout(() => {
+                        this.isLoading = false
+                    }, 1000)
+            })
         },
         editOpen (isEdit, isAdd) {
             this.isEdit = isEdit
+            this.isAdd = isAdd
+            this.getApiList()
+            var list = this.apiList
             // 是新增
             if(isAdd) {
-                this.openMgInfo = {type: "0",}
+                var defaultInfo = {
+                    auths: [],
+                    code: "",
+                    descr: null,
+                    icon: null,
+                    id: "",
+                    name: "开放平台应用",
+                    pk: "",
+                    school: this.$store.state.userInfo.schoolCode,
+                    status: 0,
+                    token: "",
+                    ttl: -1
+                }
+                this.openList.unshift(defaultInfo)
+                this.openMgInfo = defaultInfo
+            } else {
             }
         },
         delOpen () {
             this.$Modal.confirm({
                 title: "您确定删除XXXXXX平台吗?",
                 onOk: () => {
-                    this.$Message.info('Clicked ok');
+                    this.$Message.info('删除成功');
                 },
                 onCancel: () => {
-                    this.$Message.info('Clicked cancel');
+                    this.$Message.info('已取消删除');
                 },
             })
         },
         list (index) {
             this.nowIndex = index
+            var info = this.openList[index]
+            this.openMgInfo = info
         },
         // 编辑平台信息
         editMgmt (type) {
+            var that = this
             this.isEdit = type
+            if(type) {
+                let req = {}
+                if(!this.isAdd) {
+                    req = {id: this.openMgInfo.id}
+                }
+                console.log(req);
+                this.$api.openMgmt.editOpenInfo(req).then(res => {
+                        if (res) {
+                            console.log(res)
+                            that.$Message.success("保存成功!");
+                        }
+                    }, err => {
+                        this.$Message.warning("保存失败")
+                        setTimeout(() => {
+                            this.isLoading = false
+                        }, 1000)
+                })
+            } else {
+                console.log(this.openList.filter(i => !i.id).length);
+                this.openList.filter(i => !i.id)
+            }            
         },
         refresh() {
-
+            var that = this
+            let req = {
+                id: this.openMgInfo.id,
+                code: this.openMgInfo.school
+            }
+            this.$api.openMgmt.getToken(req).then(res => {
+                    if (res) {
+                        console.log(res)
+                        this.openMgInfo.token = res.auth_token
+                        that.$Message.success("刷新成功!");
+                    }
+                }, err => {
+                    this.$Message.warning("刷新失败")
+                    setTimeout(() => {
+                        this.isLoading = false
+                    }, 1000)
+            })
         },
         copyToken() {
             var that = this;
@@ -233,6 +284,9 @@ export default {
                 }
             );
         },
+        select() {
+
+        },
     }
 }
 </script>

+ 9 - 3
TEAMModelOS/ClientApp/src/view/student-account/ImportStudent.vue

@@ -76,7 +76,7 @@
                     <p :class="(excelValid.reaptIds.length == 0 || excelValid.reaptIds.indexOf(row.id) == -1) ? 'account-success-tips':'account-warning-tips'">{{(excelValid.reaptIds.length == 0 || excelValid.reaptIds.indexOf(row.id) == -1)?"": $t('stuAccount.idWarning') }}</p>
                     <p :class="row.seatRepeat? 'account-error-tips':''">{{row.seatRepeat ? $t('stuAccount.setNoErr'):"" }}</p>
                     <p :class="(excelValid.gradeIds.length == 0 || excelValid.gradeIds.indexOf(row.id) == -1) ? 'account-success-tips':'account-warning-tips'">{{(excelValid.gradeIds.length == 0 || excelValid.gradeIds.indexOf(row.id) == -1)?"": $t('stuAccount.gradeWarning') }}</p>
-                    
+
                 </template>
             </Table>
             <p style="margin-top:15px;">{{$t('stuAccount.passwordTips')}}</p>
@@ -444,7 +444,7 @@ export default {
                     title: this.$t('stuAccount.classroomCode'),
                     key: 'classId'
                 },
-                
+
                 {
                     title: this.$t('stuAccount.abnormalStatus'),
                     slot: 'status'
@@ -543,7 +543,13 @@ export default {
     mounted() {
     },
     watch: {
-
+        period:{
+            handler(n,o){
+                this.tableData.forEach(item=>{
+                    item.periodId = this.period
+                })
+            }
+        }
     }
 }
 </script>

+ 93 - 2
TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.less

@@ -8,6 +8,7 @@
 	flex-direction: column;
 	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
 	.syllabus-header{
+		position: relative;
 		height:60px;
 		width: 100%;
 		border-bottom: 1px solid @borderColor;
@@ -30,6 +31,14 @@
 				background-color: #1fb06d;
 			}
 		}
+		
+		.btn-save-modify{
+			position: absolute;
+			right: 20px;
+			background-color: #1fb06d;
+			color: #fff;
+			border: none;
+		}
 	}
 	
 	.syllabus-content{
@@ -114,7 +123,7 @@
 		}
 		.syllabus-mid{
 			height: 100%;
-			width: 40%;
+			width: 30%;
 			border-right: 1px solid @borderColor;
 			z-index: 1;
 			
@@ -125,9 +134,62 @@
 		}
 		.syllabus-right{
 			height: 100%;
-			width: 40%;
+			width: 50%;
 			border-right: 1px solid @borderColor;
+			
+			.node-resource-box{
+				display: flex;
+				flex-direction: column;
+				padding: 10px 20px;
+				overflow: auto;
+				
+				.node-resource-item{
+					position: relative;
+					border: 1px solid #5d5d5d;
+					padding: 15px 20px;
+					margin-top: 5px;
+					color: #DDDDDD;
+					display: flex;
+					align-items: center;
+					background: #282828;
+					box-shadow: inset 0px 0px 0px 4px #2d2d2d;
+					
+					&:hover{
+						.node-resource-tools{
+							display: flex;
+						}
+					}
+					
+					img{
+						width: 25px;
+						height: 25;
+						margin-right: 20px;
+					}
+					
+					.node-resource-tools{
+						position: absolute;
+						right: 30px;
+						align-items: center;
+						display: none;
+						
+						.node-resource-tool{
+							display: flex;
+							align-items: center;
+							margin-right: 15px;
+							cursor: pointer;
+						}
+						
+						.ivu-icon{
+							color: #ddd;
+							font-size: 20px;
+							margin-right: 5px;
+						}
+					}
+				}
+			}
 		}
+		
+		
 	}
 	
 	.volume-active {
@@ -136,4 +198,33 @@
 	    background-image: -moz-linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
 	    background-image: linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
 	}
+	
+	.image-viewer {
+	    background-color: rgba(0, 0, 0, 0.8);
+	    z-index: 9999;
+	    width: 100%;
+	    height: 100%;
+	    position: fixed;
+	    top: 0;
+	    left: 0;
+	    overflow-y: scroll;
+	    overflow-x: hidden;
+	    text-align: center;
+	    /*display: flex;
+	    justify-content: center;
+	    align-items: center;*/
+	    padding:50px;
+	    padding-top:8%;
+	    .close-icon {
+	        position: absolute;
+	        right: -16px;
+	        top: -16px;
+	        font-size: 24px;
+	        color: black;
+	        cursor: pointer;
+	        border-radius:50px;
+	        background:white;
+	        padding:2px;
+	    }
+	}
 }

+ 233 - 17
TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue

@@ -13,6 +13,7 @@
 				  subjectIndex == activeSubjectIndex ? 'item-active' : '',
 				]" @click="onSubjectChange(subject,subjectIndex)">{{ subject.name }}</span>
 			</div>
+			<Button @click="onSaveSyllabus" class="btn-save-modify" icon="md-folder" v-if="$access.can('admin.*|Syllabus_Edit') && hasModify">存储变更</Button>
 		</div>
 		<div class="syllabus-content">
 			<div class="syllabus-left">
@@ -45,21 +46,71 @@
 					<span>课纲目录</span>
 					<span class="syllabus-content-header-tools">
 						<Icon type="md-add" @click="isAddTreeModal = true"/>
-						<Button @click="onSaveSyllabus" size="small" icon="md-folder" v-if="$access.can('admin.*|Syllabus_Edit') && hasModify">存储变更</Button>
 					</span>
 				</div>
 				<div class="syllabus-tree-box">
 					<EmptyData :top="-240" v-if="!treeOrigin.length"></EmptyData>
-					<Tree ref="treeRef" :treeData="treeOrigin" :volume="curVolume" :editable="$access.can('admin.*|Syllabus_Edit')" v-else></Tree>
+					<Tree ref="treeRef" :treeData="treeOrigin" :volume="curVolume"
+					 :editable="$access.can('admin.*|Syllabus_Edit')"
+					  @onNodeClick="onNodeClick"
+					  v-else></Tree>
 				</div>
 			</div>
 			<div class="syllabus-right">
 				<div class="syllabus-content-header">
 					<span>关联资源</span>
+					<span class="syllabus-content-header-tools">
+						<!-- <Icon type="md-add" @click="onAddResource" v-if="curNode.id"/> -->
+						<Dropdown @on-click="onAddResource">
+							<a href="javascript:void(0)" style="color: #ddd;">
+								添加资源
+								<Icon type="ios-arrow-down"></Icon>
+							</a>
+							<DropdownMenu slot="list">
+								<DropdownItem name="content">+内容资源</DropdownItem>
+								<DropdownItem name="question">+试题资源</DropdownItem>
+								<DropdownItem name="papaer">+试卷资源</DropdownItem>
+								<DropdownItem name="file">+本地文件</DropdownItem>
+								<DropdownItem name="link">+超链接</DropdownItem>
+							</DropdownMenu>
+						</Dropdown>
+					</span>
+				</div>
+				<div class="syllabus-tree-box">
+					<EmptyData :top="100" v-if="curNode.rnodes && !curNode.rnodes.length"></EmptyData>
+					<div class="node-resource-box" v-else>
+						<div class="node-resource-item" v-for="(item,index) in curNode.rnodes">
+							<img src="../../assets/source/image.png" v-if="item.type === 'image'"/>
+							<img src="../../assets/source/word.png" v-else-if="item.type === 'doc'"/>
+							<img src="../../assets/source/video.png" v-else-if="item.type === 'video'"/>
+							<img src="../../assets/source/audio.png" v-else-if="item.type === 'audio'"/>
+							<img src="../../assets/source/item.png" v-else-if="item.type === 'item'"/>
+							<img src="../../assets/source/folder.png" v-else-if="item.type === 'paper'"/>
+							<img src="../../assets/source/link.png" v-else-if="item.type === 'link'"/>
+							<img src="../../assets/source/zip.png" v-else-if="item.type === 'res'"/>
+							<img src="../../assets/source/image.png" v-else-if="item.type === 'thum'"/>
+							<img src="../../assets/source/unknow.png" v-else="item.type === 'other'"/>
+							<span v-html="item.title"></span>
+							<div class="node-resource-tools">
+								<div class="node-resource-tool" @click="onPreview(item)">
+									<Icon type="md-eye"/>
+									<span>查看</span>
+								</div>
+								<div class="node-resource-tool" @click="onDeleteResource(item,index)">
+									<Icon type="md-trash"/>
+									<span>删除</span>
+								</div>
+							</div>
+						</div>
+					</div>
 				</div>
 			</div>
 		</div>
 		
+		<!--上传文件-->
+		<!-- <UploadModal ref="uploadModal" :sasString="sasString" :urlString="urlString" :path="folder" :containerName="containerName" :quality="1" @successData="getFileUrl" :pdId="filterPeriod">
+		</UploadModal> -->
+		
 		<!-- 新增册别弹窗 -->
 		<Modal v-model="isAddVolumeModal" width="500" footer-hide class="add-volume-modal">
 			<div class="modal-header" slot="header">
@@ -90,6 +141,7 @@
 			</div>
 		</Modal>
 		
+		<!-- 新增课纲节点弹窗 -->
 		<Modal v-model="isAddTreeModal" width="500" footer-hide class="tree-modal add-volume-modal">
 		    <div class="modal-header" slot="header">新增节点</div>
 		    <div class="modal-content">
@@ -97,23 +149,63 @@
 		        <Input v-model="curVolume.name" style="width: 100%" disabled />
 		        <p class="node-title">节点名称</p>
 		        <Input v-model="nodeInfo.title" placeholder="请输入节点名称..." style="width: 100%" />
-		    </div>
-		    <Button @click="onAddTreeNode" style="width: 88%;margin-left: 6%;" class="modal-btn">确认</Button>
+		    </div> 
+		    <Button @click="onAddTreeNode" style="width: 88%;margin-left: 6%;margin-bottom: 20px;" class="modal-btn">确认</Button>
 		</Modal>
+	
+		<!-- 新增课纲节点弹窗 -->
+		<Modal v-model="isRelateContentModal" width="900" class="tree-modal add-volume-modal">
+			<ChooseContent v-if="isRelateContentModal" ref="chooseContentRef" @on-file-change="onSelectFile" @quChange="onSelectQuestion" :showSyllabus="false"></ChooseContent>
+			<Button slot="footer" @click="onRelateContent" style="margin-bottom: 20px;" class="modal-btn">确认</Button>
+		</Modal>
+		
+		<!-- 新增课纲节点弹窗 -->
+		<Modal v-model="isPreviewPaper" width="900" footer-hide class="tree-modal tree-paper-modal">
+			<div class="paper-box" v-if="previewPaper">
+				<p class="paper-title">{{ previewPaper.name }}</p>
+				<p class="paper-info">
+					<span>配分:{{ previewPaper.score }}</span>
+					<span style="margin-left: 50px;">题数:{{ previewPaper.item.length  }}</span>
+				</p>
+			</div>
+			<ExerciseList :propsList="questionList" ref="exList" isAnalysis></ExerciseList>
+		</Modal>
+		
+		<!--文件预览-->
+		<div v-if="previewStatus" class="image-viewer">
+		    <div style="width:fit-content;position:relative;margin:auto;">
+		        <Icon type="md-close" class="close-icon" @click="previewStatus = false" />
+		        <video v-if="previewFile.type == 'video'" id="previewVideo" :src="previewFile.link[0]" width="870" controls="controls" style="max-height: 800px;">
+		            {{$t('teachContent.tips8')}}
+		        </video>
+		        <audio v-else-if="previewFile.type == 'audio'" controls>
+		            <source :src="previewFile.link[0]">
+		            {{$t('teachContent.notAudio')}}
+		        </audio>
+		        <img v-else-if="previewFile.type == 'image'" :src="previewFile.link[0]" style="border-radius: 5px;max-height: 800px;max-width:870px;" />
+		        <embed v-else-if="previewFile.type == 'doc'" :src="previewFile.link[0]" width="870" height="720" />
+		        <iframe v-else :src="'https://view.officeapps.live.com/op/view.aspx?src=' + previewFile.link[0]" width='870' height='700' frameborder='1'></iframe>
+		    </div>
+		</div>
 	</div>
 </template>
 
 <script>
 	import Tree from "@/components/syllabus/DragTree";
+	import ChooseContent from '@/components/selflearn/NewChooseContent'
+	import ExerciseList from '@/components/evaluation/ExerciseList.vue'
 	export default {
-		components:{ Tree },
+		components:{ Tree , ChooseContent , ExerciseList },
 		data() {
 			return {
+				isPreviewPaper:false,
+				previewStatus:false,
 				hasModify:false,
 				isLoading:false,
 				isAddLoading:false,
 				isAddTreeModal:false,
 				isAddVolumeModal:false,
+				isRelateContentModal:false,
 				isEditVolume:false,
 				isSchool:false,
 				currentPeriodIndex: 0,
@@ -124,7 +216,14 @@
 				subjectList: [],
 				semesterList: [],
 				volumeList:[],
+				questionList:[],
 				schoolInfo:null,
+				previewFile:{},
+				previewPaper:null,
+				curNode:{
+					id:'',
+					rnodes:[]
+				},
 				treeOrigin:[],
 				curVolume:{
 					name:''
@@ -139,7 +238,8 @@
 					"pid": "",
 					"title": "",
 					"type": 1,
-					"children":[]
+					"children":[],
+					"rnodes":[]
 				},
 			}
 		},
@@ -153,7 +253,7 @@
 				this.$store.dispatch("user/getSchoolProfile").then((res) => {
 					let schoolBaseInfo = res.school_base;
 					if (schoolBaseInfo) {
-						this.schoolInfo = schoolBaseInfo;
+						this.schoolInfo = res;
 						if (schoolBaseInfo.period.length) {
 							this.periodList = schoolBaseInfo.period
 							this.gradeList = schoolBaseInfo.period[0].grades;
@@ -192,7 +292,7 @@
 				this.$api.syllabus.FindVolumes(findParams).then(res => {
 					if(!res.error){
 						this.isLoading = false
-						this.volumeList = res.volumes
+						this.volumeList = res.volumes.reverse()
 						res.volumes.length && this.onVolumeClick(res.volumes[0],0)
 					}else{
 						this.$Message.warning(res.error);
@@ -201,7 +301,6 @@
 					this.$Message.error(err);
 				})
 			},
-			
 			/* 点击某个册别 */
 			onVolumeClick(volume,volumeIndex){
 				if(volume.id === this.curVolume.id) return
@@ -211,7 +310,6 @@
 				this.getTreeByVolumeId(volume)
 				this.hasModify = false
 			},
-			
 			/* 添加册别 */
 			doAddVolume(){
 				this.addVolumeForm = {
@@ -222,7 +320,6 @@
 				this.isEditVolume = false
 				this.isAddVolumeModal = true
 			},
-			
 			/* 编辑当前册别 */
 			doEditVolume(){
 				this.addVolumeForm = {
@@ -233,7 +330,6 @@
 				this.isEditVolume = true
 				this.isAddVolumeModal = true
 			},
-			
 			/* 删除当前册别 */
 			doDeleteVolume(){
 				let curVolume = this.curVolume
@@ -261,9 +357,10 @@
 					},
 				});
 			},
-			
 			/* 根据册别查询对应课纲树形结构 */
 			getTreeByVolumeId(volume){
+				this.curNode.rnodes = []
+				this.curNode.id = ''
 				this.$api.syllabus.GetTreeByVolume({
 					id:volume.id,
 					code:volume.code.replace('Volume-',''),
@@ -281,7 +378,6 @@
 					this.isLoading = false
 				})
 			},
-			
 			/* 添加课纲的第一层节点 */
 			onAddTreeNode(){
 				this.nodeInfo.id = this.$tools.guid()
@@ -297,8 +393,7 @@
 					"children":[]
 				}
 			},
-			
-			// 存储变更保存最新课纲数据
+			/* 存储变更保存最新课纲数据 */
 			onSaveSyllabus() {
 				console.log(this.$refs.treeRef)
 				let latestTree = this.$refs.treeRef ? this.$refs.treeRef.treeDatas : [];
@@ -318,6 +413,93 @@
 					}
 				});
 			},
+			/* 点击某个节点 */
+			onNodeClick(data){
+				this.curNode = data
+			},
+			/* 点击添加关联内容 */
+			onAddResource(type){
+				console.log(type)
+				this.isRelateContentModal = true
+				this.$nextTick(() => {
+					this.$refs.chooseContentRef.clickTab(type)
+				})
+			},
+			/* 关联内容 */
+			onSelectFile(val){
+			},
+			
+			/* 关联题目 */
+			onSelectQuestion(val){
+				this.relateFiles = val.map(i => {
+					return {
+						type:'item',
+						title:i.question,
+						id:i.id,
+						code:i.code,
+						scope:i.scope,
+						cntr:i.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+						link:[i.blob]
+					}
+				})
+			},
+			// 拿到当前选择的资源内容 进行节点关联
+			onRelateContent(){
+				console.log(this.relateFiles)
+				console.log(this.$refs.treeRef.curNode)
+				this.$refs.treeRef.curNode.rnodes = this.relateFiles
+				this.isRelateContentModal = false
+				this.hasModify = true
+			},
+			
+			/* 预览关联资源 */
+			async onPreview(item){
+				this.questionList = []
+				switch (item.type){
+					case 'item':
+						this.previewPaper = null
+						this.questionList = [{
+							code:item.code,
+							id:item.id,
+							blob:item.link[0],
+							scope:item.scope
+						}]
+						this.isPreviewPaper = true
+						break;
+					case 'paper':
+						let paper = {
+							code:item.code,
+							id:item.id,
+							blob:item.link[0],
+							scope:item.scope,
+						}
+						let fullPaper = await this.$evTools.getFullPaper(paper)
+						this.previewPaper = fullPaper
+						this.questionList = fullPaper.item
+						this.isPreviewPaper = true
+						break;	
+					case 'link':
+						window.open(item.link[0]);
+						break;		
+					default:
+						this.previewFile = item
+						this.previewStatus = true
+						break;
+				}
+			},
+			
+			/* 删除关联资源 */
+			onDeleteResource(item,index){
+				this.$Modal.confirm({
+					title: this.$t('settings.modalTip4'),
+					content: '确定要移除该资源吗?',
+					onOk: () => {
+						this.$refs.treeRef.curNode.rnodes.splice(index,1)
+						this.hasModify = true
+					}
+				})
+				
+			},
 			
 			/* 提交新增册别 */
 			handleSubmit(){
@@ -355,7 +537,7 @@
 		},
 		computed:{
 			getDefaultVolumeName(){
-				return this.subjectList[this.activeSubjectIndex].name + this.gradeList[this.addVolumeForm.grade].name + this.semesterList[this.addVolumeForm.semester].name
+				return this.subjectList[this.activeSubjectIndex].name + this.gradeList[this.addVolumeForm.grade] + this.semesterList[this.addVolumeForm.semester].name
 			},
 			getGradeName(){
 				return index => {
@@ -405,6 +587,40 @@
 		}
 	}
 	
+	.tree-paper-modal{
+		
+		.ivu-modal-content{
+			background-color: #fff;
+		}
+		
+		.ivu-modal-body{
+			overflow: auto;
+			font-size: 14px !important;
+			margin: 40px 10px;
+		}
+		
+		.cp-exercise-item{
+			border: 1px solid #dfdfdf !important;
+		}
+		
+		.paper-box{
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			
+			.paper-title{
+				color: #333;
+				font-size: 20px;
+				font-weight: bold;
+			}
+			
+			.paper-info{
+				margin-top: 20px;
+				color: #939393;
+			}
+		}
+	}
+	
 	/* 修改iview Modal样式 */
 	.add-volume-modal{
 		font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;

+ 35 - 14
TEAMModelOS/ClientApp/src/view/task/index.vue

@@ -201,8 +201,7 @@
                                 <ProgPie></ProgPie>
                             </div>
                         </div>
-                        
-                        
+
                         <!-- 异常 -->
                         <div class="setting-block">
                             <p class="block-title">异常卷</p>
@@ -341,17 +340,39 @@ export default {
          * mode 0:按题 1:按人
          * stuId 学生id
          */
-        toMarkView(mode,stuId) {
+        async toMarkView(mode, stuId) {
             sessionStorage.setItem('markFrom', this.$route.name)
+            let sas = this.$store.state.user.schoolProfile.blob_sas //目前只有校本评测安排阅卷任务
+            let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri //目前只有校本评测安排阅卷任务
+            let stuInfo = this.markData.attr.find(item => {
+                return item.stuId == stuId
+            })
+            let answer, fullPaper, score
+            if (stuInfo) {
+                answer = await this.$tools.getFile(`${blobUrl}/exam/${stuInfo.info.ans}?${sas}`)
+                answer = answer ? JSON.parse(answer) : [] 
+                score = stuInfo.info.score
+                fullPaper = await this.$evTools.getFullPaper({
+                    blob: this.markData.paper
+                }, 'school')
+
+                console.log(answer)
+            }
 
-            this.getNextStu(stuId)
-            // this.$router.push({
-            //     name: 'MarkView',
-            //     params: {
-            //         type: mode,
-            //         from: this.$route.name
-            //     }
-            // })
+            // this.getNextStu(stuId)
+
+            this.$router.push({
+                name: 'ByStu',
+                params: {
+                    type: mode,
+                    from: this.$route.name,
+                    task:this.markList[this.curTaskIndex],
+                    stuId:stuInfo.stuId,
+                    answer,
+                    fullPaper,
+                    score
+                }
+            })
         },
         /**
          * 批阅下个学生
@@ -363,7 +384,7 @@ export default {
                 id: this.markList[this.curTaskIndex].id,
                 subjectId: this.markList[this.curTaskIndex].subject,
                 tmdId: this.$store.state.userInfo.TEAMModelId,
-                stuId:'hbcn070201'
+                stuId: 'hbcn070201'
             }
             this.$api.mark.FindNextStu(requestData).then(
                 res => {
@@ -461,7 +482,7 @@ export default {
         //当前阅卷任务已阅学生信息
         marked() {
             if (this.markData && this.markData.attr) {
-                return this.markData.attr.filter(item=>{
+                return this.markData.attr.filter(item => {
                     return item.info.score.indexOf(-1) == -1
                 })
             } else {
@@ -471,7 +492,7 @@ export default {
         //当前阅卷任务进行中学生信息
         marking() {
             if (this.markData && this.markData.attr) {
-                return this.markData.attr.filter(item=>{
+                return this.markData.attr.filter(item => {
                     return item.info.score.indexOf(-1) !== -1
                 })
             } else {

+ 0 - 0
TEAMModelOS/ClientApp/src/view/task/mark/ByQu.vue


+ 718 - 0
TEAMModelOS/ClientApp/src/view/task/mark/ByStu.vue

@@ -0,0 +1,718 @@
+<template>
+    <!-- 未做多语言 -->
+    <div class="mark-area">
+        <!-- 头部基础信息 -->
+        <div class="mark-header">
+            <span class="quit-marking-text">
+                <Icon type="ios-arrow-back" class="quit-marking-icon" title="退出阅卷" @click="quit" />
+            </span>
+            <span class="info-label">考试名称:</span>
+            <span class="info-value">{{taskInfo.name}}</span>
+            <span class="info-label">阅卷方式:</span>
+            <span class="info-value">{{mode == 0 ? '按题阅卷' : '按人阅卷'}}</span>
+            <span class="info-label">学生id:</span>
+            <span class="info-value stu-id-info">{{stuId}}</span>
+            <span class="info-label" v-show="mode == 1">分数:</span>
+            <span class="info-value score-info" v-show="mode == 1">{{totalScore}}</span>
+            <span class="info-label">当前题号:</span>
+            <span class="info-value cur-qu-index" v-if="childIndex > -1">{{`${quIndex + 1}-${childIndex + 1}`}}</span>
+            <span class="info-value cur-qu-index" v-else>{{quIndex + 1}}</span>
+            <!-- <span class="info-label">阅卷进度:</span>
+            <span class="info-value">{{`${12}/${41}`}}</span> -->
+            <div class="btn-wrap">
+                <span class="action-btn" @click="toggleStatus = !toggleStatus">
+                    <Icon type="md-shuffle" class="action-btn-icon" />
+                    切换
+                </span>
+                <span class="action-btn">
+                    <Icon type="md-refresh" class="action-btn-icon" />
+                    回评
+                </span>
+                <span class="action-btn">
+                    <Icon custom="iconfont icon-exception" class="action-btn-icon" />
+                    异常申报
+                </span>
+            </div>
+        </div>
+        <div class="mark-main">
+            <!-- 工具条 -->
+            <div class="mark-tools-wrap">
+                <Icon custom="iconfont icon-move" class="tool-icon" title="移动" @click="mouseStatus = 'move'" />
+                <Icon custom="iconfont icon-text" class="tool-icon" title="文字批注" @click="mouseStatus = 'text'" />
+                <Icon custom="iconfont icon-mark" class="tool-icon" title="画笔" @click="mouseStatus = 'line'" />
+                <Icon custom="iconfont icon-arrow-mark" class="tool-icon" title="箭头" @click="mouseStatus = 'arrow'" />
+                <Icon custom="iconfont icon-oval" class="tool-icon" title="椭圆" @click="mouseStatus = 'oval'" />
+                <Icon custom="iconfont icon-rect" class="tool-icon" title="方框" @click="mouseStatus = 'rect'" />
+                <Poptip transfer placement="right">
+                    <Icon custom="iconfont icon-smile" class="tool-icon" title="图标" />
+                    <div slot="content" class="expression-box">
+                        <img v-for="(item,index) in imgs" :key="index" :src="item" alt="" class="expression-img" @click="drawImg(index)">
+                    </div>
+                </Poptip>
+                <Icon custom="iconfont icon-fresh" class="tool-icon" title="清除批注" @click="clear" />
+                <!-- <Icon :custom="isFull ? 'iconfont icon-cancel-full' : 'iconfont icon-full-screen'" class="tool-icon" :title="isFull ? '取消全屏' : '全屏'" @click="togglefull" /> -->
+            </div>
+            <div class="mark-stage" :style="{paddingBottom: mode == 1 ? '120px' : '0px'}">
+                <!-- canvas部分 -->
+                <!-- <vuescroll ref="canvasScroll">
+                    <div class="container-box">
+                        <div id="container" @mousemove="canvasMouseMove" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp"></div>
+                    </div>
+                </vuescroll> -->
+                <MarkCanvas mouseStatus="move" :bgImg="ansImg" :drawImgData="drawImgData"></MarkCanvas>
+                <!-- 题号显示部分 -->
+                <div class="qu-index-box" v-show="mode == 1">
+                    <div class="qu-tips-box">
+                        <span class="qu-tips-tag">
+                            已阅
+                        </span>
+                        <span class="qu-tips-tag">
+                            未阅
+                        </span>
+                    </div>
+                    <div>
+                        <span v-for="(item,index) in paperData.item" :key="index">
+                            <!-- 综合题 -->
+                            <span v-if="item.children.length" :key="index">
+                                <span @click="toQu(index,childIndex)" v-for="(childItem,childIndex) in item.children" :key="childIndex" :class="['qu-index',stuScore[getScoreIndex(index,childIndex)] > -1 ? 'right-qu' : '']">
+                                    {{(index + 1) + '-' + (childIndex + 1)}}
+                                </span>
+                            </span>
+                            <!-- 其他题 -->
+                            <span v-else @click="toQu(index)" :class="['qu-index',stuScore[getScoreIndex(index)] > -1 ? 'right-qu' : '']">
+                                {{index + 1}}
+                            </span>
+                        </span>
+                    </div>
+                </div>
+            </div>
+            <!-- 打分部分 -->
+            <div class="score-wrap">
+                <div class="quick-score-box score-input-box">
+                    <span>分数:</span>
+                    <InputNumber style="flex:1" :max="10" :min="1" v-model="stuScore[getScoreIndex(quIndex,childIndex)]"></InputNumber>
+                </div>
+                <div class="quick-score-box">
+                    <Button size="small" type="info" style="margin-right:8px" ghost @click="score = 10">满分</Button>
+                    <Button size="small" type="error" ghost @click="score = 0">零分</Button>
+                    <Icon :type="isShowNum ? 'md-eye-off' : 'md-eye'" class="toggle-num-status" @click="isShowNum = !isShowNum" />
+                    <div :class="['score-key-box', isShowNum ? '':'hind-key-box']">
+                        <span v-for="(item,index) in quScoreArr" :key="index" :class="['score-key', stuScore[getScoreIndex(quIndex,childIndex)] == index ? 'score-key-active':'']" @click="setScore(index)">
+                            {{item}}
+                        </span>
+                    </div>
+                </div>
+                <Button type="success" class="submit-score" @click="submit()">提交分数</Button>
+                <div class="score-setting-wrap">
+                    <div class="score-setting-item">
+                        <span>提交分数自动切换题目</span>
+                        <i-switch v-model="autoQu" size="small" />
+                    </div>
+                    <div class="score-setting-item">
+                        <span>完成阅卷自动获取新学生</span>
+                        <i-switch v-model="autoStu" size="small" />
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!-- 用来单独渲染学生作答数据,提高tocanvas 的效率 -->
+        <iframe id="markIframe" :srcdoc="curAnswer" v-if="curAnswer"></iframe>
+        <Modal v-model="toggleStatus" title="切换学生">
+            进行中
+        </Modal>
+    </div>
+</template>
+
+<script>
+import Konva from 'konva'
+import html2canvas from 'html2canvas';
+import MarkCanvas from '@/view/learnactivity/markpaper/MarkCanvas';
+export default {
+    name: 'ByStu',
+    components: {
+        MarkCanvas
+    },
+    data() {
+        return {
+            drawImgData:'',
+            ansImg:'',
+            imgs:[],
+            autoQu: true,//自动切换下一题
+            autoStu: true,//自动获取下一学生
+            toggleStatus: false,
+            activeIcon: -1,
+            isShowNum: true,
+            mode: '0',//阅卷模式 1:按人阅卷 0:按题阅卷
+            quIndex: 0,
+            childIndex: -1,
+            stuData: {
+                name: '',
+                id: '',
+            },
+            paperData: {
+                item: []
+            },
+            stuAnswer: [],
+            stuScore: [],
+            taskInfo: {},
+            stuId: ''
+        }
+    },
+    methods: {
+        drawImg(imgIndex) {
+            // this.activeIcon = imgIndex
+            // this.curImg = new Image()
+            // this.curImg.src = this.imgs[imgIndex]
+            // this.curImg.onload = () => {
+            //     this.mouseStatus = 'img'
+            //     this.startImg({ x: 100, y: 100 })
+            // }
+        },
+        //清除所有批注
+        clear() {
+
+            // this.markLayer.removeChildren()
+            // this.stage.add(this.markLayer)
+        },
+        /**将答案绘制到canvas上 */
+        ansToImg() {
+            let answerIframe = document.getElementById('markIframe')
+            answerIframe.onload = () => {
+                answerIframe.style.width = '850px'
+                answerIframe.contentWindow.document.body.style.margin = '0px 20px'
+                answerIframe.contentWindow.document.body.style.padding = '10px'
+                answerIframe.contentWindow.document.body.style.minWidth = '600px'
+                answerIframe.contentWindow.document.body.style.minHeight = '240px'
+                answerIframe.contentWindow.document.body.style.height = 'fit-content'
+                answerIframe.contentWindow.document.body.style.width = 'fit-content'
+                let bodyWidth = answerIframe.contentWindow.document.body.clientWidth
+                answerIframe.style.width = (bodyWidth + 20) + 'px'
+                answerIframe.contentWindow.document.body.style.backgroundColor = '#f5f5f5'
+                console.log('markIframe', answerIframe)
+                html2canvas(answerIframe.contentWindow.document.body, {}).then((canvas) => {
+                    canvas.id = "canvas" + this.getScoreIndex(this.quIndex, this.childIndex)
+                    this.ansImg = canvas.toDataURL()
+                    // 将转出来的答案绘制到canvas上
+                    console.log(this.ansImg)
+                })
+            }
+        },
+        //将图片(答案)绘制到canvas
+        imgToCanvas(img) {
+            console.log('将图片(答案)绘制到canvas',img)
+        },
+        /** 打分 */
+        setScore(score) {
+            this.$set(this.stuScore, this.getScoreIndex(this.quIndex, this.childIndex), score)
+        },
+        toQu(index, childIndex) {
+            this.quIndex = index
+            this.childIndex = childIndex
+        },
+        /**
+         * index 题目index 必传
+         * childIndex 小题index 非必传 
+         */
+        getScoreIndex(index, childIndex) {
+            let realIndex = index
+            this.paperData.item.forEach((item, itemIndex) => {
+                if (itemIndex < index && item.children.length) {
+                    realIndex += item.children.length
+                } else if (itemIndex == index && item.children.length) {
+                    realIndex += childIndex
+                }
+            })
+            return realIndex
+        },
+        //提交分数
+        submit() {
+            let requstData = {
+                id: this.taskInfo.id,
+                stuId: this.stuId,
+                subjectId: this.taskInfo.subject,
+                tmdId: this.$store.state.userInfo.TEAMModelId,
+                score: this.stuScore,
+                count: this.taskInfo.count,
+                code: this.taskInfo.ecode.replace('Exam-', ''),
+                mark: ''
+            }
+            this.$api.mark.saveScore(requstData).then(
+                res => {
+                    this.$Message.success('保存成功')
+                    // 按人阅卷自动跳转下一题
+                    if (this.mode == 1 && this.autoQu) {
+                        this.nextQuestion()
+                        this.ansToImg()
+                    }
+                    //按题阅卷自动加载下一人
+                    else {
+
+                    }
+                },
+                err => {
+                    this.$Message.error('保存失败')
+                }
+            )
+
+        },
+        nextQuestion() {
+            // 当前不是最后一题
+            if (this.quIndex < this.paperData.item.length - 1) {
+                //当前题目是综合题
+                if (this.paperData.item[this.quIndex].children.length) {
+                    //当前小题不是最后一个
+                    if (this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                        this.childIndex++
+                    }
+                    //当前小题是最后一个,需要判断下一个题目类型
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+                //当前题目不是综合题
+                else {
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+            }
+            //如果是最后一题
+            else {
+                //当前题目是综合题,并且不是小题最后一题
+                if (this.paperData.item[this.quIndex].children.length && this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                    this.childIndex++
+                }
+                //当前目不是综合题 则代表题号已经到最后一个
+                else {
+                    //检查所有题目是否完成评测
+                    if (this.stuScore.includes(-1)) {
+                        let qu = ''
+                        let quIndex = 0
+                        let childIndex = -1
+                        let realIndex = 0
+                        // 检测具体未评分的题目
+                        for (let i = 0; i < this.paperData.item.length; i++) {
+                            if (this.paperData.item[i].children.length) {
+                                let flag = false
+                                for (let j = 0; j < this.paperData.item[i].children.length; j++) {
+                                    if (this.stuScore[realIndex++] == -1) {
+                                        quIndex = i
+                                        childIndex = j
+                                        flag = true
+                                        break
+                                    }
+                                }
+                                if (flag) {
+                                    break
+                                }
+                            } else {
+                                if (this.stuScore[realIndex++] == -1) {
+                                    quIndex = i
+                                    break
+                                }
+                            }
+                        }
+                        qu = childIndex > -1 ? `${(quIndex + 1)}-${(childIndex + 1)}` : quIndex
+                        this.$Modal.confirm({
+                            title: '未阅题目',
+                            content: `${qu}题目尚未评分,是否跳转到对应题目继续评分?`,
+                            onOk: () => {
+                                this.quIndex = quIndex
+                                this.childIndex = childIndex
+                            }
+                        })
+                    } else {
+                        this.$Message.success('已阅完')
+                    }
+
+                }
+            }
+        },
+
+        quit() {
+            // 返回页面
+            this.$router.push({
+                name: sessionStorage.getItem('markFrom')
+            })
+        },
+        uuid() {
+            function S4() {
+                return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
+            }
+            return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4())
+        },
+    },
+    mounted() {
+        this.ansToImg()
+
+    },
+    created() {
+        let routeData = this.$route.params
+        this.paperData = routeData.fullPaper
+        this.stuAnswer = routeData.answer
+        this.stuScore = routeData.score
+        this.mode = routeData.type
+        this.taskInfo = routeData.task
+        this.stuId = routeData.stuId
+        //初始化题目index
+        if (this.paperData && this.paperData.item.length) {
+            this.quIndex = 0
+            if (this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children.length) {
+                this.childIndex = 0
+            }
+        }
+        //默认表情包
+        for (let i = 0; i < 9; i++) {
+            this.imgs.push(require('@/assets/mark/' + i + '.jpg'))
+        }
+    },
+    computed: {
+        /**当期题目分数数组 */
+        quScoreArr() {
+            let score = 0
+            if (this.childIndex > -1 && this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children) {
+                score = this.paperData.item[this.quIndex].children[this.childIndex].score
+            } else {
+                score = this.paperData.item[this.quIndex].score
+            }
+            return Array.from(new Array(score + 1).keys())
+        },
+        /**当前题目作答数据 */
+        curAnswer() {
+            let index = this.getScoreIndex(this.quIndex, this.childIndex)
+            return this.stuAnswer[index]
+        },
+        totalScore() {
+            if (this.mode == 1) {
+                return this.stuScore.reduce((a, b) => {
+                    return a + b
+                }, 0)
+            } else {
+                return 0
+            }
+        }
+    }
+}
+</script>
+<style scoped lang="less">
+.score-setting-wrap {
+    margin-top: 50px;
+    padding: 0px 5px;
+}
+.score-setting-item {
+    margin-top: 15px;
+    display: flex;
+    justify-content: space-between;
+}
+.stu-id-info {
+    color: black;
+    font-size: 16px;
+}
+.score-info {
+    background: #19be6b;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+.cur-qu-index {
+    background: #2db7f5;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+#markIframe {
+    position: fixed;
+    top: 9990px;
+    width: 850px;
+    background: #f5f5f5;
+}
+.quit-marking-icon {
+    display: inline-block;
+    font-size: 18px;
+    width: 40px;
+    text-align: center;
+    cursor: pointer;
+    color: black;
+}
+.quit-marking-text {
+    margin-right: 15px;
+}
+.expression-box {
+    max-width: 300px;
+    display: flex;
+    flex-wrap: wrap;
+    max-height: 600px;
+}
+.expression-img {
+    width: 80px;
+    margin: 10px;
+    height: auto;
+}
+.container-box {
+    display: flex;
+    justify-content: center;
+    width: 100%;
+    padding: 10px;
+}
+#container {
+    max-width: 100%;
+}
+.qu-tips-box {
+    & :nth-child(1)::before {
+        background: #19be6b;
+    }
+    & :nth-child(2)::before {
+        background: #ff9900;
+    }
+    & :nth-child(3)::before {
+        background: #ed4014;
+    }
+    & :nth-child(4)::before {
+        background: #e1e1e1;
+    }
+}
+
+.qu-tips-tag {
+    display: inline-block;
+    margin-right: 15px;
+    color: #909090;
+    font-size: 12px;
+    &::before {
+        content: "";
+        width: 15px;
+        height: 10px;
+        display: inline-block;
+        border-radius: 3px;
+        vertical-align: baseline;
+    }
+}
+.qu-index {
+    width: 30px;
+    height: 22px;
+    border-radius: 5px;
+    display: inline-block;
+    border: 1px solid #e1e1e1;
+    line-height: 22px;
+    text-align: center;
+    margin-bottom: 5px;
+    margin-top: 5px;
+    margin-right: 5px;
+    cursor: pointer;
+    background: #f1f1f1;
+    &:hover {
+        background: white;
+    }
+}
+.right-qu {
+    color: #19be6b;
+    border-color: #19be6b;
+    background: white;
+}
+.err-qu {
+    color: #ed4014;
+    border-color: #ed4014;
+    background: white;
+}
+.half-qu {
+    color: #ff9900;
+    border-color: #ff9900;
+    background: white;
+}
+.submit-score {
+    width: 236px;
+    margin-left: 2px;
+}
+.score-key-box {
+    margin-top: 10px;
+    height: 120px;
+    transition: height 0.5s;
+    -webkit-transition: height 0.5s;
+    overflow: hidden;
+}
+.hind-key-box {
+    height: 0px;
+}
+.score-key {
+    display: inline-block;
+    width: 30px;
+    height: 30px;
+    border-radius: 50%;
+    border: 1px solid #e1e1e1;
+    line-height: 30px;
+    text-align: center;
+    margin-top: 5px;
+    margin-right: 5px;
+    cursor: pointer;
+    background: #f1f1f1;
+}
+.score-key-active {
+    background: white;
+    border-color: #1cc0f3;
+    color: #1cc0f3;
+}
+.score-input-box {
+    display: flex;
+    align-items: center;
+}
+.score-wrap > :nth-child(2) {
+    background: white;
+}
+.quick-score-box {
+    padding: 10px 15px;
+    border-bottom: 1px solid #e1e1e1;
+}
+
+.toggle-num-status {
+    float: right;
+    margin-right: 5px;
+    font-size: 18px;
+    cursor: pointer;
+    margin-top: 2px;
+    user-select: none;
+}
+.btn-wrap {
+    float: right;
+    // padding-right: 15px;
+}
+.action-btn {
+    margin-left: 30px;
+    cursor: pointer;
+}
+.action-btn-icon {
+    display: inline-block;
+    margin-right: 5px;
+}
+.tmd-logo {
+    width: 35px;
+    height: 35px;
+    margin: 5px 18px 5px 13px;
+    vertical-align: bottom;
+}
+.mark-area {
+    width: 100%;
+    height: 100%;
+    user-select: none;
+    // padding: 5px;
+}
+.mark-header {
+    height: 50px;
+    width: 100%;
+    border-bottom: 1px solid #e1e1e1;
+    background: #fafafa;
+    line-height: 50px;
+    text-align: left;
+    padding: 0px 18px 0px 0px;
+    box-shadow: 0px 2px 3px 0px rgba(100, 100, 100, 0.2);
+    position: relative;
+    z-index: 99;
+    .info-label {
+        color: #909090;
+    }
+    .info-value {
+        margin-right: 30px;
+    }
+}
+.mark-main {
+    display: flex;
+    flex-direction: row;
+    height: calc(100% - 50px);
+}
+.mark-tools-wrap {
+    position: relative;
+    box-shadow: 2px 0px 5px 0px rgba(100, 100, 100, 0.2);
+    z-index: 9;
+    flex: 0 0 40px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    border-right: 1px solid #e1e1e1;
+    background: #fafafa;
+}
+.mark-stage {
+    flex: 1;
+    overflow: hidden;
+    background: white;
+    text-align: center;
+    position: relative;
+    height: 100%;
+}
+.qu-index-box {
+    position: absolute;
+    bottom: 0px;
+    background: #fafafa;
+    width: 100%;
+    left: 0px;
+    height: fit-content;
+    text-align: left;
+    max-height: 180px;
+    padding: 10px 10px 20px 10px;
+    box-shadow: 0 -2px 20px -12px #595959;
+    p {
+        width: fit-content;
+    }
+}
+.score-wrap {
+    position: relative;
+    box-shadow: -2px 0px 5px 0px rgba(100, 100, 100, 0.2);
+    z-index: 9;
+    flex: 0 0 241px;
+    height: 100%;
+    border-left: 1px solid #e1e1e1;
+    background: #fafafa;
+}
+.tool-icon {
+    font-size: 20px;
+    cursor: pointer;
+    height: 40px;
+    line-height: 40px;
+    // border-bottom: 1px solid #e1e1e1;
+    width: 39px;
+    &:hover {
+        color: #1cc0f3;
+        box-shadow: 0 5px 10px -8px #1cc0f3;
+        background: white;
+        border-color: transparent;
+    }
+}
+.exception-icon:hover {
+    box-shadow: 0 5px 10px -8px #ed4014;
+}
+.tool-icon-active {
+    color: #1cc0f3;
+    box-shadow: 0 5px 10px -8px #1cc0f3;
+    background: white;
+    border-color: transparent;
+    &:last-child {
+        box-shadow: 0 5px 10px -8px #ed4014;
+    }
+}
+</style>
+<style>
+.mark-tools-wrap .ivu-poptip-title::after {
+    display: none;
+}
+.textarea:focus {
+    box-shadow: none;
+    overflow: hidden;
+    outline: none !important;
+    line-height: 1.5;
+}
+</style>

+ 3 - 3
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -1672,12 +1672,12 @@ namespace TEAMModelOS.Controllers
                 if (!requert.TryGetProperty("stuId", out JsonElement sId)) return BadRequest();
                 if (!requert.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
                 if (!requert.TryGetProperty("tmdId", out JsonElement tId)) return BadRequest();
-                if (!requert.TryGetProperty("score", out JsonElement score)) return BadRequest();
-                if (!requert.TryGetProperty("mark", out JsonElement mark)) return BadRequest();
+                if (!requert.TryGetProperty("score", out JsonElement score)) return BadRequest(); 
                 if (!requert.TryGetProperty("count", out JsonElement count)) return BadRequest();
                 if (!requert.TryGetProperty("code", out JsonElement code)) return BadRequest();
+                requert.TryGetProperty("mark", out JsonElement mark);
                 var client = _azureCosmos.GetCosmosClient();
-                var redisClient = _azureRedis.GetRedisClient(8);
+				var redisClient = _azureRedis.GetRedisClient(8);
                 List<ExamClassResult> classResults = new();
                 List<dynamic> recs = new List<dynamic>();
                 var record = await redisClient.HashGetAllAsync($"Exam:Scoring:{id}-{subjectId}");

+ 97 - 29
TEAMModelOS/Controllers/Core/BlobController.cs

@@ -20,7 +20,6 @@ using StackExchange.Redis;
 using Azure.Messaging.ServiceBus;
 using static TEAMModelOS.SDK.DI.AzureStorageBlobExtensions;
 using System.Linq;
-using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
 using Microsoft.AspNetCore.Http;
 using TEAMModelOS.SDK.Context.Constant.Common;
 using HTEXLib.COMM.Helpers;
@@ -516,20 +515,63 @@ namespace TEAMModelOS.Controllers.Core
              @"(?!((^(con)$)|^(con)\\..*|(^(prn)$)|^(prn)\\..*|(^(aux)$)|^(aux)\\..*|(^(nul)$)|^(nul)\\..*|(^(com)[1-9]$)|^(com)[1-9]\\..*|(^(lpt)[1-9]$)|^(lpt)[1-9]\\..*)|^\\s+|.*\\s$)(^[^\\\\\\:\\<\\>\\*\\?\\\\\\""\\\\|]{1,255}$)");
         }
 
+
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
         [HttpPost("bloblog-list")]
         public async Task<ActionResult> BloblogList(JsonElement request) {
-            request.TryGetProperty("name", out JsonElement name);
-            request.TryGetProperty("type", out JsonElement type);
-            var client = _azureCosmos.GetCosmosClient();
-            var queryslt = $"SELECT  value(c) FROM c WHERE c.type='{type}' c.code = 'Bloblog-{name}'";
             List<Bloblog> bloblogs = new List<Bloblog>();
-            await foreach (var item in client.GetDatabaseQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") })) {
-                bloblogs.Add(item);
+            try
+            {
+                request.TryGetProperty("name", out JsonElement name);
+                request.TryGetProperty("type", out JsonElement type);
+                request.TryGetProperty("scope", out JsonElement scope);
+                request.TryGetProperty("period", out JsonElement period);
+                var client = _azureCosmos.GetCosmosClient();
+                var queryslt = $"SELECT  value(c) FROM c WHERE c.type='{type}' and  c.period='{period}'";
+                if (scope.GetString().Equals("school"))
+                {
+                    await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                    {
+                        bloblogs.Add(item);
+                    }
+                }
+                else if (scope.GetString().Equals("teacher"))
+                {
+                    await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                    {
+                        bloblogs.Add(item);
+                    }
+                }
+                return Ok(new { bloblogs = bloblogs });
+            }
+            catch (Exception ex) {
+                return Ok(new { bloblogs = bloblogs });
             }
-            return Ok(new { bloblogs = bloblogs });
         }
 
-
+        /*
+         新增 编辑接口
+        {
+            "period": "",
+            "scope": "school",
+            "name": "hbcn",
+            "url": "video/xxx.png",
+            "opt": "add",
+        }
+         */
+        /*
+         {
+            "scope": "school",
+            "name": "hbcn",
+            "opt": "del",
+            "id": "19ccce98-c524-4ea7-aabc-887d1391e551"
+        }
+         */
         /// <summary>
         /// 
         /// </summary>
@@ -546,12 +588,12 @@ namespace TEAMModelOS.Controllers.Core
                 request.TryGetProperty("url", out JsonElement jurl);
                 request.TryGetProperty("opt", out JsonElement opt);
                 request.TryGetProperty("id", out JsonElement id);
-                request.TryGetProperty("type", out JsonElement type);
                 var url = System.Web.HttpUtility.UrlDecode(jurl.GetString(), Encoding.UTF8);
                 string[] uls = url.Split("/");
+                var u = "";
                 if (uls != null)
                 {
-                  var  u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
+                     u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
                     
                 }
                 var size = await _azureStorage.GetBlobContainerClient($"{name}").GetBlobsSize(url);
@@ -560,34 +602,60 @@ namespace TEAMModelOS.Controllers.Core
                 var client = _azureCosmos.GetCosmosClient();
                 if (opt.GetString().Equals("add"))
                 {
-                    var blob = new Bloblog
-                    {
-                        id = Guid.NewGuid().ToString(),
-                        pk = "Bloblog",
-                        code = $"Bloblog-{name}",
-                        url = url,
-                        time = now,
-                        size = size != null && size.HasValue ? size.Value : 0,
-                        period = $"{period}"
-                    };
-                    if (scope.GetString().Equals("school"))
-                    {
-                        await client.GetContainer("TEAMModelOS", "school").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                    //地址相同的,直接更新
+                    bool exsit = false;
+                    try {
+                        var queryslt = $"SELECT  value(c) FROM c WHERE c.url='{url}'";
+                        if (scope.GetString().Equals("school"))
+                        {
+                            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                            {
+                                item.size = size != null && size.HasValue ? size.Value : 0;
+                                await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Bloblog>(item, item.id, new Azure.Cosmos.PartitionKey(item.code));
+                                exsit = true;
+                            }
+                        }
+                        else if (scope.GetString().Equals("teacher")) {
+                            await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                            {
+                                item.size = size != null && size.HasValue ? size.Value : 0;
+                                await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Bloblog>(item, item.id, new Azure.Cosmos.PartitionKey(item.code));
+                                exsit = true;
+                            }
+                        }
+                    } catch (Exception ex) {
                     }
-                    else if (scope.GetString().Equals("teacher"))
-                    {
-                        await client.GetContainer("TEAMModelOS", "teacher").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                    if (!exsit) {
+                        var blob = new Bloblog
+                        {
+                            id = Guid.NewGuid().ToString(),
+                            pk = "Bloblog",
+                            code = $"Bloblog-{name}",
+                            url = url,
+                            time = now,
+                            size = size != null && size.HasValue ? size.Value : 0,
+                            period = $"{period}",
+                            type = u
+                        };
+                        if (scope.GetString().Equals("school"))
+                        {
+                            await client.GetContainer("TEAMModelOS", "School").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                        }
+                        else if (scope.GetString().Equals("teacher"))
+                        {
+                            await client.GetContainer("TEAMModelOS", "Teacher").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                        }
                     }
                 }
                 else if (opt.GetString().Equals("del"))
                 {
                     if (scope.GetString().Equals("school"))
                     {
-                        await client.GetContainer("TEAMModelOS", "school").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
+                        await client.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
                     }
                     else if (scope.GetString().Equals("teacher"))
                     {
-                        await client.GetContainer("TEAMModelOS", "teacher").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
+                        await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
                     }
                 }
                 else { return BadRequest(); }

+ 7 - 7
TEAMModelOS/Controllers/Core/OpenApiController.cs

@@ -85,15 +85,15 @@ namespace TEAMModelOS.Controllers.Core
         }
         /*
         {
-           "id": "4a54e95e-ef13-46ec-91ac-e55919d09f9e", //新增没有id  ,更新带id
-           "code":"hbcn",
+           "id": "4a54e95e-ef13-46ec-91ac-e55919d09f9e", //新增没有id  ,更新带id  
+           "code":"hbcn",
            "pk":"OpenApp",
-           "name":"醍摩豆接入紫藤课纲应用",
-           "descr":"xxxxxx",
-           "auths":[1,2,3,4],
-           "school":"hbcn",
+           "name":"醍摩豆接入紫藤课纲应用",
+           "descr":"xxxxxx",
+           "auths":[1,2,3,4],
+           "school":"hbcn",
            "token":null,
-           "status":1
+           "status":1
        }
         */
         /// <summary>

+ 0 - 1
TEAMModelOS/Services/Common/SyllabusService.cs

@@ -58,7 +58,6 @@ namespace TEAMModelOS.Services.Common
                 }
                 var node = new Tnode
                 {
-                    type = x.type,
                     title = x.title,
                     id = x.id,
                     pid = x.pid,