Browse Source

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

upon 3 months ago
parent
commit
6aa980b3d1
82 changed files with 27638 additions and 53 deletions
  1. 24 31
      TEAMModelBI/ClientApp/src/view/issueCoupons/crteadCoupon.vue
  2. 7 0
      TEAMModelOS/ClientApp/public/lang/en-US.js
  3. 7 0
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  4. 7 0
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  5. 9 3
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  6. 185 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/ResBelong.vue
  7. 703 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/UploadModal.vue
  8. 517 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/index.less
  9. 1475 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/index.vue
  10. 0 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ExerciseList.less
  11. 1014 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ExerciseList.vue
  12. 246 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/PaperDownload.less
  13. 606 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/PaperDownload.vue
  14. 99 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ShareCenter.less
  15. 496 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ShareCenter.vue
  16. 288 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/TestPaperList.less
  17. 826 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/TestPaperList.vue
  18. 86 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/index.less
  19. 388 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/index.vue
  20. 474 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseChild.vue
  21. 202 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseChildList.vue
  22. 539 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseCreateChild.vue
  23. 110 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseDiffPie.vue
  24. 315 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseEditAnsScore.vue
  25. 920 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseEditExercise.vue
  26. 1225 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseExerciseList.vue
  27. 340 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseFilter.vue
  28. 184 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseFixExamItem.vue
  29. 114 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseHiteachPaper.vue
  30. 568 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseImport.vue
  31. 122 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseObjectivePie.vue
  32. 231 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePaperItemPicker.vue
  33. 78 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePasteTool.vue
  34. 149 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePointPie.vue
  35. 483 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePoints.vue
  36. 513 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseRepair.vue
  37. 101 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseTypePie.vue
  38. 219 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/DownloadPaper.vue
  39. 20 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/index.js
  40. 749 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CommonExercise.less
  41. 327 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreateExercises.less
  42. 888 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreateExercises.vue
  43. 170 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreatePaper.less
  44. 1822 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreatePaper.vue
  45. 138 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/DfPage.less
  46. 1602 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/DfPage.vue
  47. 357 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/TestPaper.less
  48. 790 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/TestPaper.vue
  49. 26 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/WxtPage.vue
  50. 26 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/WxtPagePreview.vue
  51. 26 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/XkwPage.vue
  52. 782 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/htTestPaper.vue
  53. 218 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/index.vue
  54. 96 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseCompletion.vue
  55. 73 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseCompose.vue
  56. 92 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseConnector.vue
  57. 92 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseCorrect.vue
  58. 86 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseJudge.vue
  59. 338 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseMultiple.vue
  60. 302 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseSingle.vue
  61. 136 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseSubjective.vue
  62. 78 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/navbar/IdentityNavbar.vue
  63. 60 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/navbar/PathNavbar.vue
  64. 416 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/syllabus/Syllabus.less
  65. 3306 0
      TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/syllabus/index.vue
  66. 51 0
      TEAMModelOS/ClientApp/src/router/routes.js
  67. 13 0
      TEAMModelOS/ClientApp/src/utils/callDesktopAppMethods.js
  68. 1 1
      TEAMModelOS/ClientApp/src/utils/excel.js
  69. 2 2
      TEAMModelOS/ClientApp/src/view/areaMgmt/AreaLayout.vue
  70. 14 5
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue
  71. 9 0
      TEAMModelOS/ClientApp/src/view/evaluation/bank/index.vue
  72. 537 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/index.vue
  73. 13 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/paperDownload.vue
  74. 3 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private.vue
  75. 14 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private/bank.vue
  76. 13 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private/content.vue
  77. 13 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private/syllabus.vue
  78. 3 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school.vue
  79. 14 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school/bank.vue
  80. 14 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school/content.vue
  81. 13 0
      TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school/syllabus.vue
  82. 25 11
      TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue

+ 24 - 31
TEAMModelBI/ClientApp/src/view/issueCoupons/crteadCoupon.vue

@@ -10,15 +10,17 @@
                 </el-radio-group>
                 </el-radio-group>
             </el-form-item>
             </el-form-item>
             <el-form-item v-if="!detailSettingFlag" label="活動票券" prop="eventType">
             <el-form-item v-if="!detailSettingFlag" label="活動票券" prop="eventType">
-                <el-radio-group v-model="eventType" @change="setEventType" style="max-width: 1000px;max-height: 115px;overflow-y:scroll;">
+                <el-radio-group v-model="eventType" @change="setEventType" style="max-width: 1000px;max-height: 160px;overflow-y:scroll;">
                     <el-radio class="event1" label="hiteach50-3"  border>Hiteach 50 第三階段</el-radio>
                     <el-radio class="event1" label="hiteach50-3"  border>Hiteach 50 第三階段</el-radio>
                     <el-radio class="event1" label="hiteach333-1"  border>HiTeach 333 第一階段</el-radio>
                     <el-radio class="event1" label="hiteach333-1"  border>HiTeach 333 第一階段</el-radio>
                     <el-radio class="event1" label="hiteach333-3"  border>HiTeach 333 第三階段</el-radio>                    
                     <el-radio class="event1" label="hiteach333-3"  border>HiTeach 333 第三階段</el-radio>                    
                     <el-radio class="event2" label="giftWebIRS501M"  border>贈送WEBIRS50一個月</el-radio>
                     <el-radio class="event2" label="giftWebIRS501M"  border>贈送WEBIRS50一個月</el-radio>
                     <el-radio class="event2" label="giftWebIRS503M"  border>贈送WEBIRS50三個月</el-radio>
                     <el-radio class="event2" label="giftWebIRS503M"  border>贈送WEBIRS50三個月</el-radio>
                     <el-radio class="event2" label="giftWebIRS501Y"  border>贈送WEBIRS50一年</el-radio>
                     <el-radio class="event2" label="giftWebIRS501Y"  border>贈送WEBIRS50一年</el-radio>
+                    <el-radio class="event3" label="giftSmart1M"  border>贈送智慧評分一個月</el-radio>
                     <el-radio class="event3" label="giftSmart3M"  border>贈送智慧評分三個月</el-radio>
                     <el-radio class="event3" label="giftSmart3M"  border>贈送智慧評分三個月</el-radio>
                     <el-radio class="event3" label="giftSmart1Y"  border>贈送智慧評分一年</el-radio>
                     <el-radio class="event3" label="giftSmart1Y"  border>贈送智慧評分一年</el-radio>
+                    <el-radio class="event4" label="gifCollaboration1M"  border>贈送協作一個月</el-radio>
                     <el-radio class="event4" label="gifCollaboration3M"  border>贈送協作三個月</el-radio>
                     <el-radio class="event4" label="gifCollaboration3M"  border>贈送協作三個月</el-radio>
                     <el-radio class="event4" label="gifCollaboration1Y"  border>贈送協作一年</el-radio>
                     <el-radio class="event4" label="gifCollaboration1Y"  border>贈送協作一年</el-radio>
                     <el-radio class="event5" label="gifAIGPT1M"  border>贈送AI/GPT一個月</el-radio>
                     <el-radio class="event5" label="gifAIGPT1M"  border>贈送AI/GPT一個月</el-radio>
@@ -27,8 +29,6 @@
                     <el-radio class="event5" label="gifAIGPT1Y"  border>贈送AI/GPT一年</el-radio>
                     <el-radio class="event5" label="gifAIGPT1Y"  border>贈送AI/GPT一年</el-radio>
                     <el-radio label="gifVoiceToText1Y"  border>贈送語音轉錄文字一年</el-radio>
                     <el-radio label="gifVoiceToText1Y"  border>贈送語音轉錄文字一年</el-radio>
                     <el-radio label="gifAITXTAnalysis3M"  border>贈送AI文句分析三個月</el-radio>
                     <el-radio label="gifAITXTAnalysis3M"  border>贈送AI文句分析三個月</el-radio>
-                    <el-radio label="hiteachBookSPLicense"  border>HiTeach專書贈送授權</el-radio>
-                    <el-radio label="fbGroupOpening"  border>HiTeach社團開幕活動</el-radio>
                 </el-radio-group>
                 </el-radio-group>
             </el-form-item>
             </el-form-item>
             <el-form-item v-if="detailSettingFlag" label="票券類型" prop="couponType" >
             <el-form-item v-if="detailSettingFlag" label="票券類型" prop="couponType" >
@@ -341,34 +341,6 @@ const setEventType = (type)=>{
             crtCouponForm.info[2].n = 'HiTeach 333 Stage 3'
             crtCouponForm.info[2].n = 'HiTeach 333 Stage 3'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/event.php?act=view&id=162'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/event.php?act=view&id=162'
         break
         break
-        case 'hiteachBookSPLicense':
-            crtCouponForm.couponType =  'Event'
-            crtCouponForm.eventName = 'hiteachBookSPLicense'
-            crtCouponForm.rule[0].b = '901003'
-            crtCouponForm.info[0].l = 'zh-tw' 
-            crtCouponForm.info[0].n = 'HiTeach專書贈送授權'
-            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
-            crtCouponForm.info[1].l = 'zh-cn'
-            crtCouponForm.info[1].n = 'HiTeach专书赠送授权'
-            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
-            crtCouponForm.info[2].l = 'en-us'
-            crtCouponForm.info[2].n = 'HiTeach Book Special License'
-            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
-        break
-        case 'fbGroupOpening':
-            crtCouponForm.couponType =  'Event'
-            crtCouponForm.eventName = 'fbGroupOpening'
-            crtCouponForm.rule[0].b = '901008'
-            crtCouponForm.info[0].l = 'zh-tw' 
-            crtCouponForm.info[0].n = 'HiTeach社團開幕活動'
-            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
-            crtCouponForm.info[1].l = 'zh-cn'
-            crtCouponForm.info[1].n = 'HiTeach社团开幕活动'
-            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
-            crtCouponForm.info[2].l = 'en-us'
-            crtCouponForm.info[2].n = 'Facebook Group Opening'
-            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
-        break
         case 'giftWebIRS501M':
         case 'giftWebIRS501M':
             crtCouponForm.couponType =  'Event'
             crtCouponForm.couponType =  'Event'
             crtCouponForm.eventName = 'giftWebIRS501M'
             crtCouponForm.eventName = 'giftWebIRS501M'
@@ -402,6 +374,16 @@ const setEventType = (type)=>{
             crtCouponForm.info[2].l = 'en-us'
             crtCouponForm.info[2].l = 'en-us'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/'
         break
         break
+        case 'giftSmart1M':
+            crtCouponForm.couponType =  'Event'
+            crtCouponForm.eventName = 'giftSmart1M'
+            crtCouponForm.rule[0].b = '901020'
+            crtCouponForm.info[0].l = 'zh-tw' 
+            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
+            crtCouponForm.info[1].l = 'zh-cn'
+            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
+            crtCouponForm.info[2].l = 'en-us'
+            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
         case 'giftSmart3M':
         case 'giftSmart3M':
             crtCouponForm.couponType =  'Event'
             crtCouponForm.couponType =  'Event'
             crtCouponForm.eventName = 'giftSmart3M'
             crtCouponForm.eventName = 'giftSmart3M'
@@ -434,6 +416,17 @@ const setEventType = (type)=>{
             crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
             crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
             crtCouponForm.info[2].l = 'en-us'
             crtCouponForm.info[2].l = 'en-us'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/'
+        break        
+        case 'gifCollaboration1M':
+            crtCouponForm.couponType =  'Event'
+            crtCouponForm.eventName = 'gifCollaboration1M'
+            crtCouponForm.rule[0].b = '901019'
+            crtCouponForm.info[0].l = 'zh-tw' 
+            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
+            crtCouponForm.info[1].l = 'zh-cn'
+            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
+            crtCouponForm.info[2].l = 'en-us'
+            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
         break
         break
         case 'gifCollaboration3M':
         case 'gifCollaboration3M':
             crtCouponForm.couponType =  'Event'
             crtCouponForm.couponType =  'Event'

+ 7 - 0
TEAMModelOS/ClientApp/public/lang/en-US.js

@@ -8427,4 +8427,11 @@ const LANG_EN_US = {
             excelName: 'School System-Subject Comparison Table:',
             excelName: 'School System-Subject Comparison Table:',
         },
         },
     },
     },
+    hiTeachSideMenu: {
+        school: 'School',
+        private: 'Personal',
+        syllabus: 'Syllabus',
+        content: 'Content',
+        bank: 'Question Bank',
+    }
 }
 }

+ 7 - 0
TEAMModelOS/ClientApp/public/lang/zh-CN.js

@@ -8431,4 +8431,11 @@ const LANG_ZH_CN = {
             excelName: '学段-学科对照表',
             excelName: '学段-学科对照表',
         },
         },
     },
     },
+    hiTeachSideMenu: {
+        school: '学校',
+        private: '个人',
+        syllabus: '课纲',
+        content: '內容',
+        bank: '题库',
+    }
 }
 }

+ 7 - 0
TEAMModelOS/ClientApp/public/lang/zh-TW.js

@@ -8430,4 +8430,11 @@ const LANG_ZH_TW = {
             excelName: '學段-學科對照表',
             excelName: '學段-學科對照表',
         },
         },
     },
     },
+    hiTeachSideMenu: {
+        school: '學校',
+        private: '個人',
+        syllabus: '課綱',
+        content: '內容',
+        bank: '題庫',
+    }
 }
 }

+ 9 - 3
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -1,7 +1,7 @@
 <template>
 <template>
 	<div class="layout">
 	<div class="layout">
 		<!-- 头部菜单栏 -->
 		<!-- 头部菜单栏 -->
-		<Header class="header">
+		<Header class="header" :style="onlyContent ? 'display: none' : ''">
 			<div class="logo-wrap">
 			<div class="logo-wrap">
 				<img style="margin-left: 20px" src="../assets/login/ies5_logo_2.svg" :class="isCollapsed ? 'collapsed-logo-width unit-logo' : 'collapsed-logo-width  unit-logo'" v-show="isShowLogo && !isShowAreaSelect" />
 				<img style="margin-left: 20px" src="../assets/login/ies5_logo_2.svg" :class="isCollapsed ? 'collapsed-logo-width unit-logo' : 'collapsed-logo-width  unit-logo'" v-show="isShowLogo && !isShowAreaSelect" />
 				<BaseSelectArea @noArea="isShowAreaSelect = false" v-show="isShowAreaSelect"></BaseSelectArea>
 				<BaseSelectArea @noArea="isShowAreaSelect = false" v-show="isShowAreaSelect"></BaseSelectArea>
@@ -12,7 +12,7 @@
 			<slot name="header-content"></slot>
 			<slot name="header-content"></slot>
 		</Header>
 		</Header>
 		<!-- 侧边菜单栏 -->
 		<!-- 侧边菜单栏 -->
-		<Sider class="biz-menu" @mouseover.native="mouseOver" @mouseleave.native="mouseLeave" ref="side1" hide-trigger collapsible width="190" :collapsed-width="78" v-model="isCollapsed">
+		<Sider class="biz-menu" @mouseover.native="mouseOver" @mouseleave.native="mouseLeave" ref="side1" hide-trigger collapsible width="190" :collapsed-width="78" v-model="isCollapsed" :style="onlyContent ? 'display: none' : ''">
 			<div class="system-level-wrap" v-show="$store.state.userInfo.hasSchool && !isGlobalSite">
 			<div class="system-level-wrap" v-show="$store.state.userInfo.hasSchool && !isGlobalSite">
 				<p :class="[!versionsPast ? 'system-level-past' : '', 'system-level']">
 				<p :class="[!versionsPast ? 'system-level-past' : '', 'system-level']">
 					{{ systemLevel }}
 					{{ systemLevel }}
@@ -120,7 +120,7 @@
 		</Sider>
 		</Sider>
 		<Loading v-if="isLoading"></Loading>
 		<Loading v-if="isLoading"></Loading>
 		<!-- Body内容部分 -->
 		<!-- Body内容部分 -->
-		<Layout :class="!isLock ? 'collapsed-padding content-wrap' : 'content-wrap'">
+		<Layout :class="!isLock ? 'collapsed-padding content-wrap' : 'content-wrap'" :style="onlyContent ? 'padding-left: 0; top: 0' : ''">
 			<slot name="content"></slot>
 			<slot name="content"></slot>
 		</Layout>
 		</Layout>
 		<Modal v-model="updateModal" :title="$t('system.versionLog')" width="800" footer-hide>
 		<Modal v-model="updateModal" :title="$t('system.versionLog')" width="800" footer-hide>
@@ -132,6 +132,12 @@
 	import { mapGetters } from "vuex";
 	import { mapGetters } from "vuex";
 	import jwtDecode from "jwt-decode";
 	import jwtDecode from "jwt-decode";
 	export default {
 	export default {
+		props: {
+			onlyContent: {
+				type: Boolean,
+				default: false
+			}
+		},
 		data() {
 		data() {
 			return {
 			return {
 				isQingYangArea: false,
 				isQingYangArea: false,

+ 185 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/ResBelong.vue

@@ -0,0 +1,185 @@
+<template>
+    <div class="apply-select-wrap dark-iview-select">
+        <div class="apply-item">
+            <span v-show="showLabel">{{$t('teachContent.applyPd')}}:</span>
+            <Select v-model="filterPeriod" multiple style="width:200px" @on-change="pdChange" :max-tag-count="2">
+                <Option v-for="item in periods" :value="item.id" :key="item.id">{{ item.name }}</Option>
+            </Select>
+        </div>
+        <div class="apply-item">
+            <span v-show="showLabel">{{$t('teachContent.applySub')}}:</span>
+            <Select v-model="filterSubject" multiple style="width:200px" @on-change="tagChange" :max-tag-count="2">
+                <Option v-for="item in subjects" :value="item.id" :key="item.id">{{ item.name }}</Option>
+            </Select>
+        </div>
+        <div class="apply-item" style="margin-right:0px">
+            <span v-show="showLabel">{{$t('teachContent.applyGrade')}}:</span>
+            <Select v-model="filterGrade" multiple style="width:200px" @on-change="tagChange" :max-tag-count="2">
+                <Option v-for="(item,index) in grades" :value="index" :key="index">{{ item }}</Option>
+            </Select>
+        </div>
+    </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+    name: 'BasePdSelect',
+    props: {
+        // pdId: {
+        //     type: String,
+        //     default: ''
+        // },
+        showLabel: {
+            type: Boolean,
+            default: false
+        },
+        onlyPd: {
+            type: Boolean,
+            default: true
+        }
+    },
+    data() {
+        return {
+            filterGrade: [],
+            filterPeriod: [],
+            filterSubject: [],
+            schoolBase: {
+                period: []
+            }
+        }
+    },
+    created() {
+        this.$store.dispatch('user/getSchoolProfile').then(
+            (res) => {
+                if (res) {
+                    this.schoolBase = res.school_base
+                    // if (this.periods && !this.pdId) {
+                    //     this.filterPeriod = this.periods[0].id
+                    // } 
+                    // else {
+                    //     this.filterPeriod = this.pdId
+                    // }
+                }
+            },
+            (err) => {
+                // this.$Message.error('API error!')
+            }
+        )
+    },
+    methods: {
+        pdChange(data) {
+            //处理学段单选 (如果学段多选,学科和年级不知道对应的是哪个学段)(取消单选机制)
+            // if (data.length) {
+            //     this.filterPeriod = [data[data.length - 1]]
+            // }else{
+            //     this.filterPeriod = []
+            // }
+            console.log(this.filterPeriod)
+            if (!data.length) {
+                this.$Message.warning(this.$t('teachContent.sltPeriod'))
+            }
+            this.$emit('tag-change', {
+                period: this.filterPeriod,
+                grades: this.filterGrade,
+                subjects: this.filterSubject
+            })
+        },
+        tagChange() {
+            this.$emit('tag-change', {
+                period: this.filterPeriod,
+                grades: this.filterGrade,
+                subjects: this.filterSubject
+            })
+        }
+    },
+    computed: {
+        ...mapGetters({
+            periods: 'user/getPeriods', // 學制s
+        }),
+        subjects() {
+            if (this.schoolBase.period && this.filterPeriod) {
+                let pdData = this.schoolBase.period.find(item => {
+                    return item.id == this.filterPeriod
+                })
+                if (pdData) {
+                    return pdData.subjects
+                } else {
+                    return []
+                }
+            } else {
+                return []
+            }
+        },
+        grades() {
+            if (this.schoolBase.period && this.filterPeriod) {
+                let pdData = this.schoolBase.period.find(item => {
+                    return item.id == this.filterPeriod
+                })
+                if (pdData) {
+                    return pdData.grades
+                } else {
+                    return []
+                }
+            } else {
+                return []
+            }
+        }
+    },
+    watch: {
+        // pdId: {
+        //     handler() {
+        //         this.filterPeriod = [this.pdId]
+        //     },
+        //     immediate: true
+        // }
+        '$store.state.user.curPeriod': {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                if (n) {
+                    this.filterPeriod = [n.id]
+                }
+            }
+        }
+    }
+}
+</script>
+<style scope lang="less">
+.apply-select-wrap {
+    color: var(--second-text-color);
+}
+.sort-dropdown {
+    margin-right: 30px;
+    .title {
+        color: var(--primary-text-color);
+        font-size: 14px;
+    }
+    .ivu-select-dropdown {
+        border-radius: 2px;
+        .ivu-dropdown-menu {
+            li {
+                font-size: 12px !important;
+                &:hover {
+                    color: #2d2d2d;
+                }
+            }
+        }
+    }
+}
+.apply-item {
+    margin-bottom: 6px;
+    display: inline-block;
+    margin-right: 15px;
+}
+</style>
+<style lang="less">
+.apply-item .ivu-tag {
+    background: #1fb06d;
+    border-color: #1fb06d;
+}
+.apply-item .ivu-tag-text,
+.apply-item .ivu-tag .ivu-icon-ios-close {
+    color: white;
+}
+</style>

+ 703 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/UploadModal.vue

@@ -0,0 +1,703 @@
+<template>
+    <Modal v-model="uploadStatus" :ok-text="textLoading ? $t('updModal.uploading') : isComplete ? $t('updModal.complete'):$t('updModal.comfirmUpd')" :cancel-text="$t('updModal.cancelUpd')" :title="$t('teachContent.btnUpload')" class="upload-modal" :mask-closable="false" :closable="false" @on-ok="modalOk" @on-cancel="modalCancel" :loading="modalLoading">
+        <div class="upload-file-box">
+            <ResBelong v-if="$store.state.userInfo.hasSchool" @tag-change="getResTags" v-show="routerScope == 'school'" showLabel class="upd-to-pd"></ResBelong>
+            <Upload type="drag" action="" :show-upload-list="false" :webkitdirectory="false" multiple :before-upload="customUpload" class="upload-wrap" :disabled="textLoading">
+                <Icon class="upload-icon" custom="iconfont icon-upload" v-show="!uploadedList.length" />
+                <p class="upload-text" :style="{marginTop: uploadedList.length ? '25px':'0px'}">
+                    <Icon size="24" style="font-size: 22px;vertical-align: baseline;margin-right: 5px;" custom="iconfont icon-upload" v-show="uploadedList.length" />
+                    {{$t('teachContent.uploadText')}}
+                </p>
+                <!-- <p class="upload-text" style="font-size:12px;">
+                    {{$t('updModal.tips1')}}
+                </p> -->
+                <!-- pptx转和htex提示:暂时去掉了此功能 -->
+                <!-- <p class="upload-text" style="font-size:12px;">
+                    {{$t('updModal.tips3')}}
+                </p> -->
+                <p class="upload-text" :style="{fontSize:'12px',marginBottom: uploadedList.length ? '25px':'50px'}">
+                    <span v-show="routerScope == 'school'">
+                        {{$t('updModal.tips2')}}
+                    </span>
+                </p>
+            </Upload>
+            <div class="upload-file-box">
+                <div class="upload-file-item" v-for="(item,index) in uploadedList" :key="index">
+                    <div class="upload-item-left">
+                        <p class="upload-file-name">
+                            <span v-if="item.type == 'res'" class="file-type-tag" style="color: #1FFCC5;border-color: #1FFCC5">
+                                {{$t('updModal.fileType1')}}
+                            </span>
+                            <span v-else-if="item.type == 'image'" class="file-type-tag" style="color: #45C84A;border-color: #45C84A">
+                                {{$t('updModal.fileType2')}}
+                            </span>
+                            <span v-else-if="item.type == 'video'" class="file-type-tag" style="color: #8E2BDD;border-color: #8E2BDD">
+                                {{$t('updModal.fileType3')}}
+                            </span>
+                            <span v-else-if="item.type == 'audio'" class="file-type-tag" style="color: #E1027B;border-color: #E1027B">
+                                {{$t('updModal.fileType4')}}
+                            </span>
+                            <span v-else-if="item.type == 'doc'" class="file-type-tag" style="color: #03C0C2;border-color: #03C0C2">
+                                {{item.isParse ? $t('updModal.fileType5'):$t('updModal.fileType6')}}
+                            </span>
+                            <span v-else class="file-type-tag" style="color: #E87B22; border-color: #E87B22">
+                                {{$t('updModal.fileType7')}}
+                            </span>
+                            <span>{{item.name}}</span>
+                            <span v-if="item.isExist" class="exist-tag">
+                                {{$t('updModal.hasExist')}}
+                            </span>
+                        </p>
+                        <span class="upload-info-wrap">
+                            {{parseInt(item.loadedBytes * 100 / item.size)+'%'}}
+                        </span>
+                        <span class="upload-info-wrap">
+                            {{getSizeByBytes(item.loadedBytes)+'/'+getSizeByBytes(item.size)}}
+                        </span>
+                        <!-- <span v-if="item.extension == 'PPTX'" class="upload-info-wrap parse-label" @click="item.isParse = !item.isParse" :style="{color:item.isParse ? '#1cc0f3':'#808080'}">
+                            <Icon class="is-parse-htex" size="12" custom="iconfont icon-convert" />
+                            {{$t('updModal.resolve')}}
+                        </span> -->
+                        <!-- pptx是否转换 暂时隐藏此功能-->
+                        <!-- <Checkbox class="upload-info-wrap parse-label" v-if="item.extension == 'PPTX'" v-model="item.isParse">
+                            {{$t('updModal.resolve')}}
+                        </Checkbox> -->
+                        <!-- stauts 0-上传中 1-成功 2-失败 3-已存在 -->
+                        <Progress style="top:-10px;" v-if="item.status == 0" :percent="item.progress" status="active" stroke-color="#1CC0F3" :stroke-width="2" hide-info />
+                    </div>
+                    <div class="upload-item-right" v-show="!textLoading">
+                        <Icon type="ios-close" class="delete-btn" color="red" size="30" style="float:right;" @click="listDelFile(index)" />
+                    </div>
+                </div>
+            </div>
+        </div>
+    </Modal>
+
+</template>
+<script>
+import axios from 'axios'
+import BlobTool from '@/utils/blobTool.js'
+import ResBelong from '@/view/teachcontent/ResBelong.vue'
+export default {
+    components: {
+        ResBelong
+    },
+    data() {
+        return {
+            uploadedList: [],
+            containerClient: null,
+            routerScope: '',
+            uploadStatus: false,
+            textLoading: false,
+            modalLoading: true,
+            isComplete: false, //是否完成上传
+            loadingCount: 0,
+            tags: {
+                period: '',
+                gradeId: [],
+                subjectId: []
+            }
+        }
+    },
+    props: {
+        //文件路径
+        path: {
+            default: '',
+            type: String,
+            required: true
+        },
+        //文件大小限制
+        maxSize: {
+            default: 2 * 1024 * 1024 * 1024,
+            type: Number
+        },
+        //文件格式限制
+        format: {
+            default: () => {
+                return []
+            },
+            type: Array
+        },
+        //资源所属学段
+        // pdId: {
+        //     default: '',
+        //     type: String
+        // }
+    },
+    methods: {
+        //资源标签:学段、学科、年级
+        getResTags(data) {
+            this.tags.period = data.period
+            this.tags.subjectId = data.subjects
+            this.tags.gradeId = data.grades
+        },
+        listDelFile(index) {
+            if (this.uploadedList[index].status === 0) {
+                this.uploadedList.splice(index, 1)
+            } else if (this.uploadedList[index].blob) {
+                this.deleteFile(index)
+            }
+        },
+        //删除文件
+        deleteFile(index) {
+            if (this.uploadedList[index].type == 'res') {
+                this.$api.blob.deletePrefix({
+                    cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                    prefix: `res/${this.uploadedList[index].name.replace('.HTEX', '').replace('.htex', '')}`
+                }).then(
+                    res => {
+                        this.uploadedList.splice(index, 1)
+                        this.$Message.success(this.$t('updModal.delOk'))
+                    },
+                    err => {
+                        this.$Message.error(this.$t('updModal.delErr'))
+                    }
+                )
+            } else {
+                this.containerClient.deleteBlob(this.uploadedList[index].blob).then(
+                    res => {
+                        //删除缩略图和封面
+                        if (this.uploadedList[index].type == 'image') {
+                            let thum = this.uploadedList[index].blob.replace('/image/', '/thum/')
+                            this.containerClient.deleteBlob(thum)
+                        } else if (this.uploadedList[index].type == 'video') {
+                            let thum = this.uploadedList[index].blob.replace('/video/', '/thum/')
+                            thum = thum.slice(0, thum.lastIndexOf('.')) + '.png'
+                            this.containerClient.deleteBlob(thum)
+                        }
+                        this.uploadedList.splice(index, 1)
+                        this.$Message.success(this.$t('updModal.delOk'))
+                    },
+                    err => {
+                        this.$Message.error(this.$t('updModal.delErr'))
+                    }
+                )
+            }
+        },
+        getFileType(fileName) {
+            let extension = fileName.substring(fileName.lastIndexOf('.') + 1, fileName.length)
+            extension = extension.toUpperCase()
+            for (let key in this.$GLOBAL.CONTENT_TYPES) {
+                if (this.$GLOBAL.CONTENT_TYPES[key].indexOf(extension) != -1) {
+                    return key
+                }
+            }
+            return 'other'
+        },
+        getSizeByBytes(bytes) {
+            return bytes / 1024 < 1024 ? (bytes / 1024).toFixed(1) + 'KB' : bytes / 1024 / 1024 < 1024 ? (bytes / 1024 / 1024).toFixed(1) + 'M' : (bytes / 1024 / 1024 / 1024).toFixed(1) + 'G'
+        },
+        async customUpload(file) {
+            if (file.size > this.maxSize) {
+                this.$Message.error({
+                    content: this.$t('updModal.sizeErr'),
+                    duration: 3
+                })
+                return false
+            }
+
+            let extension = file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length)
+            if (this.format.length > 0 && this.format.indexOf(extension) == -1) {
+                this.$Message.error({
+                    content: this.$t('updModal.typeErr'),
+                    duration: 3
+                })
+                return false
+            }
+
+            let fileType = this.getFileType(file.name)
+            //上传文件
+            let fileInfo = {
+                url: '',
+                name: file.name,
+                size: file.size,
+                loadedBytes: 0,
+                progress: 0,
+                status: 0,
+                type: fileType,
+                file: file,
+                extension: extension.toUpperCase(),
+                isParse: false
+            }
+            this.uploadedList.push(fileInfo)
+            this.modalLoading = true
+            this.isComplete = false
+            return false
+        },
+        // modal取消事件
+        modalCancel() {
+            if (this.uploadedList.length) {
+                let blobsFile = this.uploadedList.filter(item => {
+                    let flag = true
+                    if (item.extension == 'HTEX') {
+                        flag = false
+                    }
+                    if (item.extension == 'PPTX' && item.isParse) {
+                        flag = false
+                    }
+                    return flag
+                })
+                if (blobsFile.length) {
+                    let blobs = blobsFile.map(item => {
+                        return item.blob
+                    })
+                    blobs = blobs.filter(item => { return item })
+                    this.containerClient.deleteBlobBatch(blobs).then().finally(() => {
+                        blobsFile.forEach((item) => {
+                            if (item.type == 'image') {
+                                let thum = item.blob ? item.blob.replace('/image/', '/thum/') : ''
+                                if (thum) this.containerClient.deleteBlob(thum)
+                            } else if (item.type == 'video') {
+                                let thum = item.blob ? item.blob.replace('/video/', '/thum/') : ''
+                                if (thum) {
+                                    thum = thum.slice(0, thum.lastIndexOf('.')) + '.png'
+                                    this.containerClient.deleteBlob(thum)
+                                }
+                            }
+                        })
+                        this.uploadedList = []
+                    })
+                }
+
+                let prefixFiles = this.uploadedList.filter(item => {
+                    let flag = false
+                    if (item.extension == 'HTEX') {
+                        flag = true
+                    }
+                    if (item.extension == 'PPTX' && item.isParse) {
+                        flag = true
+                    }
+                    return flag
+                })
+
+                if (prefixFiles.length) {
+                    prefixFiles.forEach(item => {
+                        if (item.blob) {
+                            this.$api.blob.deletePrefix({
+                                cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                                prefix: `res/${item.name.replace('.HTEX', '').replace('.htex', '')}`
+                            }).then(
+                                res => {
+                                },
+                                err => {
+                                    this.$Message.error(this.$t('updModal.cancelErr'))
+                                }
+                            )
+                        }
+                    })
+                    this.uploadedList = []
+                }
+            }
+            this.textLoading = false
+        },
+        // modal确认事件
+        modalOk() {
+            if (this.$store.state.userInfo.hasSchool && !this.tags.period.length) {
+                this.$Message.warning(this.$t('updModal.sltPeriod'))
+                this.modalLoading = false
+                setTimeout(() => {
+                    this.modalLoading = true
+                })
+                return
+            }
+            if (this.uploadedList.length == 0) {
+                this.$Message.warning(this.$t('updModal.sltFile'))
+                this.modalLoading = false
+                setTimeout(() => {
+                    this.modalLoading = true
+                })
+                return
+            }
+            //已完成上传操作,关闭对话框、emit
+            if (this.isComplete) {
+                //保存Blob描述信息
+                let requestData
+                if (this.routerScope == 'school') {
+                    requestData = {
+                        periodId: this.tags.period,
+                        gradeId: this.tags.gradeId.map(item => item + ''),
+                        subjectId: this.tags.subjectId,
+                        scope: 'school',
+                        name: this.$store.state.userInfo.schoolCode,
+                        url: [],
+                        opt: 'add'
+                    }
+                    this.uploadedList.forEach(item => {
+                        if (item.status == 1) {
+                            item.periodId = this.tags.period
+                            item.gradeId = this.tags.gradeId.map(item => item + '')
+                            item.subjectId = this.tags.subjectId
+                            requestData.url.push(item.blob.substring(1, item.blob.length))
+                        }
+                    })
+                }
+                //个人资源不用关联学段、学科、年级等信息
+                else {
+                    requestData = {
+                        periodId: [],
+                        gradeId: [],
+                        subjectId: [],
+                        scope: 'private',
+                        name: this.$store.state.userInfo.TEAMModelId,
+                        url: [],
+                        opt: 'add'
+                    }
+                    this.uploadedList.forEach(item => {
+                        if (item.status == 1) {
+                            requestData.url.push(item.blob.substring(1, item.blob.length))
+                        }
+                    })
+                }
+                if (requestData.url.length) {
+                    this.$api.blob.BlobInfoMgt(requestData).then(
+                        res => {
+                            if (!res.error) {
+                                //添加保存成功的id,返回直接删除可以获取到id
+                                this.uploadedList.forEach(item => {
+                                    for (let log of res.bloblog) {
+                                        log.url = '/' + log.url
+                                        if (log.url == item.blob) {
+                                            item.id = log.id
+                                            break
+                                        }
+                                    }
+                                })
+                                this.$emit("successData", this.uploadedList.filter(item => item.status == 1))
+                                this.uploadedList = []
+                            } else {
+                                // this.$Message.error('API error')
+                                this.$Message.error(this.$t('updModal.updErr'))
+                            }
+                        },
+                        err => {
+                            // this.$Message.error('API error')
+                        }
+                    )
+                }
+            } else { //还未上传文件,则上传文件
+                this.textLoading = true
+                this.uploadedList.forEach((item, index) => {
+                    if (item.loadedBytes < item.size) {
+                        this.loadingCount++
+                        //默认使用props接受的路径,没有则根据文件类型上传
+                        this.confirmUpload(item.file, this.path || item.type, index)
+                    }
+                })
+
+            }
+        },
+        // 确认上传文件
+        confirmUpload(file, fileType, index) {
+            let extension = this.uploadedList[index].extension
+            let _this = this
+            this.containerClient.upload(file, {
+                path: fileType,
+                checkExist: true
+            }, {
+                onProgress: (e) => {
+                    _this.uploadedList[index].loadedBytes = e.loadedBytes
+                    _this.uploadedList[index].progress = parseInt(e.loadedBytes * 100 / file.size)
+                }
+            }).then(
+                res => {
+                    console.error(res)
+                    this.uploadedList[index].status = 1
+                    this.uploadedList[index].url = res.url
+                    this.$set(this.uploadedList[index], 'isExist', res.isExist)
+                    if (res.isExist) this.uploadedList[index].name = res.name
+                    this.uploadedList[index].blob = res.blob
+                    if (res.type == 'image') {
+                        this.uploadThum(file, res.isExist ? res.name : file.name)
+                    } else if (res.type == 'video') {
+                        this.uploadPoster(res, res.isExist ? res.name : file.name)
+                    } else if (extension == 'PPTX') {
+                        if (_this.uploadedList[index].isParse) {
+                            let delBlob = res.blob
+                            axios({
+                                method: 'post',
+                                url: `${window.location.protocol}//${window.location.host}/import/parse-doc`,
+                                data: {
+                                    file: res.url,
+                                    scope: this.routerScope,
+                                    target: 'res'
+                                },
+                                timeout: 10 * 60 * 1000, //十分钟
+                            }).then(
+                                parseRes => {
+                                    let filename = res.name.substring(0, res.name.lastIndexOf('.'))
+                                    this.containerClient.deleteBlob(delBlob, res.size)
+                                    this.uploadedList[index].blob = `/res/${filename}/index.json`
+                                    this.uploadedList[index].url = this.uploadedList[index].url.replace('.pptx', '/index.json').replace('.PPTX', '/index.json')
+                                    this.uploadedList[index].extension = 'HTEX'
+                                    this.uploadedList[index].name = filename + '.HTEX'
+                                    this.uploadedList[index].type = 'res'
+                                },
+                                parseErr => {
+                                    this.$Message.error(this.$t('updModal.resolveErr'))
+                                }
+                            ).finally(() => {
+                                this.counterReduce()
+                            })
+                        } else {
+                            this.counterReduce()
+                        }
+                    } else if (extension == 'HTEX') {
+                        let delBlob = res.blob
+                        let parseUrl = res.url
+                        this.uploadedList[index].blob = `/res/${res.name.replace('.htex', '').replace('.HTEX', '')}/index.json`
+                        this.uploadedList[index].url = `${res.url.replace('.htex', '').replace('.HTEX', '')}/index.json`
+                        axios({
+                            method: 'post',
+                            url: `${window.location.protocol}//${window.location.host}/import/parse-doc`,
+                            data: {
+                                file: parseUrl,
+                                scope: this.routerScope,
+                                target: 'res'
+                            },
+                            timeout: 10 * 60 * 1000, //十分钟
+                        }).then(
+                            parseRes => {
+                                console.log('文件信息', JSON.stringify(this.uploadedList[index]))
+                            },
+                            parseErr => {
+                                this.$Message.error(this.$t('updModal.updErr'))
+                            }
+                        ).finally(() => {
+                            this.containerClient.deleteBlob(delBlob)
+                            this.counterReduce()
+
+                        })
+                    }
+                    this.uploadedList[index].createTime = res.createTime
+                },
+                err => {
+
+                    this.$Message.error(this.$t('updModal.updErr'))
+                    if (err.spaceError) {
+                        console.error(err.spaceError)
+                    }
+                    if (err.code == 1) {
+                        this.uploadedList[index].status = 3 //文件重复
+                    } else {
+                        this.uploadedList[index].status = 2
+                    }
+                }
+            ).finally(() => {
+                if (fileType != 'video' && fileType != 'image' && extension !== 'PPTX' && extension !== 'HTEX') {
+                    this.counterReduce()
+                }
+            })
+        },
+        // 上传/解析完成计数器减1
+        counterReduce() {
+            if (--this.loadingCount == 0) {
+                this.textLoading = false
+                this.isComplete = true
+                this.modalLoading = false
+            }
+        },
+        //处理图片缩略图
+        async uploadThum(file, name) {
+            let dataUrl = await this.$jsFn.fileToURL(file)
+            dataUrl = await this.$jsFn.compressImgByUrl(dataUrl, file.name, 0.1)
+            let f = this.$jsFn.dataURLtoFile(dataUrl, name || file.name)
+            this.containerClient.upload(f, {
+                path: 'thum'
+            }).then(
+                res => {
+                    console.log(this.$t('updModal.compressImgOk'))
+                },
+                err => {
+                    console.log(this.$t('updModal.compressImgErr'))
+                }
+            ).finally(() => {
+                this.counterReduce()
+            })
+        },
+        //处理视频封面
+        async uploadPoster(file, name) {
+            let trueName = name || file.name
+            let n = trueName.substring(0, trueName.lastIndexOf('.'))
+            try {
+                const fileSas = await this.$tools.getFileSas(file.url)
+                let dataUrl = await this.$jsFn.createVideoPoster(fileSas.url, file.name, 0.1)
+                // let dataUrl = await this.$tools.getVideoBase64(file.url + this.sasString)
+                console.log(dataUrl)
+                let f = this.$jsFn.dataURLtoFile(dataUrl, n + '.png')
+                this.containerClient.upload(f, {
+                    path: 'thum'
+                }).then(
+                    res => {
+                        console.log(this.$t('updModal.posterOk'))
+                    },
+                    err => {
+                        console.log(this.$t('updModal.posterErr'))
+                    }
+                ).finally(() => {
+                    this.counterReduce()
+                })
+            } catch {
+                this.$Message.warning(`${file.name}${this.$t('updModal.formatWarning')}`)
+                this.counterReduce()
+            }
+
+        }
+    },
+    created() {
+        let route = this.$route
+        //没有接收 scope,则通过内容模块逻辑,路由判断
+        if (!this.scope) {
+            if (route.name == 'personalcontent') {
+                this.routerScope = 'private'
+            } else {
+                this.routerScope = 'school'
+            }
+        }
+        // 使用组件接收的值
+        else {
+            this.routerScope = this.scope
+        }
+        this.containerClient = BlobTool.CreateBlobTool(this.routerScope)
+    },
+    watch: {
+        // pdId: {
+        //     handler(n, o) {
+        //         this.tags.period = this.pdId
+        //     },
+        //     immediate: true
+        // }
+        '$store.state.user.curPeriod': {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                if (n) {
+                    this.tags.period = n.id
+                }
+            }
+        }
+    }
+}
+
+</script>
+<style scoped>
+.exist-action {
+    font-size: 12px;
+    padding: 1px 15px;
+    /* margin-left: 10px; */
+    /* background: #ed4014; */
+    background: #f0f0f0;
+    cursor: pointer;
+    border: 1px solid #ed4014;
+}
+.exist-action:hover {
+    background: white;
+}
+
+.exist-action-last {
+    margin-left: -1px;
+}
+.exist-tag {
+    color: #ffffff;
+    font-size: 12px;
+    padding: 2px 5px;
+    margin-left: 10px;
+    background: #ed4014;
+}
+.upd-to-pd {
+    margin-bottom: 10px;
+}
+.upload-text {
+    margin: 10px 0px;
+    font-size: 24px;
+}
+.upload-icon {
+    font-size: 50px;
+    margin-top: 60px;
+}
+.upload-file-item {
+    color: var(--primary-text-color);
+    margin-top: 5px;
+    padding: 5px 2px 0px 5px;
+    display: flex;
+}
+.upload-item-left {
+    width: calc(100% - 25px);
+}
+.upload-item-right {
+    width: 25px;
+}
+.upload-info-wrap {
+    float: right;
+    margin-left: 10px;
+    vertical-align: middle;
+    /* color: white; */
+    line-height: 30px;
+}
+
+.upload-file-item:hover {
+    /* color: white; */
+    background: var(--hover-text-color);
+    border-radius: 5px;
+}
+
+.upload-file-item:hover .delete-btn {
+    display: inline-block;
+}
+.upload-loading-icon {
+    vertical-align: middle;
+}
+.upload-file-name {
+    display: inline-block;
+    /* color: #eeeeee; */
+    line-height: 30px;
+}
+
+.delete-btn {
+    display: none;
+    cursor: pointer;
+}
+.file-type-tag {
+    border: 1px solid;
+    /*border-color:white;*/
+    border-radius: 2px;
+    padding: 0px 4px;
+    margin-right: 5px;
+    font-size: 12px;
+}
+.parse-label {
+    cursor: pointer;
+    font-size: 12px;
+    user-select: none;
+}
+
+.is-parse-htex {
+    vertical-align: baseline;
+}
+</style>
+<style>
+.parse-label .ivu-checkbox-inner {
+    background: transparent;
+    width: 14px;
+    height: 14px;
+    border-color: #909090;
+}
+.parse-label .ivu-checkbox-checked .ivu-checkbox-inner:after {
+    content: "";
+    display: block;
+    width: 8px;
+    height: 13px;
+    border-right: 2px solid #fff;
+    border-bottom: 2px solid #fff;
+    transform: rotate(35deg);
+    position: absolute;
+    top: -4px;
+    left: 3px;
+    border-radius: 0;
+    background-color: initial;
+    border-color: #0094ff;
+}
+
+.upload-item-left .ivu-progress-inner {
+    background: #d8d8d8;
+}
+</style>

+ 517 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/index.less

@@ -0,0 +1,517 @@
+@first-bgColor: #141414;
+@second-bgColor: #1b1b1b;
+@third-bgColor: #222222;
+@borderColor: var(--border-color);
+@primary-textColor: var(--primary-text-color); //�ı�����ɫ
+@second-textColor: #a5a5a5; //�ı�������ɫ
+@primary-fontSize: 14px;
+@second-fontSize: 15px;
+
+.teach-content {
+    width: 100%;
+    height: 100%;
+
+    &-top {
+        width: 100%;
+        height: 50px;
+        border-bottom: 1px solid @borderColor;
+        padding: 0px 15px;
+    }
+
+    &-main {
+        width: 100%;
+    }
+}
+.content-file-filter {
+    padding: 0px 15px;
+    box-shadow: 0 2px 5px #e9e9e9;
+    margin-bottom: 5px;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    row-gap: 6px;
+
+    // border-bottom: 1px solid @borderColor;
+
+    span {
+        // color: white;
+    }
+
+    .tab-active {
+        color: @primary-textColor;
+        font-size: 15px;
+        font-weight: 800;
+    }
+
+    .tab-active::before {
+        transition: transform 0.3s ease-in-out;
+        transform: scaleX(1);
+        transform-origin: bottom left;
+    }
+}
+.key-word-search {
+    width: 240px;
+    margin-right: 20px;
+}
+.teach-content-top {
+    .upload-box {
+        display: inline-block;
+        text-align: right;
+        padding-right: 25px;
+        float: right;
+        color: white;
+        line-height: 50px;
+        cursor: pointer;
+
+        &:hover {
+            color: aqua;
+        }
+
+        span {
+            margin-left: 10px;
+        }
+    }
+}
+
+.teach-content-main {
+    /*height: ~"calc(100% - 50px)";*/
+    height: 100%;
+    display: flex;
+
+    .content-type-list {
+        width: 120px;
+        border-right: 1px solid @borderColor;
+        height: 100%;
+        position: relative;
+
+        .space-box {
+            width: 95%;
+            margin: auto;
+            margin-top: 20px;
+            padding-left: 10px;
+            p {
+                color: @primary-textColor;
+                margin-top: 5px;
+                font-size: 12px;
+            }
+
+            .percent-box {
+                width: 100%;
+                height: 10px;
+                border-radius: 5px;
+                background: #e4e4e4;
+                // border: 1px solid #757575;
+                overflow: hidden;
+
+                .percent-item-span {
+                    height: 10px;
+                    vertical-align: super;
+                    display: inline-block;
+                }
+            }
+
+            .percent-detail-box {
+                width: 100%;
+
+                .text-dot {
+                    width: 8px;
+                    height: 8px;
+                    border-radius: 50%;
+                    display: inline-block;
+                    margin-right: 10px;
+                }
+
+                .text-type {
+                }
+
+                .text-size {
+                    float: right;
+                    margin-right: 5px;
+                }
+            }
+
+            .storage-full {
+                background-color: red;
+            }
+
+            .storage-data {
+                background-color: #F8C006;
+            }
+
+            .storage-image {
+                background-color: #45C84A;
+            }
+
+            .storage-video {
+                background-color: #8E2BDD;
+            }
+
+            .storage-audio {
+                background-color: #E1027B;
+            }
+
+            .storage-doc {
+                background-color: #03C0C2;
+            }
+
+            .storage-res {
+                background-color: #1FFCC5;
+            }
+
+            .storage-other {
+                background-color: #E87B22;
+            }
+        }
+
+        .content-type-item {
+            width: 100%;
+            height: 80px;
+            line-height: 80px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            // border-bottom: 1px solid @borderColor;
+            cursor: pointer;
+            z-index: 1;
+            position: relative;
+            // color: white;
+            outline: none;
+
+            &:hover{
+                background-color: var(--hover-text-color);
+            }
+
+            &::before {
+                content: '';
+                z-index: -1;
+                position: absolute;
+                top: 0;
+                bottom: 0;
+                left: 0;
+                right: 0;
+                background-color: var(--active-item-start);
+                transform-origin: center right;
+                transform: scaleX(0);
+            }
+
+            span {
+                color: @primary-textColor;
+                font-size: 16px;
+                font-weight: 500;
+                /*letter-spacing: 5px;*/
+                margin-left: 0.5rem;
+            }
+        }
+
+        .content-type-item-active::before {
+            transform-origin: center left;
+            transform: scaleX(1);
+            transition: transform 0.15s ease-in;
+        }
+    }
+
+    .content-file-list-box {
+        width: ~'calc(100% - 120px)';
+        height: 100%;
+        position: relative;
+
+
+        .content-file-list {
+            height: ~"calc(100dvh - 50px)";
+            padding: 15px;
+            overflow-y: auto;
+            // background-image: linear-gradient(to bottom right, #282828, #505050);
+
+            &::-webkit-scrollbar { /*������������ʽ*/
+                width: 10px; /*�߿��ֱ��Ӧ�����������ijߴ�*/
+                height: 1px;
+            }
+
+            &::-webkit-scrollbar-thumb { /*��������������*/
+                -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
+                background: rgb(124,124,124);
+            }
+
+            &::-webkit-scrollbar-track { /*������������*/
+                -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
+                background: rgba(68,68,68,.5);
+            }
+        }
+    }
+}
+
+.list-item {
+    cursor: pointer;
+    width: 100%;
+    height: 140px;
+    border: 1px solid @borderColor;
+    background: #505050;
+    margin-bottom: 15px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    padding: 15px 30px;
+    position: relative;
+    border-radius: 5px;
+
+    &:hover {
+        box-shadow: 0 26px 40px -24px rgb(0,0,0);
+        transform: translateY(-6px);
+        transition: all .2s ease 0s;
+        background: #606060;
+        z-index: 999;
+    }
+
+    &:hover .file-info p {
+        color: aqua;
+    }
+
+    &:hover .file-info .file-detail-info span {
+        color: white;
+    }
+
+    .file-info {
+        margin-left: 35px;
+
+        p {
+            font-size: 24px;
+            color: white;
+        }
+
+        .file-detail-info {
+            margin-top: 10px;
+
+            span {
+                color: #DDDDDD;
+                font-size: 16px;
+                margin-right: 30px;
+            }
+        }
+    }
+
+    .file-action {
+        position: absolute;
+        right: 50px;
+    }
+}
+
+.card-item {
+    width: 285px;
+    height: fit-content;
+    max-height: 200px;
+    overflow: hidden;
+    position: relative;
+    margin: auto;
+    margin-top: 15px;
+    display: inline-block;
+    margin: 10px 12px;
+    border-radius: 5px;
+
+    &:hover::before {
+        transition: background .5s;
+    }
+
+    &:hover {
+        transform: translateY(-6px);
+        transition: all .2s ease 0s;
+    }
+
+    &::before {
+        content: "";
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        left: 0px;
+        top: 0px;
+        display: block;
+    }
+}
+.file-name {
+    font-size: 14px;
+    color: #eeeeee;
+    height: fit-content;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    overflow: hidden;
+    padding: 0px 5px;
+    text-align: left;
+    word-break: break-all;
+    position: absolute;
+    width: 100%;
+    z-index: 9999;
+}
+.img-mask {
+    position: absolute;
+    bottom: 7px;
+    left: 7px;
+    right: 7px;
+    top: 7px;
+    opacity: 0;
+    background: rgba(20, 20, 20, 0.6);
+}
+
+.img-box {
+    .item-tools {
+        position: absolute;
+        bottom: 0px;
+        left: 0px;
+        right: 0px;
+        position: absolute;
+        z-index: 9999;
+        padding-left: 10px;
+        padding: 4px;
+        text-align: left;
+    }
+
+    &:hover .img-mask {
+        opacity: 1;
+        transition: opacity 0.2s;
+    }
+
+    &:hover .item-tools {
+        opacity: 1;
+        transition: opacity 0.2s;
+    }
+}
+.item-tools{
+    opacity:0;
+}
+.btn-dropdown-menu {
+    display: inline-block;
+    height: 40px;
+    width: 40px;
+    margin-left: 12px;
+    vertical-align: middle;
+    line-height: 40px;
+    background-color: #AAAAAA;
+    border-radius: 0px 4px 4px 0px;
+
+    &:hover {
+        background: #999999;
+    }
+}
+
+.file-show-type {
+    float: right;
+    margin-top: 13px;
+    margin-right: 30px;
+}
+
+.file-icon {
+    display: inline-block;
+    // vertical-align: sub;
+    img {
+        display: inline-block;
+        margin-right: 10px;
+        vertical-align: text-top;
+        border-radius: 2px;
+    }
+}
+.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;
+        z-index: 9999;
+    }
+}
+.card-box {
+    width: 100%;
+    height: 100%;
+}
+
+.page-wrap {
+    width: 100%;
+    text-align: center;
+    padding-top: 10px;
+    padding-bottom: 10px;
+}
+
+.toggle-btn-icon {
+    font-size: 16px;
+}
+
+.action-btn-wrap {
+    margin-right: 30px;
+    cursor: pointer;
+    // color: white;
+    user-select: none;
+    color: #40A8F0;
+}
+
+.action-btn-wrap-disabled {
+    cursor: not-allowed;
+    color: @second-textColor !important;
+
+    &:hover {
+        color: @second-textColor !important;
+    }
+}
+.binding-point{
+    color:white;
+}
+
+.rename-action-icon{
+    display: inline-block;
+    margin-left: 10px;
+    cursor: pointer;
+}
+
+.pd-filter-wrap{
+    color: #a5a5a5;
+    float: left;
+    margin-left: 10px;
+    margin-top: 15px;
+}
+.content-filter-wrap{
+    display: flex;
+    margin-right: auto;
+}
+.table-file-name{
+    text-overflow:ellipsis; 
+    overflow:hidden; 
+    white-space:nowrap; 
+    width: ~"calc(100% - 30px)";
+    display: inline-block;
+    vertical-align: top;
+}
+.text-ellipsis{
+    text-overflow:ellipsis; 
+    overflow:hidden; 
+    white-space:nowrap; 
+    width: 100%;
+    display: inline-block;
+}
+.video-format-tips{
+    color: #ff9900 !important;
+    font-size: 12px;
+    line-height: 24px;
+}
+.space-tips{
+    margin-left:5px;
+    margin-top: -5px;
+    color:#1cc0f3;
+    cursor: pointer;
+}

File diff suppressed because it is too large
+ 1475 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/content/index.vue


+ 0 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ExerciseList.less


File diff suppressed because it is too large
+ 1014 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ExerciseList.vue


+ 246 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/PaperDownload.less

@@ -0,0 +1,246 @@
+.dp-container {
+	// font-family: '微软雅黑';
+	position: relative;
+	display: flex;
+
+	.back-to-top {
+		position: fixed;
+		right: 30px;
+		bottom: 20px;
+		height: 58px;
+		width: 60px;
+		background: #2d8cf0;
+		color: #fff;
+		z-index: 99999;
+		border-radius: 50%;
+		cursor: pointer;
+	}
+
+	.dp-left {
+		width: calc(100% - 300px);
+	}
+
+	.dp-right {
+		height: 100%;
+		width: 300px;
+		background-color: #fff;
+		position: fixed;
+		right: 0;
+		top: 0;
+		display: flex;
+		flex-direction: column;
+		padding: 70px 20px 20px 20px;
+		box-shadow: 0 0 5px #c4c4c4;
+		z-index: 2;
+
+		.ivu-btn {
+			width: 100%;
+			margin-top: 10px;
+			height: 36px;
+		}
+
+		&-title {
+			font-size: 14px;
+			font-weight: bold;
+			margin: 15px 5px;
+
+			&::before {
+				content: "";
+				width: 5px;
+				height: 10px;
+				background-color: #2181ff;
+				display: inline-block;
+				margin-right: 10px;
+			}
+		}
+
+		.ivu-checkbox-wrapper {
+			margin: 10px;
+		}
+
+	}
+}
+
+.download-paper-wrap {
+	position: relative;
+	width: 826px;
+	margin: 20px auto;
+	display: flex;
+	flex-direction: column;
+	color: #000;
+
+	.base-bg {
+		position: absolute;
+		background: #fff;
+		z-index: 0;
+
+		&-page {
+			position: relative;
+			width: 826px;
+			height: 1170px;
+			// border: 1px solid red;
+
+			.dp-line {
+				position: absolute;
+				top: 40%;
+				left: -40%;
+				margin: 20px 0 0 20px;
+				transform: rotate(-90deg);
+				border-bottom: 1px dashed #a6a6a6;
+
+				.line {
+					display: inline-block;
+					width: 125px;
+					margin: 0 5px;
+					border-bottom: 1px solid;
+				}
+
+				.line-bottom {
+					font-size: 12px;
+					margin: 10px auto;
+					display: inline-block;
+					text-align: center;
+					width: 100%;
+				}
+			}
+
+			.page-num {
+				position: absolute;
+				bottom: 20px;
+				width: 100%;
+				font-size: 12px;
+				text-align: center;
+			}
+		}
+	}
+
+	.dp-top {
+		margin-top: 20px;
+		z-index: 1;
+
+		&-title {
+			text-align: center;
+			font-size: 24px;
+			font-weight: bold;
+		}
+
+		&-desc {
+			text-align: center;
+			padding: 10px 0;
+			color: #787878;
+
+			&:hover {
+				background: rgba(66, 159, 255, 0.6);
+				color: #fff;
+				cursor: pointer;
+			}
+		}
+
+		&-tip {
+			margin: 20px 0 10px 120px;
+			padding: 10px;
+			color: #949494;
+
+			&:hover {
+				background: rgba(66, 159, 255, 0.6);
+				color: #fff;
+				cursor: pointer;
+			}
+		}
+	}
+
+	.secret-logo {
+		position: absolute;
+		left: 20px;
+		top: 20px;
+		font-weight: bold;
+		z-index: 1;
+	}
+
+
+
+	.dp-page-content {
+		display: flex;
+	}
+
+	.dp-content {
+		margin: 0 50px 0 110px;
+
+
+
+		.dp-group-title {
+			position: relative;
+			z-index: 1;
+			font-size: 16px;
+			font-weight: bold;
+			padding: 20px 0;
+			z-index: 1;
+
+			&:hover {
+				background: rgba(66, 159, 255, 0.6) !important;
+				color: #fff;
+				cursor: pointer;
+			}
+		}
+
+		.dp-item {
+			position: relative;
+			display: flex;
+			flex-direction: column;
+			font-size: 14px;
+			margin-top: 10px;
+			margin-left: 10px;
+			// border: 1px solid #FF0000;
+
+
+
+			.props {
+				display: none;
+				position: absolute;
+				right: 20px;
+				bottom: 20px;
+				color: #FF0000;
+				font-weight: bold;
+			}
+
+			&-question {
+				flex: 1;
+				padding-left: 10px;
+
+				span {
+					display: inline-block;
+				}
+			}
+
+			&-children {
+				margin-left: 20px;
+
+				.child-item {
+					margin: 20px 0;
+				}
+			}
+
+			&-explain {
+				display: flex;
+				padding: 5px 0;
+				color: #149ede;
+
+				.title {
+					min-width: 60px;
+				}
+			}
+		}
+
+		.dp-item-options {
+
+			.item-option-order {
+				min-width: 25px;
+			}
+
+			.item-option-content {
+				display: flex;
+				margin: 10px 0;
+			}
+		}
+	}
+}

+ 606 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/PaperDownload.vue

@@ -0,0 +1,606 @@
+<template>
+  <div class="dp-container" ref="dpContainer">
+    <div class="back-to-top flex-col-center" :title="$t('evaluation.backToTop')" @click="onBackToTop">
+      <Icon type="ios-arrow-up" size="24" color="#fff" />
+      <span style="font-size: 12px;">{{ curPage }} / {{ pageArr.length }}</span>
+    </div>
+    <div class="dp-left">
+      <div class="download-paper-wrap" ref="childRef" id="paperDom">
+        <span class="secret-logo" v-show="settingArr[3]">{{ $t('answerSheet.dp.secret') }}</span>
+        <div class="dp-top">
+          <p class="dp-top-title">{{ paperItem.name }}</p>
+          <p class="dp-top-desc" v-show="settingArr[1]" v-html="paperItem.description" :title="$t('answerSheet.dp.editTip')" @click.capture="doEdit(paperItem.description,'desc')"></p>
+          <p class="dp-top-tip" v-html="paperItem.tip" v-show="settingArr[2]" :title="$t('answerSheet.dp.editTip')" @click.capture="doEdit(paperItem.tip,'tip')"></p>
+        </div>
+        <div class="base-bg">
+          <div class="base-bg-page" v-for="(page,pageIndex) in pageArr" :key="pageIndex" :style="{ borderBottom: isDownloading ? 'none' : '1px solid #ccc' }">
+            <div class="dp-line" v-show="settingArr[0] && (printMode === 'A3' ? pageIndex % 2 === 0 : true)">
+              <!-- <img :src="getPealLineImg"> -->
+              <!-- <BaseSealLine></BaseSealLine> -->
+              <p class="line-top">{{ $t('answerSheet.dp.school') }}:<span class="line"></span> {{ $t('answerSheet.dp.className') }}:<span class="line"></span> {{ $t('answerSheet.dp.name') }}:<span class="line"></span> {{ $t('answerSheet.dp.id') }}:<span class="line"></span></p>
+              <p class="line-bottom">{{ $t('answerSheet.dp.lineTip') }}</p>
+            </div>
+          </div>
+        </div>
+        <div class="dp-page-content">
+          <div class="dp-content">
+            <div class="dp-group-item" v-for="(gItem,gIndex) in groupList">
+              <!-- 渲染大题类型 -->
+              <p v-show="gItem.list.length" class="dp-group-title split-dom" :title="$t('answerSheet.dp.editTip')" @click="doEdit($event,'title')">
+                <span>{{ $tools.getChineseByNum(gIndex + 1) }} . &nbsp;
+                  {{ exersicesType[gItem.type] }}</span>
+                <span style="font-size: 14px;">({{ gItem.score || 0 }}{{$t('evaluation.paperList.score')}})
+                </span>
+              </p>
+              <div class="dp-item" v-for="(item,index) in gItem.list">
+                <span class="props">{{ propsArr[getFlatIndex(item.id)] }}</span>
+                <!-- 渲染题干 -->
+                <div style="display: flex;" class="split-dom">
+                  <span class="dp-item-order">{{ getPaperItemIndex(item.id) }}. </span>
+                  <span class="dp-item-question" v-html="filterTags(item.question)"></span>
+                </div>
+                <!-- 选项部分 -->
+                <div class="dp-item-options split-dom" v-if="item.option.length">
+                  <div v-for="(option,optionIndex) in item.option" :key="optionIndex" class="dp-item-options">
+                    <div class="item-option-content">
+                      <div class="item-option-order">
+                        {{String.fromCharCode(64 + parseInt(optionIndex+1))}}
+                        .
+                        &nbsp;
+                      </div>
+                      <div class="item-option-text" v-html="option.value"></div>
+                    </div>
+                  </div>
+                </div>
+                <!-- 答案部分 -->
+                <div class="dp-item-explain split-dom" v-if="item.type !== 'compose' && settingArr[4]">
+                  <span class="title">【{{ $t('evaluation.answer') }}】</span>
+                  <!-- 问答题答案 -->
+                  <div v-if="item.type === 'subjective' || item.type === 'complete' || item.type === 'connector' || item.type === 'correct'">
+                    <span v-for="(answer,index) in item.answer" :key="index" v-html="item.answer.length ? answer : $t('utils.noData')"></span>
+                  </div>
+                  <!-- 问答题答案 -->
+                  <div v-else-if="item.type === 'judge'">
+                    <span>{{ item.answer.length ? (item.answer[0] === 'A' ? $t('evaluation.isTrue') : $t('evaluation.isFalse')) : $t('evaluation.answer') }}</span>
+                  </div>
+                  <!-- 其余题型答案 -->
+                  <div v-else>
+                    <span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer" :key="index">{{answer}}</span>
+                  </div>
+                </div>
+                <!-- 解析部分 -->
+                <div class="dp-item-explain split-dom" v-if="item.type !== 'compose' && settingArr[4]">
+                  <span class="title">【{{ $t('evaluation.explain') }}】</span>
+                  <span v-html="item.explain"></span>
+                </div>
+                <!-- 渲染综合题小题部分 -->
+                <div class="dp-item-children" v-if="item.children.length">
+                  <div class="dp-item child-item" v-for="(child,childIndex) in item.children">
+                    <span class="props">{{ propsArr[getFlatIndex(child.id)] }}</span>
+                    <div style="display: flex;" class="split-dom">
+                      <span class="dp-item-order">({{ childIndex + 1 }})</span>
+                      <p class="dp-item-question" v-html="child.question"></p>
+                    </div>
+                    <div class="split-dom" v-if="child.option && child.option.length">
+                      <!-- 选项部分 -->
+                      <div v-for="(option,optionIndex) in child.option" :key="optionIndex" class="dp-item-options" style="pointer-events:none">
+                        <div class="item-option-content">
+                          <div class="item-option-order">
+                            {{String.fromCharCode(64 + parseInt(optionIndex+1))}}
+                            . &nbsp;
+                          </div>
+                          <div class="item-option-text" v-html="option.value"></div>
+                        </div>
+                      </div>
+                    </div>
+                    <div class="dp-item-explain split-dom" v-if="settingArr[4]">
+                      <span class="title">【{{ $t('evaluation.answer') }}】</span>
+                      <span v-html="child.answer.length ? child.answer[0] : $t('utils.noData')"></span>
+                    </div>
+                    <div class="dp-item-explain split-dom" v-if="settingArr[4]">
+                      <span class="title">【{{ $t('evaluation.explain') }}】</span>
+                      <span v-html="child.explain || $t('utils.noData')"></span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="dp-right">
+      <Button type="primary" @click="goBack">{{ $t('answerSheet.back') }}</Button>
+      <Button type="success" @click="pdfModal = true">{{ $t('answerSheet.dp.download') }}</Button>
+      <div>
+        <p class="dp-right-title">{{ $t('answerSheet.dp.settings') }}</p>
+        <Checkbox v-for="(item,index) in settingOptions" :key="index" v-model="settingArr[index]" @on-change="onSettingChange(index)">&nbsp;{{ item }}</Checkbox>
+      </div>
+    </div>
+
+    <Modal v-model="pdfModal" class-name="add-type-modal pdf-modal" width="650" :title="$t('answerSheet.dp.download')" @on-ok="onDownloadPaper">
+      <div class="pdf-wrap">
+        <div style="display: flex;flex-direction: column;align-items: center;">
+          <div :class="['a4', printMode ==='A4' ? 'mode-active' : '']" @click="printMode ='A4'">A4</div>
+          <p style="margin-top: 10px;">{{ $t('answerSheet.printA4') }}</p>
+          <p style="margin-top: 10px;font-size: 12px;">{{ $t('answerSheet.singleColumn') }} (210mm * 297mm)</p>
+        </div>
+        <div style="display: flex;flex-direction: column;align-items: center;">
+          <div :class="['a3', printMode ==='A3' ? 'mode-active' : '']" @click="printMode ='A3'">A3</div>
+          <p style="margin-top: 10px;">{{ $t('answerSheet.printA3') }}</p>
+          <p style="margin-top: 10px;font-size: 12px;">{{ $t('answerSheet.doubleColumn') }} (420mm * 297mm)</p>
+        </div>
+      </div>
+    </Modal>
+    <!-- 编辑内容弹窗 -->
+    <Modal v-model="editModal" class-name="add-type-modal pdf-modal" width="650" :title="$t('answerSheet.dp.edit')" @on-ok="onEditFinish">
+      <div id="editBox"></div>
+    </Modal>
+  </div>
+</template>
+<script>
+import E from 'wangeditor'
+import domtoimage from '@/utils/dom_to_image.js';
+import JsPDF from 'jspdf'
+export default {
+  name: 'DownloadPaper',
+  props: {
+    paper: {
+      type: Object,
+      default: () => { }
+    },
+  },
+  data(vm) {
+    return {
+      curPage: 1,
+      curEditType: '',
+      curEditDom: null,
+      myEditor: null,
+      editModal: false,
+      pdfModal: false,
+      printMode: 'A4',
+      scrollTop: 0,
+      isDownloading: false,
+      pageArr: [0, 1],
+      stepDomArr: [],
+      propsArr: [],
+      flatItems: [],
+      exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
+      settingOptions: [vm.$t('answerSheet.dp.setting1'), vm.$t('answerSheet.dp.setting2'), vm.$t('answerSheet.dp.setting3'), vm.$t('answerSheet.dp.setting4'), vm.$t('answerSheet.dp.setting5')],
+      settingArr: [true, true, true, true],
+      paperItem: {
+        name: '',
+        description: '',
+        item: [],
+        tip: ``
+      },
+      typeList: ['single', 'multiple', 'judge', 'complete', 'subjective', 'connector', 'correct', 'compose'],
+      groupList: [],
+      fromRouter: ''
+    }
+  },
+  created() {
+    let localPaper = localStorage.getItem('local_paper')
+    let paramsPaper = this.$route.params.paper
+    this.fromRouter = this.$route.params.fromRouter
+    this.paperItem = paramsPaper ? paramsPaper : localPaper ? JSON.parse(localPaper) : null
+    this.paperItem.description = `<p>${this.$t('answerSheet.dp.totalScore')}:${this.paperItem.score} ${this.$t('answerSheet.dp.score')}</p>`;
+    this.paperItem.tip = `<p> ${this.$t('answerSheet.dp.tip1')}:</p>
+					<p>1.${this.$t('answerSheet.dp.tip2')};</p>
+					<p>2.${this.$t('answerSheet.dp.tip3')};</p>`
+    this.groupList = this.doGroupPaper(this.paperItem)
+    localStorage.setItem('local_paper', JSON.stringify(this.paperItem))
+    this.flatItems = this.getFlatItems(this.paperItem.item)
+    this.propsArr = new Array(this.flatItems.length).fill(0)
+  },
+  methods: {
+    /* 返回顶部 */
+    onBackToTop() {
+      this.$refs.dpContainer.scrollIntoView()
+    },
+    /* 点击编辑 */
+    doEdit(content, type) {
+      this.curEditDom = null
+      this.curEditType = type
+      if (type === 'tip') {
+        this.myEditor.txt.html(content)
+      } else if (type === 'desc') {
+        this.myEditor.txt.html(content)
+      } else {
+        let event = content;
+        let clickDom = event.target.localName === 'span' ? event.path[1] : event.target
+        this.curEditDom = clickDom
+        this.myEditor.txt.html(clickDom.innerHTML)
+      }
+      this.editModal = true
+
+    },
+    /* 编辑完成 */
+    onEditFinish() {
+      if (this.curEditType === 'title') {
+        this.curEditDom.innerHTML = this.myEditor.txt.html()
+      } else if (this.curEditType === 'tip') {
+        this.paperItem.tip = this.myEditor.txt.html()
+      } else {
+        this.paperItem.description = this.myEditor.txt.html()
+      }
+      this.doRender()
+    },
+    /* 设置修改 */
+    onSettingChange(val) {
+      if (val !== 3) {
+        this.$nextTick(() => {
+          this.doRender()
+        })
+      }
+    },
+    /* 返回上级 */
+    goBack() {
+      this.$router.back();
+    },
+    /* 拉平所有题目 */
+    getFlatItems(arr) {
+      let flatArr = []
+      arr.forEach(item => {
+        flatArr.push(item)
+        if (item.children.length) {
+          flatArr.push(...item.children)
+        }
+      })
+      return flatArr
+    },
+    imgCompress(dom) {
+      const canvas = document.createElement("canvas")
+      const context = canvas.getContext("2d")
+      let originWidth = dom.width
+      let originHeight = dom.height
+      // define max width
+      let maxWidth = 800
+      let maxHeight = 0
+      if (originWidth > originHeight) {
+        maxHeight = 800 * originWidth / originHeight
+      } else {
+        maxWidth = originWidth
+        maxHeight = originHeight
+      }
+      canvas.width = maxWidth
+      canvas.height = maxHeight
+      context.clearRect(0, 0, maxWidth, maxHeight)
+      context.drawImage(dom, 0, 0, maxWidth, maxHeight)
+      return canvas.toDataURL("image/jpeg", 0.8)
+    },
+    filterTags(html) {
+      return html.replaceAll('<o:p></o:p>', '')
+    },
+    /* 下载试卷 */
+    onDownloadPaper() {
+      let that = this
+      const imageList = document.querySelectorAll("img");
+      for (let i = 0; i < imageList.length; i++) {
+        if (imageList[i].className === 'xkw-math-img') {
+          this.$Message.warning('学科网来源的试卷暂不支持下载!')
+          return
+        }
+      }
+      // 过滤掉 HTML 文档中的 <o:p> 标签 从word文档粘贴的会导致生成失败
+      const opElements = Array.from(document.getElementsByTagName('o:p'))
+      opElements.forEach(function (opElement) {
+        opElement.parentNode.removeChild(opElement);
+      });
+      setTimeout(() => {
+        this.isDownloading = true
+        domtoimage.toJpeg(document.getElementById('paperDom'), {
+          bgcolor: '#fff'
+        })
+          .then((pageData) => {
+            console.error(pageData)
+            // return
+            let img = new Image();
+            img.src = pageData;
+            img.onload = function () {
+              let contentWidth = img.width
+              let contentHeight = img.height
+              let pageHeight = contentWidth / 592.28 * 841.89
+              let leftHeight = contentHeight
+              let position = 0
+              let mode = that.printMode
+              console.log(mode);
+              let PDF = null
+              const a4HeightPx = 1168
+              const a4WidthPx = 826
+              const a4Height = 297
+              const a4Width = 210
+              let imgWidth = a4Width
+              let imgHeight = a4Width / contentWidth * contentHeight
+              let mm2px = that.$tools.getOneMmsPx()
+              let xMarginMM = 15 / mm2px - 0.2
+              let yMarginMM = 40 / mm2px
+
+              /* 分割长图 */
+              function segmentationImage(img, width, height) {
+                let canvas = document.createElement("canvas");
+                canvas.width = width;
+                canvas.height = height;
+                let ctx = canvas.getContext("2d");
+                let imgList = [];
+                let cropCount = Math.ceil(img.height / height);
+                for (let num = 0; num < cropCount; num++) {
+                  ctx.clearRect(0, 0, canvas.width, canvas.height);
+                  ctx.drawImage(img, 0, num * height, width, height, 0, 0, width, height);
+                  imgList.push(canvas.toDataURL());
+                }
+                return imgList
+              }
+              /* 用JSPDF绘制每一页的外框页码和答题卡编号 */
+              function pdfDrawInfo(pageNo) {
+                let curPage = mode === 'A4' ? pageNo : pageNo / 2
+                // 页码二进制框
+                let i = curPage.toString(2)
+
+                PDF.setFont('Times New Roman')
+                if (mode === 'A4') {
+                  // 页码
+                  PDF.text(`${curPage} / ${totalPage}`, a4Width / 2 - 5, a4Height - 4, 'center')
+                } else {
+                  // 页码
+                  PDF.text(`${curPage} / ${totalPage}`, a4Width, a4Height - 4, 'center')
+                }
+
+              }
+              // 获取分割的图片数量
+              let cropCount = that.pageArr.length;
+              if (cropCount === 0) return
+              let totalPage = mode === 'A4' ? cropCount : ((cropCount % 2 === 0) ? (cropCount / 2) : ((cropCount + 1) / 2))
+              if (mode === 'A4') {
+                PDF = new JsPDF({
+                  orientation: 'p',
+                  unit: 'mm',
+                  format: 'a4',
+                  putOnlyUsedFonts: true
+                })
+                PDF.setFont('Times New Roman')
+                PDF.setFontSize(10);
+                let curPage = 1
+                // 如果是一页的情况
+                if (leftHeight <= pageHeight) {
+                  PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth,
+                    imgHeight)
+                  pdfDrawInfo(1)
+                } else {
+                  // 如果超出则多页
+                  while (leftHeight > 0) {
+                    PDF.addImage(pageData, 'JPEG', 0, position,
+                      imgWidth, imgHeight)
+                    leftHeight -= pageHeight
+                    position -= a4Height
+                    pdfDrawInfo(curPage)
+                    if (leftHeight > 0) {
+                      PDF.addPage()
+                    }
+                    curPage++
+                  }
+                }
+              } else {
+                /* 渲染A3纸张 */
+                PDF = new JsPDF({
+                  orientation: 'l',
+                  unit: 'mm',
+                  format: 'a3',
+                  putOnlyUsedFonts: true
+                })
+                PDF.setFont('Times New Roman')
+                PDF.setFontSize(10);
+                let pageImgArr = segmentationImage(img, a4WidthPx, a4HeightPx);
+                let curPage = 1
+                // 如果是一页的情况
+                if (leftHeight <= pageHeight) {
+                  PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
+                  pdfDrawInfo(2)
+                } else {
+                  // 如果超出则多页
+                  while (leftHeight > 0) {
+                    if (curPage % 2 === 0) {
+                      PDF.addImage(pageImgArr[curPage - 1], 'JPEG', a4Width, 0, a4Width, a4Height)
+                      leftHeight -= pageHeight
+                      pdfDrawInfo(curPage)
+                      if (leftHeight > 0) {
+                        PDF.addPage()
+                      }
+                    } else {
+                      PDF.addImage(pageImgArr[curPage - 1], 'JPEG', 0, 0, a4Width, a4Height)
+                      leftHeight -= pageHeight
+                      if (leftHeight <= 0) {
+                        pdfDrawInfo(totalPage * 2)
+                      }
+                    }
+                    curPage++
+                  }
+                }
+              }
+              PDF.save(that.paperItem.name + '.pdf')
+              that.isDownloading = false
+              that.printMode = 'A4'
+            }
+          }).catch(err => {
+            this.$Message.warning('试卷数据异常,无法生成!')
+            console.log(err)
+            this.isDownloading = false
+          }).catch(err => {
+            console.error(err)
+          })
+      }, 500)
+    },
+    /* 对试卷题目进行题型分组 */
+    doGroupPaper(paper) {
+      let that = this
+      let groupList = []
+      if (!this.typeList.length) return
+      this.typeList.forEach(item => {
+        this._.mapKeys(this._.groupBy(paper.item, 'type'), function (value, key) {
+          if (key === item) {
+            /* 按照题型排序,并且计算每种题型的总分 */
+            groupList.push({
+              type: key,
+              list: value,
+              score: value.reduce((p, e) => parseInt(p) + parseInt(e.score), 0)
+            })
+          }
+        })
+      });
+      return groupList
+    },
+    /* 进行跨页渲染 */
+    doRender() {
+      // console.log(this.scrollTop)
+      setTimeout(() => {
+        // 获取到所有的splitDom
+        let splitDomArr = Array.from(document.getElementsByClassName('split-dom'))
+        let curPage = 0
+        let extraMarTop = 0
+        let stepDomArr = []
+        // 先清空之前的Margin
+        this.stepDomArr.forEach(item => {
+          item.dom.style.marginTop = '0px'
+        })
+        // 置空跨页DOM
+        this.stepDomArr = []
+        for (let i = 0; i < splitDomArr.length; i++) {
+          let dom = splitDomArr[i]
+          // 单页面高度
+          let pageHeight = 1168
+          // 每个页面的边距
+          let pageMargin = 50
+          // 计算当前DOM距离当前页面的Y值 70是减去header+margin距离 再减去已经超出的页面距离 再加上跨页造成的下移距离
+          let domY = (dom.getBoundingClientRect().top + this.scrollTop - 70) - (pageHeight * curPage) + extraMarTop
+          let domH = dom.getBoundingClientRect().height
+          dom.style.marginTop = '0px'
+          dom.style.backgroundColor = 'transparent'
+          // 如果当前页面不足以渲染当前DOM 则需要进行跨页处理
+          if (domY + domH > pageHeight - pageMargin) {
+            // 需要跨页的DOM 设置marginTop 为单页剩余Y 再加上 第二页的 margin 30
+            let marginTop = pageHeight - domY + pageMargin
+            // dom.style.backgroundColor = '#1f4fdd'
+            // 将需要跨页的DOM及需要下移的距离存起来
+            this.stepDomArr.push({
+              dom: dom,
+              top: marginTop
+            })
+            extraMarTop = extraMarTop + marginTop
+            curPage = curPage + 1
+          }
+        }
+        // 更新页数
+        this.pageArr = new Array(this.stepDomArr.length + 1).fill(0)
+        // 针对需要跨页的DOM进行样式修改
+        this.stepDomArr.forEach(item => {
+          item.dom.style.marginTop = item.top + 'px'
+        })
+      }, 200)
+    }
+  },
+  mounted() {
+    this.doRender()
+    /* 监听当前页面滚动距离 */
+    this.$EventBus.$off('evScroll')
+    this.$EventBus.$on('evScroll', val => {
+      this.scrollTop = val
+      this.curPage = parseInt(val / 1168) + 1
+    })
+
+    const editor = new E('#editBox')
+    editor.config.height = 300
+    // 配置菜单栏,删减菜单,调整顺序
+    editor.config.menus = [
+      'head',
+      'bold',
+      'fontSize',
+      'fontName',
+      'italic',
+      'underline',
+      'strikeThrough',
+      'undo',
+      'redo',
+    ]
+    this.$editorTools.initEditorLang(editor)
+    editor.create()
+    this.myEditor = editor
+  },
+  computed: {
+    getFlatIndex() {
+      return id => {
+        return this.flatItems.findIndex(i => i.id === id) + 1
+      }
+    },
+    getPaperItemIndex() {
+      return id => {
+        let itemIndex = this.paperItem.item.findIndex(i => i.id === id)
+        return itemIndex > -1 ? (itemIndex + 1) : ''
+      }
+    },
+    getPealLineImg() {
+      let lang = localStorage.getItem('local')
+      return lang === 'zh-cn' ? require('@/assets/image/peal_line.png') : require('@/assets/image/peal_line_tw.png')
+    }
+  },
+  watch: {
+    isDownloading(n) {
+      if (n) {
+        let that = this
+        this.$Spin.show({
+          render: (h) => {
+            return h('div', [
+              h('Icon', {
+                'class': 'demo-spin-icon-load',
+                props: {
+                  type: 'ios-loading',
+                  size: 18
+                }
+              }),
+              h('div', that.$t('answerSheet.dp.loading'))
+            ])
+          }
+        });
+      } else {
+        this.$Spin.hide()
+      }
+    },
+  }
+}
+</script>
+
+<style lang="less" scoped>
+@import "./PaperDownload.less";
+</style>
+<style lang="less">
+.dp-item img {
+  max-width: 90% !important;
+}
+
+.pdf-modal {
+  .pdf-wrap {
+    display: flex;
+    align-items: center;
+    justify-content: space-around;
+
+    .a4 {
+      padding: 60px 40px;
+      border: 2px solid #c0c0c0;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+
+    .a3 {
+      padding: 60px 80px;
+      border: 2px solid #c0c0c0;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+
+    .mode-active {
+      background: #53c6ff;
+      color: #fff;
+      font-size: 14px;
+    }
+  }
+}
+</style>

+ 99 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ShareCenter.less

@@ -0,0 +1,99 @@
+.share-center-container{
+    width: 100%;
+    height: 100%;
+}
+.share-center-tab-wrap{
+    height: 45px;
+    width: 100%;
+    line-height: 45px;
+    padding-left: 20px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    background-color: white;
+	position: sticky;
+	top:0;
+	z-index: 99;
+	
+	
+	.share-center-tab{
+	    display: inline-block;
+	    line-height: 26px;
+	    cursor: pointer;
+	    color: var(--second-text-color);
+	    margin: 0px 30px 0px 0px;
+	    user-select: none;
+	    font-size: 14px;
+	}
+	.share-center-tab-active{
+	    border-bottom: 2px solid var(--tabs-bottom-color);
+	    color: var(--tabs-text-color);
+	    font-weight: 600;
+	}
+	.action-wrap{
+	    float: right;
+	    margin-right: 20px;
+	    user-select: none;
+	}
+	.action-item-btn{
+	    cursor: pointer;
+	}
+}
+
+
+.share-content{
+	display: flex;
+	justify-content: space-between;
+	margin-top: 20px;
+	&-left{
+		width: 76%;
+	}
+	
+	&-right{
+		width: 23%;
+		background-color: #fff;
+		padding: 10px 20px 50px 20px;
+		height: calc(100% - 265px);
+		position: sticky;
+		top: 65px;
+		.title{
+			font-size: 18px;
+			font-weight: bold;
+		}
+		
+		.selected-wrap{
+			margin-top: 20px;
+			.selected-list{
+				height: 200px;
+				overflow: auto;
+				.selected-item{
+					margin: 3px 0;
+					padding: 10px;
+					border-radius: 4px;
+					background: #e6f7ff;
+					border-color: #91d5ff;
+					color: #579aff;
+				}
+			}
+		}
+		
+		.select-item{
+			margin: 20px 0;
+			
+			.select-item-title{
+				font-size: 14px;
+				margin: 5px 2px;
+			}
+			
+			.result-text{
+				margin-top: 10px;
+				font-size: 18px;
+				font-weight: bold;
+				color: var(--tabs-bottom-color);
+			}
+		}
+		
+		.ivu-btn{
+			width: 100%;
+			height: 40px;
+		}
+	}
+}

+ 496 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/ShareCenter.vue

@@ -0,0 +1,496 @@
+<template>
+	<div class="share-center-container">
+		<div class="share-center-tab-wrap">
+			<span @click="tabClick('exercise')"
+				:class="['share-center-tab',tabName == 'exercise' ? 'share-center-tab-active':'']">
+				{{ $t('evaluation.share.shareItem') }}
+			</span>
+			<span @click="tabClick('paper')"
+				:class="['share-center-tab',tabName == 'paper' ? 'share-center-tab-active':'']">
+				{{ $t('evaluation.share.sharePaper') }}
+			</span>
+			<div class="action-wrap">
+				<span class="action-item-btn" @click="goBack">
+					<Icon type="md-arrow-back" size="16" />
+					{{ $t('evaluation.share.back') }}
+				</span>
+			</div>
+		</div>
+		<div class="share-content">
+			<div class="share-content-left">
+				<vuescroll>
+					<ExerciseList ref="exListRef" isComponent @chooseQuChange="chooseQuChange"
+						v-if="tabName === 'exercise'">
+					</ExerciseList>
+					<BasePaperList ref="paperListRef" isComponent chooseModel @onPaperChange="onPaperChange" v-else></BasePaperList>
+				</vuescroll>
+			</div>
+			<div class="share-content-right">
+				<p class="title" v-if="tabName === 'exercise'">{{ $t('evaluation.share.checkItem') }}({{ selectedItems.length }})</p>
+				<p class="title" v-if="tabName === 'paper'">{{ $t('evaluation.share.checkPaper') }}({{ selectedPapers.length }})</p>
+				<div class="selected-wrap" v-if="tabName === 'exercise'">
+					<EmptyData v-if="!selectedItems.length"></EmptyData>
+					<div class="selected-list" v-else>
+						<div class="selected-item text-cut" v-for="(item,index) in selectedItems" :key="index">
+							{{ getSimpleText(item.question) }}</div>
+					</div>
+				</div>
+				<div class="selected-wrap" v-if="tabName === 'paper'">
+					<EmptyData v-if="!selectedPapers.length"></EmptyData>
+					<div class="selected-list" v-else>
+						<div class="selected-item text-cut" v-for="(item,index) in selectedPapers" :key="index">
+							{{ item.name }}</div>
+					</div>
+				</div>
+				<p class="title" style="margin-top: 40px;">{{ $t('evaluation.share.shareTo') }}</p>
+				<div class="select-item">
+					<p class="select-item-title">{{ $t('evaluation.filter.period') }}</p>
+					<Select v-model="sharePeriodId" @on-change="onPeriodChange">
+						<Option v-for="item in periodList" :value="item.id" :key="item.id">{{ item.name }}</Option>
+					</Select>
+				</div>
+				<div class="select-item">
+					<p class="select-item-title">{{ $t('evaluation.filter.subject') }}</p>
+					<Select v-model="shareSubjectId">
+						<Option v-for="item in subjectList" :value="item.id" :key="item.id">{{ item.name }}</Option>
+					</Select>
+				</div>
+				<div class="select-item">
+					<p class="select-item-title">{{ $t('evaluation.filter.grade') }}</p>
+					<Select v-model="shareGradeIds" multiple
+						:placeholder="$t('evaluation.newExercise.gradePlaceholder')">
+						<Option v-for="(item,index) in gradeList" :value="index" :key="index">{{ item }}</Option>
+					</Select>
+				</div>
+				<Checkbox v-model="isSavePoints" class="select-item">
+					{{ $t('evaluation.syncPoints') }}
+				</Checkbox>
+				<Button type="info" :loading="btnLoading" :disabled="btnDisable" @click="doShare">
+					{{ $t('evaluation.share.confirmShare') }}
+				</Button>
+			</div>
+			<!-- 已存在弹窗 -->
+			<Modal v-model="isShowExist" width="800px" :title="$t('evaluation.share.existTip')">
+				<p>{{ $t('evaluation.share.existTip1') }} <span style="color: #FF0000;font-weight: bold;">{{ $t('evaluation.share.existTip2') }}</span> </p>
+				<div style="margin: 10px 0;">
+					<Tag color="blue" size="medium" v-for="(item,index) in existList" :key="index">{{ tabName === 'paper' ? item.name : getSimpleText(item.question) }}</Tag>
+				</div>
+				<p>{{ $t('evaluation.share.existTip3') }}</p>
+				<div slot="footer">
+					<Button @click="onIgnoreExist">{{ $t('evaluation.share.ignore') }}</Button>
+					<Button type="success" @click="onConfirmCover" :loading="coverLoading">{{ $t('evaluation.share.confirmCover') }}</Button>
+				</div>
+			</Modal>
+		</div>
+	</div>
+</template>
+<script>
+	import ExerciseList from '@/view/evaluation/bank/ExerciseList.vue'
+	import BasePaperList from '@/view/evaluation/bank/TestPaperList.vue'
+	import blobTool from "@/utils/blobTool.js";
+	export default {
+		components: {
+			ExerciseList,
+			BasePaperList,
+		},
+		data() {
+			return {
+				coverLoading:false,
+				isShowExist:false,
+				schoolBlobClient:null,
+				privateBlobClient:null,
+				selectedItems: [],
+				selectedPapers: [],
+				btnLoading: false,
+				tabName: 'exercise',
+				schoolInfo: {},
+				sharePeriodId: '',
+				shareSubjectId: '',
+				shareGradeIds: [],
+				periodList: [],
+				subjectList: [],
+				gradeList: [],
+				existList:[],
+				isSavePoints: false
+			}
+		},
+		created() {
+			let tabName = this.$route.params.tabName
+			this.tabName = tabName || 'exercise'
+			this.getSchoolInfo()
+			
+		},
+		methods: {
+			/* 忽略重复 */
+			onIgnoreExist(){
+				this.$router.push({
+					name:'schoolBank',
+					params:{
+						tabName:this.tabName
+					}
+				})
+			},
+			/* 确认覆盖 */
+			onConfirmCover(){
+				let promiseArr = []
+				if(this.tabName === 'paper'){
+					this.existList.forEach(paper => {
+						paper.scope = 'school'
+						paper.periodId = this.sharePeriodId
+						paper.subjectId = this.shareSubjectId
+						paper.subjectName = this.subjectList.find(i => i.id === this.shareSubjectId).name
+						paper.gradeIds = this.shareGradeIds.length ? this.shareGradeIds.map(i => i + '') : this.gradeList.map((i, index) => index + '')
+						paper.code = this.$store.state.userInfo.schoolCode
+						promiseArr.push(this.saveCosmosPaper(paper,true))
+						promiseArr.push(this.saveBlobPaper(paper))
+					})
+				}else{
+					this.existList.forEach(i => {
+						promiseArr.push(this.saveShareItem(i,true))
+					})
+				}
+				this.coverLoading = true
+				Promise.all(promiseArr).then(result => {
+					this.$Message.success(this.$t('evaluation.share.shareSuc'))
+					this.coverLoading = false
+					this.$router.push({
+						name:'schoolBank',
+						params:{
+							tabName:this.tabName
+						}
+					})
+				})
+			},
+			/* 初始化BLOB实例 */
+			initBlobClient(scope){
+				return new Promise(async (r,j) => {
+					try{
+						let sasData = scope === 'school' ? await this.$tools.getSchoolSas() :  await this.$tools.getPrivateSas();
+						r(new blobTool(sasData.url,sasData.name,sasData.sas,scope))
+					}catch(e){
+						j(e)
+					}
+				})
+			},
+			/** 获取区班校信息 */
+			getSchoolInfo() {
+				try {
+					this.$store.dispatch("user/getSchoolProfile").then(async (res) => {
+						let schoolBaseInfo = res.school_base;
+						if (schoolBaseInfo) {
+							this.schoolInfo = schoolBaseInfo;
+							this.periodList = schoolBaseInfo.period;
+							if (schoolBaseInfo.period.length) {
+								this.sharePeriodId = schoolBaseInfo.period[0].id
+								this.gradeList = schoolBaseInfo.period[0].grades;
+								this.subjectList = schoolBaseInfo.period[0].subjects;
+								this.shareSubjectId = this.subjectList[0].id
+							}
+						}
+					});
+				} catch (e) {}
+			},
+			/* 确认分享 */
+			async doShare() {
+				let promiseArr = []
+				let allPoints = []
+				this.existList = []
+				this.schoolBlobClient = await this.initBlobClient('school')
+				this.privateBlobClient = await this.initBlobClient('private')
+				this.btnLoading = true
+				if(this.tabName === 'exercise'){
+					// 对所有分享的题目进行信息补充及保存事件处理
+					this.selectedItems.forEach(item => {
+						item.scope = 'school'
+						item.periodId = this.sharePeriodId
+						item.subjectId = this.shareSubjectId
+						item.gradeIds = this.shareGradeIds.length ? this.shareGradeIds.map(i => i + '') : this.gradeList.map((i, index) => index + '')
+						item.code = 'Item-' + this.$store.state.userInfo.schoolCode
+						allPoints = allPoints.concat(allPoints, item.knowledge)
+						// 如果选择的是综合题 则需要把小题也进行复制操作
+						if(item.children.length){
+							item.children.forEach(child => {
+								child.scope = 'school'
+								child.periodId = this.sharePeriodId
+								child.subjectId = this.shareSubjectId
+								child.gradeIds = this.shareGradeIds.length ? this.shareGradeIds.map(i => i + '') : this.gradeList.map((i, index) => index + '')
+								child.code = 'Item-' + this.$store.state.userInfo.schoolCode
+								allPoints = allPoints.concat(allPoints, child.knowledge)
+								promiseArr.push(this.saveShareItem(child))
+								// promiseArr.push(this.saveBlobItem(child))
+							})
+						}
+						promiseArr.push(this.saveShareItem(item))
+						// promiseArr.push(this.saveBlobItem(item))
+					})
+				} else {
+					this.existList = await this.checkPapers(this.selectedPapers)
+					// 对所有分享的题目进行信息补充及保存事件处理
+					this.selectedPapers.forEach(paper => {
+						if(!this.existList.find(i => i.id === paper.id)){
+							paper.scope = 'school'
+							paper.periodId = this.sharePeriodId
+							paper.subjectId = this.shareSubjectId
+							paper.subjectName = this.subjectList.find(i => i.id === this.shareSubjectId).name
+							paper.gradeIds = this.shareGradeIds.length ? this.shareGradeIds.map(i => i + '') : this.gradeList.map((i, index) => index + '')
+							paper.code = this.$store.state.userInfo.schoolCode
+							promiseArr.push(this.saveCosmosPaper(paper))
+							promiseArr.push(this.saveBlobPaper(paper))
+						}
+					})
+				}
+				// 执行分享同步操作
+				Promise.all(promiseArr).then(async result => {
+					if(this.isSavePoints) {
+						if(this.tabName === 'paper') {
+							let arr = await this.getPaperKnow(result)
+							allPoints = arr.flat()
+						}
+						let paperItem = {
+							periodId: this.sharePeriodId,
+							subjectId: this.shareSubjectId,
+							code: this.$store.state.userInfo.schoolCode,
+						}
+						await this.saveImportPoints([...new Set(allPoints)], paperItem)
+					}
+				}).catch(e => {
+					this.$Message.error(this.$t('evaluation.share.shareFail'))
+				}).finally(() => {
+					if(this.existList.length) {
+						this.isShowExist = true
+					} else {
+						this.$Message.success(this.$t('evaluation.share.shareSuc'))
+						this.$router.push({
+							name:'schoolBank',
+							params:{
+								tabName:this.tabName
+							}
+						})
+					}
+					this.btnLoading = false
+				})
+			},
+			getPaperKnow(paperList) {
+				return new Promise((resolve, reject) => {
+					let promiseArr = []
+					paperList.forEach(async item => {
+						if(item?.paper) {
+							promiseArr.push(new Promise(async (r, j) => {
+								let jsonData = await this.$evTools.getFullPaper(item.paper, 'private')
+								if(jsonData) {
+									r(jsonData.points)
+								}
+							}))
+							
+						}
+					})
+					Promise.all(promiseArr).then(result => {
+						let arr = []
+						result.forEach(i => {
+							arr.push(i)
+						})
+						resolve(arr)
+					})
+				})
+			},
+			/* 保存试题到数据库 */
+			saveShareItem(shareItem,isUpdate) {
+				return new Promise(async (r, j) => {
+					let cosmosItem = await this.$evTools.createCosmosItem(shareItem)
+					this.$api.newEvaluation.SaveSingleExercise({
+						itemInfo: cosmosItem,
+						option: isUpdate ? "update" : "insert",
+					}).then((res) => {
+						if (!res.error) {
+							/* 代表试题已经存在于校本库中 需要询问用户是否覆盖 */
+							if(res.code && res.code === 409){
+								this.existList.push(shareItem)
+								r(409)
+							}else{
+								// 避免影响试题列表显示
+								let item = this._.cloneDeep(shareItem)
+								// 如果是综合题 blob内只存储子题的ID集合
+								if(item.children.length){
+									item.children = item.children.map(i => i.id)
+								}
+								this.schoolBlobClient.copyFolder('item/' + item.id + '/', 'item/' + item.id, this.privateBlobClient,false).then(async res => {
+									let removeItem = await this.$editorTools.doRemoveMideaHost(item)
+									let blobItemJson = await this.$evTools.createBlobItem(removeItem)
+									let file = new File([JSON.stringify(blobItemJson)], item.id + ".json");
+									let blobFile = await this.schoolBlobClient.upload(file, { path:"item/" + item.id });
+									r(200)
+								}).catch(e => {
+									j(500)
+								})
+							}
+						} else {
+							j(500);
+						}
+					});
+				})
+			},
+			/* 保存分享试题JSON到blob */
+			saveBlobItem(val){
+				return new Promise(async (r, j) => {
+					try{
+						// 避免影响试题列表显示
+						let item = this._.cloneDeep(val)
+						// 如果是综合题 blob内只存储子题的ID集合
+						if(item.children.length){
+							item.children = item.children.map(i => i.id)
+						}
+						this.schoolBlobClient.copyFolder('item/' + item.id + '/', 'item/' + item.id, this.privateBlobClient,false).then(async res => {
+							let removeItem = await this.$editorTools.doRemoveMideaHost(item)
+							let blobItemJson = await this.$evTools.createBlobItem(removeItem)
+							let file = new File([JSON.stringify(blobItemJson)], item.id + ".json");
+							let blobFile = await this.schoolBlobClient.upload(file, { path:"item/" + item.id });
+							r(200)
+						})
+					}catch(e){
+						j(e)
+					}
+					
+				})
+			},
+			/* 保存试题到数据库 */
+			saveCosmosPaper(item,isUpdate) {
+				return new Promise(async (r, j) => {
+					let cosmosPaper = await this.$evTools.createCosmosPaper(item)
+					this.$api.learnActivity.SaveExamPaper({
+						paper: cosmosPaper,
+						option: isUpdate ? 'update' : 'insert'
+					}).then((res) => {
+						if (!res.error) {
+							r(res);
+						} else {
+							j(500);
+						}
+					});
+				})
+			},
+			/* 保存分享试题JSON到blob */
+			saveBlobPaper(paper){
+				return new Promise(async (r, j) => {
+					try{
+						let newPaper = this._.cloneDeep(paper)
+						let tmdId = this.$store.state.userInfo.TEAMModelId
+						this.schoolBlobClient.copyFolder('paper/' + newPaper.name + '/', 'paper/' + newPaper.name, this.privateBlobClient,false).then(async res => {
+							// 试卷拷贝完成后 需要对新拷贝的index.json添加学段等信息 所以拿旧的index.json改一下
+							let privateSas = await this.$evTools.getBlobPrivateSas(tmdId)
+							let fullPath = this.$evTools.getBlobHost() +  '/' + tmdId + paper.blob + '/index.json' + privateSas
+							let jsonInfo = await this.$tools.getFile(fullPath)
+							let indexJsonData = JSON.parse(jsonInfo)
+							indexJsonData.periodId = this.sharePeriodId
+							indexJsonData.subjectId = this.shareSubjectId
+							indexJsonData.subjectName = this.subjectList.find(i => i.id === this.shareSubjectId).name
+							indexJsonData.gradeIds = this.shareGradeIds.length ? this.shareGradeIds.map(i => i + '') : this.gradeList.map((i, index) => index + '')
+							// 完善json信息后再上传回去进行更新
+							let file = new File([JSON.stringify(indexJsonData)], "index.json");
+							let blobFile = await this.schoolBlobClient.upload(file, { path:"paper/" + paper.name });
+							r(200)
+						})
+					}catch(e){
+						j(e)
+					}
+					
+				})
+			},
+			/* 检测分享的试卷是否有重名 */
+			checkPapers(papers){
+				return new Promise((resolve,reject) => {
+					let promiseArr = []
+					papers.forEach(i => {
+						promiseArr.push(this.checkPaperExist(i.name))
+					})
+					
+					Promise.all(promiseArr).then(result => {
+						let arr = []
+						result.forEach((i,index) => {
+							if(i){
+								arr.push(papers[index])
+							}
+						})
+						resolve(arr)
+					})
+				})
+				
+			},
+			/* 检测试卷是否已存在于校本库 */
+			checkPaperExist(paperName){
+				return new Promise((r,j) => {
+					this.$api.newEvaluation.checkPaperName({
+						scope:'school',
+						code:this.$store.state.userInfo.schoolCode,
+						name:paperName
+					}).then(res => {
+						if(!res.error){
+							r(res.papers.length > 0)
+						}else{
+							j(false)
+						}
+					}).catch(e => {
+						j(false)
+					})
+				})
+				
+			},
+			/* 保存导入卷子的所有知识点 */
+			saveImportPoints(points, paperItem) {
+				return new Promise((r, j) => {
+					points = points.map(i => i.trim())
+					this.$api.knowledge.SavePoints({
+						"periodId": paperItem.periodId,
+						"subjectId": paperItem.subjectId,
+						"owner": paperItem.code,
+						"points": points
+					}).then(res => {
+						if (!res.error) {
+							r(200)
+						}
+					}).catch(e => {
+						j(e)
+					})
+				})
+			},
+			/* 学段切换 */
+			onPeriodChange(val) {
+				let period = this.periodList.find(i => i.id === val)
+				this.gradeList = period.grades;
+				this.subjectList = period.subjects;
+				this.shareSubjectId = this.subjectList[0].id
+				this.shareGradeIds = []
+			},
+			/* 切换TAB */
+			tabClick(tab) {
+				this.tabName = tab
+				this.selectedItems = []
+				this.selectedPapers = []
+			},
+			/* 返回 */
+			goBack() {
+				this.$router.go(-1)
+			},
+			/* 选择试题变化 */
+			chooseQuChange(arr) {
+				this.selectedItems = arr
+			},
+			/* 选择试卷变化 */
+			onPaperChange(arr){
+				console.log(arr)
+				this.selectedPapers = arr
+			},
+			/* 获取试题简要题干 */
+			getSimpleText(str) {
+				return str.replace(/<[^>]+>/g, "")
+			}
+		},
+		computed:{
+			btnDisable(){
+				return (this.tabName === 'exercise' && !this.selectedItems.length) || (this.tabName === 'paper' && !this.selectedPapers.length)
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+	@import "./ShareCenter.less";
+</style>

+ 288 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/TestPaperList.less

@@ -0,0 +1,288 @@
+.pl-container {
+	position: relative;
+    height: 100%;
+    width: 98%;
+    margin: 0 auto;
+	
+	.bank-action-bar{
+		background-color: #fff;
+		padding: 10px 15px;
+		margin: 10px 0;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		
+		
+		/deep/ .ivu-input{
+			padding: 4px 12px;
+		}
+		
+		.action-tools{
+			.action-tool{
+				display: flex;
+				align-items: center;
+				
+				.action-title{
+					display: inline-block;
+					min-width: 50px;
+				}
+			}
+		}
+	}
+}
+
+.pl-filter-wrap {
+    
+        width: 100%;
+        padding: 20px;
+        background: #fff;
+
+    .filter-item:first-child {
+        margin-top: 0px;
+    }
+
+    .filter-item {
+        margin-top: 10px;
+    }
+
+    .filter-title {
+        font-size: 16px;
+        font-weight: bold;
+        margin-right: 10px;
+    }
+
+    .ivu-radio-group {
+        padding-bottom: 4px;
+    }
+
+    .ivu-radio-wrapper {
+        padding: 0 10px;
+        margin-left: 15px;
+        height: 28px;
+        line-height: 28px;
+        box-shadow: none !important;
+        border: none !important;
+        font-size: 14px;
+        border-radius: 0 !important;
+    }
+
+    .ivu-radio-wrapper .ivu-icon {
+        margin-bottom: 2px;
+    }
+
+    .ivu-radio-wrapper:after, .ivu-radio-group-button .ivu-radio-wrapper:before {
+        content: none;
+    }
+
+    .ivu-radio-group-item {
+        border-radius: 5px !important;
+    }
+
+    .ivu-radio-wrapper-checked {
+        background: rgb(16, 171, 231);
+        box-shadow: none !important;
+        color: white;
+    }
+
+    .ivu-radio-group-button .ivu-radio-wrapper-checked:hover {
+        color: #fff !important;
+    }
+
+
+
+    .ivu-checkbox-group {
+        padding-bottom: 4px;
+        display: inline-block;
+    }
+
+    .ivu-checkbox-inner,
+    .ivu-checkbox-checked,
+    .ivu-checkbox {
+        display: none;
+    }
+
+    .ivu-checkbox-wrapper {
+        padding: 0 10px;
+        margin-left: 15px;
+        height: 28px;
+        line-height: 28px;
+        box-shadow: none !important;
+        border: none !important;
+        font-size: 14px;
+        border-radius: 0 !important;
+    }
+
+    .ivu-checkbox-wrapper .ivu-icon {
+        margin-bottom: 2px;
+    }
+
+    .ivu-checkbox-wrapper:after, .ivu-checkbox-group-button .ivu-checkbox-wrapper:before {
+        content: none;
+    }
+
+    .ivu-checkbox-group-item {
+        border-radius: 5px !important;
+    }
+
+    .ivu-checkbox-wrapper-checked {
+        background: rgb(16, 171, 231);
+        box-shadow: none !important;
+        color: white;
+    }
+
+    .ivu-checkbox-group-button .ivu-checkbox-wrapper-checked:hover {
+        color: #fff !important;
+    }
+}
+
+.pl-content-wrap {
+    width: 100%;
+	position: relative;
+    .fl-col-center;
+
+    .paper-item {
+        position: relative;
+        width: 100%;
+        height: 90px;
+        background: White;
+        margin-bottom: 10px;
+        padding-left: 10px;
+        .fl-col-center;
+        align-items: flex-start;
+        cursor: pointer;
+
+        &-name {
+            font-size: 20px;
+            font-weight: bold;
+            .fl-row-center;
+        }
+
+        &-tag {
+            font-size: 12px;
+            padding: 1px 10px;
+            border-radius: 5px;
+            color: #fff;
+            background: #15a06c;
+            margin: 0 10px;
+        }
+
+        &-info {
+            margin-top: 15px;
+
+            .info-item {
+                font-size: 12px;
+                padding: 0 10px;
+
+                .info-bold {
+                    font-weight: bold;
+                    color: #70B1E7;
+                }
+            }
+
+            .info-item:not(:last-child) {
+                border-right: 2px solid #f3f3f3;
+            }
+        }
+
+        &-tools {
+            position: absolute;
+            height: 100%;
+            right: 50px;
+            top: 0;
+            font-size: 14px;
+			color: #767676;
+            .fl-row-center;
+            // display: none;
+            // color: var(--normal-icon-color);
+
+			
+			.ivu-icon{
+				margin-right: 5px;
+			}
+
+            &-delete{
+                // font-weight:bold;
+            }
+			
+			&-edit{
+				// font-weight:bold;
+				margin-right: 30px;
+			}
+        }
+
+        &:hover {
+            .paper-item-tools {
+                display: flex;
+            }
+        }
+		
+		 &-select{
+			 position: absolute;
+			 height: 100%;
+			 right: 50px;
+			 top: 0;
+			 font-size: 14px;
+			 .fl-row-center;
+		 }
+    }
+	
+	
+	.pl-review-wrap{
+		width: 100%;
+		display: flex;
+		
+		&-left{
+			width: 75%;
+		}
+		
+		&-right{
+			position: relative;
+			flex:1;
+			margin: 30px 0 20px 30px;
+			background-color: #fff;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			padding: 30px 20px 0 20px;
+			
+			.btn-back-list{
+				position: absolute;
+				right: 20px;
+				top: 10px;
+				color: gray;
+				cursor: pointer;
+			}
+		}
+	}
+	
+	.pl-answersheet-wrap{
+		width: 100%;
+		display: flex;
+		justify-content: center;
+	}
+}
+
+.no-data-text {
+    width: 100%;
+    padding: 30px;
+    background: #fff;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    margin-top: 10px;
+    font-size: 16px;
+}
+
+.fl-col-center {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+
+.fl-row-center {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}

+ 826 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/TestPaperList.vue

@@ -0,0 +1,826 @@
+<template>
+  <div class="pl-container" ref="plContainer">
+    <!-- 条件筛选部分 -->
+    <BaseFilter ref="baseFilter" @onChange="onFilterChange" :filterCounts="filterCounts" :isFilterPaper="isFilterPaper" :hideOriginFilter="true"
+      v-show="!isPreview && !isComponent"></BaseFilter>
+
+    <div class="bank-action-bar light-iview-select light-iview-input" v-show="!isPreview"
+      :style="{ margin: isComponent ? '0 0 10px 0' : '10px 0' }">
+      <!-- 试卷列表的搜索功能 -->
+      <div class="action-tools">
+        <div class="action-tool">
+          <span class="action-title">{{ $t('evaluation.tag') }}:</span>
+          <Select v-model="selectTags" multiple style="min-width:100px" @on-change="onTagChange">
+            <Option v-for="item in tags" :value="item" :key="item">{{ item }}</Option>
+          </Select>
+        </div>
+        <div class="action-tool">
+          <span class="action-title">{{ $t('evaluation.tag') }}:</span>
+          <Select v-model="selectMode" style="min-width:100px" @on-change="onModeChange">
+            <Option value="all">{{ $t('schoolBaseInfo.all') }}</Option>
+            <Option value="quickPaper">{{ $t('evaluation.quickPaper.title') }}</Option>
+            <Option value="markMode">{{ $t('evaluation.markMode.tip4') }}</Option>
+          </Select>
+        </div>
+        <div class="action-tool">
+          <Input v-special-char suffix="ios-search" v-model="searchVal" clearable
+            :placeholder="$t('evaluation.paperList.searchPaper')" style="min-width: 240px" @on-click="onCloseSearch"
+            @on-change="onSearchChange" />
+        </div>
+        <span>{{ $t('evaluation.exerciseList.totalTip1') }}<span
+            style="font-size: 18px;color: #ff0206;margin: 0 10px;font-weight: bold;">{{ totalNum }}</span>{{
+              $t('unit.text3') }}</span>
+      </div>
+    </div>
+
+    <!-- 空数据展示 -->
+    <div v-if="paperList.length === 0" class="no-data-text">
+      <img src="@/assets/icon/no_data.svg" width="120" />
+      <span style="margin-top:15px;color:#808080">{{ $t('evaluation.noData') }}</span>
+    </div>
+
+    <!-- 试卷列表页面 -->
+    <div class="pl-content-wrap" v-else>
+      <Loading :top="100" v-show="dataLoading" type="1" hideMask></Loading>
+      <div style="width: 100%;" v-if="!isPreview">
+        <div class="paper-item" v-for="(paper, index) in paperList" :key="index" style="cursor: auto;">
+          <div class="paper-item-name">
+            <span class="paper-item-tag" v-if="isSchool">{{ paper.subjectName }}</span>
+            <span style="margin-left: 8px;">{{ paper.name }}</span>
+            <span style="margin-left: 8px;" v-for="(tag, tagIndex) in paper.tags">
+              <Tag color="geekblue" style="vertical-align: sub;">{{ tag }}</Tag>
+            </span>
+            <span style="margin-left: 8px;" v-if="paper.mode">
+              <Tag color="green" style="vertical-align: sub;">{{ paper.mode }}</Tag>
+            </span>
+            <span style="margin-left: 8px;" v-if="paper.qamode">
+              <Tag color="orange" style="vertical-align: sub;">{{ $t('evaluation.quickPaper.title') }}</Tag>
+            </span>
+            <span style="margin-left: 8px;" v-if="paper.markModel">
+              <Tag color="cyan" style="vertical-align: sub;">{{ $t('evaluation.markMode.tip4') }}</Tag>
+            </span>
+          </div>
+          <div class="paper-item-info">
+            <span class="info-item" v-if="isSchool">{{ $t('evaluation.paperList.usePeriod') }}:<span class="info-bold">{{
+              getPeriodName(paper.periodId) }}</span></span>
+            <span class="info-item" v-if="isSchool">{{ $t('evaluation.paperList.useGrade') }}:<span class="info-bold"
+                v-for="(grade, gIndex) in paper.gradeIds" :key="gIndex">{{ getGradeName(paper.periodId, grade) }}
+                <span v-show="gIndex !== paper.gradeIds.length - 1"> / </span></span></span>
+            <span class="info-item">{{ $t('evaluation.paperList.itemCount') }}:<span class="info-bold">{{ paper.scoring ?
+              paper.scoring.length : 0 }}</span></span>
+            <span class="info-item">{{ $t('evaluation.paperList.sortType') }}:<span class="info-bold">{{ paper.itemSort ===
+              1 ? $t('evaluation.paperList.sortByOrder') : $t('evaluation.paperList.sortByType') }}</span></span>
+            <span class="info-item">{{ $t('evaluation.updateTime') }}:<span class="info-bold">{{
+              $tools.formatTime(paper.createTime) || 0 }} </span></span>
+            <!-- <span class="info-item">
+							<span>标签:</span>
+							<span class="info-bold" v-for="(tag,tagIndex) in paper.tags"><Tag color="blue">{{ tag }}</Tag></span>
+						</span> -->
+          </div>
+          <div class="paper-item-tools" v-if="!chooseModel">
+            <span class="paper-item-tools-edit" @click.stop="onPreviewPaper(paper)" style="cursor: pointer;">
+              <Icon type="md-eye" />
+              <span>{{ $t('syllabus.preview') }}</span>
+            </span>
+            <span class="paper-item-tools-edit" @click.stop="goToDownload(paper)" style="cursor: pointer;">
+              <Icon type="md-download" />
+              <span>{{ $t('syllabus.download') }}</span>
+            </span>
+            <!-- <span class="paper-item-tools-edit" @click.stop="doCopyPaper(paper)">
+              <Icon type="md-copy" />
+                      <span>{{ $t('evaluation.paperList.copyTip3') }}</span>
+            </span>
+            <span class="paper-item-tools-edit" @click.stop="goToPaper(paper)"
+              v-if="($access.can('admin.*||exercise-upd') || !isSchool)">
+              <Icon type="md-create" />
+              <span>{{ $t('evaluation.editItem') }}</span>
+            </span>
+            <span class="paper-item-tools-delete" @click.stop="onDeletePaper(paper)"
+              v-if="($access.can('admin.*||exercise-upd') || !isSchool)">
+              <Icon type="md-trash" />
+              <span>{{ $t('evaluation.deleteItem') }}</span>
+            </span> -->
+          </div>
+          <div v-if="chooseModel" class="paper-item-select">
+            <div v-if="!singleChoose">
+              <Button :type="checkedPaperList.map(i => i.id).includes(paper.id) ? 'warning' : 'info'"
+                @click.stop="checkedPaperList.map(i => i.id).includes(paper.id) ? onCancelCheck(paper) : onCheckPaper(paper)">
+                {{ checkedPaperList.map(i => i.id).includes(paper.id) ? $t('evaluation.paperList.choosed') :
+                  $t('evaluation.paperList.choosePaper') }}
+              </Button>
+            </div>
+            <div v-else>
+              <Button :type="selectPaperId === paper.id ? 'warning' : 'info'"
+                @click.stop="selectPaperId === paper.id ? onCheckPaper(paper) : onCheckPaper(paper)">{{ selectPaperId ===
+                  paper.id ? $t('evaluation.paperList.choosed') : $t('evaluation.paperList.choosePaper') }}</Button>
+            </div>
+          </div>
+        </div>
+
+        <!-- 底部分页区域 -->
+        <Page :total="totalNum" show-sizer show-total :page-size="pageSize" :current="pageNum"
+          @on-page-size-change="pageSizeChange" @on-change="pageChange" :page-size-opts="[10, 20, 30, 40]" />
+      </div>
+
+      <!-- 预览试卷部分 -->
+      <div class="pl-review-wrap animated fadeIn" v-if="isPreview">
+        <div class="pl-review-wrap-left" :style="{ width: isComponent ? '100%' : '75%' }">
+          <TestPaper :paper="evaluationInfo" isExamPaper hidePie :isSharePreview="isComponent" @exitPreview="exitPreview" @backToPaperList="() => $emit('backToPaperList')">
+          </TestPaper>
+        </div>
+
+        <div class="pl-review-wrap-right" v-if="!isComponent">
+          <h2>{{ $t('evaluation.paperList.paperAnalysis') }}</h2>
+          <p style="margin-bottom: 20px;margin-top: 10px;">
+            ({{ $t('evaluation.paperList.totalScore') }}:{{ evaluationInfo.score }}
+            {{ $t('evaluation.paperList.score') }})
+          </p>
+          <BaseTypePie :echartsData="evaluationInfo"></BaseTypePie>
+          <BaseObjectivePie :echartsData="evaluationInfo"></BaseObjectivePie>
+          <BaseDiffPie :echartsData="evaluationInfo"></BaseDiffPie>
+          <BasePointPie :echartsData="evaluationInfo"></BasePointPie>
+        </div>
+      </div>
+
+      <!-- 预览答题卡部分 -->
+      <!-- <div class="pl-answersheet-wrap animated fadeIn" v-if="isShowSheet">
+				<AnswerSheet></AnswerSheet>
+			</div> -->
+    </div>
+
+    <!-- <Modal v-model="isShowDownLoad" width="950px" title="下载试卷" class-name="preview-modal">
+			<DownloadPaper :paper="fullPaperJson" ref="dpRef"></DownloadPaper>
+			<div slot="footer">
+				<Button @click="">取消</Button>
+				<Button type="success" @click="onDownloadPaper">下载试卷PDF</Button>
+			</div>
+		</Modal> -->
+
+    <!-- 		<Modal v-model="isShowSheet" width="800px"  title="答题卡" class-name="preview-modal" >
+			<div v-if="isShowSheet">
+				<AnswerSheet></AnswerSheet>
+			</div>
+		</Modal> -->
+
+  </div>
+</template>
+<script>
+import blobTool from '@/utils/blobTool.js'
+import Loading from '@/common/Loading.vue'
+import BaseFilter from '../components/BaseFilter'
+import BaseImport from '../components/BaseImport'
+import TestPaper from '../index/TestPaper.vue'
+export default {
+  props: {
+    chooseModel: {
+      type: Boolean,
+      default: false
+    },
+    singleChoose: {
+      type: Boolean,
+      default: false
+    },
+    isComponent: {
+      type: Boolean,
+      default: false
+    }
+  },
+  components: {
+    Loading,
+    BaseFilter,
+    BaseImport,
+    TestPaper,
+  },
+  data() {
+    return {
+      containerClient: null,
+      selectPaperId: '',
+      curPaper: null,
+      selectTags: [],
+      selectMode: 'all',
+      tags: [],
+      searchVal: '',
+      checkedPaperList: [],
+      isShowSheet: false,
+      schoolCode: '',
+      totalNum: 0,
+      pageSize: 10,
+      pageNum: 1,
+      dataLoading: false,
+      randomModal: false,
+      isFilterPaper: true,
+      paperList: [],
+      periodList: [],
+      gradeList: [],
+      subjectList: [],
+      filterParams: {},
+      findCountParams: {},
+      originList: [],
+      originRes: [],
+      schoolInfo: {},
+      filterSort: 'createTime',
+      evaluationInfo: null,
+      isPreview: false,
+      paperInfo: {
+        name: "",
+        score: 100,
+        item: []
+      },
+      fullPaperJson: {
+        id: '0'
+      },
+      isShowDownLoad: false,
+      flag: false,
+      filterCounts: {
+        gradeCountArr: [],
+        subjectCountArr: [],
+        periodCountArr: []
+      },
+      isShowSchoolBank: false
+    }
+  },
+  created() {
+    // this.getPaperList()
+    // this.doFilter()
+    this.isShowSchoolBank = this.$route.name === "schoolBank";
+
+  },
+  activated() {
+    this.isShowSchoolBank = this.$route.name === "schoolBank";
+  },
+  methods: {
+    onDownloadPaper() {
+      this.$refs.dpRef.onDownloadPaper()
+    },
+    exitPreview() {
+      this.isPreview = false
+    },
+    onCheckPaper(item) {
+      if (this.singleChoose) {
+        this.selectPaperId = item.id
+        this.checkedPaperList = [item]
+      } else {
+        this.checkedPaperList.push(item)
+      }
+      this.$emit('onPaperChange', this.checkedPaperList)
+    },
+
+    onCancelCheck(item) {
+      if (this.singleChoose) {
+        this.selectPaperId = ''
+        this.checkedPaperList = []
+      } else {
+        this.checkedPaperList.splice(this.checkedPaperList.indexOf(item), 1)
+
+      }
+      this.$emit('onPaperChange', this.checkedPaperList)
+    },
+
+    async onPreviewPaper(paper) {
+      if (this.chooseModel && !this.isComponent) {
+        this.$emit('previewPaper', paper)
+        return
+      }
+      this.dataLoading = true
+      let curPaper = paper
+      this.curPaper = paper
+      sessionStorage.setItem('isSave', 0)
+      try {
+        // 获取完整试卷数据再跳转编辑页面
+        let fullPaperJson = !curPaper.qamode && this.fullPaperJson.id === curPaper.id ? this.fullPaperJson : await this
+          .$evTools.getFullPaper(curPaper)
+        this.fullPaperJson = fullPaperJson
+        fullPaperJson.code = curPaper.code
+        fullPaperJson.sheet = curPaper.sheet
+        fullPaperJson.sheetNo = curPaper.sheetNo
+        fullPaperJson.mode = curPaper.mode
+        let paper = fullPaperJson
+        let schoolInfo = null
+        if (paper.scope === 'school') {
+          schoolInfo = await this.getSchoolBaseInfo()
+          this.subjectList = schoolInfo ? this.schoolInfo.period.filter(i => i.id === paper.periodId)[0]
+            .subjects : []
+        }
+        this.evaluationInfo = {
+          id: paper.id,
+          name: paper.name,
+          code: paper.code,
+          type: paper.scope,
+          scope: paper.scope,
+          itemSort: paper.itemSort,
+          paperPeriod: schoolInfo && paper.scope === 'school' ? this.schoolInfo.period.map(i => i.id)
+            .indexOf(paper.periodId) : null,
+          paperGrade: schoolInfo && paper.scope === 'school' ? paper.gradeIds : [],
+          paperSubject: schoolInfo && paper.scope === 'school' ? this.subjectList.map(i => i.id)
+            .indexOf(paper.subjectId) : [],
+          score: paper.score,
+          item: paper.item,
+          qamode: paper.qamode,
+          blob: paper.blob,
+          attachments: paper.attachments,
+          sheet: paper.sheet || null,
+          sheetNo: paper.sheetNo || null,
+          mode: paper.mode || null,
+          multipleRule: paper.multipleRule,
+          startTime: 0,
+          endTime: 0,
+          createType: 'manual',
+          typeSummaryInfo: paper.typeSummaryInfo,
+          orderTemp: paper.orderTemp
+        }
+        this.isPreview = true
+        this.$emit('onPaperClick')
+        this.dataLoading = false
+      } catch (e) {
+        console.log(e)
+        this.dataLoading = false
+        this.$Message.error(this.$t('evaluation.paperList.paperErrorTip'))
+      }
+    },
+
+    /** 获取当前学校基础数据 */
+    getSchoolBaseInfo() {
+      return new Promise((r, j) => {
+        this.$store.dispatch("user/getSchoolProfile").then((res) => {
+          let schoolBaseInfo = res.school_base;
+          if (schoolBaseInfo) {
+            r(schoolBaseInfo)
+          } else {
+            r(null)
+          }
+        });
+      })
+
+    },
+
+    /** 执行筛选条件获取数据 */
+    doFilter(filterKey) {
+      this.dataLoading = true
+      this.isPreview = false
+      this.getPaperList(this.filterParams, filterKey)
+    },
+
+    onFilterChange(val) {
+      let filterParams = val.params
+      let filterKey = val.key
+      let isSchool = filterParams.code === this.$store.state.userInfo.schoolCode
+      this.filterParams = {
+        // '@DESC': filterParams.filterSort,
+        'code': filterParams.code,
+        'scope': isSchool ? 'school' : 'private',
+        'periodId': isSchool && this.flag ? filterParams.periodId[0] : [],
+        "gradeIds[*]": isSchool ? filterParams.gradeIds : [],
+        'subjectId': isSchool ? filterParams.subjectId : []
+      }
+      if (filterParams.filterSort) {
+        this.filterParams['@DESC'] = 'createTime'
+      } else {
+        this.filterParams['@ASC'] = 'createTime'
+      }
+      this.doFilter(filterKey)
+    },
+    /** 获取试卷列表 */
+    getPaperList(params, filterKey) {
+      let that = this
+      this.$api.learnActivity.FindExamPaper(params).then(async res => {
+        let list = res.papers
+        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)
+          let bankFilterConds = localStorage.getItem('bankFilterConds') ? JSON.parse(localStorage.getItem('bankFilterConds')) : null
+          let activePeriodIndex = bankFilterConds ? bankFilterConds.period : 0
+          list = res.papers.filter(i => i.periodId === this.periodList[activePeriodIndex].id)
+          this.flag = true
+        }
+        // this.getItemsCount(list,filterKey)
+        this.paperList = list
+        this.originList = list
+        this.originRes = JSON.parse(JSON.stringify(list))
+        this.totalNum = list.length
+        if (list.length) {
+          this.tags = [...new Set(list.map(i => i.tags).flat(1))]
+          sessionStorage.setItem('paperTags', JSON.stringify(this.tags))
+        }
+        this.pageChange(1)
+        setTimeout(() => {
+          that.dataLoading = false
+        }, 1000)
+      }).catch(err => {
+        console.log(err)
+        setTimeout(() => {
+          this.$Message.error(this.$t('evaluation.paperList.paperErrorTip'))
+          that.dataLoading = false
+        }, 1000)
+      })
+    },
+    /* 搜索词汇发生变化 */
+    onSearchChange() {
+      let val = this.searchVal
+      this.originList = this.originRes.filter(i => i.name.indexOf(val) > -1)
+      this.totalNum = this.originList.length
+      this.pageChange(1)
+    },
+    /* 关闭搜索 */
+    onCloseSearch() {
+      this.searchVal = ''
+      this.originList = JSON.parse(JSON.stringify(this.originRes))
+      this.totalNum = this.originList.length
+      this.pageChange(1)
+    },
+    /* 标签选择发生变化 */
+    onTagChange(val) {
+      this.originList = val.length ? this.originRes.filter(i => val.find(j => i.tags.includes(j))) : this
+        .originRes
+      this.totalNum = this.originList.length
+      this.pageChange(1)
+    },
+    onModeChange(val) {
+      if(val === 'all'){
+        this.originList = this.originRes
+      }else if(val === 'quickPaper'){
+        this.originList = this.originRes.filter(i => i.qamode)
+      }else{
+        this.originList = this.originRes.filter(i => i.markModel)
+      }
+      this.totalNum = this.originList.length
+      this.pageChange(1)
+    },
+    /* 获取所有学段下的试题总数 */
+    getPeriodCount(papers) {
+      let periodIdArr = this.periodList.map(i => i.id)
+      let periodCountArr = periodIdArr.map(i => {
+        return papers.filter(j => j.periodId === i).length
+      })
+      return periodCountArr
+    },
+
+    /* 获取试题的年级、科目的数量统计 */
+    getItemsCount(papers, filterKey) {
+      if (filterKey === 'period' || !filterKey) {
+        let gradeIdArr = this.$refs.baseFilter.gradeList.map((i, index) => index)
+        this.filterCounts.gradeCountArr = gradeIdArr.map(i => {
+          return papers.filter(j => j.gradeIds && j.gradeIds.includes(i + '')).length
+        })
+        let subjectIdArr = this.$refs.baseFilter.subjectList.map(i => i.id)
+        this.filterCounts.subjectCountArr = subjectIdArr.map(i => {
+          return papers.filter(j => j.subjectId === i).length
+        })
+      }
+
+      console.log(this.filterCounts);
+    },
+
+    /**
+     * 点击查看试卷详情
+     * @param paper
+     */
+    async goToPaper(paper) {
+      this.dataLoading = true
+      sessionStorage.setItem('isSave', 0)
+      try {
+        // 获取完整试卷数据再跳转编辑页面
+        let fullPaperJson = !paper.qamode && this.fullPaperJson.id === paper.id ? this.fullPaperJson : await this.$evTools
+          .getFullPaper(paper)
+        this.fullPaperJson = fullPaperJson
+        fullPaperJson.code = paper.code
+        fullPaperJson.sheet = paper.sheet
+        fullPaperJson.sheetNo = paper.sheetNo
+        console.error('fullPaperJson',fullPaperJson)
+        this.dataLoading = false
+
+        if(paper.qamode){
+          this.$emit('editQuickPaper',fullPaperJson)
+          return
+        }
+
+        this.$router.push({
+          name: paper.scope === 'school' ? 'newSchoolPaper' : 'newPrivatePaper',
+          params: {
+            paper: fullPaperJson
+          }
+        })
+      } catch (e) {
+        this.$Message.error(this.$t('evaluation.paperList.paperErrorTip'))
+        this.dataLoading = false
+      }
+    },
+
+    /* 创建副本 */
+    doCopyPaper(paper) {
+      console.log(paper);
+      this.$Modal.confirm({
+        title: this.$t('evaluation.newExercise.modalTip'),
+        content: `${this.$t('evaluation.paperList.copyTip1')}【${paper.name}】${this.$t('evaluation.paperList.copyTip2')}?`,
+        onOk: async () => {
+          this.dataLoading = true
+          try {
+            let isSchool = this.$route.name === "schoolBank"
+            // 获取初始化Blob需要的数据
+            let sasData = isSchool ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+            //初始化Blob
+            this.containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.isSchool ? 'school' : 'private')
+            // 获取新试卷名称
+            let newPaperName = await this.getCopyPaperName(paper.name)
+            // 获取新试卷新ID
+            let newPaperId = this.$tools.guid()
+            // 第一步 - 生成新试卷基础信息
+            let newPaperItem = this._.cloneDeep(paper)
+            newPaperItem.id = newPaperId
+            newPaperItem.name = newPaperName
+            newPaperItem.sheet = null
+            newPaperItem.sheetNo = null
+            newPaperItem.blob = paper.blob.replaceAll(paper.name, newPaperName)
+            newPaperItem.code = paper.code.replaceAll('Paper-', '')
+            console.log(newPaperItem)
+            // 第二步 - 复制BLOB试卷Folder
+            this.containerClient.copyFolder('paper/' + newPaperName + '/', 'paper/' + paper.name, this.containerClient, false).then(async res => {
+              console.error(res)
+              // 第三步 - 要修改index.json基础ID信息
+              let fullPath = this.$evTools.getBlobHost() + '/' + paper.code.replaceAll('Paper-', '') + paper.blob + '/index.json' + sasData.sas
+              let jsonInfo = await this.$tools.getFile(fullPath)
+              let indexJsonData = JSON.parse(jsonInfo)
+              indexJsonData.id = newPaperId
+              indexJsonData.name = newPaperName
+              let indexJsonFile = new File([JSON.stringify(indexJsonData)], "index.json");
+              this.containerClient.upload(indexJsonFile, {
+                path: 'paper/' + newPaperName,
+                checkSize: false
+              }).then(result => {
+                this.$api.learnActivity.SaveExamPaper({
+                  paper: newPaperItem,
+                  option: 'insert'
+                }).then(insertRes => {
+                  console.error(insertRes)
+                  if (!insertRes.error) {
+                    this.$Message.success(this.$t('syllabus.doSuc'))
+                    this.doFilter()
+                  } else {
+                    this.$Message.success(this.$t('syllabus.doFail'))
+                  }
+                  this.dataLoading = false
+                })
+              })
+            })
+          } catch (error) {
+            this.$Message.success(this.$t('syllabus.doFail'))
+            this.dataLoading = false
+          }
+        }
+      })
+    },
+
+    /* 获取可用的新试卷名称 */
+    async getCopyPaperName(originPaperName) {
+      return new Promise(async (r, j) => {
+        let copyNum = 1
+        let newPaperName = originPaperName + '(' + copyNum + ')'
+        let checkName = async name => {
+          let isExist = await this.containerClient.exists('paper/' + name + '/index.json')
+          if (isExist) {
+            copyNum++
+            newPaperName = originPaperName + '(' + copyNum + ')'
+            checkName(newPaperName)
+          } else {
+            r(newPaperName)
+          }
+        }
+        checkName(newPaperName)
+      })
+
+
+    },
+
+    /* 下载试卷 */
+    async goToDownload(paper) {
+      let fullPaperJson = !paper.qamode && this.fullPaperJson.id === paper.id ? this.fullPaperJson : await this.$evTools
+        .getFullPaper(paper)
+      this.fullPaperJson = fullPaperJson
+      // this.isShowDownLoad = true'
+      this.$router.push({
+        name: 'HTPaperDownload',
+        params: {
+          paper: fullPaperJson,
+          fromRouter: this.$route.name
+        }
+      })
+    },
+
+    /**
+     * 删除试卷
+     * @param item
+     */
+    onDeletePaper(item) {
+      this.$Modal.confirm({
+        title: this.$t('evaluation.newExercise.modalTip'),
+        content: this.$t('evaluation.paperList.confirmDelete'),
+        onOk: async () => {
+          this.dataLoading = true
+          this.$api.learnActivity.DeleteExamPaper({
+            id: item.id,
+            code: item.code,
+            scope: item.scope
+          }).then(async res => {
+            if (!res.error) {
+              this.deleteBlobPrefix(item).then(r => {
+                this.$Message.success(this.$t('evaluation.deleteSuc'))
+                this.doFilter()
+              })
+            } else {
+              this.$Message.warning(this.$t('evaluation.deleteFail'))
+            }
+          }).catch(err => {
+            console.log(err)
+            this.$Message.warning(this.$t('evaluation.deleteFail'))
+          })
+        }
+      })
+    },
+
+    /* 删除blob指定试题目录下所有 */
+    deleteBlobPrefix(paper) {
+      return new Promise((resolve, reject) => {
+        this.$api.blob.deletePrefix({
+          "cntr": paper.scope === 'school' ? this.$store.state.userInfo.schoolCode : this
+            .$store.state.userInfo.TEAMModelId,
+          "prefix": "paper/" + paper.name
+        }).then(
+          (res) => {
+            if (!res.error) {
+              resolve(200)
+            } else {
+              resolve(500)
+            }
+          },
+          (err) => {
+            reject(err)
+          }
+        )
+      })
+    },
+
+    /**
+     * 切换页码操作
+     * @param page
+     */
+    pageChange(page) {
+      this.pageNum = page
+      let start = this.pageSize * (page - 1)
+      let end = this.pageSize * page
+      // 拿到当前页码需要展示的数据
+      this.paperList = this.originList.slice(start, end)
+      this.$emit('onBackToTop')
+      // this.doFilter()
+    },
+
+    /**
+     * 切换每页显示数量
+     * @param val
+     */
+    pageSizeChange(val) {
+      this.pageSize = val
+      this.pageChange(1)
+    },
+
+    /** 前往试卷页面 */
+    goCreatePaper() {
+      this.$router.push({
+        name: 'testPaper',
+        params: {
+          paper: this.paperInfo
+        }
+      })
+    },
+
+    /** 前往手动挑题页面 */
+    goPickExercises() {
+      this.$router.push({
+        name: 'exercisesList'
+      })
+    },
+
+    /**
+     * 根据SubjectCode换取SubjectName
+     * @param code
+     */
+    getPeriodName(code) {
+      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')
+    },
+
+    /**
+     * 根据GradeCode换取GradeName
+     * @param code
+     */
+    getGradeName(periodId, code) {
+      return this.$store.state.user.schoolProfile.school_base ? this.$store.state.user.schoolProfile.school_base
+        .period.filter(i => i.id === periodId)[0].grades[+code] : this.$t('evaluation.noData')
+    },
+
+    /**
+     * 计算试卷题目平均难度
+     * @param arr 试题集合
+     */
+    handleDiffCalc(arr) {
+      let levelArr = arr.map(i => i.level)
+      return this._.meanBy(levelArr).toFixed(1)
+    }
+
+  },
+  async mounted() {
+    let schoolProfile = await this.$store.dispatch('user/getSchoolProfile')
+    if (schoolProfile.school_base) {
+      this.schoolInfo = schoolProfile.school_base
+      this.periodList = this.schoolInfo.period
+    }
+    // if(!this.isShowSchoolBank){
+    // 	this.$nextTick(() => {
+    // 		this.$refs.baseFilter.filterOriginChange(this.$store.state.userInfo.TEAMModelId)
+    // 	})
+    // }
+  },
+  computed: {
+    isSchool() {
+      return this.$route.path.includes('school');
+    }
+  }
+}
+</script>
+<style src="./TestPaperList.less" lang="less" scoped></style>
+
+<style>
+.preview-modal .ivu-modal-body {
+  height: 650px;
+  overflow: scroll;
+}
+
+.pl-content-wrap .ivu-page {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  margin: 20px 0;
+}
+
+.pl-review-wrap-left .paper-base-info {
+  top: 0 !important;
+}
+
+.pl-container .ivu-checkbox-inner {
+  display: none !important;
+}
+
+.random-pick-modal .question-condition-wrap .question-condition-item .condition-label,
+.random-pick-modal .question-condition-wrap .question-condition-item {
+  color: #000;
+  font-size: 14px;
+}
+
+.random-pick-modal .question-num-item .ivu-select-selection {
+  color: #333333;
+}
+
+.random-pick-modal .question-num-item .ivu-input-number,
+.random-pick-modal .question-num-item .ivu-input-number-small input {
+  color: #000;
+}
+
+.random-pick-modal .auto-create-wrap {
+  background: transparent;
+}
+
+.random-pick-modal .auto-create-name {
+  color: #515a6e;
+  text-align: left;
+  font-size: 14px;
+  font-weight: normal;
+  margin-top: 0;
+}
+
+.random-pick-modal .question-condition-wrap .ivu-btn {
+  height: 40px;
+  line-height: 40px;
+  margin-top: 0;
+}
+
+.random-pick-modal .question-condition-wrap {
+  overflow: hidden;
+}
+
+.random-pick-modal .ivu-tag {
+  background: transparent !important;
+}
+
+.random-pick-modal .ivu-tag-color-white {
+  color: #515a6e !important;
+}
+
+.random-pick-modal .ivu-tag .ivu-icon-ios-close {
+  color: #515a6e !important;
+}
+
+.action-tools {
+    flex-wrap: wrap;
+    row-gap: 6px;
+}
+</style>

+ 86 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/index.less

@@ -0,0 +1,86 @@
+.bank-container {
+    position:relative;
+	// font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+	.back-to-top {
+	    position:fixed;
+	    right:30px;
+	    bottom:20px;
+	    height:48px;
+	    width:50px;
+	    background:#aaa;
+	    z-index:99999;
+		border-radius: 50%;
+	    cursor:pointer;
+	}
+	
+	 .back-to-top:hover {
+	    background:rgb(128,128,128);
+	}
+	
+	    .back-to-top .ivu-icon {
+	        font-size:26px;
+	        color:white;
+	    }
+
+    .ev-list-operation {
+        position: absolute;
+        top: 45px;
+        right: 10px;
+        height: 60px;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: space-between;
+		z-index: 99;
+		
+
+        .ivu-checkbox-wrapper {
+            font-size: 14px;
+        }
+
+        .ivu-checkbox {
+            margin: 10px;
+        }
+
+        .import-exercise {
+            font-size: 13px;
+            display: flex;
+			
+			.bank-tools-btn{
+				color: var(--second-text-color);
+				margin-right: 30px;
+				font-weight: 500;
+				cursor: pointer;
+				display: flex;
+				align-items: center;
+                color: #40A8F0;
+			}
+
+        }
+
+        .ivu-icon {
+            font-size: 18px;
+            cursor: pointer;
+        }
+
+        .ivu-badge-count {
+            right: 12px;
+        }
+
+        .ivu-checkbox-checked {
+            .ivu-checkbox-inner {
+                background: #01b4ef;
+                border-color: #01b4ef;
+            }
+        }
+    }
+}
+
+.bank-container .ivu-tabs-nav .ivu-tabs-tab:active,
+.bank-container .ivu-tabs-nav .ivu-tabs-tab:hover,
+.bank-container .ivu-tabs-nav .ivu-tabs-tab-active,
+.bank-container .ivu-tabs-nav .ivu-tabs-tab {
+    color: #515050;
+    font-weight: bold;
+    font-size: 16px;
+}

+ 388 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/bank/index.vue

@@ -0,0 +1,388 @@
+<template>
+    <div class="bank-container" ref="bankContainer">
+        <!-- <div class="back-to-top flex-col-center" :title="$t('evaluation.backToTop')" @click="onBackToTop">
+            <Icon type="ios-arrow-up" />
+        </div> -->
+        <Tabs :value="tabName" name="listTab" @on-click="onTabClick" :animated="false" style="position: relative;">
+					<button class="editOnExternalBtn" @click="editOnExternal">
+              <Icon type="md-create" size="20" color="#42a9f0"/>
+					</button>
+					<TabPane :label="$t('evaluation.index.item')" name="exercise" tab="listTab">
+					<ExerciseList ref="exList" @chooseQuChange="onItemCheckChange"></ExerciseList>
+					</TabPane>
+					<TabPane :label="$t('evaluation.index.paper')" name="paper" tab="listTab">
+					<PaperList ref="paperList" @onPaperClick="isShowBackList = true" @onBackToTop="onBackToTop" @editQuickPaper="editQuickPaper" @backToPaperList="onShowPaperList"></PaperList>
+					</TabPane>
+        </Tabs>
+        <Modal v-model="paperExamModal" width="950px" :title="$t('evaluation.quickPaper.title')" class-name="preview-modal">
+        <BaseQuickPaper ref="quickPaperRef" :editPaper="fullPaperJson" @finish="onQuickFinish" v-if="paperExamModal"></BaseQuickPaper>
+        <div slot="footer">
+            <Button @click="paperExamModal = false">{{ $t('homework.form.cancel') }}</Button>
+            <Button type="primary" @click="doSaveQuickPaper" :loading="quickLoading">{{ $t('homework.form.save') }}</Button>
+        </div>
+        </Modal>
+    </div>
+</template>
+<script>
+	import BaseImport from "../components/BaseImport";
+	import PaperList from "./TestPaperList";
+	import ExerciseList from "./ExerciseList";
+	import { callDesktopAppMethods } from '@/utils/callDesktopAppMethods.js'
+
+	export default {
+		name: "schoolBank",
+		components: {
+			PaperList,
+			ExerciseList,
+			BaseImport
+		},
+		data() {
+			return {
+				fullPaperJson:null,
+				quickLoading: false,
+				checkItemArr: [],
+				isLoading: true,
+				tabName: "exercise",
+				currentTab: "exercise",
+				isAllOpen: false,
+				isShowBackList: false,
+				paperExamModal: false
+			};
+		},
+		methods: {
+			doSaveQuickPaper() {
+				this.$refs.quickPaperRef.onConfirmSave();
+			},
+			editQuickPaper(fullPaperJson){
+				this.paperExamModal = true
+				this.fullPaperJson = fullPaperJson
+				console.error(fullPaperJson.attachments)
+			},
+			onQuickFinish(){
+				this.quickLoading = false
+				this.paperExamModal = false
+				this.fullPaperJson = null
+				this.$refs.paperList.doFilter()
+				this.$Message.success(this.$t('result.tip4'))
+ 			},
+			onItemCheckChange(arr) {
+				this.checkItemArr = arr;
+			},
+			/* 批量删除 */
+			doBatchDelete() {
+				let exListVm = this.$refs.exList;
+
+				this.$Modal.confirm({
+					title: this.$t("evaluation.newExercise.modalTip"),
+					content: this.$t("stuAccount.tips2Content3"),
+					onOk: () => {
+						let ids = this.checkItemArr.map((i) => i.id);
+						let item = this.checkItemArr[0];
+						exListVm.dataLoading = true;
+						this.$api.newEvaluation
+							.DeleteExamItem({
+								ids: ids,
+								code: item.code,
+								scope: item.scope
+							})
+							.then((res) => {
+								if (res.code == 200) {
+									exListVm.isSelectAll = false;
+									exListVm.selectedArr = [];
+									exListVm.doFilter();
+									this.checkItemArr = [];
+									this.$Message.success(this.$t("evaluation.deleteSuc"));
+								} else {
+									this.$Message.warning(this.$t("evaluation.deleteFail"));
+									exListVm.dataLoading = false;
+								}
+							})
+							.catch((err) => {
+								console.log(err);
+								this.$Message.warning(this.$t("evaluation.deleteFail"));
+								exListVm.dataLoading = false;
+							});
+					}
+				});
+			},
+			onBackToTop() {
+			//	this.$refs.bankContainer.scrollIntoView();
+			},
+			onTabClick(val) {
+				this.currentTab = val;
+				this.$router.replace({
+					path: this.$route.path,
+					query: {
+						params: JSON.stringify({
+							tab: val
+						})
+					}
+				});
+				this.onBackToTop();
+			},
+			/* 展示试卷列表 */
+			onShowPaperList() {
+				this.$refs.paperList.isPreview = false;
+				this.$refs.paperList.isShowSheet = false;
+				this.isShowBackList = false;
+				this.$refs.bankContainer.scrollIntoView();
+			},
+			/* 编辑当前预览的试卷 */
+			onEditPaper() {
+				this.$refs.paperList.goToPaper(this.$refs.paperList.curPaper);
+			},
+
+			/** 切换全部展开与折叠 */
+			onHandleToggle() {
+				this.$refs.exList.onHandleToggle(this.isAllOpen);
+				this.isAllOpen = !this.isAllOpen;
+			},
+
+			/* 跳转到分享页面 */
+			goShare() {
+				this.$router.push({
+					name: "shareCenter",
+					params: {
+						tabName: this.currentTab
+					}
+				});
+			},
+			/* 确认是否允许携带手机号进行注册 */
+			doConfirmAgree() {
+				return new Promise((r, j) => {
+					this.$Modal.confirm({
+						title: "授权提示",
+						content: "检测到您暂未绑定学科网账号,是否允许以醍摩豆云平台关联手机号进行认证?",
+						okText: "允许",
+						cancelText: "拒绝",
+						onOk: () => {
+							r(1);
+						},
+						onCancel: () => {
+							r(0);
+						}
+					});
+				});
+			},
+
+			/* 跳转学科网 */
+			doXkwAuth() {
+				this.$api.auth.checkBind({}).then(async (res) => {
+					// 判断是否已经绑定学科网
+					let isBind = res.auth.find((i) => i.type === "xkw");
+					// 如果没有绑定 则询问用户是否允许携带手机号进行注册
+					let agree = isBind ? 1 : await this.doConfirmAgree();
+					// 判断资源类型
+					let module = this.currentTab === "exercise" ? "item" : "paper";
+					// 存到本地
+					localStorage.setItem("xkw_module", module);
+					// 发送授权请求
+					this.$api.auth
+						.xkwOauth({
+							module: module,
+							agree: agree
+						})
+						.then((res) => {
+							window.open(res.redirect);
+						});
+				});
+			},
+			// 跳转页面,进行多分题库挑选
+			dodfAuth() {
+				this.$router.push({
+					name: this.isSchool ? 'schoolDf' : 'privateDf',
+				})
+			},
+
+			/**
+			 * exList的collapseList变化
+			 * @param list
+			 */
+			onToggleChange(list) {
+				this.isAllOpen = list.length !== 0;
+			},
+
+			/** 返回创建试题页面 */
+			goCreateExercise() {
+				this.$router.push({
+					name: this.$route.name === "personalBank" ? "newPrivateExercise" : "newSchoolExercise",
+					params: {
+						scope: this.$route.name === "personalBank" ? "private" : "school"
+					}
+				});
+			},
+			/* 快速组卷纸本测验 */
+			goPaperExam() {
+				this.paperExamModal = true;
+				this.fullPaperJson = null
+			},
+
+			goXkwPick() {
+				this.$api.auth
+					.xkwOauth({
+						module: "ezj",
+						agree: 1
+					})
+					.then((res) => {
+						this.$router.push({
+							name: "xkwPage",
+							params: {
+								iframeSrc: res.redirect
+							}
+						});
+					});
+			},
+
+			/** 前往组卷页面 */
+			goCreatePaper(type, isXkwMode) {
+				if (isXkwMode) {
+					// 发送授权请求
+					this.$api.auth
+						.xkwOauth({
+							module: "ezj",
+							agree: 1
+						})
+						.then((res) => {
+							this.$router.push({
+								name: this.$route.name === "personalBank" ? "newPrivatePaper" : "newSchoolPaper",
+								params: {
+									scope: this.$route.name === "personalBank" ? "private" : "school",
+									type: type,
+									isXkwMode: true,
+									iframeSrc: res.redirect,
+									isFromItemBank: this.currentTab === "exercise"
+								}
+							});
+						});
+				} else {
+					this.$router.push({
+						name: this.$route.name === "personalBank" ? "newPrivatePaper" : "newSchoolPaper",
+						params: {
+							scope: this.$route.name === "personalBank" ? "private" : "school",
+							type: type,
+							isXkwMode: isXkwMode,
+							isFromItemBank: this.currentTab === "exercise"
+						}
+					});
+				}
+			},
+			editOnExternal() {
+				const isSchool = this.$route.path.includes('school');
+				const path = `/home/evaluation/${isSchool ? 'schoolBank' : 'personalBank'}`;
+
+				const paperId = this.$refs.paperList._data.curPaper?.id ?? null;
+				const params = {
+					tab: this.currentTab,
+					paperId 
+				};
+
+				const query = `?params=${JSON.stringify(params)}`;
+				callDesktopAppMethods.openExternal(path + query);
+			}
+		},
+		mounted() {
+
+			if (this.$route.params.tabName) {
+				this.currentTab = this.$route.params.tabName;
+				this.tabName = this.$route.params.tabName;
+			}
+
+			if (this.$route.name === "schoolBank" && this.$route.params.activePeriod) {
+				this.$EventBus.$emit("showSchoolBank", this.$route.params.activePeriod);
+			}
+
+			if (this.$route.query.params) {
+				const params = JSON.parse(this.$route.query.params);
+				const tab = params.tab;
+
+				this.tabName = tab;
+				this.onTabClick(tab);
+			}
+		},
+		computed: {
+			isSchool() {
+				return this.$route.name === "schoolBank";
+			},
+			hasSchool() {
+				return this.$store.state.userInfo.hasSchool;
+			},
+			paperScrollTop() {
+				return this.$store.state.totalAnalysis.paperScrollTop;
+			},
+			/* 判断是否为国际站 */
+			inGlobalSite() {
+				// return localStorage.getItem("location") === "Global";
+				return this.$store.state.config.srvAdr === 'Global'
+			},
+			hasXkwAuth() {
+				// if (localStorage.school_profile) {
+				//   let schoolProfile = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8"))
+				//   return schoolProfile.schoolShows && schoolProfile.schoolShows.find(i => i.type === 'xkw')
+				// } else {
+				//   return false
+				// }
+				return true;
+			},
+			isTestSite() {
+				return (this.$store.state.config.srvAdrType === "test" && this.$store.state.config.srvAdr === 'China') || ((this.$store.state.config.srvAdrType === "product" || this.$store.state.config.srvAdrType === "rc") && (this.$store.state.userInfo.schoolCode === "habook" || this.$store.state.userInfo.schoolCode === "hbcn") && this.$store.state.config.srvAdr === 'China')
+			}
+		},
+		beforeRouteLeave(to, from, next) {
+			// 如果是从试卷库预览跳转到生成答题卡 则需要保留试卷库页面的缓存状态
+			if (to.name === "answerSheet" && from.name === "schoolBank") {
+				from.meta.isKeep = true;
+			} else {
+				from.meta.isKeep = false;
+			}
+			next();
+		}
+	};
+</script>
+<style src="./index.less" lang="less" scoped></style>
+<style>
+	.bank-container .ivu-tabs {
+		overflow: unset;
+	}
+
+	.bank-container .ivu-tabs-bar {
+		/* position: sticky; */
+		top: 0;
+		padding: 8px 15px;
+		z-index: 99;
+		background-color: var(--body-bg);
+		margin-bottom: 2rem;
+	}
+
+	.bank-container .ivu-tabs-nav .ivu-tabs-tab:active,
+	.bank-container .ivu-tabs-nav .ivu-tabs-tab-active,
+	.bank-container .ivu-tabs-nav .ivu-tabs-tab {
+		color: var(--second-text-color);
+		/* font-weight: bold; */
+		font-size: 14px;
+	}
+
+	.bank-container .ivu-tabs-nav .ivu-tabs-tab-active {
+		color: var(--tabs-text-color);
+		font-weight: bold;
+	}
+
+	.bank-container .ivu-tabs-ink-bar {
+		height: 2px;
+		background: var(--tabs-bottom-color);
+		/*margin-left:20px;*/
+	}
+
+	.ivu-tabs-bar {
+		margin-bottom: 0 !important;
+	}
+
+	.editOnExternalBtn {
+		all: unset;
+		position: absolute;
+		z-index: 99;
+		top: 28px;
+		right: 20px;
+		transform: translateY(-50%);
+		cursor: pointer;
+	}
+</style>

+ 474 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseChild.vue

@@ -0,0 +1,474 @@
+<template>
+  <div class="child-wrap" ref="childRef">
+    <div class="child-item" v-for="(item,index) in children" :key="index" @click.stop="onQuestionToggle(index, item.id, $event)">
+      <!-- 工具栏部分 -->
+      <div class="child-tools-wrap">
+        <div class="child-tools-t flex-row-center" v-if="isShowScore" @click.stop="handleChildEdit(item,index)">
+          {{$t('evaluation.editItem')}}{{$t('evaluation.child')}}
+        </div>
+        <div class="child-tools-t flex-row-center" v-if="isChangePaper" @click.stop="handleChildEdit(item,index)">
+          调整{{$t('evaluation.child')}}
+        </div>
+        <div class="child-tools-t flex-row-center" v-if="canFix" @click.stop="handleFixChild(item,index)">
+          {{ $t('evaluation.fixTip1') }}
+        </div>
+      </div>
+      <div class="child-item-question">
+        <span class="child-item-question-order">({{ index + 1 }})</span>
+        <p class="child-item-question-content" v-html="item.question"></p>
+      </div>
+      <!-- 选项部分 -->
+      <div v-for="(option,optionIndex) in item.option" :key="optionIndex" class="child-item-option" style="pointer-events:none">
+        <div>
+          <div class="child-item-option-order">{{String.fromCharCode(64 + parseInt(optionIndex+1))}} : </div>
+          <div class="child-item-option-text" v-html="option.value"></div>
+        </div>
+      </div>
+      <div class="item-btn-toggle">
+        <template v-if="!inBank">
+          <template v-if="isShowScore || isChangePaper">
+            <InputNumber :min="0" v-model="item.score" v-if="df" :step="0.5" @on-change="val => scoreChange(val, item.pid, item.id)" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;" @click.stop>
+            </InputNumber>
+            <InputNumber :min="0" v-model="item.score" v-else :step="0.5" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;" @click.stop>
+            </InputNumber>
+          </template>
+          <span v-else style="margin-right: 10px;color: #2db7f5;font-weight: bold;">{{ item.score }}</span>
+          <span style="margin-right: 20px;">{{$t('evaluation.paperList.score')}}</span>
+        </template>
+        <Icon :type="collapseList.includes(children.indexOf(item)) ? 'ios-arrow-dropup' : 'ios-arrow-dropdown'" />
+      </div>
+      <transition name="slide">
+        <div v-if="collapseList.includes(children.indexOf(item))" class="toggle-area">
+          <!-- 答案展示部分 -->
+          <div class="item-explain">
+            <span class="explain-title">【{{$t('evaluation.answer')}}】</span>
+            <div class="item-explain-details">
+              <!-- 问答题答案 -->
+              <div v-if="item.type === 'subjective' || item.type === 'complete'">
+                <span v-for="(answer,index) in item.answer" :key="index" v-html="item.answer && item.answer.length ? answer : $t('utils.noData')"></span>
+              </div>
+              <!-- 问答题答案 -->
+              <div v-else-if="item.type === 'judge'">
+                <span>{{ item.answer && item.answer.length ? (item.answer[0] === 'A' ? $t('evaluation.isTrue') : $t('evaluation.isFalse')) : $t('evaluation.answer') }}</span>
+              </div>
+              <!-- 其余题型答案 -->
+              <div v-else>
+                <span :class="[ item.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in item.answer" :key="index" v-html="answer"></span>
+              </div>
+            </div>
+          </div>
+          <!-- 解析部分 -->
+          <div class="item-explain">
+            <span class="explain-title">【{{$t('evaluation.explain')}}】</span>
+            <div class="item-explain-details">
+              <span v-html="item.explain || $t('utils.noData')"></span>
+            </div>
+          </div>
+          <!-- 知识点部分 -->
+          <div class="item-explain">
+            <span class="explain-title">【{{$t('evaluation.knowledgePoints')}}】</span>
+            <div class="item-explain-details">
+              <span v-if="!item.knowledge || !item.knowledge.length">{{ $t('utils.noData') }}</span>
+              <div v-else>
+                <span v-for="(point,index) in item.knowledge" :key="index" class="item-point-tag">
+                  <span>{{ point }}</span>
+                </span>
+              </div>
+            </div>
+          </div>
+          <!-- 认知层次部分 -->
+          <div class="item-explain">
+            <span class="explain-title" :style="{ color:isShowAnalysis ? '#099209' : '#10abe7' }">【{{$t('evaluation.field')}}】</span>
+            <div class="item-explain-details">
+              <span>{{ exersicesField[item.field - 1] }}</span>
+            </div>
+          </div>
+          <!-- 补救资源部分 -->
+          <div class="item-explain">
+            <span class="explain-title">【{{ $t('evaluation.newExercise.repair') }}】</span>
+            <div class="item-explain-details">
+              <div v-if="item.repair && item.repair.length" style="display: flex;flex-wrap: wrap;">
+                <div class="repair-item" v-for="(link,index) in item.repair">
+                  <img :src="$tools.getFileThum(link.type,link.name)" width="20" />
+                  <span class="repair-item-link" @click.stop="onRepairLinkClick(link)">{{ link.name }}</span>
+                </div>
+              </div>
+              <span v-else>{{ $t('utils.noData') }}</span>
+            </div>
+          </div>
+          <div class="item-explain" v-if="isShowAnalysis">
+            <span class="explain-title">【{{ $t('totalAnalysis.showAnalysis') }}】</span>
+            <div class="item-explain-details">
+              <AnalysisItemTable :analysisJson="analysisJson[index]"></AnalysisItemTable>
+              </br>
+              <OptionsTable v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'" :options="item.option.map(i => i.code)" :optionRate="optionRate[index]" :answer="item.answer"></OptionsTable>
+              </br>
+              <Row>
+                <Col span="12" v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'">
+                <BaseRateLine :ids="'cR1' + item.id" :echartsData="getOptionLineData(item,index)"></BaseRateLine>
+                </Col>
+                <Col span="12">
+                <BaseLine :ids="'cr' + item.id" :echartsData="analysisJson[index]"></BaseLine>
+                </Col>
+              </Row>
+              <Row>
+                <Col span="24">
+                <BaseTestSingleScatter :ids="'singleChildScatter' + item.id" :scatterData="scatterData" :currentIndex="childIndex(index)"></BaseTestSingleScatter>
+                </Col>
+              </Row>
+            </div>
+          </div>
+        </div>
+      </transition>
+    </div>
+
+    <!-- 添加子题弹窗 -->
+    <Modal v-model="editChildModal" width="1080" class="edit-exercise-modal" :styles="{top: '20px'}" :mask-closable="false">
+      <div class="modal-header" slot="header">{{$t('evaluation.exerciseList.editChild')}}</div>
+      <!-- <BaseCreateChild v-if="editChildModal" @addFinish='onEditChildFinish' refId="childListEdit" :editItem="curItem" ref="createChildRef"></BaseCreateChild> -->
+      <BaseEditExercise v-if="editChildModal" :exerciseItem="curItem" @onEditSuccess="onEditChildFinish" refId="paperEdit" ref="createChildRef" hidePeriod></BaseEditExercise>
+      <div slot="footer">
+        <Button type="success" @click="onSaveChild">{{$t('evaluation.confirm')}}</Button>
+      </div>
+    </Modal>
+    <!-- 修改试题答案、配分弹窗 -->
+    <Modal v-model="editAnsScoreModal" width="1080" class="edit-exercise-modal" :styles="{top: '20px'}" :mask-closable="false">
+      <div class="modal-header" slot="header">{{$t('evaluation.exerciseList.editChild')}}</div>
+      <BaseEditAnsScore v-if="editAnsScoreModal" :exerciseItem="curItem" @onEditSuccess="onEditChildFinish" refId="paperEdit" ref="paperEdit"></BaseEditAnsScore>
+      <div slot="footer">
+        <Button type="success" @click="onSaveChild">{{$t('evaluation.confirm')}}</Button>
+      </div>
+    </Modal>
+
+    <!-- 补充解析和补救资源弹窗 -->
+    <Modal v-model="addExplainModal" :styles="{top: '20px'}" width="1000px" :title="$t('evaluation.fixTip1')">
+      <BaseFixExamItem ref="fixRef" :fixItem="curItem" @onFixFinish="onFixFinish"></BaseFixExamItem>
+      <div slot="footer">
+        <Button @click="addExplainModal = false">{{$t('evaluation.cancel')}}</Button>
+        <Button type="success" :loading="editLoading" @click="doSaveFixItem">{{$t('evaluation.confirm')}}</Button>
+      </div>
+    </Modal>
+  </div>
+</template>
+<script>
+import '@/utils/Math.uuid'
+import AnalysisItemTable from '@/components/evaluation/AnalysisItemTable'
+import OptionsTable from '@/components/evaluation/OptionsTable'
+import BaseLine from '@/components/student-analysis/total/BaseLine.vue'
+import BaseTestSingleScatter from '@/components/student-analysis/total/BaseTestSingleScatter.vue'
+import BaseRateLine from '@/components/student-analysis/total/BaseRateLine.vue'
+export default {
+  name: 'BaseChild',
+  components: { AnalysisItemTable, OptionsTable, BaseRateLine, BaseLine, BaseTestSingleScatter },
+  props: {
+    children: {
+      type: Array,
+      default: []
+    },
+    totalScore: {
+      type: Number,
+      default: 0
+    },
+    parentIndex: {
+      type: Number,
+      default: 0
+    },
+    analysisJson: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    },
+    optionRate: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    },
+    isShowScore: {
+      type: Boolean,
+      default: false
+    },
+    isChangePaper: { //评测页面需修改答案、配分等
+      type: Boolean,
+      default: false
+    },
+    inBank: {
+      type: Boolean,
+      default: false
+    },
+    isShowAnalysis: {
+      type: Boolean,
+      default: false
+    },
+    canFix: {
+      type: Boolean,
+      default: false
+    },
+    df: {
+      type: Boolean,
+      default: false
+    },
+    scoreChange: {
+      type: Function,
+      require: true,
+      default: null
+    },
+  },
+  data() {
+    return {
+      editLoading: false,
+      addExplainModal: false,
+      curIndex: null,
+      curItem: {},
+      editChildModal: false,
+      collapseList: [],
+      surPlusScore: 0,
+      exersicesField: this.$GLOBAL.EXERCISE_LEVELS(),
+      scatterData: [],
+      editAnsScoreModal: false,
+    }
+  },
+  created() {
+    if (this.children && this.children.length) {
+      this.children.forEach(i => {
+        if (!i.score) {
+          i.score = 0
+        }
+        this.surPlusScore = this.totalScore - i.score
+      })
+    }
+    this.isShowAnalysis && this.getScatterData()
+
+  },
+  methods: {
+    getScatterData() {
+      let analysisJson = JSON.parse(JSON.stringify(this.getAnalysisJson))
+      let curSubjectIndex = analysisJson.subjects.map(i => i.name).indexOf(this.$store.state.totalAnalysis
+        .currentSubject)
+      let result = []
+      analysisJson.paper[curSubjectIndex].value.forEach((exercise, exerciseIndex) => {
+        let obj = {}
+        analysisJson.paperKey.forEach((key, index) => {
+          obj[key] = exercise[index]
+        })
+        result.push(obj)
+      })
+      let arr = result
+      let newArr = []
+      arr.forEach(item => {
+        let arr2 = []
+        arr2.push((+item.X).toFixed(2))
+        arr2.push((item.Y * 100).toFixed(2))
+        arr2.push(item.type)
+        arr2.push(item.id)
+        newArr.push(arr2)
+      })
+      this.scatterData = newArr
+    },
+    onSaveChild() {
+      if(this.isChangePaper) {
+        this.$refs.paperEdit.saveContent(this.curItem.type)
+      } else {
+        this.$refs.createChildRef.getContent(this.curItem.type)
+      }
+    },
+    handleChildEdit(item, index) {
+      this.curItem = item
+      this.curIndex = index
+      this.isChangePaper ? this.editAnsScoreModal = true : this.editChildModal = true
+    },
+    handleFixChild(item, index) {
+      this.curItem = item
+      this.curIndex = index
+      this.addExplainModal = true
+    },
+    /* 保存补充解析的题目 */
+    doSaveFixItem() {
+      this.editLoading = true
+      this.$refs.fixRef.doSave()
+    },
+    /* FIX完成 */
+    onFixFinish(newItem) {
+      // this.exerciseList.splice(this.currentExerciseIndex, 1, newItem)
+      // this.curTypeItems.splice(this.curIndex, 1, newItem)
+      // this.$emit('dataUpdate', this.exerciseList)
+      this.addExplainModal = false
+      this.editLoading = false
+      this.$emit('onEditChildFinish', newItem)
+      this.$Message.success(this.$t('evaluation.editSuc'))
+    },
+    /* 补救资源点击事件 */
+    onRepairLinkClick(link) {
+      window.open(/^(http:|https:)/i.test(link.blobUrl) ? link.blobUrl : "http://" + link.blobUrl)
+    },
+    onEditChildFinish(item) {
+      console.log(item)
+      this.isChangePaper ? this.editAnsScoreModal = false : this.editChildModal = false
+      this.$emit('onEditChildFinish', item)
+    },
+    getOptionLineData(item, index) {
+      let result = []
+      console.log(this.optionRate)
+      let n = this.optionRate[index]
+      let total = this.$store.state.totalAnalysis.analysisJson.all.total // 取总人数
+      let phCount = Math.floor(total * 0.27) //取高分组人数
+      let plCount = Math.ceil(total * 0.27) //取低分组人数
+      item.option.map(i => i.code).forEach(key => {
+        result.push({
+          option: key,
+          rate: n.record[key] ? ((n.record[key] / total) * 100).toFixed(1) : 0,
+          PH: n.ph[key] ? ((n.ph[key] / phCount) * 100).toFixed(1) : 0,
+          PL: n.pl[key] ? ((n.pl[key] / plCount) * 100).toFixed(1) : 0,
+        })
+      })
+      console.log(result)
+      return result
+    },
+
+    onQuestionToggle(index, id, e) {
+      let curClassName = e.target.className;
+      if (
+        curClassName === "item-tools" ||
+        curClassName === "richText-video" ||
+        curClassName === "richText-audio"
+      )
+        return;
+      e.stopPropagation();
+      let listIndex = this.collapseList.indexOf(index);
+      if (listIndex > -1) {
+        this.collapseList.splice(listIndex, 1);
+      } else {
+        this.collapseList.push(index);
+        /* let exerciseItemDom = e.path.filter(
+          (i) => i.className === "exercise-item"
+        ); */
+        // if (exerciseItemDom.length) {
+        // 	this.pageScrollTo(exerciseItemDom[0].offsetTop + 240);
+        // }
+      }
+
+      this.$emit("toggleChange", this.collapseList);
+    },
+
+  },
+
+  computed: {
+    // 获取最新试题分析模块数据
+    getAnalysisJson() {
+      return this.$store.state.totalAnalysis.analysisJson
+    },
+    childIndex() {
+      return index => {
+        let childIndex = this.parentIndex + index + 3
+        return childIndex > 9 ? '' + childIndex : '0' + childIndex
+      }
+    }
+  },
+
+  watch: {
+    children: {
+      handler(n) {
+        this.$nextTick(() => {
+          // this.$MathJax.MathQueue(this.$refs.childRef);
+        })
+      }
+    },
+    totalScore: {
+      handler(n) {
+
+      }
+    },
+  }
+
+}
+</script>
+
+<style lang="less" scoped>
+@import "../../../components/evaluation/ExerciseList.less";
+</style>
+<style lang="less" scoped>
+.child-wrap {
+  .child-item {
+    position: relative;
+    margin: 20px 10px 0 20px;
+    padding: 10px;
+    border: 1px solid transparent;
+    font-size: 14px;
+
+    &:hover {
+      border: 1px solid #01b4ef;
+      .child-tools-wrap,
+      .child-tools-t {
+        display: inline-flex;
+      }
+    }
+
+    .child-tools-wrap {
+      position: absolute;
+      right: -2px;
+      top: -30px;
+      height: 30px;
+      margin: 0;
+      padding: 0;
+      background: #11baa1;
+      display: none;
+    }
+
+    .child-tools-t {
+      height: 100%;
+      padding: 0 15px;
+      float: left;
+      color: white;
+      cursor: pointer;
+      font-size: 14px;
+
+      &:hover {
+        background-color: #4a5ae6;
+      }
+    }
+
+    .item-btn-toggle {
+      position: absolute;
+      right: 20px;
+      top: 5px !important;
+    }
+
+    &-question {
+      max-width: 85%;
+      // color:#01b087;
+
+      &-order {
+        // color: #10abe7;
+        display: inline-block;
+        width: 20px;
+        vertical-align: middle;
+      }
+
+      &-content {
+        display: inline-block;
+        margin-left: 5px;
+        width: calc(90% - 20px);
+        vertical-align: text-top;
+      }
+    }
+
+    &-option {
+      margin: 10px 5px;
+      margin-left: 20px;
+
+      &-order {
+        display: inline-block;
+        width: 25px;
+      }
+
+      &-text {
+        display: inline-block;
+        margin-left: 5px;
+        width: calc(90% - 25px);
+        vertical-align: text-top;
+      }
+    }
+  }
+}
+</style>

+ 202 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseChildList.vue

@@ -0,0 +1,202 @@
+<template>
+	<div>
+		<div class="child-list-wrap">
+			<draggable class="list-group" tag="div" v-model="childrenList" v-bind="dragOptions" @start="drag = true" @end="onDragEnd">
+				<transition-group type="transition" :name="!drag ? 'flip-list' : null">
+					<div class="child-item" v-for="(item,index) in childrenList" :key="index">
+						<div class="child-item-question">
+							<span class="child-item-question-order">({{ index + 1}})</span>
+							<p class="child-item-question-content" v-html="item.question"></p>
+						</div>
+						<div class="item-tools-wrap">
+							<div class="item-tools-t flex-row-center" @click="onEditChild(item,index)" v-if="!isHideEdit">
+								<Icon type="ios-brush-outline" />{{$t('evaluation.editItem')}}</div>
+							<div class="item-tools-t flex-row-center" @click="onDeleteChild(item,index)">
+								<Icon type="ios-archive-outline" />{{$t('evaluation.deleteItem')}}</div>
+						</div>
+					</div>
+				</transition-group>
+			</draggable>
+		</div>
+		<!-- 添加子题弹窗 -->
+		<Modal v-model="editChildModal" width="1080" footer-hide class="">
+			<div class="modal-header" slot="header">{{$t('evaluation.exerciseList.editChild')}}</div>
+			<BaseCreateChild v-if="editChildModal" @addFinish='onEditChildFinish' refId="childListEdit" :editItem="curItem" :curPeriodIndex="curPeriodIndex"
+			 :curSubjectIndex="curSubjectIndex"></BaseCreateChild>
+		</Modal>
+	</div>
+</template>
+<script>
+	import draggable from "vuedraggable"
+	export default {
+		name:'BaseChildList',
+		components: {
+			draggable
+		},
+		props: {
+			childList: {
+				type: Array,
+				default: []
+			},
+			curPeriodIndex: {
+				type: Number,
+				default: 0,
+			},
+			curSubjectIndex: {
+				type: Number,
+				default: 0,
+			},
+		},
+		data() {
+			return {
+				editChildModal: false,
+				drag: false,
+				childrenList: [],
+				items:[],
+				curIndex: null,
+				curItem: null
+			}
+		},
+		created() {
+			this.childrenList = this.childList
+		},
+		methods: {
+			
+			// 拖拽完成之后
+			onDragEnd(val) {
+				console.log(this.childrenList)
+				this.$emit('onEditChildFinish', this.childrenList)
+				this.drag = false
+			},
+			
+			onEditChild(item, index) {
+				this.curItem = item
+				this.curIndex = index
+				this.editChildModal = true
+			},
+
+			onEditChildFinish(item) {
+				console.log(item)
+				this.$set(this.childrenList, this.curIndex, item)
+				this.editChildModal = false
+				this.$emit('onEditChildFinish', this.childrenList)
+			},
+
+			onDeleteChild(item, index) {
+				this.$Modal.confirm({
+					title: this.$t('evaluation.newExercise.modalTip'),
+					content: this.$t('evaluation.exerciseList.confirmDelete'),
+					onOk: () => {
+						this.childrenList.splice(index, 1)
+						this.$emit('onEditChildFinish', this.childrenList)
+						this.$emit('onDeleteChild', item)
+					}
+				})
+
+			}
+
+
+		},
+		
+		computed: {
+			dragOptions() {
+				return {
+					animation: 200,
+					group: "description",
+					disabled: false,
+					ghostClass: "ghost"
+				};
+			},
+			isHideEdit(){
+				return sessionStorage.getItem('editorEnv') && sessionStorage.getItem('editorEnv') !== 'normal'
+			}
+		},
+		mounted() {
+			// this.childrenList = this.childList
+		},
+
+		watch: {
+			childList: {
+				handler(newValue) {
+					if (newValue) {
+						console.log(newValue)
+						this.childrenList = JSON.parse(JSON.stringify(newValue))
+					}
+				},
+				deep: true
+			}
+		}
+
+	}
+</script>
+
+<style lang="less">
+	.child-list-wrap {
+		.child-item {
+			position: relative;
+			margin: 10px 0;
+			padding: 20px 10px;
+			border: 1px solid #e6e6e6;
+			cursor: move;
+
+			&-question {
+
+				&-order {
+					display: inline-block;
+					vertical-align: middle;
+				}
+
+				&-content {
+					display: inline-block;
+					margin-left: 5px;
+				}
+				
+				tr,td{
+					border-bottom: 1px solid #ccc;
+					border-right: 1px solid #ccc;
+					padding: 3px 5px;
+				}
+			}
+
+			&:hover {
+				border: 1px solid #01b4ef;
+
+				.item-tools-wrap {
+					display: unset;
+				}
+			}
+		}
+
+		.item-tools-wrap {
+			position: absolute;
+			right: 0;
+			top: -30px;
+			width: auto;
+			height: 30px;
+			margin: 0;
+			padding: 0;
+			background: #01b4ef;
+			display: none;
+		}
+
+		.item-tools-t {
+			height: 100%;
+			padding: 0 15px;
+			float: left;
+			color: white;
+			cursor: pointer;
+		}
+
+		.item-tools-t:hover {
+			background: #4a5ae6;
+		}
+
+		/*横向垂直水平居中*/
+		.flex-row-center {
+			display: flex;
+			flex-direction: row;
+			justify-content: center;
+			align-items: center;
+		}
+	}
+</style>

+ 539 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseCreateChild.vue

@@ -0,0 +1,539 @@
+<template>
+	<div class="ev-container" :id="refId">
+		<div class="display-flex">
+			<div class="exersices-attr my-radio-style">
+				<IconText :text="$t('evaluation.newExercise.field')" :color="'#00b8ff'" :icon="'md-planet'"></IconText>
+				<Select v-model="exerciseField">
+					<Option v-for="(item, index) in fieldList" :value="index" :key="index">{{ item }}</Option>
+				</Select>
+			</div>
+			<div class="exersices-attr my-radio-style">
+				<IconText :text="$t('evaluation.newExercise.knowledge')" :color="'#00b8ff'" :icon="'md-infinite'"></IconText>
+				<Button type="info" style="margin-top: 20px" @click="onSelectPoints" v-if="exercisePoints.length === 0">{{ $t("evaluation.newExercise.choosePoint") }}</Button>
+				<div v-else style="margin-top: 10px">
+					<span v-for="(item, index) in exercisePoints" :key="index" class="exercise-item-point">
+						{{ item }}
+						<span class="exercise-item-point-close"> <Icon type="md-close" @click="onDeletePoint(index)" /></span>
+					</span>
+					<span class="exercise-item-point-modify" @click="selectPointsModal = true">{{ $t("evaluation.newExercise.modify") }}</span>
+				</div>
+			</div>
+		</div>
+		<div class="exersices-attr display-flex">
+			<div class="exersices-attr my-radio-style">
+				<IconText :text="$t('evaluation.newExercise.type')" :color="'#00b8ff'" :icon="'md-pricetags'"></IconText>
+				<RadioGroup v-model="exersicesType" type="button" @on-change="typeChange">
+					<Radio label="single" :disabled="isEdit">{{ $t("evaluation.single") }}</Radio>
+					<Radio label="multiple" :disabled="isEdit">{{ $t("evaluation.multiple") }}</Radio>
+					<Radio label="judge" :disabled="isEdit">{{ $t("evaluation.judge") }}</Radio>
+					<Radio label="complete" :disabled="isEdit">{{ $t("evaluation.complete") }}</Radio>
+					<Radio label="subjective" :disabled="isEdit">{{ $t("evaluation.subjective") }}</Radio>
+					<Radio label="connector" :disabled="isEdit">{{ $t("evaluation.connector") }}</Radio>
+					<Radio label="correct" :disabled="isEdit">{{ $t("evaluation.correct") }}</Radio>
+				</RadioGroup>
+			</div>
+			<div class="exersices-attr edit-exersices-attr-diff my-radio-style">
+				<IconText :text="$t('evaluation.newExercise.diff')" :color="'#00b8ff'" :icon="'md-pulse'"></IconText>
+				<RadioGroup v-model="exersicesDiff" type="button">
+					<Radio label="1" @click.native="diffChange($event, '1')">{{ $t("evaluation.diff1") }}</Radio>
+					<Radio label="2" @click.native="diffChange($event, '2')">{{ $t("evaluation.diff2") }}</Radio>
+					<Radio label="3" @click.native="diffChange($event, '3')">{{ $t("evaluation.diff3") }}</Radio>
+					<Radio label="4" @click.native="diffChange($event, '4')">{{ $t("evaluation.diff4") }}</Radio>
+					<Radio label="5" @click.native="diffChange($event, '5')">{{ $t("evaluation.diff5") }}</Radio>
+				</RadioGroup>
+			</div>
+		</div>
+
+		<BaseSingle v-if="exersicesType === 'single'" ref="single" :editInfo="editInfo"></BaseSingle>
+		<BaseMultiple v-else-if="exersicesType === 'multiple'" ref="multiple" :editInfo="editInfo"></BaseMultiple>
+		<BaseJudge v-else-if="exersicesType === 'judge'" ref="judge" :editInfo="editInfo"></BaseJudge>
+		<BaseCompletion v-else-if="exersicesType === 'complete'" ref="complete" :editInfo="editInfo"></BaseCompletion>
+		<BaseConnector v-else-if="exersicesType === 'connector'" ref="connector" :editInfo="editInfo"></BaseConnector>
+		<BaseCorrect v-else-if="exersicesType === 'correct'" ref="correct" :editInfo="editInfo"></BaseCorrect>
+		<BaseSubjective v-else-if="exersicesType === 'subjective'" ref="subjective" :editInfo="editInfo"></BaseSubjective>
+
+		<!-- 解析的富文本部分 -->
+		<div class="exersices-analysis child-exercise-analysis">
+			<IconText :text="$t('evaluation.explain')" :color="'#2892DD'" :icon="'md-list'" style="margin-bottom: 10px"></IconText>
+			<div>
+				<div ref="analysisEditor" style="text-align: left"></div>
+			</div>
+		</div>
+
+		<!-- 补救的富文本部分 -->
+		<div class="exersices-analysis" v-show="exersicesType !== 'compose'">
+			<IconText :text="$t('evaluation.newExercise.repair')" :color="'#2892DD'" :icon="'md-link'" style="margin-bottom: 10px"></IconText>
+			<BaseRepair ref="childRepairRef" :repairs="relateFileList"></BaseRepair>
+		</div>
+
+		<div class="save-wrap display-flex">
+			<Button type="success" @click="getContent(exersicesType)" :loading="saveLoading">{{ $t("evaluation.newExercise.save") }}</Button>
+		</div>
+
+		<!-- 选择知识点弹窗 -->
+		<Modal v-model="selectPointsModal" :title="$t('evaluation.newExercise.choosePoint')" width="600px" footer-hide class="related-point-modal" style="z-index: 99999">
+			<BasePoints v-if="selectPointsModal" ref="pointRef" :period="schoolInfo.period[curPeriodIndex].id" :subject="schoolInfo.period[curPeriodIndex].subjects[curSubjectIndex].id" @onCheckChange="onCheckChange" @onCancel="selectPointsModal = false" :points="exercisePoints" :scope="curScope"></BasePoints>
+		</Modal>
+	</div>
+</template>
+<script>
+	import "videojs-contrib-hls.js/src/videojs.hlsjs";
+	import IconText from "@/components/evaluation/IconText.vue";
+	import BaseSingle from "@/view/evaluation/types/BaseSingle.vue";
+	import BaseMultiple from "@/view/evaluation/types/BaseMultiple.vue";
+	import BaseCompletion from "@/view/evaluation/types/BaseCompletion.vue";
+	import BaseJudge from "@/view/evaluation/types/BaseJudge.vue";
+	import BaseSubjective from "@/view/evaluation/types/BaseSubjective.vue";
+	import BaseCorrect from "@/view/evaluation/types/BaseCorrect.vue";
+	import BaseConnector from "@/view/evaluation/types/BaseConnector.vue";
+	import NewChooseContent from "@/components/selflearn/NewChooseContent";
+	import E from "wangeditor";
+	// 默认创建题目模板
+	const defaultExercise = {
+		question: "",
+		option: [],
+		level: 1,
+		answer: [],
+		explain: "",
+		type: "",
+		answerType: "text",
+		useAutoScore: false,
+		answerLang: "en-US"
+	};
+	export default {
+		name: "BaseCreateChild",
+		components: {
+			IconText,
+			BaseSingle,
+			BaseJudge,
+			BaseMultiple,
+			BaseCompletion,
+			BaseSubjective,
+			BaseCorrect,
+			BaseConnector,
+			NewChooseContent
+		},
+		props: {
+			editItem: {
+				type: Object,
+				default: null
+			},
+			refId: {
+				type: String,
+				default: "createChild"
+			},
+			curPeriodIndex: {
+				type: Number,
+				default: 0
+			},
+			curSubjectIndex: {
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				isRelatedContent: false,
+				selectPointsModal: false,
+				isEdit: false,
+				isFalse: false,
+				isLoading: false,
+				relateFileList: [],
+				editInfo: {},
+				schoolInfo: {},
+				saveLoading: false,
+				exersicesType: "single", //single
+				exerciseField: 0,
+				exercisePeriod: 0,
+				exerciseGrade: [],
+				exerciseSubject: 0,
+				exerciseScope: 0,
+				exercisePoints: [],
+				scopeList: [this.$t("evaluation.filter.schoolBank"), this.$t("evaluation.filter.privateBank")],
+				fieldList: [this.$t("evaluation.level1"), this.$t("evaluation.level2"), this.$t("evaluation.level3"), this.$t("evaluation.level4"), this.$t("evaluation.level5"), this.$t("evaluation.level6")],
+				periodList: [],
+				gradeList: [],
+				subjectList: [],
+				exersicesDiff: "1",
+				analysisContent: "",
+				repairContent: "",
+				stemContent: "",
+				analysisEditor: null,
+				repairEditor: null,
+				curId: ""
+			};
+		},
+		created() {
+			this.getSchoolInfo();
+		},
+		methods: {
+			getSchoolInfo() {
+				this.$store.dispatch("user/getSchoolProfile").then((res) => {
+					let schoolBaseInfo = res.school_base;
+					if (schoolBaseInfo) {
+						this.schoolInfo = schoolBaseInfo;
+						if (schoolBaseInfo.period.length) {
+							this.gradeList = schoolBaseInfo.period[0].grades;
+							this.subjectList = schoolBaseInfo.period[0].subjects;
+						}
+					}
+				});
+			},
+
+			onSelectPoints() {
+				if (this.hasSchool) {
+					console.log(this.schoolInfo);
+					console.log(this.curPeriodIndex);
+					console.log(this.curSubjectIndex);
+					this.selectPointsModal = true;
+				} else {
+					this.$Message.warning(this.$t("evaluation.newExercise.noSchoolTip"));
+				}
+			},
+
+			onSelectFile(val) {
+				this.relateFileList = val.files;
+			},
+
+			onConfirmRelate() {
+				console.log(this.relateFileList);
+				this.isRelatedContent = false;
+			},
+
+			async getContent(type) {
+				let exerciseItem = this.editInfo && this.editInfo.id ? JSON.parse(JSON.stringify(this.editInfo)) : Object.assign({}, defaultExercise);
+				switch (type) {
+					case "single":
+						this.$refs.single.doSave();
+						exerciseItem.question = this.$refs.single.stemContent;
+						exerciseItem.option = this.checkOptionNull(this.$refs.single.optionsContent) ? this.$refs.single.optionsContent : null;
+						exerciseItem.type = this.exersicesType;
+						exerciseItem.level = +this.exersicesDiff;
+						exerciseItem.explain = this.analysisContent;
+						exerciseItem.answer = [String.fromCharCode(64 + parseInt(this.$refs.single.trueIndex + 1))];
+						break;
+					case "multiple":
+						this.$refs.multiple.doSave();
+						exerciseItem.question = this.$refs.multiple.stemContent;
+						exerciseItem.option = this.checkOptionNull(this.$refs.multiple.optionsContent) ? this.$refs.multiple.optionsContent : null;
+						exerciseItem.type = this.exersicesType;
+						exerciseItem.level = +this.exersicesDiff;
+						exerciseItem.explain = this.analysisContent;
+						exerciseItem.answer = this.$refs.multiple.multipleAnswers;
+						break;
+					case "judge":
+						exerciseItem.question = this.$refs.judge.stemContent;
+						exerciseItem.option = [];
+						exerciseItem.type = this.exersicesType;
+						exerciseItem.level = +this.exersicesDiff;
+						exerciseItem.explain = this.analysisContent;
+						exerciseItem.answer = [this.$refs.judge.trueAnswer];
+						break;
+					case "complete":
+						exerciseItem.question = this.$refs.complete.stemContent;
+						exerciseItem.option = [];
+						exerciseItem.type = this.exersicesType;
+						exerciseItem.level = +this.exersicesDiff;
+						exerciseItem.explain = this.analysisContent;
+						exerciseItem.answer = [this.$refs.complete.answerContent];
+						exerciseItem.blankCount = this.$refs.complete.blankCount || 1;
+						break;
+					case "subjective":
+						exerciseItem.question = this.$refs.subjective.stemContent;
+						exerciseItem.option = [];
+						exerciseItem.type = this.exersicesType;
+						exerciseItem.level = +this.exersicesDiff;
+						exerciseItem.explain = this.analysisContent;
+						exerciseItem.answer = [this.$refs.subjective.answerContent];
+						exerciseItem.answerType = this.$refs.subjective.answerType; // 02040301 问答题增加作答类型字段
+						exerciseItem.useAutoScore = this.$refs.subjective.useAutoScore; // 02040301 问答题增加作答类型字段
+						exerciseItem.answerLang = this.$refs.subjective.answerLang; // 02040301 问答题增加作答类型字段
+						break;
+					case "connector":
+						exerciseItem.question = this.$refs.connector.stemContent;
+						exerciseItem.option = [];
+						exerciseItem.type = this.exersicesType;
+						exerciseItem.level = +this.exersicesDiff;
+						exerciseItem.explain = this.analysisContent;
+						exerciseItem.answer = [this.$refs.connector.answerContent];
+						break;
+					case "correct":
+						exerciseItem.question = this.$refs.correct.stemContent;
+						exerciseItem.option = [];
+						exerciseItem.type = this.exersicesType;
+						exerciseItem.level = +this.exersicesDiff;
+						exerciseItem.explain = this.analysisContent;
+						exerciseItem.answer = [this.$refs.correct.answerContent];
+						break;
+				}
+				exerciseItem.repair = this.repairContent;
+				exerciseItem.repair = this.formatRepairResource(this.$refs.childRepairRef.datas);
+				exerciseItem.field = this.exerciseField + 1;
+				exerciseItem.knowledge = this.exercisePoints;
+				exerciseItem.children = [];
+				exerciseItem.score = this.editInfo && this.editInfo.score ? this.editInfo.score : 0;
+				exerciseItem.code = this.$parent.$parent.exerciseScope === 0 ? this.$store.state.userInfo.TEAMModelId : this.$store.state.userInfo.schoolCode;
+
+				let confirmSave = await this.checkContent(exerciseItem);
+				// 判断获取的数据是否有空数据以及是否为空字符串
+				if (confirmSave) {
+					// this.saveLoading = true
+					exerciseItem.id = this.curId;
+					console.log("编辑后的小题", exerciseItem);
+					this.$emit("addFinish", exerciseItem);
+				}
+			},
+
+			/* 知识点勾选变动事件 */
+			onCheckChange(val, list) {
+				this.exercisePoints = val;
+				this.selectPointsModal = false;
+			},
+
+			/**
+			 * 移除指定知识点
+			 * @param index
+			 */
+			onDeletePoint(index) {
+				this.exercisePoints.splice(index, 1);
+				// this.$refs.pointRef.$children[1].onDeletePoint(index)
+			},
+
+			// 题目类型转换
+			typeChange(val) {
+				if (this.isEdit) {
+					this.$Message.warning(this.$t("evaluation.newExercise.typeChangeTip"));
+				} else {
+					this.exersicesType = val;
+					this.analysisEditor.txt.clear();
+					// this.repairEditor.txt.clear()
+				}
+			},
+
+			/* 检测补救资源超链接 去除无效链接 */
+			formatRepairResource(list) {
+				if (list.length) {
+					let arr = [];
+					list.forEach((i, index) => {
+						i.blobUrl.forEach((j) => {
+							arr.push({
+								blobUrl: j.url,
+								name: i.name,
+								type: i.type
+							});
+						});
+					});
+					return arr;
+				} else {
+					return [];
+				}
+			},
+
+			// 难度与背景颜色切换
+			diffChange(e, type) {
+				this.exersicesDiff = +type;
+				e.preventDefault();
+				let colorArr = ["#10abe7", "#E8BE15", "#F19300", "#EB5E00", "#D30000"];
+				let ac = document.getElementById(this.refId).getElementsByClassName("edit-exersices-attr-diff")[0].children[1].children;
+				for (let i = 0; i < ac.length; i++) {
+					ac[i].style.background = "#fff";
+					ac[i].style.color = "#515a6e";
+				}
+				e.target.style.background = colorArr[type - 1];
+				e.target.style.color = "#fff";
+			},
+
+			// 提取富文本内容中的文本
+			getSimpleText(html) {
+				var r = /<(?!img|video|audio).*?>/g;
+				return html.replace(r, "");
+			},
+
+			/* 检测数组是否有空数据 */
+			checkOptionNull(arr) {
+				let flag = true;
+				// for (let i = 0; i < arr.length; i++) {
+				// 	if (this.getSimpleText(arr[i].value) === "") {
+				// 		flag = false;
+				// 	}
+				// }
+				return flag;
+			},
+
+			// 排除对象空属性
+			checkContent(Obj) {
+				return new Promise(async (r, j) => {
+					let flag = true;
+					let whiteList = this.getWhiteListByType(Obj.type);
+					console.log("富文本获取的原始试题数据", Obj);
+					for (let key in Obj) {
+						if (whiteList.includes(key) && typeof Obj[key] === "string") {
+							if (!this.getSimpleText(Obj[key])) {
+								flag = await this.emptyConfirm(key);
+								r(flag);
+								return;
+							}
+						} else {
+							if (whiteList.includes(key) && !Obj[key]) {
+								flag = await this.emptyConfirm(key);
+								r(flag);
+								return;
+							}
+						}
+					}
+					r(flag);
+				});
+			},
+
+			/* 确认是否继续保存 */
+			emptyConfirm(key) {
+				return new Promise((r, j) => {
+					this.$Modal.confirm({
+						title: this.$t("evaluation.newExercise.modalTip"),
+						content: `${this.$t("evaluation.currentItem")}${key === "question" ? this.$t("evaluation.newExercise.stem") : this.$t("evaluation.newExercise.option")}${this.$t("evaluation.emptyTip1")}`,
+						onOk: () => {
+							r(true);
+						},
+						onCancel: () => {
+							r(false);
+						}
+					});
+				});
+			},
+
+			// 根据不同题型 给出需要必填选项
+			getWhiteListByType(type) {
+				switch (type) {
+					case "single":
+						return ["question", "option", "answer"];
+						break;
+					case "multiple":
+						return ["question", "option", "answer"];
+						break;
+					case "complete":
+						return ["question", "answer"];
+						break;
+					default:
+						return ["question", "answer"];
+						break;
+				}
+			},
+
+			// 渲染编辑习题内容回显
+			async renderExercise(editItem) {
+				console.log("当前小题", editItem);
+				this.isEdit = true;
+				this.exersicesDiff = editItem.level.toString() || "0";
+				this.exerciseScope = editItem.code === this.$store.state.userInfo.TEAMModelId ? 0 : 1;
+				this.exersicesType = editItem.type;
+				this.exercisePoints = editItem.knowledge || editItem.points;
+				if (editItem.level) {
+					let ac = document.getElementById(this.refId).getElementsByClassName("edit-exersices-attr-diff")[0].children[1].children;
+					for (let i = 0; i < ac.length; i++) {
+						ac[i].style.background = "#fff";
+						ac[i].style.color = "#515a6e";
+					}
+					// 重新渲染题目难度
+					let diffDom = document.getElementById(this.refId).getElementsByClassName("edit-exersices-attr-diff")[0].children[1].children[editItem.level - 1];
+					let colorArr = ["#32CF74", "#E8BE15", "#F19300", "#EB5E00", "#D30000"];
+					diffDom.style.background = colorArr[editItem.level - 1];
+					diffDom.style.color = "#fff";
+				} else {
+					let ac = document.getElementById(this.refId).getElementsByClassName("edit-exersices-attr-diff")[0].children[1].children;
+					for (let i = 0; i < ac.length; i++) {
+						ac[i].style.background = "#fff";
+						ac[i].style.color = "#515a6e";
+					}
+				}
+
+				let schoolInfo = await this.$store.dispatch("user/getSchoolProfile");
+				this.schoolInfo = schoolInfo.school_base;
+
+				this.stemContent = editItem.question;
+				this.relateFileList = editItem.repair || [];
+				this.optionsContent = editItem.option;
+				this.analysisContent = editItem.explain;
+				this.analysisEditor.txt.html(editItem.explain);
+				this.editInfo = JSON.parse(JSON.stringify(editItem));
+				this.exerciseField = this.editInfo.field - 1;
+			}
+		},
+		mounted() {
+			let analysisEditor = new E(this.$refs.analysisEditor);
+			analysisEditor.config.uploadImgShowBase64 = true;
+			(analysisEditor.config.onchange = (html) => {
+				this.analysisContent = html;
+			}),
+				this.$editorTools.initMyEditor(analysisEditor, this);
+			analysisEditor.create();
+			this.analysisEditor = analysisEditor;
+
+			this.curId = this.$tools.guid();
+			if (this.editItem && this.editItem.id) {
+				this.renderExercise(JSON.parse(JSON.stringify(this.editItem)));
+				// 先生成随机ID
+				this.curId = this.editItem.id || this.$tools.guid();
+			}
+
+			if (this.isEdit) {
+				this.exerciseScope = this.editItem.scope === "school" ? 1 : 0;
+			} else {
+				this.exerciseScope = this.$parent.$parent.exerciseScope;
+			}
+			console.log(this.exerciseScope);
+		},
+		computed: {
+			curScope() {
+				return this.exerciseScope === 1 ? "school" : "private";
+			},
+			hasSchool() {
+				return this.$store.state.userInfo.hasSchool;
+			},
+			isPaperEnv() {
+				return sessionStorage.getItem("editorEnv") && sessionStorage.getItem("editorEnv") !== "normal";
+			}
+		},
+		watch: {
+			editItem: {
+				handler(newValue, oldValue) {
+					if (newValue) {
+						console.log("要编辑的小题");
+						console.log(newValue);
+						this.renderExercise(JSON.parse(JSON.stringify(newValue)));
+						this.curId = newValue.id || this.$tools.guid();
+						// this.$refs.pointRef.doReset()
+					}
+				}
+				//immediate:true
+			}
+		}
+	};
+</script>
+<style src="../index/CreateExercises.less" lang="less" scoped></style>
+
+<style>
+	.related-point-modal .ivu-modal-header-inner {
+		font-weight: bold;
+	}
+
+	.exersices-attr .ivu-select-multiple .ivu-tag {
+		height: 32px;
+		line-height: 31px;
+	}
+
+	.exersices-attr .ivu-select-multiple .ivu-tag i {
+		top: 9px;
+	}
+
+	.exersices-attr .ivu-select-multiple .ivu-select-selection .ivu-select-placeholder {
+		line-height: 38px;
+	}
+
+	.child-exercise-analysis .w-e-toolbar {
+		z-index: 0 !important;
+	}
+
+	.child-exercise-analysis .w-e-text-container {
+		position: unset;
+	}
+</style>

+ 110 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseDiffPie.vue

@@ -0,0 +1,110 @@
+<template>
+    <div id="myDiffPie"></div>
+</template>
+
+<script>
+    export default {
+        name: 'BaseDiffPie',
+        props: ['pieId','echartsData'],
+        data() {
+            return {
+                pieData: [],
+				types:this.$GLOBAL.EXERCISE_DIFFS()
+            }
+        },
+        methods: {
+
+            drawLine(data) {
+                let that = this
+                // 基于准备好的dom,初始化echarts实例
+                let myPie = this.$echarts.init(document.getElementById('myDiffPie'), 'chalk')
+
+                // 指定图表的配置项和数据
+                var option = {
+                    title: {
+                        'text':  this.$t('evaluation.echarts.diffPie'),
+                        'left': 'center',
+                        'textStyle': {
+                            'fontSize': 14,
+                            'color': '#595959'
+                        }
+                    },
+					legend: {
+						left: 'center',
+						bottom: 0,
+						type: 'scroll',
+						formatter: function (name,val) {
+						    if(data.length){
+						    	return name + ' (' + data.filter(i => i.name === name)[0].value + that.$t('unit.text10') + ')';
+						    }
+						}
+					},
+                    color: ['#8378ea', '#e7bcf3', '#9fe6b8', '#ff9f7f', '#fb7293', '#e7bcf3', '#8378ea'],
+                    // color: new Array(10).fill("#"+Math.random().toString(16).slice(-6)),
+                    tooltip: {
+                        trigger: 'item',
+                        formatter: '{a} <br/>{b} : {c} ({d}%)'
+                    },
+                    calculable: true,
+                    series: [
+                        {
+                            name: that.$t('evaluation.echarts.diffPie'),
+                            type: 'pie',
+                            radius: [0, 80],
+                            data: data
+                        }
+                    ]
+                }
+
+                // 绘制图表
+                myPie.setOption(option)
+
+                window.addEventListener('resize', function() {
+                    myPie.resize()
+                })
+            }
+        },
+        mounted() {
+			let arr = []
+			let tempArr = []
+			this.echartsData.item.forEach(i => {
+				if(i.type === 'compose' && i.children.length){
+					tempArr.push(...i.children)
+				}else{
+					tempArr.push(i)
+				}
+			})
+			let typeList = this._.groupBy(tempArr, 'level')
+			for(let key in typeList){
+				arr.push({
+					value:typeList[key].length,
+					name: this.types[+key - 1]
+				})
+			}
+			this.drawLine(arr)
+        },
+        computed: {
+            // 获取最新知识点占比饼图数据
+            getPieData() {
+                return this.$store.state.totalAnalysis.knowledgeData
+            }
+        },
+        watch: {
+            echartsData(val) {
+
+            }
+        }
+
+    }
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+    #myDiffPie {
+        width: 100%;
+        height: 320px;
+        margin: 20px auto;
+        display: block;
+    }
+</style>

+ 315 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseEditAnsScore.vue

@@ -0,0 +1,315 @@
+<template>
+    <div class="ev-container" ref="editContainer">
+        <div>
+            <div>
+                <div class="exersices-content">
+                    <IconText :text="$t(`evaluation.${exersicesType}`) + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+                    <p v-html="editInfo.question"></p>
+                </div>
+                <div class="exersices-option" ref="optionRefs">
+                    <template v-if="exersicesType === 'single' || exersicesType === 'multiple'">
+                        <IconText :text="$t(`evaluation.newExercise.${exersicesType}Option`)" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+                        <div v-for="(item, index) in editInfo.option" :key="index" :ref="'optionBox' + index" :class="'editor-wrap-' + item" style="margin-top:10px;display:flex">
+                            <span class="fl-center option-order" :ref="'optionOrder' + index" :data-index="index">{{ renderIndex(index) }}</span>
+                            <!-- <span class="fl-center option-order">{{String.fromCharCode(64 + parseInt(index+1))}}</span> -->
+                            <div class="option-editor" v-html="item.value" style="display: flex; align-items: center; padding-left: 10px;"></div>
+                            <span :class="['fl-center', 'option-setting', trueArr.indexOf(index) > -1 ? 'option-true':'']" @click="settingAnswer(index)">{{ trueArr.indexOf(index) > -1 ? $t('evaluation.newExercise.trueAnswer') :$t('evaluation.newExercise.setAnswer') }}</span>
+                        </div>
+			            <p class="option-add">
+                            <span style="color:rgb(60,196,82);margin-left:15px;font-weight:bold">{{ $t('evaluation.newExercise.trueAnswer') }}:{{ paperAnswers.join("") }}</span>
+                        </p>
+                    </template>
+                    <template v-else-if="exersicesType === 'judge'">
+                        <IconText :text="$t('evaluation.newExercise.option')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+                        <RadioGroup v-model="paperAnswers[0]" type="button" size="large">
+                            <Radio label="A">{{ $t('evaluation.isTrue') }}</Radio>
+                            <Radio label="B">{{ $t('evaluation.isFalse') }}</Radio>
+                        </RadioGroup>
+                    </template>
+                    <template v-else>
+                        <IconText :text="$t('evaluation.newExercise.answerTitle')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+                        <div style="margin-top:15px;">
+                            <div ref="answerEditor" style="text-align:left"></div>
+                        </div>
+                    </template>
+                </div>
+            </div>
+            <div class="exersices-attr my-radio-style">
+                <IconText :text="$t('evaluation.newExercise.knowledge')" :color="'#00b8ff'" :icon="'md-infinite'"></IconText>
+                <Button type="info" style="margin-top: 20px" @click="onSelectPoints" v-if="exercisePoints.length === 0">{{editInfo.scope === 'school' ? $t('evaluation.newExercise.choosePoint') : $t('evaluation.newExercise.tips1') }}</Button>
+                <div v-else style="margin-top: 10px">
+                    <span v-for="(item, index) in exercisePoints" :key="index" class="exercise-item-point">
+                        {{ item }}
+                        <span class="exercise-item-point-close" v-if="editInfo.scope === 'school'">
+                            <Icon type="md-close" @click="onDeletePoint(index)" />
+                        </span>
+                    </span>
+                    <span v-if="editInfo.scope === 'school'" class="exercise-item-point-modify" @click="selectPointsModal = true">{{ $t('evaluation.newExercise.modify') }}</span>
+                    <span v-else class="exercise-item-point-modify">{{ $t('evaluation.newExercise.tips1') }}</span>
+                </div>
+            </div>
+        </div>
+        <Modal v-model="selectPointsModal" :title="$t('evaluation.newExercise.choosePoint')" footer-hide ref="editPointRef" width="600px" style="z-index: 99999">
+            <BasePoints v-if="selectPointsModal" :period="schoolInfo.period[exercisePeriod].id" :subject="schoolInfo.period[exercisePeriod].subjects[exerciseSubject].id" @onCheckChange="onCheckChange" @onCancel="selectPointsModal = false" :points="exercisePoints" ref="pointRef" :scope="curScope"></BasePoints>
+        </Modal>
+    </div>
+</template>
+
+<script>
+import E from 'wangeditor'
+import IconText from '@/components/evaluation/IconText.vue'
+export default {
+    components: {
+        IconText
+    },
+    props: {
+        exerciseItem: {
+            type: Object,
+            default: () => null
+        },
+        refId: {
+            type: String,
+            default: () => ''
+        },
+    },
+    data () {
+        return {
+            editInfo: null,
+            selectPointsModal: false,
+            schoolInfo: {
+                period: [],
+            },
+            exercisePeriod: 0,
+            exercisePoints: [],
+            exerciseScope: 0,
+            exerciseSubject: 0,
+            subjectList: [],
+            exersicesType: null,
+            oldChildList: [],
+			optionTrueIndex: 0,
+			trueArr: [0],
+			paperAnswers: ["A"],
+			answerEditor: null,
+        }
+    },
+    mounted () {
+    },
+    watch: {
+        exerciseItem: {
+            handler(newValue, oldValue) {
+                if (Object.keys(newValue).length) {
+                    console.log("BaseEditExerciese接受的", newValue);
+                    this.renderExercise(JSON.parse(JSON.stringify(newValue)));
+                }
+            },
+            immediate: true,
+            deep: true
+        },
+    },
+    computed: {
+        curScope() {
+            return this.exerciseScope === 1 ? 'school' : 'private'
+        },
+        hasSchool() {
+            return this.$store.state.userInfo.hasSchool
+        },
+    },
+    methods: {
+        // 渲染编辑习题内容回显
+        async renderExercise(editItem) {
+            console.log('edit-item', editItem);
+            this.editInfo = editItem;
+            // this.curId = editItem.id
+
+            let schoolProfile = await this.$store.dispatch('user/getSchoolProfile')
+            let schoolInfo = schoolProfile.school_base;
+
+            if (editItem.scope === "school" && schoolInfo) {
+                this.schoolInfo = schoolInfo;
+                this.exercisePeriod = schoolInfo.period.map((item) => item.id).indexOf(editItem.periodId);
+                this.subjectList = schoolInfo.period[this.exercisePeriod].subjects;
+                // this.gradeList = schoolInfo.period[this.exercisePeriod].grades;
+                // this.exerciseGrade = editItem.gradeIds.map(i => +i);
+                this.exerciseSubject = this.subjectList.map((item) => item.id).indexOf(editItem.subjectId);
+            }
+            // this.fieldList = [this.$t('evaluation.level1'), this.$t('evaluation.level2'), this.$t('evaluation.level3'), this.$t('evaluation.level4'), this.$t('evaluation.level5'), this.$t('evaluation.level6')]
+            // this.isEdit = true;
+            // this.exersicesDiff = editItem.level.toString() || "0";
+            this.exerciseScope = editItem.scope === "private" ? 0 : 1;
+            this.exersicesType = editItem.type;
+            // this.exerciseField = editItem.field - 1;
+            this.exercisePoints = editItem.knowledge || editItem.points || [];
+
+            if (editItem.type === 'compose') {
+                this.oldChildList = JSON.parse(JSON.stringify(editItem.children)) || []
+            }
+            // this.childList = editItem.children || [];
+            // this.stemContent = editItem.question;
+            // this.relateFileList = editItem.repair || [];
+            // this.optionsContent = editItem.option;
+            this.trueArr = editItem.answer.map((item) => item.charCodeAt() - 65)
+            this.paperAnswers = editItem.answer
+            // this.analysisContent = editItem.explain;
+            // this.analysisEditor.txt.html(editItem.explain);
+            console.log('当前编辑的试题', this.editInfo)
+            window.MathJax.startup.promise.then(() => {
+                window.MathJax.typesetPromise([this.$refs.editContainer])
+            })
+            if(!['single', 'multiple', 'judge'].includes(this.exersicesType)) {
+                let answerEditor = new E(this.$refs.answerEditor)
+                answerEditor.config.onchange = (html) => {
+                    this.paperAnswers = [html]
+                }
+                answerEditor.config.uploadImgShowBase64 = true;
+                this.$editorTools.initMyEditor(answerEditor,this)
+                answerEditor.create()
+
+                this.answerEditor = answerEditor
+                this.answerEditor.txt.html(this.paperAnswers[0])
+            }
+        },
+        /* 根据下标渲染对应的字母顺序 */
+        renderIndex(index) {
+            return String.fromCharCode(64 + parseInt(index + 1))
+        },
+        /* 设置正确答案 */
+        settingAnswer(index) {
+            this.$nextTick(() => {
+                if(this.exersicesType === 'single') {
+                    if (this.trueArr.indexOf(index) === -1) {
+                        this.trueArr = [index]
+                    }
+                } else {
+                    if (this.trueArr.indexOf(index) > -1) {
+                        if (this.trueArr.length === 1) {
+                            this.$Message.warning(this.$t("evaluation.addTip3"));
+                        } else {
+                            this.trueArr.splice(this.trueArr.indexOf(index), 1);
+                        }
+                    } else {
+                        this.trueArr.push(index);
+                        this.trueArr = this.trueArr.sort(); // 选项排序
+                    }
+                }
+                this.getAnswerOrder(this.trueArr);
+            });
+        },
+        /* 获取最新答案选项 */
+        getAnswerOrder(arr) {
+            let arr2 = [];
+            arr.forEach((i) => {
+                arr2.push(this.getOrderCode(i));
+            });
+            this.paperAnswers = arr2.sort();
+        },
+        /* 根据index获取对应选项字母的值 */
+        getOrderCode(index) {
+            let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName("option-order"));
+            for (let i = 0; i < wraps.length; i++) {
+                let item = wraps[i];
+                if (+index === +item.dataset.index) {
+                    return item.innerText;
+                    break;
+                }
+            }
+        },
+        onSelectPoints() {
+            if (this.hasSchool) {
+                this.selectPointsModal = true
+            } else {
+                this.$Message.warning(this.$t('evaluation.newExercise.noSchoolTip'))
+            }
+        },
+        /**
+         * 删除选好的关联知识点
+         * @param index
+         */
+        onDeletePoint(index) {
+            this.exercisePoints.splice(index, 1);
+        },
+        onCheckChange(val, list) {
+            console.log(val);
+            this.exercisePoints = val;
+            this.selectPointsModal = false
+        },
+        /* 滚回顶部 */
+        backToTop() {
+            this.$refs.editContainer.scrollIntoView();
+            // this.exersicesType = ''
+        },
+        saveContent(type) {
+            let exerciseItem = JSON.parse(JSON.stringify(this.editInfo))
+            this.isLoading = true;
+            
+            exerciseItem.answer = this.paperAnswers
+            // exerciseItem.explain = this.analysisContent;
+            // exerciseItem.repair = this.formatRepairResource(this.$refs.repairRef.datas);
+            // exerciseItem.field = this.exerciseField + 1;
+            exerciseItem.knowledge = this.exercisePoints;
+            // exerciseItem.periodId = this.isSchool ? this.schoolInfo.period[this.exercisePeriod].id : null;
+            // exerciseItem.gradeIds = this.isSchool ? this.exerciseGrade.length ? this.exerciseGrade.map(i => i + '') : this.gradeList.map((i, index) => index + '') : null;
+            // exerciseItem.subjectId = this.isSchool ? this.schoolInfo.period[this.exercisePeriod].subjects[this.exerciseSubject].id : null;
+            // exerciseItem.code = this.editInfo.scope === "school" ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId;
+            // exerciseItem.scope = this.exerciseScope === 0 ? "private" : "school";
+            // 收集编辑器宽度 方便压缩富文本图片中宽度为百分比的图片
+            /* let editorWidth = {}
+            if (exerciseItem.type === 'single') {
+                editorWidth.questionWidth = this.$refs.single.stemEditor.$textContainerElem.elems[0].clientWidth
+                editorWidth.optionWidth = this.$refs.single.optionEditors[0].$textContainerElem.elems[0].clientWidth
+            } else if (exerciseItem.type === 'multiple') {
+                editorWidth.questionWidth = this.$refs.multiple.stemEditor.$textContainerElem.elems[0].clientWidth
+                editorWidth.optionWidth = this.$refs.multiple.optionEditors[0].$textContainerElem.elems[0].clientWidth
+            } else {
+                editorWidth.questionWidth = this.analysisEditor.$textContainerElem.elems[0].clientWidth
+            } */
+            console.log('333333333333333', exerciseItem);
+            this.$emit("onEditSuccess", exerciseItem)
+        },
+        // 排除对象空属性
+        checkContent(Obj) {
+            return new Promise(async (r, j) => {
+                let flag = true;
+                let whiteList = this.getWhiteListByType(Obj.type);
+                console.log("富文本获取的原始试题数据", Obj);
+                for (let key in Obj) {
+                    if (whiteList.includes(key) && typeof Obj[key] === "string") {
+                        if (!this.getSimpleText(Obj[key])) {
+                            flag = await this.emptyConfirm(key)
+                            r(flag);
+                            return
+                        }
+                    } else {
+                        if (whiteList.includes(key) && !Obj[key]) {
+                            flag = await this.emptyConfirm(key)
+                            r(flag);
+                            return
+                        }
+                    }
+                }
+                r(flag);
+            })
+        },
+        // 根据不同题型 给出需要必填选项
+        getWhiteListByType(type) {
+            switch (type) {
+                case "single":
+                    return ["question", "option", "answer"];
+                    break;
+                case "multiple":
+                    return ["question", "option", "answer"];
+                    break;
+                case "complete":
+                    return ["question", "answer"];
+                    break;
+                default:
+                    return ["question"];
+                    break;
+            }
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+@import "../index/CreateExercises.less";
+</style>

+ 920 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseEditExercise.vue

@@ -0,0 +1,920 @@
+<template>
+  <div class="ev-container component-ev-container" :id="refId" ref="editContainer">
+    <div class="display-flex" v-if="isSchool && !hidePeriod">
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.choosePeriod')" :color="'#00b8ff'" :icon="'md-school'"></IconText>
+        <Select v-model="exercisePeriod" @on-change="onPeriodChange">
+          <Option v-for="(period, index) in schoolInfo.period" :value="index" :key="index">{{ period.name }}</Option>
+        </Select>
+      </div>
+      <div class="my-radio-style exersices-attr">
+        <IconText :text="$t('evaluation.newExercise.chooseGrade')" :color="'#00b8ff'" :icon="'logo-buffer'"></IconText>
+        <Select v-model="exerciseGrade" multiple :max-tag-count="3">
+          <Option v-for="(grade, gradeIndex) in gradeList" :value="gradeIndex" :key="grade" :label="grade">{{ grade }}</Option>
+        </Select>
+      </div>
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.chooseSubject')" :color="'#00b8ff'" :icon="'md-bookmarks'"></IconText>
+        <Select v-model="exerciseSubject" @on-change="onSubjectChange">
+          <Option v-for="(subject, index) in subjectList" :value="index" :key="subject.id">{{ subject.name }}</Option>
+        </Select>
+      </div>
+    </div>
+    <div class="display-flex">
+      <div class="exersices-attr edit-exersices-attr-diff my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.diff')" :color="'#00b8ff'" :icon="'md-pulse'"></IconText>
+        <RadioGroup v-model="exersicesDiff" type="button" ref="diffRef">
+          <Radio label="1" @click.native="diffChange($event, '1')">{{$t('evaluation.diff1')}}</Radio>
+          <Radio label="2" @click.native="diffChange($event, '2')">{{$t('evaluation.diff2')}}</Radio>
+          <Radio label="3" @click.native="diffChange($event, '3')">{{$t('evaluation.diff3')}}</Radio>
+          <Radio label="4" @click.native="diffChange($event, '4')">{{$t('evaluation.diff4')}}</Radio>
+          <Radio label="5" @click.native="diffChange($event, '5')">{{$t('evaluation.diff5')}}</Radio>
+        </RadioGroup>
+      </div>
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.field')" :color="'#00b8ff'" :icon="'md-planet'"></IconText>
+        <Select v-model="exerciseField">
+          <Option v-for="(item, index) in fieldList" :value="index" :key="item">{{ item }}</Option>
+        </Select>
+      </div>
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.knowledge')" :color="'#00b8ff'" :icon="'md-infinite'"></IconText>
+        <Button type="info" style="margin-top: 20px" @click="onSelectPoints" v-if="exercisePoints.length === 0">{{$t('evaluation.newExercise.choosePoint')}}</Button>
+        <div v-else style="margin-top: 10px">
+          <span v-for="(item, index) in exercisePoints" :key="index" class="exercise-item-point">
+            {{ item }}
+            <span class="exercise-item-point-close">
+              <Icon type="md-close" @click="onDeletePoint(index)" />
+            </span>
+          </span>
+          <span class="exercise-item-point-modify" @click="selectPointsModal = true">{{ this.$t('evaluation.newExercise.modify') }}</span>
+        </div>
+      </div>
+    </div>
+    <div class="exersices-attr display-flex">
+      <div class="exersices-attr-diff my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.type')" :color="'#00b8ff'" :icon="'md-pricetags'"></IconText>
+        <RadioGroup v-model="exersicesType" type="button" @on-change="typeChange">
+          <Radio label="single" :disabled="isEdit">{{ $t('evaluation.single') }}</Radio>
+          <Radio label="multiple" :disabled="isEdit">{{ $t('evaluation.multiple') }}</Radio>
+          <Radio label="judge" :disabled="isEdit">{{ $t('evaluation.judge') }}</Radio>
+          <Radio label="complete" :disabled="isEdit">{{ $t('evaluation.complete') }}</Radio>
+          <Radio label="subjective" :disabled="isEdit">{{ $t('evaluation.subjective') }}</Radio>
+          <Radio label="connector" :disabled="isEdit">{{ $t('evaluation.connector') }}</Radio>
+          <Radio label="correct" :disabled="isEdit">{{ $t('evaluation.correct') }}</Radio>
+          <Radio label="compose" :disabled="isEdit">{{ $t('evaluation.compose') }}</Radio>
+        </RadioGroup>
+      </div>
+
+    </div>
+
+    <BaseSingle v-if="exersicesType === 'single'" ref="single" :editInfo="editInfo" isEdit></BaseSingle>
+    <BaseMultiple v-else-if="exersicesType === 'multiple'" ref="multiple" :editInfo="editInfo" isEdit></BaseMultiple>
+    <BaseJudge v-else-if="exersicesType === 'judge'" ref="judge" :editInfo="editInfo" isEdit></BaseJudge>
+    <BaseCompletion v-else-if="exersicesType === 'complete'" ref="complete" :editInfo="editInfo" isEdit></BaseCompletion>
+    <BaseSubjective v-else-if="exersicesType === 'subjective'" ref="subjective" :editInfo="editInfo" isEdit></BaseSubjective>
+    <BaseConnector v-else-if="exersicesType === 'connector'" ref="connector" :editInfo="editInfo" isEdit></BaseConnector>
+    <BaseCorrect v-else-if="exersicesType === 'correct'" ref="correct" :editInfo="editInfo" isEdit></BaseCorrect>
+    <BaseCompose v-else-if="exersicesType === 'compose'" ref="compose" :editInfo="editInfo" isEdit></BaseCompose>
+
+    <!-- 解析的富文本部分 -->
+    <div class="exersices-analysis" v-show="exersicesType !== 'compose'">
+      <IconText :text="$t('evaluation.explain')" :color="'#2892DD'" :icon="'md-list'" style="margin-bottom: 10px"></IconText>
+      <div>
+        <div ref="analysisEditor" style="text-align: left"></div>
+      </div>
+    </div>
+
+    <!-- 补救的富文本部分 -->
+    <div class="exersices-analysis" v-show="exersicesType !== 'compose'">
+      <IconText :text="$t('evaluation.newExercise.repair')" :color="'#2892DD'" :icon="'md-link'" style="margin-bottom: 10px"></IconText>
+      <BaseRepair ref="repairRef" :rapairs="relateFileList || []"></BaseRepair>
+    </div>
+
+    <!-- 小题展示区域 -->
+    <div class="child-list-wrap" v-show="exersicesType === 'compose' && childList.length">
+      <IconText :text="$t('evaluation.newExercise.childList')" :color="'#00b8ff'" :icon="'md-list'"></IconText>
+      <BaseChildList :childList="childList" @onEditChildFinish="onEditChildFinish" @onDeleteChild="onDeleteChild" :curPeriodIndex="exercisePeriod" :curSubjectIndex="exerciseSubject"></BaseChildList>
+    </div>
+
+    <div class="save-wrap display-flex">
+      <Button type="info" @click="addChildModal = true" style="margin-right: 10px" v-show="exersicesType === 'compose'">{{ $t('evaluation.newExercise.addChild')}}</Button>
+      <!-- <Button type="success" @click="getContent(exersicesType)" :loading="isLoading">{{ $t('evaluation.newExercise.save') }}</Button> -->
+    </div>
+
+    <Modal v-model="selectPointsModal" :title="$t('evaluation.newExercise.choosePoint')" footer-hide ref="editPointRef" width="600px" style="z-index: 99999">
+      <BasePoints v-if="selectPointsModal" :period="schoolInfo.period[exercisePeriod].id" :subject="schoolInfo.period[exercisePeriod].subjects[exerciseSubject].id" @onCheckChange="onCheckChange" @onCancel="selectPointsModal = false" :points="exercisePoints" ref="pointRef" :scope="curScope"></BasePoints>
+    </Modal>
+
+    <!-- 添加小题弹窗 -->
+    <Modal v-model="addChildModal" width="1080" footer-hide class="">
+      <div class="modal-header" slot="header">{{ $t('evaluation.newExercise.addChild') }}</div>
+      <BaseCreateChild @addFinish="onAddChildFinish" v-if="addChildModal" :curPeriodIndex="exercisePeriod" :curSubjectIndex="exerciseSubject"></BaseCreateChild>
+    </Modal>
+  </div>
+</template>
+<script>
+import blobTool from "@/utils/blobTool.js";
+import "videojs-contrib-hls.js/src/videojs.hlsjs";
+import IconText from "@/components/evaluation/IconText.vue";
+import BaseSingle from "@/view/evaluation/types/BaseSingle.vue";
+import BaseMultiple from "@/view/evaluation/types/BaseMultiple.vue";
+import BaseCompletion from "@/view/evaluation/types/BaseCompletion.vue";
+import BaseJudge from "@/view/evaluation/types/BaseJudge.vue";
+import BaseSubjective from "@/view/evaluation/types/BaseSubjective.vue";
+import BaseCorrect from "@/view/evaluation/types/BaseCorrect.vue";
+import BaseConnector from "@/view/evaluation/types/BaseConnector.vue";
+import BaseCompose from "@/view/evaluation/types/BaseCompose.vue";
+import E from "wangeditor";
+
+export default {
+  name: 'BaseEditExercise',
+  // props: ["exerciseItem", "refId", "hidePeriod"],
+  props: {
+    exerciseItem: {
+      type: Object,
+      default: () => null
+    },
+    refId: {
+      type: String,
+      default: () => ''
+    },
+    hidePeriod: {
+      type: Boolean,
+      default: false
+    }
+  },
+  components: {
+    IconText,
+    BaseSingle,
+    BaseJudge,
+    BaseMultiple,
+    BaseCompletion,
+    BaseSubjective,
+    BaseCorrect,
+    BaseConnector,
+    BaseCompose,
+  },
+  data() {
+    return {
+      isFalse: false,
+      isLoading: false,
+      addChildModal: false,
+      isRelatedContent: false,
+      selectPointsModal: false,
+      isEdit: false,
+      editInfo: null,
+      schoolInfo: {
+        period: [],
+      },
+      exersicesType: null,
+      exerciseField: 0,
+      exercisePeriod: 0,
+      exerciseGrade: [],
+      exerciseSubject: 0,
+      exerciseScope: 0,
+      exercisePoints: [],
+      childList: [],
+      oldChildList: [],
+      scopeList: [this.$t('evaluation.filter.schoolBank'), this.$t('evaluation.filter.privateBank')],
+      fieldList: [],
+      periodList: [],
+      gradeList: [],
+      subjectList: [],
+      relateFileList: [],
+      exersicesDiff: 0,
+      analysisContent: "",
+      repairContent: "",
+      stemContent: "",
+      analysisEditor: null,
+      repairEditor: null,
+      videoHtml: "",
+      deleteChildrens: [],
+      curId: null
+    };
+  },
+  created() {
+    // 初始化区班校信息
+    this.getSchoolInfo();
+  },
+  methods: {
+    getSchoolInfo() {
+      this.$store.dispatch("user/getSchoolProfile").then((res) => {
+        let schoolBaseInfo = res.school_base;
+        if (schoolBaseInfo) {
+          this.schoolInfo = schoolBaseInfo;
+          if (schoolBaseInfo.period.length) {
+            this.gradeList = schoolBaseInfo.period[0].grades;
+            this.subjectList = schoolBaseInfo.period[0].subjects;
+          }
+        }
+      });
+    },
+
+    onSelectPoints() {
+      if (this.hasSchool) {
+        this.selectPointsModal = true
+      } else {
+        this.$Message.warning(this.$t('evaluation.newExercise.noSchoolTip'))
+      }
+    },
+
+    /* 滚回顶部 */
+    backToTop() {
+      this.$refs.editContainer.scrollIntoView();
+      this.exersicesType = ''
+    },
+
+    onSelectFile(val) {
+      this.relateFileList = val.files;
+    },
+
+    onConfirmRelate() {
+      this.isRelatedContent = false;
+    },
+
+    async getContent(type) {
+      let exerciseItem = JSON.parse(JSON.stringify(this.editInfo))
+      this.isLoading = true;
+      switch (type) {
+        case "single":
+          this.$refs.single.doSave()
+          exerciseItem.question = this.$refs.single.stemContent;
+          exerciseItem.option =
+            this.checkOptionNull(this.$refs.single.optionsContent) ?
+              this.$refs.single.optionsContent :
+              null;
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [
+            String.fromCharCode(64 + parseInt(this.$refs.single.trueIndex + 1)),
+          ];
+
+          break;
+        case "multiple":
+          this.$refs.multiple.doSave()
+          exerciseItem.question = this.$refs.multiple.stemContent;
+          exerciseItem.option = this.checkOptionNull(this.$refs.multiple.optionsContent) ?
+            this.$refs.multiple.optionsContent :
+            null;
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = this.$refs.multiple.multipleAnswers;
+          break;
+        case "judge":
+          exerciseItem.question = this.$refs.judge._data.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.answer = [this.$refs.judge._data.trueAnswer];
+          break;
+        case "complete":
+          exerciseItem.question = this.$refs.complete.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.complete.answerContent];
+          exerciseItem.blankCount = this.$refs.complete.blankCount || 1;
+          break;
+        case "connector":
+          exerciseItem.question = this.$refs.connector.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.connector.answerContent];
+          break;
+        case "correct":
+          exerciseItem.question = this.$refs.correct.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.correct.answerContent];
+          break;
+        case "subjective":
+          exerciseItem.question = this.$refs.subjective.stemContent;
+           exerciseItem.answerType = this.$refs.subjective.answerType; // 02040301 问答题增加作答类型字段
+          exerciseItem.useAutoScore = this.$refs.subjective.useAutoScore; // 02040301 问答题增加作答类型字段
+          exerciseItem.answerLang = this.$refs.subjective.answerLang; // 02040301 问答题增加作答类型字段
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.subjective.answerContent];
+          break;
+        case "compose":
+          exerciseItem.question = this.$refs.compose.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.children = this.childList;
+          break;
+        default:
+          break;
+      }
+      exerciseItem.explain = this.analysisContent;
+      exerciseItem.repair = this.formatRepairResource(
+        this.$refs.repairRef.datas
+      );
+      exerciseItem.field = this.exerciseField + 1;
+      exerciseItem.knowledge = this.exercisePoints;
+      exerciseItem.periodId = this.isSchool ?
+        this.schoolInfo.period[this.exercisePeriod].id :
+        null;
+      exerciseItem.gradeIds = this.isSchool ?
+        this.exerciseGrade.length ?
+          this.exerciseGrade.map(i => i + '') :
+          this.gradeList.map((i, index) => index + '') :
+        null;
+      exerciseItem.subjectId = this.isSchool ?
+        this.schoolInfo.period[this.exercisePeriod].subjects[
+          this.exerciseSubject
+        ].id :
+        null;
+      exerciseItem.code =
+        this.editInfo.scope === "school" ?
+          this.$store.state.userInfo.schoolCode :
+          this.$store.state.userInfo.TEAMModelId;
+      exerciseItem.scope = this.exerciseScope === 0 ? "private" : "school";
+      // 收集编辑器宽度 方便压缩富文本图片中宽度为百分比的图片
+      let editorWidth = {}
+      if (exerciseItem.type === 'single') {
+        editorWidth.questionWidth = this.$refs.single.stemEditor.$textContainerElem.elems[0].clientWidth
+        editorWidth.optionWidth = this.$refs.single.optionEditors[0].$textContainerElem.elems[0].clientWidth
+      } else if (exerciseItem.type === 'multiple') {
+        editorWidth.questionWidth = this.$refs.multiple.stemEditor.$textContainerElem.elems[0].clientWidth
+        editorWidth.optionWidth = this.$refs.multiple.optionEditors[0].$textContainerElem.elems[0].clientWidth
+      } else {
+        editorWidth.questionWidth = this.analysisEditor.$textContainerElem.elems[0].clientWidth
+      }
+
+      let confirmSave = await this.checkContent(exerciseItem)
+      // 判断获取的数据是否有空数据以及是否为空字符串
+      if (confirmSave) {
+        exerciseItem = await this.$editorTools.transBase64Src(exerciseItem, editorWidth)
+        if (this.refId !== "paperEdit") {
+          // 生成JSON文件名称以及新增试题的ID
+          const guid = this.editInfo.id ? this.editInfo.id : this.$tools.guid();
+          // 给新增的试题赋值ID
+          exerciseItem.id = guid;
+          if (exerciseItem.children && exerciseItem.children.length && exerciseItem.type === 'compose') {
+            console.log('编辑前的children', this.oldChildList)
+            exerciseItem.children.forEach((child) => {
+              child.periodId = exerciseItem.periodId
+              child.gradeIds = exerciseItem.gradeIds
+              child.subjectId = exerciseItem.subjectId
+              child.scope = exerciseItem.scope
+              child.pid = exerciseItem.id
+            })
+            let modifyChildren = this.checkModifyChildrens(exerciseItem.children)
+            console.log('修改了的children', modifyChildren)
+            exerciseItem.children = await this.saveChildrens(modifyChildren, exerciseItem.children)
+            console.log(exerciseItem)
+          }
+          // 将当前的试题数据转化为BLOB内部的试题JSON格式
+          const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
+          // 首先保存新题目的JSON文件到Blob 然后返回URL链接
+          let file = new File([JSON.stringify(itemJsonFile)], guid + ".json", {
+            type: "",
+          });
+          // 获取初始化Blob需要的数据
+          let sasData =
+            this.exerciseScope === 0 ?
+              await this.$tools.getPrivateSas() :
+              await this.$tools.getSchoolSas();
+          //初始化Blob
+          let containerClient = new blobTool(
+            sasData.url,
+            sasData.name,
+            sasData.sas,
+            exerciseItem.scope
+          );
+
+          try {
+            console.log(exerciseItem)
+            // 等待上传blob的返回结果
+            let blobFile = await containerClient.upload(file, { path: "item/" + guid });
+            if (blobFile.blob) {
+              console.log(exerciseItem)
+              // 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+              exerciseItem.blob = blobFile.blob;
+              let cosmosItem = await this.$evTools.createCosmosItem(
+                exerciseItem
+              );
+              console.log(exerciseItem)
+              this.saveExercise(cosmosItem);
+            } else {
+              this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+            }
+          } catch (e) {
+            this.$Message.error(e.spaceError);
+          }
+        } else {
+          // 如果是试卷内编辑试题 则返回编辑好的数据 再统一进行保存
+          exerciseItem = await this.$evTools.doAddHost(exerciseItem, JSON.parse(sessionStorage.getItem('cp_paper')))
+          exerciseItem.blob = null
+          this.saveExercise(exerciseItem);
+        }
+      } else {
+        // this.$Message.warning(this.$t('evaluation.newExercise.unCompleteTip'));
+        this.isLoading = false;
+      }
+      console.log(exerciseItem);
+    },
+
+    /* 检查需要更新的子题 */
+    checkModifyChildrens(childrens) {
+      if (!this.oldChildList.length) {
+        return childrens
+      }
+      let oldChildren = JSON.parse(JSON.stringify(this.oldChildList))
+      let result = childrens.filter((v) => {
+        var str = JSON.stringify(v);
+        return oldChildren.every((v) => JSON.stringify(v) != str);
+      });
+      return result
+    },
+
+
+    /* 保存综合题的子题 */
+    saveChildrens(modifyChildrens, childrens) {
+      return new Promise((resolve, reject) => {
+        let promiseArr = []
+        modifyChildrens.forEach(exerciseItem => {
+          promiseArr.push(new Promise(async (r, j) => {
+            // 处理小题的多媒体资源
+            exerciseItem = await this.$editorTools.transBase64Src(exerciseItem)
+            // 将当前的试题数据转化为BLOB内部的试题JSON格式
+            const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
+            // 首先保存新题目的JSON文件到Blob 然后返回URL链接
+            let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+            // 获取初始化Blob需要的数据
+            let sasData =
+              exerciseItem.scope === 'private' ?
+                await this.$tools.getPrivateSas() :
+                await this.$tools.getSchoolSas();
+            //初始化Blob
+            let containerClient = new blobTool(
+              sasData.url,
+              sasData.name,
+              sasData.sas,
+              exerciseItem.scope
+            );
+            try {
+              // 等待上传blob的返回结果
+              let blobFile = await containerClient.upload(file, { path: "item/" + exerciseItem.id });
+              if (blobFile.blob) {
+                // 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+                exerciseItem.blob = blobFile.blob;
+                let cosmosItem = await this.$evTools.createCosmosItem(exerciseItem)
+                console.log(this.oldChildList.map(i => i.id))
+                console.log(exerciseItem.id)
+                let isNew = this.oldChildList.map(i => i.id).indexOf(exerciseItem.id) === -1
+                this.$api.newEvaluation.SaveSingleExercise({
+                  itemInfo: cosmosItem,
+                  option: isNew ? "insert" : "update",
+                }).then((res) => {
+                  console.log('保存了一个子题', res.itemInfo)
+                  r(res.itemInfo)
+                });
+              } else {
+                this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+              }
+            } catch (e) {
+              this.$Message.error(e);
+            }
+          }))
+        })
+
+        /* 如果存在删除的子题 则需要把子题从blob和cosmos中进行删除 */
+        if (this.deleteChildrens.length) {
+          this.deleteChildrens.forEach(child => {
+            if (this.oldChildList.map(i => i.id).indexOf(child.id) > -1) {
+              promiseArr.push(new Promise((r, j) => {
+                this.onDeleteChildBlob(child).then(res => {
+                  if (!res.error) {
+                    r(200)
+                  }
+                })
+              }))
+            }
+          })
+        }
+
+
+        Promise.all(promiseArr).then(result => {
+          resolve(childrens.map(i => i.id))
+        })
+      })
+    },
+
+    /* 删除blob和cosmos里面的子题 */
+    onDeleteChildBlob(item) {
+      return new Promise((r, j) => {
+        this.$api.newEvaluation
+          .DeleteExamItem({
+            id: item.id,
+            code: item.code,
+            scope: item.scope,
+          })
+          .then(async (res) => {
+            // 获取初始化Blob需要的数据
+            this.deleteBlobPrefix(item).then(status => {
+              r(status)
+            })
+          })
+          .catch((err) => {
+            j(err);
+            this.$Message.warning(this.$t('evaluation.deleteFail'));
+          });
+      })
+
+    },
+
+    /* 删除blob指定试题目录下所有 */
+    deleteBlobPrefix(item) {
+      return new Promise((resolve, reject) => {
+        this.$api.blob.deletePrefix({
+          "cntr": item.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+          "prefix": "item/" + item.id
+        }).then(
+          (res) => {
+            if (!res.error) {
+              resolve(200)
+            } else {
+              resolve(500)
+            }
+          },
+          (err) => {
+            reject(err)
+          }
+        )
+      })
+    },
+
+    /**
+     * 保存编辑后的试题
+     * @param item
+     */
+    saveExercise(item) {
+      this.$emit("onEditSuccess", item);
+    },
+
+    /* 检测补救资源超链接 去除无效链接 */
+    formatRepairResource(list) {
+      if (list.length) {
+        let arr = [];
+        list.forEach((i, index) => {
+          i.blobUrl.forEach(j => {
+            arr.push({
+              blobUrl: j.url,
+              name: i.name,
+              type: i.type
+            })
+          })
+        });
+        return arr;
+      } else {
+        return [];
+      }
+    },
+
+    onEditChildFinish(list, deleteId) {
+      list.forEach((i) => {
+        i.code = this.editInfo.code;
+      });
+      this.childList = list;
+      console.log('编辑完之后的list', list)
+    },
+
+    onDeleteChild(item) {
+      this.deleteChildrens.push(item)
+      console.log('需要删除的子题', this.deleteChildrens)
+    },
+
+    onConfirm() {
+      this.selectPointsModal = false;
+    },
+
+    onCheckChange(val, list) {
+      console.log(val);
+      this.exercisePoints = val;
+      this.selectPointsModal = false
+    },
+
+    /**
+     * 删除选好的关联知识点
+     * @param index
+     */
+    onDeletePoint(index) {
+      this.exercisePoints.splice(index, 1);
+    },
+
+    // 题目类型转换
+    typeChange(val) {
+      if (this.isEdit) {
+        this.$Message.warning(this.$t('evaluation.newExercise.typeChangeTip'));
+      } else {
+        this.exersicesType = val;
+        this.analysisEditor.txt.clear();
+        this.repairEditor.txt.clear();
+      }
+    },
+
+    // 难度与背景颜色切换
+    diffChange(e, type) {
+      this.exersicesDiff = +type;
+      e.preventDefault();
+      let colorArr = ["#10abe7", "#E8BE15", "#F19300", "#EB5E00", "#D30000"];
+      /** 通过动态赋予ID 解决多个组件下获取DOM覆盖问题 */
+      let ac = document
+        .getElementById(this.refId)
+        .getElementsByClassName("edit-exersices-attr-diff")[0].children[1]
+        .children;
+      for (let i = 0; i < ac.length; i++) {
+        ac[i].style.background = "#fff";
+        ac[i].style.color = "#515a6e";
+      }
+      e.target.style.background = colorArr[+type - 1];
+      e.target.style.color = "#fff";
+    },
+
+    onPeriodChange(val) {
+      if (!val && val !== 0) return;
+      this.gradeList = this.schoolInfo.period[val].grades;
+      this.subjectList = this.schoolInfo.period[val].subjects;
+      this.exerciseGrade = [];
+      this.exerciseSubject = 0;
+      this.exercisePoints = []; // 切换学段后 知识点需要重新选择
+    },
+
+    /* 切换科目 将知识点重置 */
+    onSubjectChange() {
+      this.exercisePoints = []
+    },
+
+    // 提取富文本内容中的文本
+    getSimpleText(html) {
+      var r = /<(?!img|video|audio).*?>/g;
+      return html.replace(r, "");
+    },
+    // 排除对象空属性
+    checkContent(Obj) {
+      return new Promise(async (r, j) => {
+        let flag = true;
+        let whiteList = this.getWhiteListByType(Obj.type);
+        console.log("富文本获取的原始试题数据", Obj);
+        for (let key in Obj) {
+          if (whiteList.includes(key) && typeof Obj[key] === "string") {
+            if (!this.getSimpleText(Obj[key])) {
+              flag = await this.emptyConfirm(key)
+              r(flag);
+              return
+            }
+          } else {
+            if (whiteList.includes(key) && !Obj[key]) {
+              flag = await this.emptyConfirm(key)
+              r(flag);
+              return
+            }
+          }
+        }
+        r(flag);
+      })
+    },
+
+    /* 确认是否继续保存 */
+    emptyConfirm(key) {
+      return new Promise((r, j) => {
+        this.$Modal.confirm({
+          title: this.$t('evaluation.newExercise.modalTip'),
+          content: `${this.$t('evaluation.currentItem')}${key === 'question' ? this.$t('evaluation.newExercise.stem') : this.$t('evaluation.newExercise.option')}${this.$t('evaluation.emptyTip1')}`,
+          onOk: () => {
+            r(true)
+          },
+          onCancel: () => {
+            r(false)
+          }
+        });
+      })
+    },
+
+    // 重置编辑器
+    resetEditor() {
+      this.$router.push({
+        name: "personalBank",
+        params: {
+          tabName: "exercise",
+        },
+      });
+    },
+
+    onAddChildFinish(item) {
+      console.log("获取到小题");
+      console.log(item);
+      this.addChildModal = false;
+      this.childList.push(item);
+    },
+
+    // 重置
+    reloadCreate() {
+      location.reload();
+    },
+
+    /* 检测数组是否有空数据 */
+    checkOptionNull(arr) {
+      let flag = true;
+      // for (let i = 0; i < arr.length; i++) {
+      // 	if (this.getSimpleText(arr[i].value) === "") {
+      // 		flag = false;
+      // 	}
+      // }
+      return flag;
+    },
+
+    // 根据不同题型 给出需要必填选项
+    getWhiteListByType(type) {
+      switch (type) {
+        case "single":
+          return ["question", "option", "answer"];
+          break;
+        case "multiple":
+          return ["question", "option", "answer"];
+          break;
+        case "complete":
+          return ["question", "answer"];
+          break;
+        default:
+          return ["question"];
+          break;
+      }
+    },
+
+    // 渲染编辑习题内容回显
+    async renderExercise(editItem) {
+      console.log('edit-item', editItem);
+      this.editInfo = editItem;
+      this.curId = editItem.id
+
+      let schoolProfile = await this.$store.dispatch('user/getSchoolProfile')
+      let schoolInfo = schoolProfile.school_base;
+
+      if (editItem.scope === "school" && schoolInfo) {
+        this.schoolInfo = schoolInfo;
+        this.exercisePeriod = schoolInfo.period
+          .map((item) => item.id)
+          .indexOf(editItem.periodId);
+        this.subjectList = schoolInfo.period[this.exercisePeriod].subjects;
+        this.gradeList = schoolInfo.period[this.exercisePeriod].grades;
+        this.exerciseGrade = editItem.gradeIds.map(i => +i);
+        this.exerciseSubject = this.subjectList
+          .map((item) => item.id)
+          .indexOf(editItem.subjectId);
+      }
+      this.fieldList = [this.$t('evaluation.level1'), this.$t('evaluation.level2'), this.$t('evaluation.level3'),
+      this.$t('evaluation.level4'), this.$t('evaluation.level5'), this.$t('evaluation.level6')]
+      this.isEdit = true;
+      this.exersicesDiff = editItem.level.toString() || "0";
+      this.exerciseScope = editItem.scope === "private" ? 0 : 1;
+      this.exersicesType = editItem.type;
+      this.exerciseField = editItem.field - 1;
+      this.exercisePoints = editItem.knowledge || editItem.points || [];
+      if (editItem.level) {
+        let ac = document
+          .getElementById(this.refId)
+          .getElementsByClassName("edit-exersices-attr-diff")[0].children[1]
+          .children;
+        for (let i = 0; i < ac.length; i++) {
+          ac[i].style.background = "#fff";
+          ac[i].style.color = "#515a6e";
+        }
+        // 重新渲染题目难度
+        let diffDom = document
+          .getElementById(this.refId)
+          .getElementsByClassName("edit-exersices-attr-diff")[0].children[1]
+          .children[editItem.level - 1];
+        let colorArr = ["#32CF74", "#E8BE15", "#F19300", "#EB5E00", "#D30000"];
+        diffDom.style.background = colorArr[editItem.level - 1];
+        diffDom.style.color = "#fff";
+      } else {
+        let ac = document
+          .getElementById(this.refId)
+          .getElementsByClassName("edit-exersices-attr-diff")[0].children[1]
+          .children;
+        for (let i = 0; i < ac.length; i++) {
+          ac[i].style.background = "#fff";
+          ac[i].style.color = "#515a6e";
+        }
+      }
+
+      if (editItem.type === 'compose') {
+        this.oldChildList = JSON.parse(JSON.stringify(editItem.children)) || []
+      }
+      this.childList = editItem.children || [];
+      this.stemContent = editItem.question;
+      this.relateFileList = editItem.repair || [];
+      this.optionsContent = editItem.option;
+      this.analysisContent = editItem.explain;
+      this.analysisEditor.txt.html(editItem.explain);
+      console.log('当前编辑的试题', this.editInfo)
+    },
+  },
+  mounted() {
+    let analysisEditor = new E(this.$refs.analysisEditor);
+    analysisEditor.config.uploadImgShowBase64 = true;
+    analysisEditor.config.onchange = (html) => {
+      this.analysisContent = html;
+    },
+      this.$editorTools.initMyEditor(analysisEditor, this)
+    // this.$editorTools.addResource(this,analysisEditor)
+    analysisEditor.create();
+    this.analysisEditor = analysisEditor;
+
+    // this.$EventBus.$off('clickResource')
+    // this.$EventBus.$on('clickResource',val => {
+    // 	console.log('xxxxxxxxxxxxxx')
+    // })
+  },
+  computed: {
+    isSchool() {
+      return this.exerciseScope === 1;
+    },
+    curScope() {
+      return this.exerciseScope === 1 ? 'school' : 'private'
+    },
+    hasSchool() {
+      return this.$store.state.userInfo.hasSchool
+    },
+  },
+  watch: {
+    exerciseItem: {
+      handler(newValue, oldValue) {
+        if (Object.keys(newValue).length) {
+          console.log("BaseEditExerciese接受的", newValue);
+          this.isLoading = false;
+          this.renderExercise(JSON.parse(JSON.stringify(newValue)));
+        }
+      },
+      immediate: true,
+      deep: true
+    },
+  },
+};
+</script>
+<style src="../index/CreateExercises.less" lang="less" scoped>
+</style>
+
+<style>
+.exersices-attr .ivu-select-multiple .ivu-tag {
+  height: 32px;
+  line-height: 31px;
+}
+
+.exersices-attr .ivu-select-multiple .ivu-tag i {
+  top: 9px;
+}
+
+.exersices-attr
+  .ivu-select-multiple
+  .ivu-select-selection
+  .ivu-select-placeholder {
+  line-height: 38px;
+}
+
+.component-ev-container
+  .ivu-select-single
+  .ivu-select-selection
+  .ivu-select-placeholder {
+  line-height: 38px;
+  height: 38px;
+}
+
+.related-point-modal .ivu-modal-header-inner {
+  font-weight: bold;
+}
+
+.exersices-attr .ivu-select-multiple .ivu-tag {
+  height: 32px;
+  line-height: 31px;
+}
+
+.exersices-attr .ivu-select-multiple .ivu-tag i {
+  top: 9px;
+}
+
+.exersices-attr
+  .ivu-select-multiple
+  .ivu-select-selection
+  .ivu-select-placeholder {
+  line-height: 38px;
+}
+
+.btn-relate-content {
+  right: 0;
+}
+</style>

File diff suppressed because it is too large
+ 1225 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseExerciseList.vue


+ 340 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseFilter.vue

@@ -0,0 +1,340 @@
+<template>
+  <div class="filter-wrap">
+    <div class="filter-item" v-if="!hideOriginFilter" v-show="!isShowSchoolBank && !isComponent">
+      <span class="filter-title">{{$t('evaluation.filter.origin')}}:</span>
+      <RadioGroup v-model="filterOrigin" type="button" @on-change="filterOriginChange">
+        <Radio :label="schoolCode" v-if="isShowSchoolBank">{{$t('evaluation.filter.schoolBank')}}</Radio>
+        <Radio :label="userId">{{$t('evaluation.filter.privateBank')}}</Radio>
+        <Radio :label="schoolCode" v-if="!isShowSchoolBank && hasSchool">{{$t('evaluation.filter.schoolBank')}}</Radio>
+      </RadioGroup>
+    </div>
+    <div class="filter-item" v-show="(isShowSchoolBank || (filterOrigin === schoolCode)) &&  hasSchool">
+      <span class="filter-title">{{$t('evaluation.filter.period')}}:</span>
+      <RadioGroup v-model="filterPeriod" type="button" @on-change="filterPeriodChange">
+        <Radio v-for="(item, index) in periodList" :key="index" :label="index">{{ item.name }}</Radio>
+      </RadioGroup>
+    </div>
+    <div class="filter-item" v-show="(isShowSchoolBank || (filterOrigin === schoolCode)) &&  hasSchool">
+      <span class="filter-title">{{$t('evaluation.filter.subject')}}:</span>
+      <RadioGroup v-model="filterSubject" type="button" @on-change="filterSubjectChange">
+        <Radio v-for="(item, index) in subjectList" :key="index" :label="index" style="margin-bottom: 5px;">{{ item.name }}</Radio>
+      </RadioGroup>
+    </div>
+    <div class="filter-item" v-show="(isShowSchoolBank || (filterOrigin === schoolCode)) &&  hasSchool">
+      <span class="filter-title">{{$t('evaluation.filter.grade')}}:</span>
+      <CheckboxGroup v-model="filterGrade" border @on-change="filterGradeChange">
+        <Checkbox lable="all">{{$t('evaluation.filter.all')}}</Checkbox>
+        <Checkbox v-for="(item, index) in gradeList" :key="index" :label="index">{{ item }}</Checkbox>
+      </CheckboxGroup>
+    </div>
+
+    <div class="filter-item light-iview-input">
+      <span class="filter-title">{{$t('evaluation.filter.sort')}}:</span>
+      <span @click="onSortCreateTime(isDesc)" class="createTime-sort">{{$t('evaluation.filter.createTime')}}
+        <Icon :type="isDesc ? 'md-arrow-round-down' : 'md-arrow-round-up'" />
+      </span>
+      <!-- 			<RadioGroup v-model="filterSort" type="button" @on-change="filterSortChange">
+				<Radio label="createTime">{{$t('evaluation.filter.createTime')}}
+					<Icon type="md-arrow-round-down" />
+				</Radio>
+			</RadioGroup> -->
+      <!-- 试卷列表的搜索功能 -->
+      <!-- <Input v-special-char v-model="searchVal" clearable  :placeholder="$t('evaluation.paperList.searchPaper')" style="width: 300px" @on-click="onCloseSearch" @on-change="onSearchChange"/> -->
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  components: {},
+  props: {
+    period: {
+      type: String,
+      default: "",
+    },
+    subject: {
+      type: String,
+      default: "",
+    },
+    isFilterPaper: {
+      type: Boolean,
+      default: false,
+    },
+    filterCounts: {
+      type: Object,
+      default: () => { }
+    },
+    hideOriginFilter: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      isDesc: true,
+      userId: "",
+      schoolCode: "",
+      schoolInfo: {},
+      searchVal: '',
+      isShowSchoolBank: false,
+      isShowUploadList: false,
+      exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
+      exersicesDiff: this.$GLOBAL.EXERCISE_DIFFS(),
+      exersicesField: this.$GLOBAL.EXERCISE_LEVELS(),
+      diffColors: ["#32CF74", "#E8BE15", "#F19300", "#EB5E00", "#D30000"],
+      filterType: ["all"],
+      filterOrigin: "",
+      filterDiff: ["all"],
+      filterField: ["all"],
+      filterSort: "createTime",
+      filterPeriod: 0,
+      filterGrade: [false],
+      filterSubject: 0,
+      collapseList: [],
+      periodList: [],
+      gradeList: [],
+      subjectList: [],
+      filterParams: {},
+    };
+  },
+  created() {
+    this.isShowSchoolBank = this.$route.path.includes('school');
+    this.userId = this.$store.state.userInfo.TEAMModelId;
+    this.getSchoolInfo();
+    this.$EventBus.$on("showSchoolBank", (val) => {
+      console.error('paper => ', val)
+      if (val) {
+        this.isShowSchoolBank = val;
+        this.filterOrigin = this.schoolCode;
+      } else {
+        this.isShowSchoolBank = false;
+        this.filterOrigin = this.$store.state.userInfo.TEAMModelId;
+      }
+    });
+  },
+  methods: {
+    /** 获取区班校信息 */
+    getSchoolInfo() {
+      if (!this.hasSchool) {
+        this.userId = this.$store.state.userInfo.TEAMModelId;
+        this.filterOrigin = this.$store.state.userInfo.TEAMModelId;
+        this.doFilter();
+      } else {
+        this.$store.dispatch("user/getSchoolProfile").then((res) => {
+          let schoolBaseInfo = res.school_base;
+          if (schoolBaseInfo) {
+            this.schoolInfo = schoolBaseInfo;
+            this.schoolCode = schoolBaseInfo.id;
+            this.userId = this.$store.state.userInfo.TEAMModelId;
+            this.filterOrigin = this.isShowSchoolBank ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId;
+            this.periodList = schoolBaseInfo.period;
+            if (schoolBaseInfo.period.length) {
+              let inBank = this.$route.name === 'schoolBank'
+              let bankFilterConds = localStorage.getItem('bankFilterConds') ? JSON.parse(localStorage.getItem('bankFilterConds')) : null
+              let activePeriodIndex = inBank && bankFilterConds ? bankFilterConds.period : 0
+              this.filterPeriod = activePeriodIndex
+              this.gradeList = schoolBaseInfo.period[activePeriodIndex].grades;
+              this.subjectList = schoolBaseInfo.period[activePeriodIndex].subjects;
+              this.filterSubject = inBank && bankFilterConds ? bankFilterConds.subject : 0
+              console.error(bankFilterConds)
+              console.error(this.filterSubject)
+            }
+          }
+          this.doFilter();
+        }).catch(err => {
+          console.log(err)
+        })
+      }
+
+    },
+
+    /* 升序降序操作 */
+    onSortCreateTime(isDesc) {
+      this.isDesc = !this.isDesc
+      this.doFilter()
+    },
+
+    /** 执行筛选条件获取数据 */
+    doFilter(filterKey) {
+      /** 定义查询接口的参数规格 */
+      this.filterParams = {
+        filterSort: this.isDesc,
+        code: (this.isShowSchoolBank || (this.filterOrigin === this.schoolCode)) ? this.$store.state.userInfo.schoolCode :
+          this.$store.state.userInfo.TEAMModelId,
+        periodId: (this.isShowSchoolBank || (this.filterOrigin === this.schoolCode)) && this.periodList.length ? [this.periodList[
+          this.filterPeriod].id] : [],
+        gradeIds: (this.isShowSchoolBank || (this.filterOrigin === this.schoolCode)) ?
+          this.deleteFalse(this.filterGrade).map(i => i + '') : [],
+        subjectId: (this.isShowSchoolBank || (this.filterOrigin === this.schoolCode)) ?
+          [this.subjectList[this.filterSubject].id] : [],
+        level: this.deleteFalse(this.filterDiff),
+        type: this.deleteFalse(this.filterType),
+        field: this.deleteFalse(this.filterField)
+      };
+
+      this.$emit("onChange", {
+        params: this.filterParams,
+        key: filterKey
+      });
+    },
+
+    /**
+     * 筛选学段条件
+     * @param val
+     */
+    filterPeriodChange(val) {
+      // this.filterPeriod = this.periodList[val].periodCode
+      this.gradeList = this.schoolInfo.period[val].grades;
+      this.subjectList = this.schoolInfo.period[val].subjects;
+      this.filterGrade = [false];
+      this.filterSubject = 0;
+      this.doFilter('period');
+      let bankFilterConds = localStorage.getItem('bankFilterConds') ? JSON.parse(localStorage.getItem('bankFilterConds')) : null
+      if (bankFilterConds) {
+        bankFilterConds.period = val
+        bankFilterConds.subject = 0
+        localStorage.setItem('bankFilterConds', JSON.stringify(bankFilterConds))
+      }
+    },
+
+    /**
+     * 筛选年级
+     * @param val
+     */
+    filterGradeChange(val) {
+      if (val.length > 1 && val.indexOf(false) === 0) {
+        this.filterGrade.splice(val.indexOf(false), 1);
+      } else if (val.indexOf(false) > -1 || val.length === 0) {
+        this.filterGrade = [false];
+      }
+      this.doFilter('grade');
+    },
+
+    /**
+     * 筛选科目
+     * @param val
+     */
+    filterSubjectChange(val) {
+      let bankFilterConds = localStorage.getItem('bankFilterConds') ? JSON.parse(localStorage.getItem('bankFilterConds')) : null
+      if (bankFilterConds) {
+        bankFilterConds.subject = val
+        localStorage.setItem('bankFilterConds', JSON.stringify(bankFilterConds))
+      }
+      this.doFilter('subject');
+    },
+
+    /**
+     * 根据题库加载题目
+     * @param val
+     */
+    filterOriginChange(origin) {
+      this.filterOrigin = origin;
+      this.doFilter();
+    },
+
+    /**
+     * 筛选题型
+     * @param val
+     */
+    filterTypeChange(val) {
+      if (val.length > 1 && val.indexOf("all") === 0) {
+        this.filterType.splice(val.indexOf("all"), 1);
+      } else if (val.indexOf("all") > -1 || val.length === 0) {
+        this.filterType = ["all"];
+      }
+      this.doFilter();
+    },
+
+    /**
+     * 筛选难度
+     * @param val
+     */
+    filterDiffChange(val) {
+      if (val.length > 1 && val.indexOf("all") === 0) {
+        this.filterDiff.splice(val.indexOf("all"), 1);
+      } else if (val.indexOf("all") > -1 || val.length === 0) {
+        this.filterDiff = ["all"];
+      }
+      this.doFilter();
+    },
+
+    /**
+     * 筛选认知层次
+     * @param val
+     */
+    filterFieldChange(val) {
+      if (val.length > 1 && val.indexOf("all") === 0) {
+        this.filterField.splice(val.indexOf("all"), 1);
+      } else if (val.indexOf("all") > -1 || val.length === 0) {
+        this.filterField = ["all"];
+      }
+      this.doFilter();
+    },
+
+    /**
+     * 排序条件更换
+     * @param val
+     */
+    filterSortChange(val) {
+      this.doFilter(1);
+    },
+
+    /**
+     * 删除筛选条件里面的False值与All值
+     * @param arr
+     */
+    deleteFalse(arr) {
+      let list = JSON.parse(JSON.stringify(arr));
+      list.forEach((item, index) => {
+        if ((!item || item === "all") && item !== 0) list.splice(index, 1);
+      });
+      return list;
+    },
+
+    /* 搜索词汇发生变化 */
+    onSearchChange() {
+      this.$emit('onSearchChange', this.searchVal)
+    },
+    /* 关闭搜索 */
+    onCloseSearch() {
+      this.$emit('onCloseSearch')
+    },
+  },
+
+  mounted() {
+
+  },
+  computed: {
+    hasSchool() {
+      return this.$store.state.userInfo.hasSchool
+    },
+    isComponent() {
+      return this.$route.name === 'shareCenter'
+    }
+  },
+};
+</script>
+
+<style lang="less">
+@import "../index/CommonExercise.less";
+</style>
+<style>
+.filter-wrap .filter-item .ivu-input {
+  border-radius: 100px;
+  padding: 4px 12px;
+}
+
+.filter-wrap .filter-item .ivu-input-wrapper {
+  margin-left: 30px;
+}
+
+.filter-wrap .createTime-sort {
+  background-color: #10abe7;
+  color: #fff;
+  padding: 0 10px;
+  margin-left: 15px;
+  height: 28px;
+  line-height: 28px;
+  border-radius: 5px;
+  cursor: pointer;
+}
+</style>

+ 184 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseFixExamItem.vue

@@ -0,0 +1,184 @@
+<template>
+	<div class="base-fix-container">
+		<p class="fix-title">{{ $t('evaluation.explain') }}</p>
+		<div ref="analysisEditor" style="margin: 20px 0;"></div>
+		<p class="fix-title">{{ $t('evaluation.newExercise.repair') }}</p>
+		<div class="repair-box">
+			<BaseRepair ref="repairRef" :rapairs="fixItem.repair"></BaseRepair>
+		</div>
+	</div>
+</template>
+
+<script>
+	import E from "wangeditor";
+	import blobTool from "@/utils/blobTool.js";
+	export default {
+		props:{
+			fixItem:{
+				type:Object,
+				default:() => {}
+			}
+		},
+		data(){
+			return {
+				analysisContent:'',
+				analysisEditor:null,
+				editItem:null,
+			}
+		},
+		methods:{
+			/* 保存新内容 */
+			async doSave(){
+				let newExplain = this.analysisContent
+				let newRepairs = this.formatRepairResource(
+					this.$refs.repairRef.datas
+				);
+				this.editItem.explain = newExplain
+				this.editItem.repair = newRepairs
+				// 根据试题所在评量的scope初始化blobClient
+				let examScope = sessionStorage.getItem('examScope')
+				let sasData = examScope === 'school' ? await this.$tools.getSchoolSas() :  await this.$tools.getPrivateSas();
+				let blobClient = new blobTool(sasData.url,sasData.name,sasData.sas,examScope)
+				// 获取试题原本JSON数据进行修改
+				let oldBlobJson = JSON.parse(await this.$tools.getFile(this.editItem.blob + sasData.sas))
+				oldBlobJson.exercise.explain = newExplain
+				oldBlobJson.exercise.repair = newRepairs
+				// 修改完成后将最新的JSON上传替换
+				let newBlobFile = new File([JSON.stringify(oldBlobJson)], oldBlobJson.id + ".json");
+				let promiseArr = []
+				let curPaperItem = JSON.parse(sessionStorage.getItem('cp_paper'))
+				// 如果拥有操作试题试卷的权限或者是个人的试卷 则需要询问用户是否要更新到试卷库
+				if(this.hasPaperAuth || curPaperItem.scope === 'private'){
+					let isCoverPaperItem = await this.confirmCoverPaepr()
+					if(isCoverPaperItem){
+						promiseArr.push(this.replacePaperItemJson(curPaperItem,newBlobFile))
+					}
+				}
+				// 覆盖exam的试题
+				promiseArr.push(this.replaceExamItemJson(blobClient,newBlobFile))
+				// 进行覆盖操作
+				Promise.all(promiseArr).then((result) => {
+					this.$emit('onFixFinish',this.editItem)
+				})
+			},
+			/* 确认是否继续保存 */
+			confirmCoverPaepr(key) {
+				return new Promise((r, j) => {
+					this.$Modal.confirm({
+						title: this.$t('evaluation.newExercise.modalTip'),
+						content: this.$t('evaluation.fixTip2'),
+						onOk: () => {
+							r(true)
+						},
+						onCancel: () => {
+							r(false)
+						}
+					});
+				})
+			},
+			/* 覆盖exam目录下的文件 */
+			replaceExamItemJson(blobClient,newBlobFile){
+				return new Promise((r,j) => {
+					blobClient.upload(newBlobFile, { path:this.getUploadPath(this.editItem) }).then((result) => {
+						r(200)
+					}).catch((err) => {
+						j(500)
+					});
+				})
+			},
+			/* 覆盖paper目录下的文件 */
+			replacePaperItemJson(curPaperItem,newBlobFile){
+				return new Promise(async (r,j) => {
+					let sasData = curPaperItem.scope === 'school' ? await this.$tools.getSchoolSas() :  await this.$tools.getPrivateSas();
+					let blobClient = new blobTool(sasData.url,sasData.name,sasData.sas,curPaperItem.scope)
+					blobClient.upload(newBlobFile, { path:`paper/${ curPaperItem.name }` }).then((result) => {
+						r(200)
+					}).catch((err) => {
+						j(500)
+					});
+				})
+			},
+			/* 获取上传路径 */
+			getUploadPath(item){
+				let path = 'exam' + item.blob.split('/exam')[1].replace(`/${item.id}.json`,'')
+				console.log(path)
+				return path
+			},
+			/* 检测补救资源超链接 去除无效链接 */
+			formatRepairResource(list) {
+				if (list.length) {
+					let arr = [];
+					list.forEach((i, index) => {
+						i.blobUrl.forEach(j => {
+							arr.push({
+								blobUrl:j.url,
+								name:i.name,
+								type:i.type
+							})
+						})
+					});
+					return arr;
+				} else {
+					return [];
+				}
+			},
+		},
+		mounted() {
+			let analysisEditor = new E(this.$refs.analysisEditor);
+			analysisEditor.config.uploadImgShowBase64 = true;
+			analysisEditor.config.onchange = (html) => {
+				this.analysisContent = html;
+			},
+			this.$editorTools.initMyEditor(analysisEditor,this)
+			analysisEditor.config.menus = [
+				'bold',
+				'italic',
+				'underline',
+				'textDot',
+				'indent',
+				'link',
+				'list',
+				'quote',
+				'image',
+				'table',
+				'canvas',
+				'formula',
+				'undo'
+			]
+			analysisEditor.create();
+			this.analysisEditor = analysisEditor;
+		},
+		computed:{
+			hasPaperAuth(){
+				return this.$access.can('admin.*|exercise-upd')
+			}
+		},
+		watch:{
+			fixItem:{
+				handler(editItem){
+					console.error(editItem)
+					this.editItem = this._.cloneDeep(editItem)
+					this.analysisEditor.txt.html(editItem.explain);
+				},
+			}
+		}
+	}
+</script>
+
+<style lang="less">
+	.base-fix-container{
+		.fix-title{
+			font-size: 16px;
+			font-weight: bold;
+			
+			&::before{
+				content: "";
+				width: 5px;
+				height: 10px;
+				background-color: #2181ff;
+				display: inline-block;
+				margin-right: 10px;
+			}
+		}
+	}
+</style>

+ 114 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseHiteachPaper.vue

@@ -0,0 +1,114 @@
+<template>
+  <!-- 纸本测验 图片轮播 -->
+  <div class="hiteach-test-container">
+    <p style="text-align: center;font-size: 16px;margin: 20px 0;">{{ paper.name }}</p>
+    <div v-if="fullImgArr.length" style="display: flex;flex-direction: column;justify-content:center;">
+      <div style="text-align: center;">
+        <img v-lazy="fullImgArr[curImgIndex]" style="min-height:300px;max-width:100%">
+      </div>
+      <p class="page-footer" style="margin-top:40px;">
+        <Page :total="fullImgArr.length" :current="pageNum" :page-size="1" @on-change="pageChange" />
+      </p>
+    </div>
+    <div v-else class="empty-box">
+      <img src="@/assets/image/none.png">
+      <p>{{ $t('vote.noData') }}</p>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    paper: {
+      type: Object,
+      default: () => { }
+    }
+  },
+  data() {
+    return {
+      pageNum: 1,
+      isRender: false,
+      curImgIndex: 0,
+      fullImgArr: []
+    }
+  },
+  methods: {
+    pageChange(val) {
+      this.curImgIndex = val - 1
+    },
+    async doRender(paper) {
+      console.error('paper',paper)
+      let curScope = paper.examScope || paper.scope
+      let blobHost = curScope === 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+      let sasString = curScope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+      this.fullImgArr = paper.attachments?.map(imgName => blobHost + paper.blob + '/' + imgName + sasString.sas)
+      console.error(paper)
+      console.error(this.fullImgArr)
+    }
+  },
+  mounted() {
+    this.$EventBus.$off('EvBarChange')
+    this.$EventBus.$on('EvBarChange', index => {
+      if (index === 1) {
+        this.isRender = true
+        this.doRender(this.paper)
+      } else {
+        this.isRender = false
+      }
+    })
+
+  },
+  watch: {
+    paper: {
+      handler(newValue, oldValue) {
+        if (newValue.attachments && newValue.attachments.length) {
+          console.error('HiteachPaper > paper', newValue)
+          this.doRender(newValue)
+        }
+      },
+      immediate: true
+    }
+  }
+}
+</script>
+
+<style lang="less">
+.hiteach-test-container {
+  width: 100%;
+
+  // .ivu-carousel-track,.ivu-carousel-item{
+  // 	width: 100% !important;
+  // }
+
+  .demo-carousel {
+    img {
+      margin-left: 4%;
+      width: 92%;
+    }
+    img[lazy="loading"] {
+      width: 40px !important;
+      height: 40px !important;
+    }
+  }
+
+  .page-footer {
+    text-align: center;
+    padding: 10px 0;
+  }
+
+  .empty-box {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    font-size: 20px;
+    color: #c0c0c0;
+    font-weight: bold;
+    img {
+      width: 400px;
+    }
+  }
+}
+</style>

+ 568 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseImport.vue

@@ -0,0 +1,568 @@
+<template>
+  <div class="cp-import-container">
+    <div v-if="!isImportFinish" style="padding:40px 0;height:100%">
+      <Upload multiple :disabled="isBtnLoading" :action="uploadUrl" :headers="headers" :format="['docx','xlsx','xls']" :on-format-error="handleFormatError" :on-error="onUploadError" :show-upload-list="isShowList" :before-upload="beforeUpload" :on-success="uploadSuccess">
+        <Icon type="md-cloud-upload" size="150" color="#40A8F0" style="margin: 30px 0;" />
+        <div v-show="isBtnLoading" class="import-loading-wrap">
+          <img src="@/assets/loading/loading.svg" width="100px" />
+          <p style="color: #fff;">{{ $t('http.loading')}}</p>
+        </div>
+      </Upload>
+      <div class="import-tips">
+        <div style="display: flex;margin-bottom: 20px;justify-content: space-around;">
+          <Dropdown @on-click="onTemplateSelect">
+            <Button type="primary">
+              <Icon type="md-cloud-download" style="margin-right: 5px;" />
+              Word {{$t('evaluation.importFile.templateDownload')}}
+              <Icon type="ios-arrow-down"></Icon>
+            </Button>
+            <DropdownMenu slot="list">
+              <DropdownItem :name="0">中文简体(zh-CN)</DropdownItem>
+              <DropdownItem :name="1">中文繁體(zh-TW)</DropdownItem>
+              <DropdownItem :name="2">English(en-US)</DropdownItem>
+            </DropdownMenu>
+          </Dropdown>
+          <Dropdown @on-click="onTemplateSelect" style="margin-left: 15px;">
+            <Button type="primary">
+              <Icon type="md-cloud-download" style="margin-right: 5px;" />
+              Excel {{$t('evaluation.importFile.templateDownload')}}
+              <Icon type="ios-arrow-down"></Icon>
+            </Button>
+            <DropdownMenu slot="list">
+              <DropdownItem :name="3">中文简体(zh-CN)</DropdownItem>
+              <DropdownItem :name="4">中文繁體(zh-TW)</DropdownItem>
+              <DropdownItem :name="5">English(en-US)</DropdownItem>
+            </DropdownMenu>
+          </Dropdown>
+          <Dropdown @on-click="onTemplateSelect" style="margin-left: 15px;">
+            <Button type="primary">
+              <Icon type="md-cloud-download" style="margin-right: 5px;" />
+              {{$t('evaluation.importFile.downloadDetails')}}
+              <Icon type="ios-arrow-down"></Icon>
+            </Button>
+            <DropdownMenu slot="list">
+              <DropdownItem :name="6">中文简体(zh-CN)</DropdownItem>
+              <DropdownItem :name="7">中文繁體(zh-TW)</DropdownItem>
+              <DropdownItem :name="8">English(en-US)</DropdownItem>
+            </DropdownMenu>
+          </Dropdown>
+          <!-- <Button type="primary" icon="md-cloud-download" style="margin-left: 15px;" @click="onDownloadDetails">{{$t('evaluation.importFile.downloadDetails')}}</Button>	 -->
+        </div>
+
+        <p style="font-size:18px;font-weight:bold;color: #fff;">{{$t('evaluation.importFile.importTips')}}</p>
+        <p>1.{{$t('evaluation.importFile.tips1')}}</p>
+        <p>2.{{$t('evaluation.importFile.tips2')}}</p>
+        <p>3.{{$t('evaluation.importFile.tips3')}}</p>
+        <p>4.{{$t('evaluation.importFile.tips4')}}</p>
+        <p>5.{{$t('evaluation.importFile.tips5')}}</p>
+        <p v-if="isTwLang">6.{{$t('evaluation.importFile.tips8')}}</p>
+      </div>
+    </div>
+
+    <!-- 添加小题弹窗 -->
+    <Modal v-model="errorModal" width="800" class="bmf-error-modal" :ok-text="$t('evaluation.reImport')" :cancel-text="$t('evaluation.continueImport')" @on-ok="onErrorOk" @on-cancel="onErrorCancel">
+      <div class="modal-header" slot="header">{{ $t('evaluation.imgError') }}</div>
+      <p class="error-title">{{ $t('evaluation.imgErr1') }} <mark>{{ errorList.length }}</mark>
+        {{ $t('evaluation.imgErr2') }}<mark> {{ $t('evaluation.imgErr3') }}
+        </mark>{{ $t('evaluation.imgErr4') }}<mark> {{ $t('evaluation.imgErr5') }} </mark>?</p>
+      <div class="error-item-list">
+        <p v-for="(item,index) in errorList" :key="index" v-html="item" class="error-item"></p>
+      </div>
+    </Modal>
+  </div>
+</template>
+<script>
+import excel from '@/utils/excel.js'
+import FileSaver from "file-saver";
+export default {
+  props: ['period', 'subject'],
+  data() {
+    return {
+      fileName: '',
+      curLang: '',
+      errorList: [],
+      importList: [],
+      errorModal: false,
+      isBtnLoading: false,
+      isSelectFinish: false,
+      importLoading: false,
+      isImportFinish: false,
+      isShowList: true,
+      exerciseList: [],
+      importPreviewModal: false,
+      uploadUrl: '',
+      downloadUrls: [{
+        url: '/download/%E9%A2%98%E7%9B%AE%E6%A8%A1%E6%9D%BF-%E7%AE%80%E4%BD%93.docx',
+        fileName: '题目模板(简体).docx'
+      }, {
+        url: '/download/%E9%A1%8C%E7%9B%AE%E6%A8%A1%E6%9D%BF-%E7%B9%81%E9%AB%94.docx',
+        fileName: '題目範本(繁體).docx'
+      }, {
+        url: '/download/template.docx',
+        fileName: 'template.docx'
+      }, {
+        url: '/download/%E9%A2%98%E7%9B%AE%E8%8C%83%E6%9C%AC-%E7%AE%80%E4%BD%93.xlsx',
+        fileName: '题目模板.xlsx'
+      }, {
+        url: '/download/%E9%A1%8C%E7%9B%AE%E7%AF%84%E6%9C%AC-%E7%B9%81%E9%AB%94.xlsx',
+        fileName: '題目模板.xlsx'
+      }, {
+        url: '/download/template.xlsx',
+        fileName: 'template.xlsx'
+      }, {
+        url: '/download/%E5%88%B6%E4%BD%9C%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3/IES5%E8%AF%95%E5%8D%B7%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF%E5%88%B6%E4%BD%9C%E8%AF%B4%E6%98%8E.pdf',
+        fileName: 'IES5试卷模板制作说明.pdf'
+      }, {
+        url: '/download/%E5%88%B6%E4%BD%9C%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3/%E8%A9%A6%E5%8D%B7%E7%AF%84%E6%9C%AC%E8%A3%BD%E4%BD%9C%E8%A9%B3%E6%83%85%E8%AA%AA%E6%98%8ETW.pdf',
+        fileName: 'IES5試卷模板製作說明.pdf'
+      }, {
+        url: '/download/%E5%88%B6%E4%BD%9C%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3/Instructions%20for%20Making%20Exam%20File%20with%20MS%20Word.pdf',
+        fileName: 'Instructions for making paper template.pdf'
+      }],
+      hostName: '',
+      fieldArr: ['記憶记忆1', '理解2', '應用应用3', '分析4', '评价評鑒5', '创造創造6']
+    }
+  },
+  created() {
+    this.uploadUrl = window.location.origin + '/import/parse-docx'
+    this.curLang = localStorage.getItem('local')
+    this.hostName = this.$evTools.getBlobHost()
+  },
+  methods: {
+
+    /** 点击导入习题 */
+    handleUpload() {
+      this.importPreviewModal = true
+      this.exerciseList = []
+      this.isImportFinish = false
+    },
+
+    /* 下载模板 */
+    async onTemplateSelect(val) {
+      let curFile = this.downloadUrls[val]
+      const downloadRes = async () => {
+        let response = await fetch(this.hostName + curFile.url); // 内容转变成blob地址
+        let blob = await response.blob(); // 创建隐藏的可下载链接
+        let objectUrl = window.URL.createObjectURL(blob);
+        let a = document.createElement('a');
+        a.href = objectUrl;
+        a.download = curFile.fileName;
+        a.click()
+        a.remove();
+      }
+      downloadRes();
+    },
+
+    importModalChange(val) {
+      if (!val) {
+        this.isImportFinish = false
+        this.isSelectFinish = false
+        this.isBtnLoading = false
+        this.importLoading = false
+        this.exerciseList = []
+      }
+    },
+
+    /** 重新导入文件 */
+    onReImport() {
+      this.isImportFinish = false
+      this.isSelectFinish = false
+      this.exerciseList = []
+    },
+
+    /** 选择文件上传之前 */
+    beforeUpload(file) {
+      this.isBtnLoading = true
+      this.isSelectFinish = true
+      this.fileName = file.name ? this.getFileName(file.name) : ''
+      // 如果是excel格式文档 则需要进行对应解析
+      if (this.isExcel(file)) {
+        let excelResult = []
+        this.readExcel(file, data => {
+          console.log(data)
+          if (data.results.length) {
+            if (data.header.includes('Question')) {
+              data.results.forEach(item => {
+                excelResult.push({
+                  question: item.Question || '',
+                  answer: item.Answer ? this.getItemAnswer(item.Answer + '') :
+                    [],
+                  knowledge: item.Concept ? item.Concept.toString().split(',') : [],
+                  field: this.getItemField(item),
+                  score: item.Point || 0,
+                  type: item.Type,
+                  children: [],
+                  option: this.getItemOptions(item)
+                })
+              })
+            } else if (data.header.includes('正確答案') || data.header.includes('正選')) {
+              data.results.forEach(item => {
+                excelResult.push({
+                  question: item['題目'] || item['題幹'] || '',
+                  answer: (item['正確答案'] || item['正選']) ? this.getItemAnswerByStr((item['正確答案'] || item['正選']) + '') : [],
+                  knowledge: [],
+                  field: 0,
+                  score: 0,
+                  type: 'single',
+                  children: [],
+                  option: this.getItemOptionsByStr(item)
+                })
+              })
+            } else {
+              this.$Message.warning(this.$t('evaluation.importFile.warningTips4'))
+              this.isImportFinish = false
+              this.isBtnLoading = false
+              this.exerciseList = []
+              return
+            }
+            console.log(excelResult);
+
+            this.$emit('importFinish', {
+              list: excelResult,
+              fileName: this.fileName
+            })
+            this.isImportFinish = false
+            this.isBtnLoading = false
+            this.exerciseList = []
+          } else {
+            this.$Message.warning(this.$t('evaluation.importFile.warningTips4'))
+            this.isImportFinish = false
+            this.isBtnLoading = false
+            this.exerciseList = []
+          }
+        })
+        return false
+      }
+    },
+
+    /* 获取文件名 */
+    getFileName(name) {
+      let regRule = /[`~?$^&*\|]/g
+      return name.substring(0, name.lastIndexOf(".")).replace(regRule, '')
+    },
+
+    /* 获取答案 */
+    getItemAnswer(answer) {
+      let answerArr = answer.split('')
+      let result = []
+      answerArr.forEach(i => {
+        var patternNum = new RegExp("[0-9]+");
+        if (patternNum.test(i)) {
+          result.push(String.fromCharCode(64 + parseInt(i)))
+        } else {
+          result.push(i)
+        }
+      })
+      return result
+    },
+
+    /* 根据字符串匹配答案 */
+    getItemAnswerByStr(answer) {
+      return /^[a-zA-Z]*$/.test(answer[0]) ? [answer[0]] : [String.fromCharCode(64 + parseInt(answer))]
+    },
+
+    /* 根据字符串匹配选项 */
+    getItemOptionsByStr(item) {
+      let numWords = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
+      let numsArr = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
+      let itemKeys = Object.keys(item)
+      let result = []
+      itemKeys.forEach(k => {
+        let lastWord = k.charAt(k.length - 1)
+        let index = numWords.indexOf(lastWord) === -1 ? numsArr.indexOf(lastWord) : numWords.indexOf(lastWord)
+        if (index > -1) {
+          result.push({
+            code: String.fromCharCode(64 + parseInt(index + 1)),
+            value: item[k]
+          })
+        }
+      })
+      return result
+    },
+
+    /* 获取表格解析试题的选项 */
+    getItemOptions(item) {
+      let options = []
+      let optionIndex = 0
+      if (item.Type === 'judge') {
+        return [{
+          code: 'A',
+          value: this.$t('ability.true')
+        }, {
+          code: 'B',
+          value: this.$t('ability.false')
+        }]
+      } else {
+        for (let key in item) {
+          if (key.includes('Option')) {
+            options.push({
+              code: String.fromCharCode(64 + parseInt(optionIndex + 1)),
+              value: item[key]
+            })
+            optionIndex++
+          }
+        }
+        return options
+      }
+
+
+    },
+
+    /* 获取表格解析试题的认知层次 */
+    getItemField(item) {
+      for (let i in this.fieldArr) {
+        if (this.fieldArr[i].includes(item.Edu_Goal)) {
+          return +i + 1
+          break
+        }
+      }
+    },
+
+
+    /* 判断上传的是否为excel表格文件 */
+    isExcel(file) {
+      let fileType = file.name.split('.')[file.name.split('.').length - 1]
+      return fileType === 'xlsx' || fileType === 'xls'
+    },
+
+    /* 解析excel表格 */
+    readExcel(file, callback) {
+      var reader = new FileReader();
+      reader.onload = function (e) {
+        var data = e.target.result;
+        var workbook = excel.read(data, 'binary');
+        if (callback) callback(workbook);
+      };
+      reader.readAsBinaryString(file);
+    },
+
+    /**
+     * 当上传文件格式错误
+     * @param
+     */
+    handleFormatError() {
+      this.$Message.error(this.$t('evaluation.importFile.warningTips1'))
+      this.isBtnLoading = false
+      this.isSelectFinish = false
+    },
+
+    /**
+     * 当上传文件超过限制大小
+     * @param file
+     */
+    handleMaxSize() {
+      this.$Message.error(this.$t('evaluation.importFile.warningTips2'))
+      this.isBtnLoading = false
+      this.isSelectFinish = false
+    },
+
+    /* 有异常图片情况下选择重新导入模板 */
+    onErrorOk() {
+      this.$Message.warning(this.$t('evaluation.reImportTip'))
+      this.isImportFinish = false
+      this.isBtnLoading = false
+      this.errorList = []
+      this.importList = []
+      this.exerciseList = []
+    },
+
+    /* 有异常图片情况下选择继续预览 */
+    onErrorCancel() {
+      this.$Message.success(this.$t('evaluation.importFile.warningTips3'))
+      this.$emit('importFinish', {
+        list: this.importList,
+        fileName: this.fileName
+      })
+      this.isImportFinish = false
+      this.isBtnLoading = false
+      this.exerciseList = []
+      this.errorList = []
+      this.importList = []
+    },
+
+    /* 上传异常处理 */
+    onUploadError(e) {
+      console.log(e)
+      this.$Message.error(this.$t('evaluation.importFile.warningTips4'))
+      this.isBtnLoading = false
+    },
+
+    /**
+     * 上传文件成功回调
+     * @param response
+     */
+    uploadSuccess(res) {
+      // 匹配模板文档中的数学公式
+      var regex = /(\<math.*?<\/math\>)/g;
+      var html = res.html
+      var arr = html.match(regex);
+      if (arr && Array.isArray(arr) && arr.length && window.MathJax) {
+        // 对所有的mathml进行转换成svg操作
+        arr.forEach(math => {
+          let svgHtml = window.MathJax.mathml2svg(math, {
+            em: 12,
+            ex: 6
+          })
+          var regex2 = /\<svg .*\>.*\<\/svg\>/;
+          var arr2 = svgHtml.outerHTML.match(regex2);
+          html = html.replace(math, `<span>&nbsp;</span>${arr2[0]}<span>&nbsp;</span>`)
+        })
+      }
+      // 解析成题目列表
+      this.$api.SaveAnalyzeHtml({
+        html: html
+      }).then(response => {
+        if (response.tests.length && !response.emferror.length) {
+          this.$emit('importFinish', {
+            list: response.tests,
+            fileName: this.fileName
+          })
+          this.isImportFinish = false
+          this.isBtnLoading = false
+          this.exerciseList = []
+        } else if (response.emferror.length) {
+          this.errorModal = true
+          this.importList = response.tests
+          this.errorList = response.emferror
+          this.isImportFinish = false
+          this.isBtnLoading = false
+        } else {
+          console.log('222')
+          this.$Message.error(this.$t('evaluation.importFile.warningTips4'))
+          this.isBtnLoading = false
+        }
+        this.isSelectFinish = false
+      }).catch(e => {
+        console.log(e)
+      })
+    },
+  },
+  computed: {
+    headers() {
+      let hd = {}
+      hd['Authorization'] = 'Bearer ' + localStorage.getItem('access_token')
+      hd['X-Auth-AuthToken'] = localStorage.getItem('auth_token')
+      return hd
+    },
+    isTwLang(){
+      return localStorage.local === 'zh-tw'
+    }
+
+  }
+}
+</script>
+
+<style scoped lang="less">
+@import "../index/CommonExercise.less";
+</style>
+
+<style lang="less">
+.cp-import-container {
+  // background: #404040;
+  height: 100%;
+}
+
+.bmf-error-modal {
+  .error-title {
+    margin: 15px 15px 0 15px;
+    letter-spacing: 1px;
+    font-size: 16px;
+    font-weight: bold;
+  }
+
+  mark {
+    color: red;
+  }
+
+  .error-item-list {
+    max-height: 500px;
+    overflow-y: scroll;
+    padding: 15px;
+
+    .error-item {
+      margin-top: 20px;
+      background: #efefef;
+      padding: 10px;
+    }
+  }
+}
+
+.cp-import-container,
+.cp-import-container .ivu-upload {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  .ivu-upload-list {
+    display: none;
+  }
+
+  .ivu-icon {
+    cursor: pointer;
+  }
+
+  .ivu-btn-primary {
+    background-color: #00756f;
+    border: none;
+  }
+}
+
+.cp-import-container .ivu-upload-drag {
+  padding-top: 8%;
+  background: #2e2e2e;
+  border: 1px dashed #626365;
+  color: #b8b8b8;
+  font-size: 16px;
+}
+
+.cp-import-container .ivu-modal-body,
+.edit-exercise-modal .ivu-modal-body {
+  max-height: 700px;
+  overflow: auto;
+  padding: 0;
+}
+
+.cp-import-container .item-explain-details {
+  width: 94%;
+}
+
+.cp-import-container .ivu-modal {
+  max-height: 600px;
+  position: relative;
+}
+
+.import-tips {
+  padding: 20px 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: var(--second-text-color);
+}
+
+.import-tips p {
+  margin: 5px;
+}
+
+.cp-import-container .exercise-item:not(:first-child) {
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid #dedede;
+}
+
+.cp-import-container .exercise-item .btn-edit-exercise {
+  display: none;
+}
+
+.cp-import-container .exercise-item:hover .btn-edit-exercise {
+  display: unset;
+}
+
+.cp-import-container .import-loading-wrap {
+  position: absolute;
+  margin-top: 100px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+</style>

+ 122 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseObjectivePie.vue

@@ -0,0 +1,122 @@
+<template>
+  <div id="myObjectivePie"></div>
+</template>
+
+<script>
+export default {
+  name: 'BaseObjectivePie',
+  props: ['pieId', 'echartsData'],
+  data() {
+    return {
+      pieData: [],
+      types: this.$GLOBAL.EXERCISE_TYPES()
+    }
+  },
+  methods: {
+
+    drawLine(data) {
+      let that = this
+      // 基于准备好的dom,初始化echarts实例
+      let myPie = this.$echarts.init(document.getElementById('myObjectivePie'), 'chalk')
+
+      // 指定图表的配置项和数据
+      var option = {
+        title: {
+          'text': this.$t('evaluation.echarts.objectivePie'),
+          'left': 'center',
+          'textStyle': {
+            'fontSize': 14,
+            'color': '#595959'
+          }
+        },
+        legend: {
+          left: 'center',
+          bottom: 0,
+          type: 'scroll',
+          formatter: function (name, val) {
+            return name + ' (' + data.filter(i => i.name === name)[0].value + that.$t('unit.text10') + ')';
+          }
+        },
+        color: ['#ff9f7f', '#37d2da', '#37d2da', '#ff9f7f', '#d4e676', '#ff9f7f', '#fb7293'],
+        tooltip: {
+          trigger: 'item',
+          formatter: '{a} <br/>{b} : {c} ({d}%)'
+        },
+        calculable: true,
+        series: [
+          {
+            name: that.$t('evaluation.echarts.objectivePie'),
+            type: 'pie',
+            radius: [0, 80],
+            data: data
+          }
+        ]
+      }
+
+      // 绘制图表
+      myPie.setOption(option)
+
+      window.addEventListener('resize', function () {
+        myPie.resize()
+      })
+    }
+  },
+  mounted() {
+    let arr = []
+    let typeList = this._.groupBy(this.echartsData.item, 'type')
+    let subjectiveCount = 0
+    let objectiveCount = 0
+    console.log(typeList)
+    for (let key in typeList) {
+      if (['single', 'multiple', 'judge'].includes(key)) {
+        objectiveCount += typeList[key].length
+      } else if (key === 'compose') {
+        console.log(typeList[key])
+        typeList[key].forEach(composeItem => {
+          composeItem.children.forEach(item => {
+            if (['single', 'multiple', 'judge'].includes(item.type)) {
+              objectiveCount++
+            } else {
+              subjectiveCount++
+            }
+          })
+        })
+      } else {
+        subjectiveCount += typeList[key].length
+      }
+    }
+    console.log(objectiveCount)
+    console.log(subjectiveCount)
+    arr = [{
+      name: this.$t('evaluation.type1'),
+      value: subjectiveCount
+    }, {
+      name: this.$t('evaluation.type2'),
+      value: objectiveCount
+    }]
+    this.drawLine(arr)
+  },
+  computed: {
+    // 获取最新知识点占比饼图数据
+    getPieData() {
+      return this.$store.state.totalAnalysis.knowledgeData
+    }
+  },
+  watch: {
+    echartsData(val) {
+
+    }
+  }
+
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+#myObjectivePie {
+  width: 100%;
+  height: 320px;
+  margin: 20px auto;
+  display: block;
+}
+</style>

+ 231 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePaperItemPicker.vue

@@ -0,0 +1,231 @@
+<template>
+  <div class="paper-item-picker-container">
+    <div class="paper-list">
+      <div style="display: flex;align-items: center;margin-bottom: 10px;font-size:14px !important">
+        <div class="action-tool" style="display: flex;align-items: center;">
+          <span class="action-title" style="margin-left:20px;width: 80px;">{{ $t('evaluation.tag') }}:</span>
+          <Select v-model="selectTags" multiple style="min-width:100px" @on-change="onTagChange">
+            <Option v-for="item in tags" :value="item" :key="item">{{ item }}</Option>
+          </Select>
+        </div>
+        <div class="action-tool" style="display: flex;margin-left:20px;">
+          <Input v-special-char suffix="ios-search" v-model="searchVal" clearable :placeholder="$t('evaluation.paperList.searchPaper')" style="width: 300px" @on-click="onCloseSearch" @on-change="onSearchChange" />
+        </div>
+        <div>
+          <p style="margin-left:20px;" v-if="paperList.length">{{ $t('evaluation.paperPickTip1') }} <span style="font-weight:bold;color:#42a676">{{ checkList.length }}</span> {{ $t('evaluation.paperPickTip2') }}</p>
+          <p style="margin-left:20px;" v-if="!paperList.length">{{ $t('evaluation.paperPickTip3') }}</p>
+        </div>
+      </div>
+      <Collapse simple v-if="paperList.length" v-model="collapsePaper" accordion @on-change="onPaperClick">
+        <Panel v-for="(paper,index) in paperList" color="primary" :name="paper.id">
+          <span>
+            <span style="display:inline-flex; align-items: center">
+              <span style="margin-right:10px">{{ paper.name }}</span>
+              <Tag color="blue">{{ paper.scoring ? paper.scoring.length : 0 }}{{ $t('evaluation.paperPickTip2') }}</Tag>
+              <span style="margin-left: 8px;" v-for="(tag,tagIndex) in paper.tags">
+                <Tag color="geekblue">{{ tag }}</Tag>
+              </span>
+              <span style="margin-left: 8px;" v-if="paper.mode">
+                <Tag color="green">{{ paper.mode }}</Tag>
+              </span>
+              <span style="margin-left: 8px;" v-if="paper.markModel">
+                <Tag color="cyan">{{ $t('evaluation.markMode.tip4') }}</Tag>
+              </span>
+            </span>
+            <Checkbox v-model="paper.checkAll" @on-change="checkAllItem" @click.stop.native="() => {}" v-show="collapsePaper[0] === paper.id" style="float: right;">
+              {{ paper.checkAll ? $t('teachermgmt.cancel') : $t('totalAnalysis.baseExport.checkAll') }}
+            </Checkbox>
+          </span>
+          <template #content>
+            <div class="question-list" v-if="questionList.length">
+              <div :class="[ 'question-item',checkList.find(i => i.id === item.id) ? 'question-item-active' : 'question-item' ]" v-for="(item,index) in questionList" @click="onQuestionClick(item,index)">
+                <div class="question-stem">
+                  <Tag>{{ exersicesType[item.type] }}</Tag>
+                  <!-- <Tag v-if="item.type === 'subjective'">{{ item.answerType }}</Tag> -->
+                  <span>{{ index + 1 }}.</span>
+                  <span v-html="item.question"></span>
+                </div>
+              </div>
+            </div>
+          </template>
+        </Panel>
+      </Collapse>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'BasePaperItemPicker',
+  props: {
+    gradeCode: {
+      type: Array,
+      default: () => []
+    },
+    subjectCode: {
+      type: String,
+      default: ''
+    },
+    periodCode: {
+      type: String,
+      default: ''
+    },
+    /* 是否阅卷专用 */
+    isMarkModel: {
+      type: Boolean,
+      default: false
+    },
+  },
+  data() {
+    return {
+      searchVal: '',
+      tags: [],
+      paperList: [],
+      selectTags: [],
+      originList: [],
+      questionList: [],
+      checkList: [],
+      originQuestionList: [],
+      exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
+      paperIndex: -1,
+      collapsePaper: [],
+    }
+  },
+  created() {
+    console.error(this.gradeCode, this.subjectCode, this.periodCode)
+    this.findPaper()
+  },
+  methods: {
+    /* 搜索词汇发生变化 */
+    onSearchChange() {
+      let val = this.searchVal
+      this.paperList = this.originList.filter(i => i.name.indexOf(val) > -1)
+    },
+    /* 关闭搜索 */
+    onCloseSearch() {
+      this.searchVal = ''
+      this.paperList = JSON.parse(JSON.stringify(this.originList))
+    },
+    /* 标签选择发生变化 */
+    onTagChange(val) {
+      this.paperList = val.length ? this.originList.filter(i => val.find(j => i.tags.includes(j))) : this.originList
+    },
+    onQuestionClick(item, index) {
+      let findIndex = this.checkList.findIndex(i => i.id === item.id)
+      if (findIndex > -1) {
+        this.checkList.splice(findIndex, 1)
+      } else {
+        let newItem = this.originQuestionList.find(i => i.id === item.id)
+        newItem.score = 0
+        this.checkList.push(newItem)
+      }
+      let checkNum = 0
+      this.questionList.forEach(item => {
+        checkNum += (this.checkList.find(check => check.id === item.id) ? 1 : 0)
+      })
+      this.paperList[this.paperIndex].checkAll = checkNum === this.questionList.length ? true : false
+    },
+    findPaper() {
+      this.$api.learnActivity.FindExamPaper({
+        '@DESC': "createTime",
+        'code': this.isSchool ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+        'gradeIds[*]': this.isSchool ? this.gradeCode.map(i => i + '') : [],
+        'periodId': this.isSchool ? this.periodCode : [],
+        'scope': this.isSchool ? 'school' : "private",
+        'subjectId': this.isSchool ? [this.subjectCode] : []
+      }).then(res => {
+        res.papers = res.papers.filter(i => !i.qamode)
+        let papersNoCompose = res.papers.map(item => {
+          item.checkAll = false
+          if(this.isMarkModel) item.scoring = item.scoring.filter(score => score.type !== 'compose')
+          return item
+        })
+        papersNoCompose.forEach(item => {
+          if(item.scoring.length) {
+            this.paperList.push(item)
+            this.originList.push(item)
+          }
+        })
+        /* this.paperList = res.papers
+        this.originList = this._.cloneDeep(res.papers) */
+        if (papersNoCompose.length > 0) {
+          this.tags = [...new Set(papersNoCompose.map(i => i.tags).flat(1))]
+        }
+      })
+    },
+    async onPaperClick(paperId) {
+      if (!paperId.length) return
+      this.question = []
+      let paper = this.paperList.find(i => i.id === paperId[0])
+      this.paperIndex = this.paperList.findIndex(i => i.id === paperId[0])
+      let fullPaperJson = await this.$evTools.getFullPaper(paper)
+      let fullPaperNoCompose = this.isMarkModel ? fullPaperJson.item.filter(item => item.type !== 'compose') : fullPaperJson.item
+      fullPaperNoCompose.forEach(item => item.paperName = paper.name)
+      this.questionList = fullPaperNoCompose
+      this.originQuestionList = this._.cloneDeep(fullPaperNoCompose)
+      this.questionList.forEach(i => {
+        i.question = i.question.replace(/<[^>]+>/g, "")
+      })
+      console.log(fullPaperJson)
+      let checkNum = 0
+      this.questionList.forEach(item => {
+        checkNum += (this.checkList.find(check => check.id === item.id) ? 1 : 0)
+      })
+      paper.checkAll = checkNum === this.questionList.length ? true : false
+    },
+    checkAllItem(value) {
+      this.questionList.forEach((item, index) => {
+        if(value) {
+          if(!this.checkList.find(check => check.id === item.id)) {
+            this.onQuestionClick(item, index)
+          }
+        } else {
+          this.onQuestionClick(item, index)
+        }
+      })
+    },
+    changeCheckAll() {
+      this.paperList.forEach(item => {
+        item.checkAll = false
+      })
+    },
+
+  },
+  computed: {
+    isSchool() {
+      return this.$route.name === 'newSchoolPaper'
+    },
+  }
+}
+</script>
+
+<style lang="less">
+.paper-item-picker-container {
+  min-height: 400px;
+  .ivu-collapse {
+    border: none;
+  }
+  .ivu-collapse-header {
+    height: 50px !important;
+    line-height: 50px !important;
+  }
+
+  .ivu-collapse-content {
+    padding: 0 45px;
+  }
+
+  .question-item {
+    margin: 10px 0;
+    display: flex;
+    justify-content: space-between;
+    border: 1px dashed #ccc;
+    padding: 10px;
+    cursor: pointer;
+  }
+
+  .question-item-active {
+    background: #42a676;
+    color: #fff;
+  }
+}
+</style>

+ 78 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePasteTool.vue

@@ -0,0 +1,78 @@
+<template>
+	<div>
+		<div style="display: flex;justify-content: space-between;align-items: center;padding: 0 10px;">
+			<span style="font-size: 14px;margin-left: 10px;">
+				<span v-if="isHiToolAlive" style="color: #10abe7;">
+					* 已开启醍摩豆教学助手,可以获得更好图文粘贴体验
+				</span>
+				<span v-else style="color: #e72f32;">
+					* 检测到您暂未开启醍摩豆教学助手,启动后可获得更好图文粘贴体验<span style="margin-left: 5px;text-decoration: underline;color: #0074D9;cursor: pointer;" @click="openHiTool">启动</span>
+				</span>
+			</span>
+			<Button type="info" @click="parseHtml">一键生成试题</Button>
+		</div>
+		
+		<div style="margin-top: 10px;">
+			<div style="margin:  0 10px;">
+				<div ref="editor" style="text-align:left" id="pasteEditor"></div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+	import axios from 'axios'
+	import E from 'wangeditor'
+	export default {
+		data() {
+			return {
+				stemContent: '',
+				stemEditor: null,
+				answerContent: '',
+				answerEditor: null,
+				isHiToolAlive:false
+			}
+		},
+		methods:{
+			/* 启动HiTool */
+			openHiTool(){
+				window.open("hitools://")
+			},
+			/* 检测是否启动了HiTool */
+			async isToolAlive(){
+				this.isHiToolAlive = await this.$editorTools.checkTools()
+			},
+			/* 解析HTML成试题数据 */
+			parseHtml(){
+				let html = this.stemEditor.txt.html()
+				let noUseTag = '<o:p></o:p>'
+				html = this.replaceAll(html,noUseTag,'')
+				html = this.replaceAll(html,'<font ','<span ')
+				html = this.replaceAll(html,'</font>','</span>')
+				this.$api.SaveAnalyzeHtml({ html : html }).then(response => {
+					if(response.tests.length){
+						this.$emit('addFinish',response.tests)
+					}else{
+						this.$Message.warning('试题生成失败!请检查粘贴内容是否完整!')
+					}
+				}).catch(e => {
+					this.$Message.error('试题生成失败!')
+				})
+			},
+			/* 全局替换 */
+			replaceAll(str,preVal,replaceVal){
+			    return str.replace(new RegExp(preVal,'g'),replaceVal);
+			}
+		},
+		mounted() {
+			this.isToolAlive()
+			let stemEditor = new E(this.$refs.editor)
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html
+			}
+			this.$editorTools.initMyEditor(stemEditor, this)
+			stemEditor.config.height = 600
+			stemEditor.create()
+			this.stemEditor = stemEditor
+		},
+	}
+</script>

+ 149 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePointPie.vue

@@ -0,0 +1,149 @@
+<template>
+	<div id="myPointPie"></div>
+</template>
+
+<script>
+	export default {
+		name: 'BasePointPie',
+		props: ['pieId', 'echartsData'],
+		data() {
+			return {
+				pieData: [],
+				types: this.$GLOBAL.EXERCISE_DIFFS()
+			}
+		},
+		methods: {
+			
+			getColorRandom(num){
+			    var colorarr = [];
+			    //构成颜色的字符元素
+			    var cArray=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"];
+			    for(var i=0;i<num;i++){
+			    	var color = "#";
+			    	for(var j=0;j<6;j++){
+			   	        var  cIndex = Math.round(Math.random()*15);
+			   	   	    color+=cArray[cIndex];
+			   	    }
+			    	colorarr[i] = color;
+			    }
+			    return colorarr;
+			},
+			
+
+
+			drawLine(data) {
+				let that = this
+				// 基于准备好的dom,初始化echarts实例
+				let myPie = this.$echarts.init(document.getElementById('myPointPie'), 'chalk')
+
+				// 指定图表的配置项和数据
+				var option = {
+					title: {
+						'text': this.$t('evaluation.echarts.pointPie'),
+						'left': 'center',
+						'textStyle': {
+							'fontSize': 14,
+							'color': '#595959'
+						}
+					},
+					legend: {
+						left: 'center',
+						bottom: 0,
+						type: 'scroll',
+						formatter: function(name, val) {
+							if (data.length) {
+								return name + ' (' + data.filter(i => i.name === name)[0].value + that.$t(
+									'unit.text10') + ')';
+							}
+						}
+					},
+					color:that.getColorRandom(50),
+					tooltip: {
+						trigger: 'item',
+						formatter: '{a} <br/>{b} : {c} ({d}%)'
+					},
+					calculable: true,
+					series: [{
+						name: that.$t('evaluation.echarts.pointPie'),
+						type: 'pie',
+						radius: [0, 80],
+						data: data
+					}]
+				}
+
+				// 绘制图表
+				myPie.setOption(option)
+
+				window.addEventListener('resize', function() {
+					myPie.resize()
+				})
+			}
+		},
+		mounted() {
+			let arr = []
+			let pointList = []
+			this.echartsData.item.forEach(i => {
+				if (i.type === 'compose' && i.children.length) {
+					i.children.forEach(j => {
+						if(j.knowledge){
+							pointList.push(...j.knowledge)
+						}
+					})
+				} else {
+					if(i.knowledge){
+						pointList.push(...i.knowledge)
+					}
+				}
+			})
+			let pointArr = [...new Set(pointList)]
+			if(!pointArr.length){
+				arr.push({
+					value: this.echartsData.item.length,
+					name: this.$t("studentWeb.exam.report.noKnowledge")
+				})
+			}
+			pointArr.forEach((i,index) => {
+				arr.push({
+					value: 0,
+					name: i
+				})
+				this.echartsData.item.forEach(k => {
+					if (k.type === 'compose' && k.children.length) {
+						k.children.forEach(j => {
+							if(j.knowledge && Array.isArray(j.knowledge) && j.knowledge.includes(i)){
+								arr[index].value++
+							}
+						})
+					} else {
+						if(k.knowledge && Array.isArray(k.knowledge) && k.knowledge.includes(i)){
+							arr[index].value++
+						}
+					}
+				})
+			})
+			this.drawLine(arr)
+		},
+		computed: {
+			// 获取最新知识点占比饼图数据
+			getPieData() {
+				return this.$store.state.totalAnalysis.knowledgeData
+			}
+		},
+		watch: {
+			echartsData(val) {
+
+			}
+		}
+
+	}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+	#myPointPie {
+		width: 100%;
+		height: 320px;
+		margin: 20px auto;
+		display: block;
+	}
+</style>

+ 483 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BasePoints.vue

@@ -0,0 +1,483 @@
+<template>
+  <div class="points-container">
+    <!-- <Button type="info" class="btn-add-point" @click="addPointsModal = true"
+			v-show="curScope === 'school'">{{$t('evaluation.points.addPoint')}}</Button> -->
+    <div v-if="curScope != 'school' && subject" style="margin-bottom: 10px;">
+      <span class="manual-filter-label">{{ $t('evaluation.filter.origin') }}:</span>
+      <RadioGroup v-model="radioCode" @on-change="onCodeChange">
+            <Radio label="private">
+                <span>{{ $t('cusMgt.private') }}</span>
+            </Radio>
+            <Radio label="school">
+                <span>{{ $t('cusMgt.school') }}</span>
+            </Radio>
+      </RadioGroup>
+    </div>
+    <div class="filter-wrap" v-if="radioCode === 'school'">
+      <Select v-model="selectPeriod" @on-change="onPeriodChange" :disabled="curScope === 'school'">
+        <Option v-for="(item, index) in periodList" :value="item.id" :key="index">{{ item.name }}</Option>
+      </Select>
+
+      <Select v-model="selectSubject" @on-change="onSubjectChange" :disabled="curScope === 'school'">
+        <Option v-for="(item, index) in subjectList" :value="item.id" :key="index">{{ item.name }}</Option>
+      </Select>
+    </div>
+    <div class="filter-wrap" v-else>
+      <Select v-model="selectTree" @on-change="getPoints">
+        <Option v-for="(item, index) in treeList" :value="item.id" :key="index">{{ item.name }}</Option>
+      </Select>
+      <Icon v-if="treeList.length" type="md-add-circle" color="#40A8F0" size="16" :title="$t('knowledge.addPoint')" style="cursor:pointer; margin-left: 5px;" @click="getKnowledgeTree()" />
+      <span v-else>{{ $t('knowledge.addTreeTips') }}</span>
+    </div>
+
+    <div style="margin-top: 20px;">
+      <Loading hideMask v-show="isLoading"></Loading>
+      <Input v-special-char icon="ios-close" v-model="searchSchoolPoint" :placeholder="$t('evaluation.points.searchPoint')" autofocus style="width: 98%;margin-left:1%;margin-bottom:10px" @on-change="onSearchSchoolChange" @on-enter="onSearchSchoolChange" />
+      <div v-if="schoolPointList.length === 0" style="margin:5px">{{$t('evaluation.points.noPoint')}}</div>
+      <CheckboxGroup v-model="checkedList" @on-change="onCheckChange" v-else>
+        <Checkbox v-for="(item,index) in schoolPointList" :label="item" :key="item">{{ item }}</Checkbox>
+      </CheckboxGroup>
+    </div>
+
+    <div class="points-checked-wrap">
+      <p class="points-title">{{$t('evaluation.points.checkedPoint')}}</p>
+      <span class="point-item" v-for="(item,index) in checkedList" :key="index">{{ item }}</span>
+    </div>
+
+    <div class="point-footer">
+      <Button @click="onCancel">{{$t('evaluation.cancel')}}</Button>
+      <Button type="info" @click="onConfirm">{{$t('evaluation.confirm')}}</Button>
+    </div>
+
+    <Modal v-model="addPointsModal" :title="$t('evaluation.points.addPoint')" ref="pointRef" width="400px" class="related-point-modal" style="z-index:99999">
+      <span>{{$t('evaluation.points.pointName')}}</span>
+      <span style="display: flex; align-items: center;">
+        <Input v-special-char v-model="newPointName" :placeholder="$t('evaluation.points.inputNewPoint')" style="margin:10px 0" />
+        <Button type="primary" @click="onAddNewPoint" style="margin-left: 10px;">{{ $t('evaluation.points.addLevelOne') }}</Button>
+      </span>
+      <el-tree :data="knowledgeTree" :props="defaultProps" class="tree" node-key="id" :expand-on-click-node="false" check-on-click-node highlight-current accordion ref="pointTree">
+          <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="tree-node-lable">
+                  <span class="text-cut" style="max-width: 60%;display: inline-block;vertical-align: bottom;" :title="data.name">{{ data.name }}</span>
+              </span>
+              <span class="custom-tree-tools">
+                  <Icon type="md-add" :title="$t('evaluation.points.addLevel')" v-if="node.level < 4" @click.stop="onAddPoint(node, data)" />
+              </span>
+          </span>
+      </el-tree>
+      <div slot="footer">
+        <Button type="text" @click="addPointsModal = false">{{$t('evaluation.cancel')}}</Button>
+        <Button type="primary" @click="onSureAdd" :loading="isAddLoading">{{ $t('cusMgt.save') }}</Button>
+      </div>
+    </Modal>
+  </div>
+</template>
+<script>
+import '@/utils/Math.uuid'
+export default {
+  components: {},
+  props: {
+    period: {
+      type: String,
+      default: ''
+    },
+    subject: {
+      type: String,
+      default: ''
+    },
+    scope: {
+      type: String,
+      default: 'school'
+    },
+    points: {
+      type: Array,
+      default: () => []
+    },
+    max: {
+      type: Number,
+      default: 5
+    }
+  },
+  data() {
+    return {
+      isLoading: false,
+      addPointsModal: false,
+      isAddLoading: false,
+      tabName: 'school',
+      newPointType: 'school',
+      newPointName: '',
+      searchPoint: '',
+      searchSchoolPoint: '',
+      curScope: '',
+      selectPeriod: 0,
+      selectSubject: 0,
+      periodList: [{
+        id: '0',
+        name: '0'
+      }],
+      subjectList: [{
+        id: '0',
+        name: '0'
+      }],
+      treeList: [],
+      selectTree: 0,
+      pointList: [],
+      schoolPointList: [], //展示的知识点
+      checkedList: [],
+      originPointList: [], //所有的知识点
+      originSchoolList: [], //所有的知识点
+      originList: [],
+      defaultParams: {
+        code: '',
+        periodId: null,
+        subjectId: null,
+        school_code: null,
+        type: 1 
+      },
+      curPointResponse: null,
+      radioCode: '',
+      knowledgeTree: [],
+      defaultProps: {
+          children: 'children',
+          label: 'name'
+      },
+    }
+  },
+  created() {
+    // this.radioCode = this.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+    this.radioCode = this.scope
+    console.log('period-' + this.period)
+    console.log('subject-' + this.subject)
+    console.log('scope-' + this.scope)
+    if (this.period && this.subject) {
+      this.selectPeriod = this.period
+      this.selectSubject = this.subject
+    }
+    this.initSchoolData()
+    // 个人需先获取到知识点树列表,再获取相关的知识点
+    if(this.scope === 'private') {
+      this.getPointList()
+    } else {
+      this.getPoints()
+    }
+  },
+  methods: {
+    // 获取当前学校学段学科等基本信息
+    initSchoolData() {
+      this.$store.dispatch('user/getSchoolProfile').then(res => {
+        let schoolBaseInfo = res.school_base
+        if (schoolBaseInfo && schoolBaseInfo.period.length) {
+          this.periodList = schoolBaseInfo.period
+          if (this.selectPeriod) {
+            console.log(this.selectSubject)
+            console.log(this.selectPeriod)
+            this.subjectList = this.periodList.filter(i => i.id === this.selectPeriod)[0].subjects
+            console.log(this.subjectList.map(i => i.id))
+          } else {
+            this.selectPeriod = schoolBaseInfo.period[0].id
+            this.onPeriodChange(this.periodList[0].id)
+          }
+        }
+      })
+    },
+
+    // 学段切换处理
+    onPeriodChange(val) {
+      this.subjectList = this.periodList.filter(i => i.id === val)[0].subjects
+      this.selectPeriod = val
+      this.selectSubject = this.subjectList[0].id
+      this.subjectList.length && this.getPoints()
+    },
+
+    onSubjectChange(val) {
+      this.getPoints()
+    },
+    onCodeChange(val) {
+      this.searchSchoolPoint = ''
+      this.checkedList = []
+      this.getPoints()
+    },
+
+    doReset() {
+      this.getPoints()
+      this.checkedList = []
+    },
+    /**
+     * 获取个人知识点树列表
+     */
+    getPointList() {
+        this.isLoading = true
+        let params = {
+            scope: 'private' 
+        }
+        this.$api.knowledge.getPointList(params).then(res => {
+            if(res.datas && res.datas.length) {
+                this.selectTree = res.datas[0].id
+                this.treeList = res.datas
+                if(res.datas.length) this.getPoints()
+            }
+        }).finally(() => {
+            this.isLoading = false
+        })
+    },
+
+    /**
+     * 获取知识点仓库数据
+     */
+    getPoints() {
+      this.isLoading = true
+      if (this.periodList.length && this.subjectList.length) {
+        // if (this.pointList.length && this.schoolPointList.length) return
+        this.defaultParams.code = this.$store.state.userInfo.schoolCode
+        this.defaultParams.school_code = this.$store.state.userInfo.schoolCode
+        this.defaultParams.periodId = this.selectPeriod
+        this.defaultParams.subjectId = this.selectSubject
+      }
+      let params = this.radioCode === 'school' ? this.defaultParams : {
+        scope: 'private',
+        kid: this.selectTree,//不填,则返回该教师的所有知识点树的信息
+        owner: this.$store.state.userInfo.TEAMModelId,
+      }
+
+      if (this.radioCode === 'school' ? this.defaultParams.periodId && this.defaultParams.subjectId : true) {
+        this.$api.knowledge.GetSchoolPoints(params).then(res => {
+          if (!res.error && res.length) {
+            this.curPointResponse = res[0]
+            this.schoolPointList = res[0].points
+            this.originSchoolList = res[0].points
+            this.originPointList = this.originPointList.concat(res[0].points)
+          } else {
+            this.$Message.warning(this.$t('evaluation.noData'),)
+            this.uuid = Math.uuid()
+            this.schoolPointList = []
+            this.originSchoolList = []
+            this.originPointList = []
+            this.curPointResponse = {
+              blocks: [],
+              code: `Knowledge-${this.$store.state.userInfo.schoolCode}-${this.uuid}`,
+              id: this.uuid,
+              owner: this.$store.state.userInfo.schoolCode,
+              periodId: this.defaultParams.periodId,
+              pk: 'Knowledge',
+              points: [],
+              scope: 'school',
+              subjectId: this.defaultParams.subjectId
+            }
+          }
+          this.isLoading = false
+        })
+      }
+      this.isLoading = true
+
+
+    },
+
+    /** 添加新知识点 */
+    onAddNewPoint() {
+      if(!this.newPointName) {
+        this.$Message.warning(this.$t('knowledge.knowledgeP1'))
+        return
+      }
+      this.knowledgeTree.push({
+        allcids: [],
+        children: [],
+        id: this.knowledgeTree.length ? (Number(this.knowledgeTree[this.knowledgeTree.length - 1].id) + 1).toString() : '1',
+        level: 1,
+        link: 0,
+        name: this.newPointName,
+        pid: this.selectTree,
+        subcids: [],
+        tid: null,
+        used: 0
+      })
+      this.newPointName = ''
+    },
+    onSureAdd() {
+      let params = {
+          knowledgeTree: {
+              id: this.selectTree,
+              owner: this.$store.state.userInfo.TEAMModelId,
+              scope: 'private',
+              periodId: '',
+              subjectId: '',
+              name: this.treeList.find(item => item.id === this.selectTree).name,
+              tree: this.knowledgeTree || [],
+          }
+      }
+      this.$api.knowledge.upsertPoint(params).then(res => {
+          if(res.knowledgeTree) {
+              this.$Message.success(this.$t('knowledge.saveSuccess'))
+              this.getPoints()
+              this.addPointsModal = false
+          } else {
+              this.$Message.warning(this.$t('knowledge.saveFail'))
+          }
+      }).catch(err => {
+          this.$Message.error(this.$t('knowledge.saveFail'))
+      })
+    },
+
+    /**
+     * 保存知识点
+     * @param pointItem
+     * @param BlockItem
+     */
+    savePoint() {
+      this.isAddLoading = true
+      this.$api.knowledge.SaveOrUpdateKnowledge(this.curPointResponse).then(res => {
+        if (!res.error && res) {
+          this.$Message.success(this.$t('evaluation.points.addSuc'))
+          this.addPointsModal = false
+          this.isAddLoading = false
+          this.getPoints()
+        } else {
+          this.$Message.warning(res.error)
+        }
+      }).catch(err => {
+        console.log(err)
+        this.$Message.warning(this.$t('evaluation.points.addFail'))
+      })
+    },
+
+    onCheckChange(val) {
+      if (val.length > this.max) {
+        this.$Message.warning(
+          `${this.$t('evaluation.points.numMax1') + this.max + this.$t('evaluation.points.numMax2')}`
+        )
+        this.checkedList.splice(this.checkedList.length - 1, 1)
+      } else {
+        // this.$emit('onCheckChange', val, this.originPointList)
+      }
+    },
+
+    onConfirm() {
+      this.$emit('onCheckChange', this.checkedList, this.originPointList)
+    },
+
+    onCancel() {
+      this.checkedList = []
+      this.$emit('onCancel')
+    },
+
+    onDeletePoint(index) {
+      this.checkedList.splice(index, 1)
+    },
+
+    onSearchSchoolChange() {
+      this.schoolPointList = this.originSchoolList.filter(item => item.toUpperCase().indexOf(this.searchSchoolPoint.toUpperCase()) > -1 || item.toLowerCase().indexOf(this.searchSchoolPoint.toLowerCase()) > -1 || item.indexOf(this.searchSchoolPoint) > -1)
+    },
+    getKnowledgeTree() {
+      let params = {
+          periodId: '',
+          scope: 'private',
+          school_code: '',
+          id: this.selectTree
+      }
+      this.$api.knowledge.getPointInfo(params).then(res => {
+        if(res.knowledgeTrees) {
+          this.knowledgeTree = res.knowledgeTrees.length ? res.knowledgeTrees[0]?.tree : []
+          this.addPointsModal = true
+        }
+      })
+    },
+    // 新增知识点事件
+    onAddPoint(node, data) {
+        if(!this.newPointName) {
+          this.$Message.warning(this.$t('knowledge.knowledgeP1'))
+          return
+        }
+        let idLast2 = data.children.length ? data.children[data.children.length - 1].id : '-0'
+        let id2 = idLast2.split(`-`)
+        data.children.push({
+          allcids: [],
+          children: [],
+          id: `${data.id}-${Number(id2[id2.length - 1]) + 1}`,
+          level: 1,
+          link: 0,
+          name: this.newPointName,
+          pid: data.id,
+          subcids: [],
+          tid: null,
+          used: 0
+        })
+        this.newPointName = ''
+    },
+  },
+  mounted() {
+    if (this.points.length) {
+      this.checkedList = this.points
+      // this.radioCode = this.radioCode === 'private' && this.schoolPointList.includes(this.checkedList[0]) ? 'private' : 'school'
+    }
+    this.curScope = this.scope
+  },
+  watch: {
+    scope: {
+      handler(n, o) {
+        console.log(n)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.points-container {
+  position: relative;
+  .filter-wrap {
+    padding: 5px 0 10px 5px;
+    border-bottom: 1px solid #e0e0e0;
+  }
+  .point-footer {
+    display: flex;
+    justify-content: end;
+
+    .ivu-btn {
+      margin-right: 10px;
+    }
+  }
+  .ivu-select-single {
+    width: 120px;
+    display: inline-block;
+    margin-right: 20px;
+  }
+  .ivu-checkbox-group-item {
+    display: block;
+    margin: 10px;
+  }
+  .ivu-checkbox-group {
+    max-height: 300px;
+    overflow: auto;
+  }
+  .point-item {
+    display: inline-block;
+    margin: 6px;
+    padding: 1px 10px;
+    background: #0094ff;
+    color: #fff;
+    border-radius: 5px;
+  }
+  .points-checked-wrap {
+    padding: 30px 0;
+  }
+  .points-title {
+    margin: 10px 0;
+    &:before {
+      content: "";
+      width: 5px;
+      height: 15px;
+      margin-right: 10px;
+      margin-top: 5px;
+      vertical-align: text-bottom;
+      background: #0094ff;
+      display: inline-block;
+    }
+  }
+  .btn-add-point {
+    position: absolute;
+    right: 10px;
+    top: 5px;
+    cursor: pointer;
+    z-index: 999;
+  }
+}
+</style>

+ 513 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseRepair.vue

@@ -0,0 +1,513 @@
+<template>
+	<div class="repair-wrap">
+			<!-- <TabPane :label="$t('evaluation.newExercise.innerRepair')" tab="repairTab" name="site"> -->
+			<div style="margin: 30px 0;">
+				<Button type="info" @click="doSelectContent" style="margin-right: 10px;"> + {{$t('evaluation.newExercise.innerRepair')}}</Button>
+				<Button type="info" @click="onAddLink('link')"> + {{$t('evaluation.newExercise.outRepair')}}</Button>
+			</div>
+				<draggable class="list-group" tag="div" v-model="datas" v-bind="dragOptions" @start="drag = true"
+					@end="onDragEnd">
+					<transition-group type="transition" :name="!drag ? 'flip-list' : null">
+						<div class="repair-link-wrap-item" v-for="(item,index) in datas"
+							:key="index">
+							<div class="repair-link-wrap-item-box">
+								<div class="file-icon">
+								    <img :src="$tools.getFileThum(item.type,item.name)"/>
+								</div>
+								<div class="file-info">
+									<p class="file-name">{{ item.name }}</p>
+									<div>
+										<span @click="onPreview(item)" v-if="item.type !== 'other'">{{ $t('ability.review.preview')}}</span>
+										<span @click="onDownload(item)" v-if="item.type !== 'link'">{{ $t('ability.review.download')}}</span>
+										<span @click="onDeleteRepair(item)">{{ $t('ability.review.delete')}}</span>
+									</div>
+								</div>
+							</div>
+						</div>
+					</transition-group>
+				</draggable>
+		<!-- 关联内容弹窗 -->
+		<Modal v-model="isRelatedContent" width="880" footer-hide class="related-modal base-repair-modal">
+			<div class="modal-header" slot="header">{{$t('evaluation.newExercise.contentRelate')}}</div>
+			<NewChooseContent :showSyllabus="isFalse" :showOther="isFalse" :showQuestion="isFalse" :showPaper="isFalse"
+				:defaultFiles="curRepair.blobUrl" ref="chooseContentRef" @on-file-change="onSelectFile"
+				v-if="isRelatedContent"></NewChooseContent>
+
+			<Button class="modal-btn" :loading="isLoading"
+				@click="onConfirmRelate">{{$t('evaluation.confirm')}}</Button>
+		</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.blobUrl[0].url" width="870"
+					controls="controls" style="max-height: 800px;">
+					{{$t('teachContent.tips8')}}
+				</video>
+				<audio v-else-if="previewFile.type == 'audio'" controls>
+					<source :src="previewFile.blobUrl[0].url">
+					{{$t('teachContent.notAudio')}}
+				</audio>
+			</div>
+		</div>
+
+		<!-- 添加补救资源弹窗 -->
+		<Modal v-model="isAddRepair" width="600" footer-hide>
+			<div class="modal-header" slot="header">{{ $t('evaluation.repairResourse.addRepair')}}</div>
+			<p style="margin: 15px 2px;">{{ $t('evaluation.repairResourse.description')}}</p>
+			<Input v-model="curRepair.name" :placeholder="$t('evaluation.repairResourse.place1')" />
+			<p style="margin: 15px 2px;">{{ $t('evaluation.repairResourse.link')}}</p>
+			<div class="outlink-box" v-if="!isSiteLink">
+				<!-- 手动输入 -->
+				<div v-for="(item,index) in outLinkArr" style="display: flex;align-items: center;margin-bottom: 10px;">
+					<Input v-model="item.url" :placeholder="$t('evaluation.repairResourse.place2')"
+						style="display: inline-block;" />
+					<!-- <Icon type="ios-add-circle-outline"
+						style="display: inline-block;margin-left: 8px;font-size: 20px;cursor: pointer;"
+						@click="addLinkInput" v-if="index === 0" />
+					<Icon type="ios-remove-circle-outline"
+						style="display: inline-block;margin-left: 8px;font-size: 20px;cursor: pointer;"
+						@click="removeLinkInput(index)" v-if="index !== 0" /> -->
+				</div>
+			</div>
+			<!-- 确认 -->
+			<Button type="success" @click="onAddRepair"
+				style="margin-top: 20px;width: 100%;height: 38px;">{{$t('evaluation.repairResourse.confirm')}}</Button>
+
+		</Modal>
+	</div>
+</template>
+<script>
+	import draggable from "vuedraggable"
+	import NewChooseContent from '@/components/selflearn/NewChooseContent'
+	export default {
+		name: 'BaseRepair',
+		components: {
+			NewChooseContent,
+			draggable
+		},
+		props: {
+			rapairs: {
+				type: Array,
+				default: () => []
+			}
+		},
+		data() {
+			return {
+				previewStatus:false,
+				previewFile:null,
+				docType: ['doc', 'docx'],
+				excelType: ['xls', 'csv', 'xlsx'],
+				pptType: ['ppt', 'pptx'],
+				drag: false,
+				datas: [],
+				curTab: 'site',
+				curEditIndex: null,
+				curOutLink: '',
+				isAddRepair: false,
+				isLoading: false,
+				isSiteLink: true,
+				isRelatedContent: false,
+				isFalse: false,
+				relateFileList: [],
+				defaultFiles: [],
+				curRepair: {
+					type: 'file',
+					name: '',
+					blobUrl: []
+				},
+				outLinkArr: [{
+					url: ''
+				}]
+			}
+		},
+		created() {
+
+		},
+		methods: {
+			/* 拖拽册别清单结束 */
+			onDragEnd(val) {
+				console.log(val);
+				this.drag = false
+			},
+			/* 点击添加外部或者内部资源 */
+			onAddLink(type) {
+				this.isSiteLink = false
+				this.curEditIndex = null
+				this.curRepair.name = ''
+				this.curRepair.type = 'link'
+				this.curRepair.blobUrl = []
+				if (type !== 'site') {
+					this.isAddRepair = true
+				}
+				this.defaultFiles = []
+				this.outLinkArr = [{
+					url: ''
+				}]
+			},
+
+			doSelectContent() {
+				this.onAddLink('site')
+				this.isRelatedContent = true
+				this.$nextTick(() => {
+					if (this.$refs.chooseContentRef) {
+						this.$refs.chooseContentRef.clickTab('content')
+					}
+				})
+			},
+
+			addLinkInput() {
+				this.outLinkArr.push({
+					url: ''
+				})
+			},
+
+			removeLinkInput(index) {
+				this.outLinkArr.splice(index, 1)
+			},
+
+			/* 回车添加外部资源链接 */
+			onAddOutLink() {
+				if (this.isURL(this.curOutLink)) {
+					if (this.curRepair.blobUrl.map(i => i.url).indexOf(this.curOutLink) > -1) {
+						this.$Message.warning(this.$t('evaluation.repairResourse.isExistTip'))
+					} else {
+						this.curRepair.blobUrl.push({
+							url: this.curOutLink
+						})
+						this.curOutLink = ''
+					}
+				} else {
+					this.$Message.warning(this.$t('evaluation.repairResourse.formatErrorTip'))
+				}
+			},
+
+			/* 点击添加资源 */
+			async onAddRepair() {
+				if (!this.isSiteLink) {
+					this.curRepair.blobUrl = this.outLinkArr
+				} else {
+					this.curRepair.type = 'file'
+					for (let i = 0; i < this.curRepair.blobUrl.length; i++) {
+						let item = this.curRepair.blobUrl[i]
+						item.url = item.url.includes('?') ? item.url : await this.getAddSasUrl(item.url)
+					}
+				}
+				if (this.curRepair.name.trim() === '' || this.curRepair.blobUrl.length === 0) {
+					this.$Message.warning(this.$t('evaluation.repairResourse.noCompleteTip'))
+				} else {
+					if (this.curRepair.blobUrl.every(i => this.isURL(i.url))) {
+						if (this.curEditIndex || this.curEditIndex === 0) {
+							this.datas[this.curEditIndex] = JSON.parse(JSON.stringify(this.curRepair))
+						} else {
+							this.datas.push(JSON.parse(JSON.stringify(this.curRepair)))
+						}
+						this.isAddRepair = false
+					} else {
+						this.$Message.warning(this.$t('evaluation.repairResourse.formatErrorTip'))
+					}
+				}
+
+				console.log(JSON.stringify(this.datas))
+			},
+
+			/* 获取站内内容带永久授权码的链接 */
+			getAddSasUrl(url) {
+				return new Promise((r, j) => {
+					this.$api.blob.urlSasR({
+						url: url
+					}).then(res => {
+						r(res.url)
+					}).catch(e => j(e))
+				})
+			},
+
+			/* 编辑当前补救资源 */
+			onEditRepair(item, index) {
+				console.log(item)
+				this.isSiteLink = this.curTab === 'site'
+				this.curRepair = JSON.parse(JSON.stringify(item))
+				this.isAddRepair = true
+				this.curEditIndex = this.datas.indexOf(item)
+				this.defaultFiles = this.curTab === 'site' ? this.curRepair.blobUrl : [],
+					this.outLinkArr = this.curRepair.blobUrl
+			},
+			/* 预览 */
+			onPreview(item){
+				console.log(item)
+				let url = item.blobUrl[0].url
+				if (this.getSuffix(item.name) === 'pdf') {
+					window.open('/web/viewer.html?file=' + encodeURIComponent(url));
+				} else if(item.type === 'doc'){
+					window.open('https://view.officeapps.live.com/op/view.aspx?src=' + escape(url));
+				}else if(item.type === 'image'){
+					this.$hevueImgPreview(url)
+				}else if(item.type === 'link'){
+					window.open(/^(http:|https:)/i.test(url) ? url : "http://" + url)
+				}else{
+					this.previewFile = item
+					this.previewStatus = true
+				}
+			},
+
+			/* 下载 */
+			onDownload(item){
+				this.$tools.doDownloadByUrl(item.blobUrl[0].url, item.name)
+			},
+			
+			/* 删除当前补救资源 */
+			onDeleteRepair(item) {
+				this.datas.splice(this.datas.indexOf(item), 1)
+			},
+
+			/* 删除某个链接地址LINK */
+			onDeleteRelateLink(item) {
+				this.curRepair.blobUrl.splice(this.curRepair.blobUrl.indexOf(item), 1)
+				this.defaultFiles = this.curRepair.blobUrl
+			},
+
+			/* 选择内容联动 */
+			onSelectFile(val) {
+				this.relateFileList = val.files
+				this.curRepair.blobUrl = val.files
+				console.log(this.curRepair);
+				this.datas.push
+				// this.onAddRepair()
+			},
+
+			async onConfirmRelate() {
+				for (let i = 0; i < this.curRepair.blobUrl.length; i++) {
+					let item = this.curRepair.blobUrl[i]
+					item.url = item.url.includes('?') ? item.url : await this.getAddSasUrl(item.url)
+					this.datas.push({
+						name: item.name,
+						blobUrl: [{
+							url: item.url
+						}],
+						type: item.type
+					})
+				}
+				this.isRelatedContent = false
+			},
+
+			isURL(url) {
+				const strRegex = '^((https|http|ftp)://)?' //(https或http或ftp):// 可有可无
+					+
+					'(([\\w_!~*\'()\\.&=+$%-]+: )?[\\w_!~*\'()\\.&=+$%-]+@)?' //ftp的user@ 可有可无
+					+
+					'(([0-9]{1,3}\\.){3}[0-9]{1,3}' // IP形式的URL- 3位数字.3位数字.3位数字.3位数字
+					+
+					'|' // 允许IP和DOMAIN(域名)
+					+
+					'(localhost)|' //匹配localhost
+					+
+					'([\\w_!~*\'()-]+\\.)*' // 域名- 至少一个[英文或数字_!~*\'()-]加上.
+					+
+					'\\w+\\.' // 一级域名 -英文或数字 加上.
+					+
+					'[a-zA-Z]{1,6})' // 顶级域名- 1-6位英文
+					+
+					'(:[0-9]{1,5})?' // 端口- :80 ,1-5位数字
+					+
+					'((/?)|' // url无参数结尾 - 斜杆或这没有
+					+
+					'(/[\\w_!~*\'()\\.;?:@&=+$,%#-]+)+/?)$'; //请求参数结尾- 英文或数字和[]内的各种字符
+				const re = new RegExp(strRegex, 'i'); // 大小写不敏感
+				if (re.test(encodeURI(url))) {
+					return true;
+				}
+				return false;
+			}
+
+
+		},
+		computed:{
+			dragOptions() {
+				return {
+					animation: 200,
+					group: "description",
+					disabled: false,
+					ghostClass: "ghost"
+				};
+			},
+			getSuffix() {
+				return name => {
+					return name.substr(name.lastIndexOf(".") + 1)
+				}
+			}
+		},
+		watch: {
+			rapairs: {
+				handler(val) {
+					console.error('接受到的repair', val)
+					if(val && val.length){
+						/* 转换补救资源格式 根据描述 汇总分组 */
+						let namesArr = [...new Set(val.map(i => i.name))]
+						let result = []
+						namesArr.forEach(name => {
+							let urls = []
+							let type = ''
+							val.forEach(i => {
+								if (i.name === name) {
+									urls.push({
+										url: i.blobUrl
+									})
+									type = i.type
+								}
+							})
+							result.push({
+								name: name,
+								blobUrl: urls,
+								type: type
+							})
+						})
+						this.datas = result
+					}else{
+						this.datas =  []
+					}
+				},
+				deep:true
+			},
+		}
+	}
+</script>
+
+<style>
+	.repair-wrap .ivu-input-wrapper {
+		width: 95%;
+	}
+
+	.repair-wrap .ivu-input {
+		height: 40px;
+		line-height: 40px;
+		display: inline-block;
+	}
+
+	.repair-wrap .ivu-tabs-content {
+		margin-top: 10px;
+	}
+</style>
+
+<style lang="less">
+	
+	
+	.repair-link-item {
+		margin: 10px 0;
+		padding: 4px 5px;
+		background: #a7a7a7;
+		color: #fff;
+		border-radius: 4px;
+		word-break: break-all;
+
+		.ivu-icon {
+			margin: 5px 10px;
+			vertical-align: sub;
+			color: red;
+			font-weight: bold;
+		}
+	}
+
+	.base-repair-modal {
+		.choose-content {
+			height: 90% !important;
+
+			.ivu-table-body {
+				max-height: 400px !important;
+			}
+		}
+	}
+
+	.repair-wrap {
+		min-height: 200px;
+		padding-bottom: 50px;
+		.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;
+			}
+		}
+
+		.repair-link-wrap {
+			padding: 10px 0 80px 0;
+
+			&-item {
+				margin: 10px 0;
+
+				&-box {
+					display: flex;
+					position: relative;
+					// background-color: #e3e3e3;
+					border-radius: 5px;
+					padding: 10px 0;
+					font-size: 14px;
+					
+					&:hover{
+						background-color: #ebe9e9;
+					}
+					
+					.file-icon{
+						img{
+							width: 45px;
+						}
+					}
+					
+					.file-info{
+						margin-left: 10px;
+						
+						.file-name{
+							font-weight: bold;
+							margin-bottom: 5px;
+						}
+						
+						span{
+							color: #16a3b5;
+							margin-right: 15px;
+							cursor: pointer;
+						}
+					}
+				}
+
+				.ivu-icon {
+					font-size: 24px;
+					margin-right: 10px;
+					cursor: pointer;
+				}
+
+				.error-tip {
+					color: red;
+					margin: 5px;
+				}
+			}
+
+			.flex-item-center {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+			}
+
+		}
+	}
+</style>

+ 101 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/BaseTypePie.vue

@@ -0,0 +1,101 @@
+<template>
+    <div id="myTypePie"></div>
+</template>
+
+<script>
+    export default {
+        name: 'BaseTypePie',
+        props: ['pieId','echartsData'],
+        data() {
+            return {
+                pieData: [],
+				types:this.$GLOBAL.EXERCISE_TYPES()
+            }
+        },
+        methods: {
+
+            drawLine(data) {
+                let that = this
+                // 基于准备好的dom,初始化echarts实例
+                let myPie = this.$echarts.init(document.getElementById('myTypePie'), 'chalk')
+
+                // 指定图表的配置项和数据
+                var option = {
+                    title: {
+                        'text': this.$t('evaluation.echarts.typePie'),
+                        'left': 'center',
+                        'textStyle': {
+                            'fontSize': 14,
+                            'color': '#595959'
+                        }
+                    },
+					legend: {
+						left: 'center',
+						bottom: 0,
+						type: 'scroll',
+						formatter: function (name,val) {
+						    if(data.length){
+						    	return name + ' (' + data.filter(i => i.name === name)[0].value + that.$t('unit.text10') + ')';
+						    }
+						}
+					},
+                    color: ['#37a2da', '#e7bcf3', '#9fe6b8', '#ff9f7f', '#fb7293', '#e7bcf3', '#8378ea'],
+                    tooltip: {
+                        trigger: 'item',
+                        formatter: '{a} <br/>{b} : {c} ({d}%)'
+                    },
+                    calculable: true,
+                    series: [
+                        {
+                            name: that.$t('evaluation.echarts.typePie'),
+                            type: 'pie',
+                            radius: [0, 80],
+                            data: data
+                        }
+                    ]
+                }
+
+                // 绘制图表
+                myPie.setOption(option)
+
+                window.addEventListener('resize', function() {
+                    myPie.resize()
+                })
+            }
+        },
+        mounted() {
+			let arr = []
+			let typeList = this._.groupBy(this.echartsData.item, 'type')
+			for(let key in typeList){
+				arr.push({
+					value:typeList[key].length,
+					name:this.types[key]
+				})
+			}
+			this.drawLine(arr)
+        },
+        computed: {
+            // 获取最新知识点占比饼图数据
+            getPieData() {
+                return this.$store.state.totalAnalysis.knowledgeData
+            }
+        },
+        watch: {
+            echartsData(val) {
+
+            }
+        }
+
+    }
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+    #myTypePie {
+        width: 100%;
+        height: 320px;
+        margin: 20px auto;
+        display: block;
+    }
+</style>

+ 219 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/DownloadPaper.vue

@@ -0,0 +1,219 @@
+<template>
+	<div class="download-paper-wrap" ref="childRef" id="paperDom">
+		<div class="dp-line">
+			<img src="@/assets/image/peal_line.png">
+		</div>
+		<div class="dp-top">
+			<p class="dp-top-title">{{ paperItem.name }}</p>
+		</div>
+		<div class="dp-content">
+			<div class="dp-group-item" v-for="(gItem,gIndex) in groupList">
+				<p v-show="gItem.list.length" class="dp-group-title">
+					<span>{{ $tools.getChineseByNum(gIndex + 1) }} 、 {{ exersicesType[gItem.type] }}</span>
+					<span style="font-size: 14px;">({{ gItem.score || 0 }}{{$t('evaluation.paperList.score')}}) </span>
+				</p>
+				<div class="dp-item" v-for="(item,index) in gItem.list">
+					<div style="display: flex;">
+						<span class="dp-item-order">{{ index + 1 }}. </span>
+						<span class="dp-item-question" v-html="item.question"></span>
+						<!-- <span>({{ item.score }}分) </span> -->
+					</div>
+					<div class="dp-item-options" v-if="item.option.length">
+						<!-- 选项部分 -->
+						<div v-for="(option,optionIndex) in item.option" :key="optionIndex" class="dp-item-options">
+							<div class="item-option-content">
+								<div class="item-option-order">{{String.fromCharCode(64 + parseInt(optionIndex+1))}} .
+									&nbsp;
+								</div>
+								<div class="item-option-text" v-html="option.value"></div>
+							</div>
+						</div>
+					</div>
+					<div class="dp-item-children" v-if="item.children.length">
+						<div class="child-item" v-for="(child,childIndex) in item.children">
+							<div style="display: flex;">
+								<span class="dp-item-order">({{ childIndex + 1 }})</span>
+								<p class="dp-item-question" v-html="child.question"></p>
+							</div>
+							<!-- 选项部分 -->
+							<div v-for="(option,optionIndex) in child.option" :key="optionIndex" class="dp-item-options"
+								style="pointer-events:none">
+								<div class="item-option-content">
+									<div class="item-option-order">{{String.fromCharCode(64 + parseInt(optionIndex+1))}}
+										. &nbsp; </div>
+									<div class="item-option-text" v-html="option.value"></div>
+								</div>
+							</div>
+						</div>
+					</div>
+				</div>
+
+
+			</div>
+
+		</div>
+	</div>
+</template>
+<script>
+	import domtoimage from '@/utils/dom_to_image.js';
+	import JsPDF from 'jspdf'
+	export default {
+		name: 'DownloadPaper',
+		props: {
+			paper: {
+				type: Object,
+				default: () => {}
+			},
+		},
+		data() {
+			return {
+				exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
+				paperItem: {
+					name: '',
+					item: []
+				},
+				typeList: ['single', 'multiple', 'judge', 'complete', 'subjective', 'connector', 'correct', 'compose'],
+				groupList: []
+			}
+		},
+		created() {
+
+
+		},
+		methods: {
+			onDownloadPaper() {
+				console.log(document.querySelector('#paperDom'));
+				let that = this
+				domtoimage.toJpeg(document.querySelector('#paperDom'), {
+						bgcolor: '#fff'
+					})
+					.then((pageData) => {
+						let img = new Image();
+						img.src = pageData;
+						img.onload = function() {
+							let contentWidth = img.width
+							let contentHeight = img.height
+							let pageHeight = contentWidth / 592.28 * 841.89
+							let leftHeight = contentHeight
+							let position = 0
+							const imgWidth = 595.28
+							let imgHeight = 592.28 / contentWidth * contentHeight
+							let PDF = new JsPDF('', 'pt', 'a4')
+							if (leftHeight < pageHeight) {
+								PDF.addImage(pageData, 'JPEG', 0, 10, imgWidth, imgHeight)
+							} else {
+								while (leftHeight > 0) {
+									PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
+									leftHeight -= pageHeight
+									position -= 841.89
+									if (leftHeight > 0) {
+										PDF.addPage()
+									}
+								}
+							}
+							PDF.save(that.paperItem.name + '.pdf')
+						}
+						
+					})
+			},
+			doGroupPaper(paper) {
+				/* 处理试卷内题目按照题型排序 */
+				let that = this
+				let groupList = []
+				this.typeList.forEach(item => {
+					this._.mapKeys(this._.groupBy(paper.item, 'type'), function(value, key) {
+						if (key === item) {
+							/* 按照题型排序,并且计算每种题型的总分 */
+							groupList.push({
+								type: key,
+								list: value,
+								score: value.reduce((p, e) => parseInt(p) + parseInt(e.score), 0)
+							})
+						}
+					})
+				});
+				return groupList
+			},
+
+		},
+
+		watch: {
+			paper: {
+				handler(n) {
+					if (n) {
+						this.groupList = this.doGroupPaper(n)
+						this.paperItem = n
+					}
+				}
+			},
+
+		}
+
+	}
+</script>
+
+
+<style lang="less" scoped>
+	.download-paper-wrap {
+		// font-family: '微软雅黑' !important;
+		position: relative;
+
+		.dp-line {
+			position: absolute;
+			left: 10px;
+			top: 120px;
+		}
+
+		.dp-top {
+			padding: 20px 0;
+
+			&-title {
+				text-align: center;
+				font-size: 24px;
+				font-weight: bold;
+			}
+		}
+
+		.dp-content {
+			margin: 0 50px 0 120px;
+
+			.dp-group-title {
+				font-size: 16px;
+				font-weight: bold;
+				margin: 20px 0;
+			}
+
+			.dp-item {
+				display: flex;
+				flex-direction: column;
+				font-size: 14px;
+				margin-top: 30px;
+				margin-left: 10px;
+
+				&-order {
+					// min-width: 50px;
+				}
+
+				&-question {
+					flex: 1;
+					padding-left: 10px;
+				}
+
+				&-children {
+					margin-left: 20px;
+
+					.child-item {
+						margin: 20px 0;
+					}
+				}
+			}
+
+			.dp-item-options {
+				.item-option-content {
+					display: flex;
+					margin: 10px 0;
+				}
+			}
+		}
+	}
+</style>

+ 20 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/components/index.js

@@ -0,0 +1,20 @@
+import Vue from "vue";
+function changeStr(str) {
+    //首字母大写
+    return str.charAt(0).toUpperCase() + str.slice(1);
+  }
+  const requireComponent = require.context("./", false, /\.vue$/);
+  const install = (Vue) => {
+    requireComponent.keys().forEach((fileName) => {
+      let config = requireComponent(fileName);
+      let componentName = changeStr(
+        fileName.replace(/^\.\//, "").replace(/\.\w+$/, "")
+      );
+      Vue.component(componentName, config.default || config);
+    });
+  };
+  export default {
+    install,
+  };
+  
+  

+ 749 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CommonExercise.less

@@ -0,0 +1,749 @@
+.ev-list-container {
+  position: relative;
+
+  .ev-header {
+    background: #fff;
+    padding: 10px;
+    display: flex;
+    align-items: center;
+  }
+
+  .ev-title {
+    font-size: 20px;
+    font-weight: bold;
+    margin-left: 5px;
+    vertical-align: middle;
+  }
+
+  .ev-length {
+    font-size: 12px;
+    font-weight: bold;
+    margin-left: 30px;
+    display: inline-block;
+    margin-top: 5px;
+    vertical-align: text-top;
+  }
+
+  .ivu-divider-horizontal {
+    margin: 15px 0;
+  }
+
+  .no-data-text {
+    width: 100%;
+    padding: 30px;
+    background: #fff;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    margin-top: 10px;
+    font-size: 16px;
+  }
+
+  .ivu-page {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    margin: 20px 0;
+  }
+}
+
+.ev-list-operation {
+  position: absolute;
+  top: -65px;
+  right: 0;
+  height: 60px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+
+  .ivu-checkbox-wrapper {
+    font-size: 14px;
+  }
+
+  .ivu-checkbox {
+    margin: 10px;
+  }
+
+  .import-exercise {
+    font-weight: bold;
+    color: rgb(16, 171, 231);
+    font-size: 16px;
+    display: flex;
+
+    .ivu-btn {
+      margin-right: 10px;
+      border: none !important;
+    }
+  }
+
+  .ivu-icon {
+    font-size: 16px;
+    margin-right: 2px;
+    cursor: pointer;
+    vertical-align: initial;
+  }
+
+  .ivu-badge-count {
+    right: 12px;
+  }
+
+  .ivu-checkbox-checked {
+    .ivu-checkbox-inner {
+      background: #01b4ef;
+      border-color: #01b4ef;
+    }
+  }
+}
+
+.exercise-backet-wrap {
+  .ivu-btn {
+    border: none !important;
+  }
+
+  .exercise-backet-item {
+    &:not(:first-child) {
+      margin-left: 10px;
+    }
+  }
+
+  .exercise-backet-num {
+    font-weight: bold;
+    margin: 0 5px;
+    font-size: 16px;
+    color: rgb(16, 171, 231);
+  }
+}
+
+.ev-content {
+  background: none;
+}
+
+.content-wrap {
+  position: relative;
+  width: 100%;
+  height: auto;
+  display: flex;
+  flex-direction: column;
+
+  .exercise-item {
+    position: relative;
+    width: 100%;
+    height: auto;
+    padding: 10px 20px 10px 20px;
+    margin-bottom: 10px;
+    font-size: 14px;
+    background: #fff;
+    border: 2px solid transparent;
+    cursor: pointer;
+
+    &:hover {
+      .item-tools-bind {
+        display: unset;
+      }
+    }
+  }
+}
+
+.content-wrap .exercise-item table,
+.content-wrap .exercise-item td {
+  border: 1px solid rgb(128, 128, 128);
+  border-collapse: collapse;
+  text-align: center;
+  padding: 5px 10px;
+}
+
+.complete-line {
+  margin: 0 5px;
+  padding: 0 45px;
+  border-bottom: 2px solid rgb(128, 128, 128);
+}
+
+.exercise-item {
+  .item-question {
+    position: relative;
+    cursor: pointer;
+
+    .item-question-order {
+      display: inline-block;
+      vertical-align: top;
+      width: 32px;
+    }
+
+    .item-question-text {
+      display: inline-block;
+      width: calc(90% - 30px);
+    }
+  }
+
+  img {
+    vertical-align: middle;
+    max-width: 100%;
+  }
+
+  video {
+    max-width: 100%;
+  }
+
+  .item-btn-toggle {
+    position: absolute;
+    right: 10px;
+    top: 8px;
+    width: 15%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    .ivu-icon {
+      font-size: 22px;
+      cursor: pointer;
+    }
+
+    .ivu-input-number-handler-down-inner,
+    .ivu-input-number-handler-up-inner {
+      width: 17px;
+    }
+  }
+
+  .item-difficulty {
+    display: inline-block;
+    padding: 2px 10px;
+    background: rgb(16, 171, 231);
+    border-radius: 5px;
+    color: #fff;
+    font-size: 12px;
+  }
+
+  .item-type {
+    display: inline-block;
+    padding: 1px 9px;
+    border-radius: 5px;
+    color: #fff;
+    font-size: 12px;
+    margin-left: 5px;
+    background: rgb(16, 171, 231);
+  }
+
+  .item-relevant-points {
+    display: inline-block;
+    padding: 1px 9px;
+    font-size: 13px;
+  }
+
+  .item-options {
+    margin-top: 10px;
+
+    .item-option-content {
+      margin: 7px 0;
+    }
+
+    .item-option-order {
+      display: inline-block;
+      vertical-align: top;
+      width: 30px;
+    }
+
+    .item-option-text {
+      display: inline-block;
+      width: calc(100% - 30px);
+    }
+  }
+
+  .toggle-area {
+    border-top: 1px #c3c3c34d dashed;
+    padding-top: 10px;
+    margin-top: 10px;
+  }
+
+  .item-answer {
+    display: inline-block;
+    cursor: pointer;
+    margin-top: 12px;
+    font-size: 14px;
+
+    .answer-title-line {
+      background: rgb(0, 173, 37);
+      padding: 0 3px;
+      border-radius: 5px;
+    }
+
+    .answer-title {
+      margin-left: 10px;
+      color: rgb(0, 173, 37);
+    }
+  }
+
+  .item-answer-item {
+    margin-left: 25px;
+    padding: 0 25px;
+    border-bottom: 2px solid rgb(128, 128, 128);
+
+    p {
+      display: inline-block;
+    }
+
+    &:first-child {
+      margin-left: 0;
+    }
+  }
+
+  .item-answer-details {
+    margin: 10px 20px;
+  }
+
+  .item-explain {
+    margin-top: 10px;
+    cursor: pointer;
+    font-size: 14px;
+
+    .explain-title-line {
+      background: rgb(16, 171, 231);
+      padding: 0 3px;
+      border-radius: 5px;
+    }
+
+    .explain-title {
+      width: 12%;
+      max-width: 100px;
+      display: inline-block;
+      color: rgb(16, 171, 231);
+    }
+  }
+
+  .repair-item {
+    display: inline-flex;
+    margin-right: 20px;
+    color: #21b1ff;
+    text-decoration: underline;
+    align-items: flex-end;
+
+    &-link {
+      margin-left: 5px;
+    }
+
+  }
+
+  .item-explain-item {
+    margin-left: 25px;
+    padding: 0 25px;
+    border-bottom: 2px solid rgb(128, 128, 128);
+  }
+
+  .item-explain-details {
+    vertical-align: top;
+    display: inline-block;
+    width: calc(100% - 100px);
+
+    // width: 90%;
+    .item-point-tag {
+      padding: 0 10px;
+      border: 1px solid #d6d6d6;
+      margin-left: 10px;
+      border-radius: 4px;
+
+      &:first-child {
+        margin-left: 0;
+      }
+    }
+  }
+
+  .item-tools {
+    width: 100%;
+    padding: 10px 0;
+    margin-top: 20px;
+    font-size: 14px;
+    border-top: 1px #c3c3c34d dashed;
+
+    .item-tools-action {
+      float: right;
+      color: #01b4ef;
+      margin: 0 20px;
+      margin-top: 8px;
+      font-size: 14px;
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+
+      .ivu-icon {
+        font-size: 18px;
+      }
+    }
+
+    .ivu-icon {
+      font-size: 14px;
+      font-weight: bold;
+    }
+
+    .ivu-btn {
+      float: right;
+      border-color: transparent;
+      box-shadow: none;
+    }
+
+    .item-tools-info {
+      vertical-align: sub;
+      color: #868686;
+      border-right: 2px solid #d2d2d2;
+      font-size: 12px;
+      padding: 0 10px;
+
+      &:first-child {
+        padding-left: 0;
+      }
+    }
+
+    span {
+      &:nth-child(4) {
+        border-right-width: 0;
+      }
+    }
+  }
+
+  .item-tools-tool {
+    margin-left: 10px;
+    font-weight: bold;
+    cursor: pointer;
+    color: rgb(16, 171, 231);
+    font-size: 14px;
+
+    .ivu-icon {
+      margin-left: 15px;
+      vertical-align: top;
+    }
+
+    .item-bind-point {
+      color: rgb(128, 128, 128);
+      margin-left: 5px;
+    }
+
+    .ivu-tag {
+      margin-top: -2px;
+    }
+
+    .ivu-tag-border {
+      line-height: 22px;
+    }
+  }
+}
+
+.ev-list-container h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  display: inline-block;
+}
+
+.filter-wrap {
+  width: 100%;
+  padding: 10px 20px;
+  padding-bottom: 20px;
+  background: #fff;
+
+  .filter-item {
+    margin-top: 10px;
+    display: flex;
+
+    &:first-child {
+      margin-top: 0px;
+    }
+
+    .filter-count {
+      color: red;
+    }
+  }
+
+  .filter-title {
+    font-size: 16px;
+    font-weight: bold;
+    margin-right: 10px;
+    min-width: 45px;
+    // display: inline-block;
+    // text-align: end;
+  }
+
+  .ivu-radio-group {
+    padding-bottom: 4px;
+  }
+
+  .ivu-radio-wrapper {
+    padding: 0 10px;
+    margin-left: 15px;
+    height: 28px;
+    line-height: 28px;
+    box-shadow: none !important;
+    border: none !important;
+    font-size: 14px;
+    border-radius: 0 !important;
+
+    .ivu-icon {
+      margin-bottom: 2px;
+    }
+  }
+
+  .ivu-radio-group-item {
+    border-radius: 5px !important;
+  }
+
+  .ivu-radio-wrapper-checked {
+    background: rgb(16, 171, 231);
+    box-shadow: none !important;
+    color: white;
+
+    .filter-count {
+      color: #fff;
+    }
+  }
+
+  .ivu-radio-group-button {
+    .ivu-radio-wrapper-checked {
+      &:hover {
+        color: #fff !important;
+      }
+    }
+  }
+
+  .ivu-checkbox-group {
+    padding-bottom: 4px;
+    display: inline-block;
+  }
+
+  .ivu-checkbox-wrapper {
+    padding: 0 10px;
+    margin-left: 15px;
+    height: 28px;
+    line-height: 28px;
+    box-shadow: none !important;
+    border: none !important;
+    font-size: 14px;
+    border-radius: 0 !important;
+
+    .ivu-icon {
+      margin-bottom: 2px;
+    }
+  }
+
+  .ivu-checkbox-group-item {
+    border-radius: 5px !important;
+  }
+
+  .ivu-checkbox-wrapper-checked {
+    background: rgb(16, 171, 231);
+    box-shadow: none !important;
+    color: white;
+
+    .filter-count {
+      color: #fff;
+    }
+  }
+
+  .ivu-checkbox-group-button {
+    .ivu-checkbox-wrapper-checked {
+      &:hover {
+        color: #fff !important;
+      }
+    }
+  }
+}
+
+.filter-wrap .ivu-radio-wrapper:after,
+.ivu-radio-group-button .ivu-radio-wrapper:before {
+  content: none;
+}
+
+.filter-wrap .ivu-checkbox-inner,
+.filter-wrap .ivu-checkbox-checked,
+.filter-wrap .ivu-checkbox {
+  display: none;
+}
+
+.filter-wrap .ivu-checkbox-wrapper:after,
+.ivu-checkbox-group-button .ivu-checkbox-wrapper:before {
+  content: none;
+}
+
+.bank-action-bar {
+  background-color: #fff;
+  padding: 15px 15px;
+  margin: 10px 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .action-tools {
+    display: flex;
+
+    .action-tool {
+      display: flex;
+      align-items: center;
+      margin-right: 15px;
+
+      a {
+        color: #333333;
+      }
+
+      .action-title {
+        display: inline-block;
+        min-width: 50px;
+      }
+    }
+  }
+}
+
+/*绑定知识点部分树形结构样式*/
+.singleClass {
+  cursor: pointer;
+  font-size: 14px;
+  margin: 10px;
+}
+
+.transferModal {
+  overflow: hidden;
+
+  .point-list {
+    max-height: 400px;
+    overflow: auto;
+
+    .ivu-input-wrapper {
+      width: 90%;
+      margin-bottom: 10px;
+    }
+  }
+
+  .selected-point-list {
+    padding: 10px;
+    width: 100%;
+    border-top: 1px solid rgba(128, 128, 128, .3);
+    margin-top: 15px;
+  }
+
+  .bind-title {
+    margin: 10px 0;
+    font-weight: bold;
+    font-size: 14px;
+  }
+
+  .checked-point {
+    margin: 5px 10px;
+    background: rgb(16, 171, 231);
+    padding: 5px 10px;
+    color: #fff;
+    border-radius: 5px;
+    display: inline-block;
+  }
+
+  .ivu-checkbox-checked {
+    .ivu-checkbox-inner {
+      background: rgb(16, 171, 231);
+      border-color: rgb(16, 171, 231);
+    }
+  }
+
+  .ivu-icon {
+    color: rgb(16, 171, 231);
+  }
+
+  .btn-clear {
+    float: right;
+    font-weight: 500;
+    font-size: 12px;
+    color: rgb(16, 171, 231);
+    cursor: pointer;
+    letter-spacing: 1px;
+  }
+
+  .point-checkbox {
+    margin-left: 10px;
+    border-radius: 2px;
+    width: 15px;
+    height: 15px;
+    padding: 2px;
+    vertical-align: middle;
+    box-sizing: border-box;
+    border: 1px solid #dcdee2;
+  }
+
+  .point-checked {
+    background: rgb(16, 171, 231);
+    background-clip: content-box;
+  }
+
+  .ivu-tabs-nav-scroll {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+  }
+
+  .ivu-tree-empty {
+    text-align: center;
+    margin: 15px;
+    color: rgb(128, 128, 128);
+  }
+}
+
+.point-list {
+  &::-webkit-scrollbar {
+    width: 5px;
+    height: 1px;
+  }
+
+  &:hover {
+    .ztree_box {
+      &::-webkit-scrollbar {
+        width: 5px;
+      }
+    }
+  }
+
+  &::-webkit-scrollbar-thumb {
+    border-radius: 10px;
+    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
+    background: #70707026;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: #f4f4f400;
+  }
+}
+
+.shop {
+  position: fixed;
+  top: 300px;
+  left: 400px;
+}
+
+.ball {
+  position: fixed;
+  right: 128px;
+  bottom: 45px;
+  z-index: 200;
+  transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41);
+}
+
+.inner {
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  transition: all 0.4s linear;
+
+  img {
+    width: 26px;
+    height: 26px;
+  }
+}
+
+.cart {
+  position: fixed;
+  bottom: 22px;
+  right: 32px;
+  width: 30px;
+  height: 30px;
+  background-color: rgb(0, 160, 220);
+  color: rgb(255, 255, 255);
+}
+
+.hevue-imgpreview-wrap {
+  z-index: 1999;
+}

+ 327 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreateExercises.less

@@ -0,0 +1,327 @@
+.ev-container {
+    user-select: none !important;
+    background: #fff;
+    padding: 0 30px;
+}
+	.new-exercise-header{
+		padding: 20px 0;
+		position: sticky;
+		top: 0;
+		background: #fff;
+		z-index: 899;
+		display: flex;
+		justify-content: space-between;
+		border-bottom: 1px solid #eaeaea;
+	}
+
+    .ev-container .w-e-text-container {
+		z-index:0 !important;
+		height:auto !important;
+        min-height:150px !important;
+		max-height:350px !important;
+		overflow:auto;
+    }
+
+    .ev-container .ev-title {
+        font-size: 20px;
+        font-weight: bold;
+        margin-left: 5px;
+        vertical-align: middle;
+    }
+
+    .ev-container .ev-title .ivu-icon {
+        margin-right: 6px;
+        margin-bottom: 6px;
+        font-size: 30px;
+        color: rgb(16, 171, 231);
+    }
+
+    .display-flex {
+        display: flex;
+        flex-direction: row;
+        justify-content:flex-start;
+    }
+
+.exersices-attr {
+    width: 100%;
+    margin-top: 30px;
+}
+
+    .exersices-attr .ivu-select-selection {
+        max-width:300px;
+        margin-top:20px;
+        min-height:40px;
+    }
+        .exersices-attr .ivu-select-selection .ivu-select-selected-value {
+            height: 40px;
+            line-height: 40px;
+        }
+		
+		.exersices-attr .ivu-select-multiple .ivu-select-selection .ivu-select-placeholder{
+			height: 40px;
+		}
+    .exersices-attr .ivu-select-dropdown {
+        min-width: 300px !important;
+    }
+
+
+    .exersices-attr-type {
+        /* width: 33%; */
+    }
+
+.exersices-attr .exercise-item-point {
+    position: relative;
+    display: inline-block;
+    margin: 6px;
+    padding: 5px 15px;
+    background: #00d523;
+    color: #fff;
+    border-radius: 5px;
+}
+
+.exersices-attr .exercise-item-point-close {
+    cursor:pointer;
+}
+
+    .exersices-attr .exercise-item-point-modify {
+        text-decoration:underline;
+        cursor:pointer;
+        margin-left:10px;
+    }
+
+
+
+.my-radio-style .ivu-radio-group-button .ivu-radio-wrapper {
+    margin-right: 10px;
+	margin-top: 10px;
+    border-radius: 5px;
+    background: #fff;
+    border: 1px solid #dcdee2;
+}
+
+    .my-radio-style  .ivu-radio-group-button .ivu-radio-wrapper:after {
+        content: none;
+    }
+
+    .my-radio-style .ivu-radio-group-button .ivu-radio-wrapper:after, .ivu-radio-group-button .ivu-radio-wrapper:before {
+        content: none;
+    }
+
+.my-radio-style .ivu-radio-group-button .ivu-radio-wrapper-checked {
+    background: #10abe7;
+    color: white;
+    box-shadow: none !important;
+}
+
+.my-radio-style .ivu-radio-group {
+    margin-top: 15px;
+}
+
+exersices-attr-diff {
+    width: 50%;
+}
+.exersices-content {
+    position:relative;
+    margin-top:35px;
+}
+
+
+
+.exersices-option {
+    margin-top:50px;
+}
+.ev-container .option-item {
+    margin-top: 15px;
+}
+.ev-container .exersices-analysis {
+    position: relative;
+    margin-top: 50px;
+}
+.ev-container .exersices-analysis .w-e-toolbar{
+    position: unset;
+}
+
+
+
+.ev-container .w-e-toolbar{
+    font-size:14px;
+    z-index:auto !important;
+	background-color: #f1f1f1;
+}
+.ev-container .w-e-toolbar .w-e-menu {
+    z-index: 1 !important;
+}
+
+
+.option-editor {
+    position: relative;
+    min-height: 40px;
+    width: 90%;
+    border-top: 1px solid rgba(204, 204, 204, 0.49);
+    border-bottom: 1px solid rgba(204, 204, 204, 0.49);
+}
+
+    .option-editor .w-e-toolbar {
+        position:absolute;
+        top:-40px;
+        left:0px;
+        line-height: 20px;
+        visibility:hidden;
+        font-size: 12px;
+    }
+    .option-editor .w-e-text {
+        overflow:hidden;
+        width:auto !important;
+        line-height:40px;
+    }
+    .option-editor .w-e-text-container {
+        border:none !important;
+		min-height: auto !important;
+        height:auto !important;
+		z-index: 1 !important;
+		position: unset !important;
+    }
+.ev-container .option-order {
+    min-height: 40px;
+    width: 60px;
+    background: rgb(240,240,240);
+    float: left;
+    color: #a0a0a0;
+    text-align: center;
+    line-height: 40px;
+    border-top: 1px solid rgba(204, 204, 204, 0.49);
+    border-bottom: 1px solid rgba(204, 204, 204, 0.49);
+    border-left: 1px solid rgba(204, 204, 204, 0.49);
+    border-radius: 5px 0 0 5px;
+}
+
+.ev-container .option-setting {
+    display: inline-flex !important;
+    width: 120px;
+    min-height: 40px;
+    color: #a0a0a0;
+    background: rgb(240,240,240);
+    cursor: pointer;
+    border-top: 1px solid rgba(204, 204, 204, 0.49);
+    border-bottom: 1px solid rgba(204, 204, 204, 0.49);
+    border-right: 1px solid rgba(204, 204, 204, 0.49);
+    border-radius: 0 5px 5px 0;
+}
+
+.ev-container .option-true {
+    background: rgb(60,196,82);
+    color: #fff;
+    font-weight: bold;
+}
+
+.ev-container .option-delete {
+    font-size: 20px;
+    margin-right: 10px;
+    cursor: pointer;
+    color: red;
+}
+
+.fl-center {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.exersices-option .option-add {
+    float:right;
+    margin-top:20px;
+    margin-right:2%;
+    color:#2d8cf0;
+    font-size:16px;
+    font-weight:normal;
+    cursor:pointer;
+}
+
+
+.exersices-option .ivu-radio-group {
+    margin:20px 10px;
+}
+
+
+.exersices-option .ivu-radio-wrapper-checked {
+    background: #2d8cf0;
+    color: white;
+    box-shadow: none !important;
+}
+
+.exersices-content .add-underline {
+    position:absolute;
+    right:10px;
+    top:0px;
+
+}
+
+
+.exersices-content .complete-line {
+    text-indent: 0;
+    cursor: pointer;
+    border-bottom: solid 1px #FF5500;
+    min-width: 4em;
+    color: #FF5500;
+    display: inline-block;
+    vertical-align: middle;
+    line-height: 8px;
+    height: 16px;
+    text-align: center;
+    margin: 0 2px;
+    font-size:16px;
+}
+
+.ev-container .child-list-wrap{
+	margin: 20px 0;
+	
+	.child-item{
+		padding: 20px 10px;
+		
+		&-question{
+			font-size: 14px;
+			
+			&-content{
+				display: inline-block;
+				margin-left: 5px;
+			}
+		}
+	}
+}
+.save-wrap {
+    width:100%;
+    justify-content:center;
+	margin-bottom: 20px;
+    padding-bottom: 20px;
+}
+    .save-wrap .ivu-btn {
+           font-size:16px;
+           width:150px;
+           height:40px;
+           margin-top:25px;
+    }
+
+.ev-container .btn-upload {
+    position:absolute;
+    left:92%;
+    top:0px;
+}
+
+.w-e-text-container .w-e-panel-container {
+    left: 28%;
+}
+
+
+.video-js .vjs-big-play-button {
+    top: 50%;
+    left: 50%;
+    margin-left: -40px;
+    margin-top: -20px;
+}
+
+
+.btn-relate-content{
+    position:absolute;
+    right:10px;
+    top:-10px;
+}

+ 888 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreateExercises.vue

@@ -0,0 +1,888 @@
+<template>
+  <div class="ev-container">
+    <div class="new-exercise-header">
+      <span class="ev-title">
+        <Icon type="ios-paper" />
+        {{ exerciseScope === 1 ? $t('evaluation.newExercise.newSchoolItem') : $t('evaluation.newExercise.newPrivateItem') }}
+        <span style="font-size: 12px;margin-left: 10px;display: none">
+          <span v-if="isHiToolAlive" style="color: #10abe7;">
+            * {{ $t('evaluation.toolOpenTip') }}
+          </span>
+          <span v-else style="color: #e72f32;">
+            * {{ $t('evaluation.toolCloseTip') }}
+            <span style="margin-left: 5px;text-decoration: underline;color: #0074D9;cursor: pointer;" @click="openHiTool">{{ $t('evaluation.start') }}</span>
+            <!-- <span style="margin-left: 5px;text-decoration: underline;color: #0074D9;cursor: pointer;" @click="doDownload">下载</span> -->
+          </span>
+        </span>
+      </span>
+      <div>
+        <Button type="text" @click="backToBank" class="btn-back-to-bank" icon="md-arrow-round-back" style="margin-left: 10px">{{ $t('evaluation.newExercise.backToBank')}}</Button>
+        <!-- <Button type="text" @click="backToBank" class="btn-back-to-bank" icon="md-arrow-round-back"
+					style="margin-left: 10px">{{ $t('evaluation.newExercise.backToBank')}}</Button> -->
+        <Button type="success" @click="getContent(exersicesType)" icon="md-folder-open" :loading="saveLoading">{{ $t('evaluation.newExercise.save') }}</Button>
+      </div>
+    </div>
+    <!-- <Divider /> -->
+    <div class="display-flex" v-if="isSchool">
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.choosePeriod')" :color="'#00b8ff'" :icon="'md-school'">
+        </IconText>
+        <Select v-model="exercisePeriod" @on-change="onPeriodChange">
+          <Option v-for="(period, index) in schoolInfo.period" :value="index" :key="index">{{ period.name }}
+          </Option>
+        </Select>
+      </div>
+      <div class="my-radio-style exersices-attr">
+        <IconText :text="$t('evaluation.newExercise.chooseGrade')" :color="'#00b8ff'" :icon="'logo-buffer'">
+        </IconText>
+        <Select v-model="exerciseGrade" multiple :placeholder="$t('evaluation.newExercise.gradePlaceholder')">
+          <Option v-for="(grade, index) in gradeList" :value="index" :key="grade">{{ grade }}</Option>
+        </Select>
+      </div>
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.chooseSubject')" :color="'#00b8ff'" :icon="'md-bookmarks'">
+        </IconText>
+        <Select v-model="exerciseSubject" @on-change="onSubjectChange">
+          <Option v-for="(subject, index) in subjectList" :value="index" :key="index">{{ subject.name }}
+          </Option>
+        </Select>
+      </div>
+    </div>
+    <div class="display-flex">
+      <div class="exersices-attr exercises-attr-diff my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.diff')" :color="'#00b8ff'" :icon="'md-pulse'"></IconText>
+        <RadioGroup v-model="exersicesDiff" type="button">
+          <Radio label="1" @click.native="diffChange($event, '1')">{{$t('evaluation.diff1')}}</Radio>
+          <Radio label="2" @click.native="diffChange($event, '2')">{{$t('evaluation.diff2')}}</Radio>
+          <Radio label="3" @click.native="diffChange($event, '3')">{{$t('evaluation.diff3')}}</Radio>
+          <Radio label="4" @click.native="diffChange($event, '4')">{{$t('evaluation.diff4')}}</Radio>
+          <Radio label="5" @click.native="diffChange($event, '5')">{{$t('evaluation.diff5')}}</Radio>
+        </RadioGroup>
+      </div>
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.field')" :color="'#00b8ff'" :icon="'md-planet'"></IconText>
+        <Select v-model="exerciseField">
+          <Option v-for="(item, index) in fieldList" :value="index" :key="index">{{ item }}</Option>
+        </Select>
+      </div>
+
+      <div class="exersices-attr my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.knowledge')" :color="'#00b8ff'" :icon="'md-infinite'">
+        </IconText>
+        <Button type="info" style="margin-top: 20px" @click="onSelectPoints" v-if="exercisePoints.length === 0">{{$t('evaluation.newExercise.choosePoint')}}</Button>
+        <div v-else style="margin-top: 10px">
+          <span v-for="(item, index) in exercisePoints" :key="index" class="exercise-item-point">
+            {{ item }}
+            <span class="exercise-item-point-close">
+              <Icon type="md-close" @click="onDeletePoint(index)" />
+            </span>
+          </span>
+          <span class="exercise-item-point-modify" @click="selectPointsModal = true">{{ $t('evaluation.newExercise.modify') }}</span>
+        </div>
+      </div>
+    </div>
+    <div class="exersices-attr display-flex">
+      <div class="exersices-attr-type my-radio-style">
+        <IconText :text="$t('evaluation.newExercise.type')" :color="'#00b8ff'" :icon="'md-pricetags'">
+        </IconText>
+        <RadioGroup v-model="exersicesType" type="button" @on-change="typeChange">
+          <Radio label="single" :disabled="isEdit">{{ $t('evaluation.single') }}</Radio>
+          <Radio label="multiple" :disabled="isEdit">{{ $t('evaluation.multiple') }}</Radio>
+          <Radio label="judge" :disabled="isEdit">{{ $t('evaluation.judge') }}</Radio>
+          <Radio label="complete" :disabled="isEdit">{{ $t('evaluation.complete') }}</Radio>
+          <Radio label="subjective" :disabled="isEdit">{{ $t('evaluation.subjective') }}</Radio>
+          <Radio label="connector" :disabled="isEdit">{{ $t('evaluation.connector') }}</Radio>
+          <Radio label="correct" :disabled="isEdit">{{ $t('evaluation.correct') }}</Radio>
+          <Radio label="compose" :disabled="isEdit">{{ $t('evaluation.compose') }}</Radio>
+        </RadioGroup>
+      </div>
+    </div>
+
+    <BaseSingle v-if="exersicesType === 'single'" ref="single" :editInfo="editInfo"></BaseSingle>
+    <BaseMultiple v-else-if="exersicesType === 'multiple'" ref="multiple" :editInfo="editInfo"></BaseMultiple>
+    <BaseJudge v-else-if="exersicesType === 'judge'" ref="judge" :editInfo="editInfo"></BaseJudge>
+    <BaseCompletion v-else-if="exersicesType === 'complete'" ref="complete" :editInfo="editInfo"></BaseCompletion>
+    <BaseSubjective v-else-if="exersicesType === 'subjective'" ref="subjective" :editInfo="editInfo">
+    </BaseSubjective>
+    <BaseConnector v-else-if="exersicesType === 'connector'" ref="connector" :editInfo="editInfo"></BaseConnector>
+    <BaseCorrect v-else-if="exersicesType === 'correct'" ref="correct" :editInfo="editInfo"></BaseCorrect>
+    <BaseCompose v-else-if="exersicesType === 'compose'" ref="compose" :editInfo="editInfo"></BaseCompose>
+
+    <!-- 解析的富文本部分 -->
+    <div class="exersices-analysis" v-show="exersicesType !== 'compose'">
+      <IconText :text="$t('evaluation.explain')" :color="'#2892DD'" :icon="'md-list'" style="margin-bottom: 10px">
+      </IconText>
+      <div id="analysisEditor">
+        <div ref="analysisEditor" style="text-align: left"></div>
+      </div>
+    </div>
+
+    <!-- 补救的富文本部分 -->
+    <div class="exersices-analysis" v-show="exersicesType !== 'compose'">
+      <IconText :text="$t('evaluation.newExercise.repair')" :color="'#2892DD'" :icon="'md-link'" style="margin-bottom: 10px"></IconText>
+      <BaseRepair ref="repairRef"></BaseRepair>
+    </div>
+
+    <!-- 小题展示区域 -->
+    <div class="child-list-wrap" v-show="exersicesType === 'compose' && childList.length">
+      <IconText :text="$t('evaluation.newExercise.childList')" :color="'#00b8ff'" :icon="'md-list'"></IconText>
+      <BaseChildList :childList="childList"></BaseChildList>
+    </div>
+
+    <div class="save-wrap display-flex">
+      <Button type="info" @click="onAddChild" style="margin-right: 10px" v-show="exersicesType === 'compose'">{{ $t('evaluation.newExercise.addChild')}}</Button>
+      <!-- <Button type="success" @click="getContent(exersicesType)"
+				:loading="saveLoading">{{ $t('evaluation.newExercise.save') }}</Button> -->
+    </div>
+
+    <Modal v-model="selectPointsModal" :title="$t('evaluation.newExercise.choosePoint')" width="600px" class="related-point-modal" footer-hide style="z-index: 99999">
+      <BasePoints v-if="selectPointsModal" :period="schoolInfo.period ? schoolInfo.period[exercisePeriod].id : ''" :subject="subjectList.length ? subjectList[exerciseSubject].id : ''" @onCheckChange="onCheckChange" @onCancel="selectPointsModal = false" :points="exercisePoints" :scope="curScope" ref="pointRef"></BasePoints>
+    </Modal>
+
+    <!-- 添加小题弹窗 -->
+    <Modal v-model="addComposeChildModal" width="1080" footer-hide class="">
+      <div class="modal-header" slot="header">{{ $t('evaluation.newExercise.addChild') }}</div>
+      <BaseCreateChild @addFinish="onAddChildFinish" ref="createAddChild" v-if="addComposeChildModal" :curPeriodIndex="exercisePeriod" :curSubjectIndex="exerciseSubject"></BaseCreateChild>
+    </Modal>
+  </div>
+</template>
+<script>
+import blobTool from "@/utils/blobTool.js";
+import IconText from "@/components/evaluation/IconText.vue";
+import BaseSingle from "@/view/evaluation/types/BaseSingle.vue";
+import BaseMultiple from "@/view/evaluation/types/BaseMultiple.vue";
+import BaseCompletion from "@/view/evaluation/types/BaseCompletion.vue";
+import BaseJudge from "@/view/evaluation/types/BaseJudge.vue";
+import BaseSubjective from "@/view/evaluation/types/BaseSubjective.vue";
+import BaseCorrect from "@/view/evaluation/types/BaseCorrect.vue";
+import BaseConnector from "@/view/evaluation/types/BaseConnector.vue";
+import BaseCompose from "@/view/evaluation/types/BaseCompose.vue";
+import BasePoints from "@/view/evaluation/components/BasePoints";
+import BaseRepair from "@/view/evaluation/components/BaseRepair";
+import BaseCreateChild from "@/view/evaluation/components/BaseCreateChild";
+import E from "wangeditor";
+// import {
+// 	EMFJS,
+// 	RTFJS,
+// 	WMFJS
+// } from 'rtf.js';
+// 默认创建题目模板
+const defaultExercise = {
+  question: "",
+  option: [],
+  level: 1,
+  answer: [],
+  explain: "",
+  type: "",
+  answerType:'text',
+  useAutoScore: false,
+	answerLang: "en-US",
+};
+export default {
+  components: {
+    IconText,
+    BaseSingle,
+    BaseJudge,
+    BaseMultiple,
+    BaseCompletion,
+    BaseSubjective,
+    BaseCorrect,
+    BaseConnector,
+    BaseCompose,
+    BasePoints,
+    BaseRepair,
+    BaseCreateChild,
+  },
+  data() {
+    return {
+      isHiToolAlive: false,
+      isShowHiToolTip: false,
+      fromScope: "",
+      isRelatedContent: false,
+      selectPointsModal: false,
+      addComposeChildModal: false,
+      isEdit: false,
+      isFalse: false,
+      isLoading: false,
+      relateFileList: [],
+      editInfo: {},
+      schoolInfo: {},
+      saveLoading: false,
+      exersicesType: "single",
+      exerciseField: 0,
+      exercisePeriod: 0,
+      exerciseGrade: [],
+      exerciseSubject: 0,
+      exerciseScope: 0,
+      exercisePoints: [],
+      scopeList: [this.$t('evaluation.filter.schoolBank'), this.$t('evaluation.filter.privateBank')],
+      fieldList: [this.$t('evaluation.level1'), this.$t('evaluation.level2'), this.$t('evaluation.level3'),
+      this.$t('evaluation.level4'), this.$t('evaluation.level5'), this.$t('evaluation.level6')
+      ],
+      periodList: [],
+      gradeList: [],
+      subjectList: [],
+      childList: [],
+      exersicesDiff: "1",
+      analysisContent: "",
+      repairContent: "",
+      stemContent: "",
+      analysisEditor: null,
+      repairEditor: null,
+      curId: '',
+      isComplete: false,
+      toolTimer: null
+    };
+  },
+  created() {
+    // 设置轮询来检测教学助手的开启状态
+    // this.isToolAlive()
+    // this.toolTimer = setInterval(this.isToolAlive, 3000)
+
+    let scope = this.$route.name === 'newSchoolExercise' ? 'school' : 'private'; // 编辑题目
+    if (scope) {
+      this.exerciseScope = scope === "private" ? 0 : 1;
+      this.fromScope = scope;
+    }
+    // 先生成随机ID
+    this.curId = this.$tools.guid()
+  },
+  beforeDestroy() {
+    clearInterval(this.toolTimer)
+  },
+  methods: {
+    getSchoolInfo(periodIndex, storageInfo) {
+      this.$store.dispatch("user/getSchoolProfile").then((res) => {
+        let schoolBaseInfo = res.school_base;
+        if (schoolBaseInfo) {
+          this.schoolInfo = schoolBaseInfo;
+          if (schoolBaseInfo.period.length) {
+            this.onPeriodChange(periodIndex, storageInfo)
+          }
+        }
+      });
+    },
+
+    /* 检测是否启动了HiTool */
+    async isToolAlive() {
+      this.isHiToolAlive = await this.$editorTools.checkTools()
+      /* 如果启动成功了 则关闭轮询 */
+      if (this.isHiToolAlive) {
+        clearInterval(this.toolTimer)
+      }
+    },
+    /* 启动HiTool */
+    openHiTool() {
+      window.open("hitools://")
+    },
+    doDownload() {
+
+    },
+
+    onSelectPoints() {
+      if (this.isSchool && (!this.subjectList.length || !this.gradeList.length)) {
+        this.$Message.warning(this.$t('evaluation.completeTip'))
+        return
+      }
+      // if (this.hasSchool) {
+        if (this.exersicesType === 'compose') {
+          this.$Message.warning(this.$t('evaluation.newExercise.composeTip'))
+        } else {
+          this.selectPointsModal = true
+        }
+      /* } else {
+        this.$Message.warning(this.$t('evaluation.newExercise.noSchoolTip'))
+      } */
+    },
+
+    onSelectFile(val) {
+      this.relateFileList = val.files;
+    },
+
+    onConfirmRelate() {
+      console.log(this.relateFileList);
+      this.isRelatedContent = false;
+    },
+
+    async getContent(type) {
+      if (this.isSchool && (!this.subjectList.length || !this.gradeList.length)) {
+        this.$Message.warning(this.$t('evaluation.completeTip'))
+        return
+      }
+      let exerciseItem = Object.assign({}, defaultExercise);
+      switch (type) {
+        case "single":
+          this.$refs.single.doSave()
+          exerciseItem.question = this.$refs.single.stemContent;
+          exerciseItem.option = this.checkOptionNull(this.$refs.single.optionsContent) ? this.$refs
+            .single.optionsContent :
+            null;
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [
+            String.fromCharCode(64 + parseInt(this.$refs.single.trueIndex + 1)),
+          ];
+          break;
+        case "multiple":
+          this.$refs.multiple.doSave()
+          exerciseItem.question = this.$refs.multiple.stemContent;
+          exerciseItem.option = this.checkOptionNull(this.$refs.multiple.optionsContent) ?
+            this.$refs.multiple.optionsContent :
+            null;
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = this.$refs.multiple.multipleAnswers;
+          break;
+        case "judge":
+          exerciseItem.question = this.$refs.judge.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.judge.trueAnswer];
+          break;
+        case "complete":
+          exerciseItem.question = this.$refs.complete.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.complete.answerContent];
+          exerciseItem.blankCount = this.$refs.complete.blankCount || 1;
+          break;
+        case "subjective":
+          exerciseItem.question = this.$refs.subjective.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answerType = this.$refs.subjective.answerType; // 02040301 问答题增加作答类型字段
+          exerciseItem.useAutoScore = this.$refs.subjective.useAutoScore; // 02040301 问答题增加作答类型字段
+          exerciseItem.answerLang = this.$refs.subjective.answerLang; // 02040301 问答题增加作答类型字段
+          exerciseItem.answer = [this.$refs.subjective.answerContent];
+          break;
+        case "connector":
+          exerciseItem.question = this.$refs.connector.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.connector.answerContent];
+          break;
+        case "correct":
+          exerciseItem.question = this.$refs.correct.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.answer = [this.$refs.correct.answerContent];
+          break;
+        case "compose":
+          exerciseItem.question = this.$refs.compose.stemContent;
+          exerciseItem.option = [];
+          exerciseItem.type = this.exersicesType;
+          exerciseItem.level = +this.exersicesDiff;
+          exerciseItem.explain = this.analysisContent;
+          exerciseItem.children = this.childList;
+          exerciseItem.answer = []
+          break;
+        default:
+          break;
+      }
+      console.log(exerciseItem.children);
+      console.log(this.childList);
+      exerciseItem.repair = this.formatRepairResource(this.$refs.repairRef.datas);
+      exerciseItem.field = this.exerciseField + 1;
+      exerciseItem.knowledge = this.exercisePoints; //新知识点
+      exerciseItem.periodId = this.isSchool ? this.schoolInfo.period[this.exercisePeriod].id : null;
+      exerciseItem.gradeIds = this.isSchool ?
+        this.exerciseGrade.length ?
+          this.exerciseGrade.map(i => i + '') :
+          this.gradeList.map((i, index) => index + '') :
+        null;
+      exerciseItem.subjectId = this.isSchool ?
+        this.schoolInfo.period[this.exercisePeriod].subjects[
+          this.exerciseSubject
+        ].id :
+        null;
+      exerciseItem.code =
+        this.exerciseScope === 0 ?
+          this.$store.state.userInfo.TEAMModelId :
+          this.schoolInfo.id;
+      exerciseItem.scope = this.exerciseScope === 0 ? "private" : "school";
+
+      // 收集编辑器宽度 方便压缩富文本图片中宽度为百分比的图片
+      let editorWidth = {}
+      if (exerciseItem.type === 'single') {
+        editorWidth.questionWidth = this.$refs.single.stemEditor.$textContainerElem.elems[0].clientWidth
+        editorWidth.optionWidth = this.$refs.single.optionEditors[0].$textContainerElem.elems[0]
+          .clientWidth
+      } else if (exerciseItem.type === 'multiple') {
+        editorWidth.questionWidth = this.$refs.multiple.stemEditor.$textContainerElem.elems[0].clientWidth
+        editorWidth.optionWidth = this.$refs.multiple.optionEditors[0].$textContainerElem.elems[0]
+          .clientWidth
+      } else {
+        editorWidth.questionWidth = this.analysisEditor.$textContainerElem.elems[0].clientWidth
+      }
+      let confirmSave = await this.checkContent(exerciseItem)
+      // 判断获取的数据是否有空数据以及是否为空字符串
+      if (confirmSave) {
+        exerciseItem = await this.$editorTools.transBase64Src(exerciseItem, editorWidth)
+        console.log('转换后的试题')
+        console.log(exerciseItem)
+        this.saveLoading = true;
+        // 生成JSON文件名称以及新增试题的ID
+        const guid = this.curId;
+        // 给新增的试题赋值ID
+        exerciseItem.id = guid;
+        // 如果是综合题 则需要完善子题的信息 并且进行保存
+        if (exerciseItem.children && exerciseItem.children.length && exerciseItem.type === 'compose') {
+          exerciseItem.children.forEach(async (child) => {
+            child.periodId = exerciseItem.periodId
+            child.gradeIds = exerciseItem.gradeIds
+            child.subjectId = exerciseItem.subjectId
+            child.scope = exerciseItem.scope
+            child.code = exerciseItem.code
+            child.pid = exerciseItem.id
+          })
+          let containerName = exerciseItem.scope === 'private' ? this.$store.state.userInfo.TEAMModelId :
+            this.$store.state.userInfo.schoolCode
+          // 保存完题目返回子题的ID集合 作为cosmos内综合题的children字段
+          exerciseItem.children = await this.saveChildrens(exerciseItem.children, containerName)
+        }
+        console.log(exerciseItem.children)
+        // 将当前的试题数据转化为BLOB内部的试题JSON格式
+        const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
+        // 首先保存新题目的JSON文件到Blob 然后返回URL链接
+        let file = new File([JSON.stringify(itemJsonFile)], guid + ".json", {
+          type: "",
+        });
+        // 获取初始化Blob需要的数据
+        let sasData =
+          this.exerciseScope === 0 ?
+            await this.$tools.getPrivateSas() :
+            await this.$tools.getSchoolSas();
+        //初始化Blob
+        let containerClient = new blobTool(
+          sasData.url,
+          sasData.name,
+          sasData.sas,
+          exerciseItem.scope
+        );
+
+        try {
+          // 等待上传blob的返回结果
+          let blobFile = await containerClient.upload(file, { path: "item/" + guid });
+          if (blobFile.blob) {
+            // 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+            exerciseItem.blob = blobFile.blob;
+            this.saveExercise({
+              itemInfo: await this.$evTools.createCosmosItem(exerciseItem),
+              option: "insert",
+            }).then((res) => {
+              this.isComplete = true
+              localStorage.setItem('noSave', '[]')
+              // 如果是校本的试题 则保留当前试题所选择的学段科目和年级 便于下次快速选择
+              if (this.isSchool) {
+                localStorage.setItem('bankFilterConds', JSON.stringify({
+                  period: this.exercisePeriod,
+                  subject: this.exerciseSubject,
+                  grade: this.exerciseGrade
+                }))
+              }
+              this.$router.push({
+                name: this.exerciseScope === 0 ? "personalBank" : "schoolBank",
+                params: {
+                  tabName: "exercise",
+                  activePeriod: exerciseItem.periodId
+                },
+              });
+              this.saveLoading = false;
+            });
+          } else {
+            this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+          }
+        } catch (e) {
+          this.$Message.error(e.spaceError);
+        }
+      }
+    },
+
+    /* 保存综合题的子题 */
+    saveChildrens(childrens, containerName) {
+      return new Promise(async (resolve, reject) => {
+        let promiseArr = []
+        childrens.forEach(exerciseItem => {
+          promiseArr.push(new Promise(async (r, j) => {
+            // 处理小题的多媒体资源
+            exerciseItem = await this.$editorTools.transBase64Src(exerciseItem)
+            // 将当前的试题数据转化为BLOB内部的试题JSON格式
+            const itemJsonFile = await this.$evTools.createBlobItem(
+              exerciseItem);
+            // 首先保存新题目的JSON文件到Blob 然后返回URL链接
+            let file = new File([JSON.stringify(itemJsonFile)],
+              exerciseItem.id + ".json");
+            // 获取初始化Blob需要的数据
+            let sasData =
+              exerciseItem.scope === 'private' ?
+                await this.$tools.getPrivateSas() :
+                await this.$tools.getSchoolSas();
+            //初始化Blob
+            let containerClient = new blobTool(
+              sasData.url,
+              sasData.name,
+              sasData.sas,
+              exerciseItem.scope
+            );
+            try {
+              // 等待上传blob的返回结果
+              let blobFile = await containerClient.upload(file, { path: "item/" + exerciseItem.id });
+              if (blobFile.blob) {
+                // 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
+                exerciseItem.blob = blobFile.blob;
+                this.saveExercise({
+                  itemInfo: await this.$evTools
+                    .createCosmosItem(exerciseItem),
+                  option: "insert",
+                }).then((res) => {
+                  r({
+                    cosmosItem: res.itemInfo,
+                    blobItem: blobFile
+                  })
+                  console.log(res.itemInfo);
+                  console.log(blobFile);
+                });
+
+              } else {
+                this.$Message.error(this.$t(
+                  'evaluation.newExercise.uploadErrorTip'
+                ));
+              }
+            } catch (e) {
+              this.$Message.error(e.spaceError);
+            }
+          }))
+        })
+
+        Promise.all(promiseArr).then(result => {
+          if (result.length) {
+            // 子题保存之后 统一更新blobSize
+            // blobTool.updateSize(result.map(i => i.blobItem),containerName).then(res => {
+            // 	resolve(result.map(i => i.cosmosItem.id))
+            // }).catch(err => {
+            // 	this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+            // })
+            resolve(result.map(i => i.cosmosItem.id))
+          } else {
+            resolve([])
+          }
+        })
+      })
+    },
+
+
+    /* 保存单个试题 */
+    saveExercise(item) {
+      return new Promise((r, j) => {
+        this.$api.newEvaluation.SaveSingleExercise(item).then((res) => {
+          if (res) {
+            r(res);
+          } else {
+            j(res);
+            this.$Message.error("服务器繁忙!");
+            this.saveLoading = false;
+          }
+        });
+      });
+    },
+
+    /* 检测补救资源超链接 去除无效链接 */
+    formatRepairResource(list) {
+      if (list.length) {
+        let arr = [];
+        list.forEach((i, index) => {
+          i.blobUrl.forEach(j => {
+            arr.push({
+              blobUrl: j.url,
+              name: i.name,
+              type: i.type
+            })
+          })
+        });
+        return arr;
+      } else {
+        return [];
+      }
+    },
+
+    /* 添加小题 重置新增页面 */
+    onAddChild() {
+      this.addComposeChildModal = true;
+      if (this.$refs.createAddChild) this.$refs.createAddChild.doReset();
+    },
+
+    /* 添加小题完成 */
+    onAddChildFinish(item) {
+      console.log(item);
+      this.addComposeChildModal = false;
+      this.childList.push(item);
+    },
+
+    /* 知识点勾选变动事件 */
+    onCheckChange(val, list) {
+      this.exercisePoints = val;
+      this.selectPointsModal = false
+    },
+
+    /**
+     * 移除指定知识点
+     * @param index
+     */
+    onDeletePoint(index) {
+      this.exercisePoints.splice(index, 1);
+    },
+
+    // 题目类型转换
+    typeChange(val) {
+      if (this.isEdit) {
+        this.$Message.warning(this.$t('evaluation.newExercise.typeChangeTip'));
+      } else {
+        this.exersicesType = val;
+        this.childList = [];
+        this.analysisEditor.txt.clear();
+        // this.repairEditor.txt.clear()
+      }
+    },
+
+    // 难度与背景颜色切换
+    diffChange(e, type) {
+      this.exersicesDiff = +type;
+      e.preventDefault();
+      let colorArr = ["#10abe7", "#E8BE15", "#F19300", "#EB5E00", "#D30000"];
+      let ac = document.getElementsByClassName("exercises-attr-diff")[0]
+        .children[1].children;
+      for (let i = 0; i < ac.length; i++) {
+        ac[i].style.background = "#fff";
+        ac[i].style.color = "#515a6e";
+      }
+      e.target.style.background = colorArr[type - 1];
+      e.target.style.color = "#fff";
+    },
+
+    /* 学段变化 */
+    onPeriodChange(val, storageInfo) {
+      this.exercisePeriod = val
+      this.gradeList = this.schoolInfo.period[val].grades;
+      this.subjectList = this.schoolInfo.period[val].subjects;
+      this.exerciseGrade = storageInfo ? storageInfo.grade : [];
+      this.exerciseSubject = storageInfo ? storageInfo.subject : 0;
+      this.exercisePoints = []; // 切换学段后 知识点需要重新选择
+    },
+
+    /* 切换科目 将知识点重置 */
+    onSubjectChange() {
+      this.exercisePoints = []
+    },
+
+    // 提取富文本内容中的文本
+    getSimpleText(html) {
+      var r = /<(?!img|video|audio).*?>/g;
+      return html.replace(r, "");
+    },
+
+    /* 检测数组是否有空数据 */
+    checkOptionNull(arr) {
+      let flag = true;
+      // for (let i = 0; i < arr.length; i++) {
+      // 	if (this.getSimpleText(arr[i].value) === "") {
+      // 		flag = false;
+      // 	}
+      // }
+      return flag;
+    },
+
+    // 排除对象空属性
+    checkContent(Obj) {
+      return new Promise(async (r, j) => {
+        let flag = true;
+        let whiteList = this.getWhiteListByType(Obj.type);
+        console.log("富文本获取的原始试题数据", Obj);
+        for (let key in Obj) {
+          if (whiteList.includes(key) && typeof Obj[key] === "string") {
+            if (!this.getSimpleText(Obj[key])) {
+              flag = await this.emptyConfirm(key)
+              r(flag);
+              return
+            }
+          } else {
+            if (whiteList.includes(key) && !Obj[key]) {
+              flag = await this.emptyConfirm(key)
+              r(flag);
+              return
+            }
+          }
+        }
+        r(flag);
+      })
+    },
+
+    /* 确认是否继续保存 */
+    emptyConfirm(key) {
+      return new Promise((r, j) => {
+        this.$Modal.confirm({
+          title: this.$t('evaluation.newExercise.modalTip'),
+          content: `${this.$t('evaluation.currentItem')}${key === 'question' ? this.$t('evaluation.newExercise.stem') : this.$t('evaluation.newExercise.option')}${this.$t('evaluation.emptyTip1')}`,
+          onOk: () => {
+            r(true)
+          },
+          onCancel: () => {
+            r(false)
+          }
+        });
+      })
+    },
+
+    // 重置编辑器
+    backToBank() {
+      this.$router.push({
+        name: this.fromScope === "private" ? "personalBank" : "schoolBank",
+        params: {
+          tabName: "exercise",
+        },
+      });
+    },
+
+    // 重置
+    reloadCreate() {
+      location.reload();
+    },
+
+    // 根据不同题型 给出需要必填选项
+    getWhiteListByType(type) {
+      switch (type) {
+        case "single":
+          return ["question", "option", "answer"];
+          break;
+        case "multiple":
+          return ["question", "option", "answer"];
+          break;
+        case "complete":
+          return ["question", "answer"];
+          break;
+        default:
+          return ["question"];
+          break;
+      }
+    },
+  },
+  mounted() {
+    // var htmlToRtf = require('html-to-rtf')
+    let analysisEditor = new E(this.$refs.analysisEditor);
+    analysisEditor.config.uploadImgShowBase64 = true;
+    analysisEditor.config.onchange = (html) => {
+      this.analysisContent = html;
+      // const rtf = htmlToRtf.convertHtmlToRtf(html)
+      // htmlToRtf.saveRtfInFile('<Path>/<FileName>.rtf', htmlToRtf.convertHtmlToRtf(html))
+
+      // console.log(htmlToRtf.convertHtmlToRtf(html))
+    },
+      this.$editorTools.initMyEditor(analysisEditor, this)
+    // this.$editorTools.addResource(this,analysisEditor)
+    analysisEditor.create();
+    this.analysisEditor = analysisEditor;
+
+    // 如果本地有缓存上次保存试题的学段科目年级信息 则直接读取
+    if (this.canLoadStorage) {
+      let storageInfo = JSON.parse(localStorage.getItem('bankFilterConds'))
+      this.getSchoolInfo(storageInfo.period, storageInfo)
+      // this.$Message.info('已自动为您快速切换到上次题目的学段科目和年级信息')
+    }
+
+    /* 检测到HiTool没有启动 再次开启轮询监听 启动过程 */
+    this.$EventBus.$off('toolsFail')
+    this.$EventBus.$on('toolsFail', val => {
+      this.isToolAlive()
+      this.toolTimer = setInterval(this.isToolAlive, 3000)
+    })
+
+
+  },
+  computed: {
+    isSchool() {
+      return this.$route.name === 'newSchoolExercise';
+    },
+    curScope() {
+      return this.$route.name === 'newSchoolExercise' ? 'school' : 'private'
+    },
+    hasSchool() {
+      return this.$store.state.userInfo.hasSchool
+    },
+    canLoadStorage() {
+      return this.isSchool && localStorage.getItem('bankFilterConds')
+    },
+
+  },
+  watch: {
+    '$store.state.user.curPeriod': {
+      deep: true,
+      immediate: true,
+      handler(n, o) {
+        if (n && !this.canLoadStorage) {
+          this.getSchoolInfo(n.periodIndex)
+          this.exercisePeriod = n.periodIndex
+          console.log(this.exercisePeriod);
+        }
+      }
+    }
+  },
+  // 路由离开生命周期函数
+  beforeRouteLeave(to, from, next) {
+    if (this.isComplete) {
+      if (to.name === 'schoolBank' || to.name === 'personalBank') {
+        // 设置下一个路由的 meta
+        to.meta.isKeep = false; // 让 A 缓存,即不刷新
+      }
+      next();
+    } else {
+      this.$Modal.confirm({
+        title: this.$t('evaluation.newExercise.modalTip'),
+        content: this.$t('evaluation.newExercise.unSaveTip'),
+        onOk: () => {
+          if (to.name === 'schoolBank' || to.name === 'personalBank') {
+            // 设置下一个路由的 meta
+            to.meta.isKeep = false; // 让 A 缓存,即不刷新
+          }
+          next();
+        }
+      });
+    }
+  },
+};
+</script>
+<style src="../index/CreateExercises.less" lang="less" scoped>
+/*@import"../index/CreateExercises.css";*/
+</style>
+
+<style>
+.related-point-modal .ivu-modal-header-inner {
+  font-weight: bold;
+}
+
+.exersices-attr .ivu-select-multiple .ivu-tag {
+  height: 32px;
+  line-height: 31px;
+}
+
+.exersices-attr .ivu-select-multiple .ivu-tag i {
+  top: 9px;
+}
+
+.exersices-attr
+  .ivu-select-multiple
+  .ivu-select-selection
+  .ivu-select-placeholder {
+  line-height: 38px;
+}
+</style>

+ 170 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreatePaper.less

@@ -0,0 +1,170 @@
+@main-bgColor: rgb(40, 40, 40); //主背景颜色
+@borderColor: var(--border-color);
+@primary-textColor: var(--primary-text-color); //文本主颜色
+@second-textColor: #a5a5a5; //文本副级颜色
+@primary-fontSize: 14px;
+@second-fontSize: 16px;
+
+
+.create-evaluation-container {
+  width: 100%;
+  height: 100%;
+
+  .save-tips {
+    position: absolute;
+    right: 40px;
+    top: 15px;
+    display: flex;
+    align-items: center;
+    // color: #d0d0d0;
+  }
+
+  .create-header {
+    width: 100%;
+    height: 45px;
+    border-bottom: 1px solid @borderColor;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .create-header-title {
+      height: 45px;
+      line-height: 45px;
+      color: @primary-textColor;
+      padding-left: 20px;
+      font-size: 16px;
+      display: inline-flex;
+    }
+
+    .btn-save {
+      margin-top: 6px;
+      margin-right: 20px;
+      height: 28px;
+      line-height: 28px;
+    }
+  }
+
+  .create-body {
+    width: 100%;
+    height: ~"calc(100% - 45px)";
+    display: flex;
+    flex-direction: row;
+  }
+}
+
+.create-body {
+  .demo-split-pane {
+    height: 100%;
+  }
+
+  .evaluation-attr-wrap {
+    border-right: 1px solid @borderColor;
+    height: 100%;
+    padding-left: 20px;
+
+    .wrap-label {
+      // color: white;
+      font-size: @primary-fontSize;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid @borderColor;
+    }
+  }
+
+  .evaluation-question-wrap {
+    height: 100%;
+    padding-left: 10px;
+
+    .wrap-label {
+      color: white;
+      font-size: @primary-fontSize;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid @borderColor;
+
+      p {
+        display: inline-block;
+        margin-right: 25px;
+      }
+
+      .subject-item {
+        display: inline-block;
+        margin-right: 30px;
+        color: @second-textColor;
+        cursor: pointer;
+        line-height: 39px;
+        min-width: 50px;
+        font-size: 16px;
+        text-align: center;
+
+        .delete-subject-btn {
+          width: 0px;
+          transition: all ease 0.2s;
+          overflow: hidden;
+        }
+
+        &:hover .delete-subject-btn {
+          width: 18px;
+        }
+      }
+
+      .subject-item-active {
+        color: @primary-textColor;
+        border-bottom: 2px solid white;
+        font-weight: 600;
+
+        .delete-subject-btn {
+          width: 18px;
+        }
+      }
+    }
+  }
+}
+
+.evaluation-attr-form {
+  /*margin-top:30px;*/
+  margin-right: 10px;
+
+}
+
+.evaluation-question-main {
+  width: 100%;
+  height: ~"calc(100% - 10px)";
+
+  .create-type-wrap {
+    color: white;
+    padding-bottom: 8px;
+    padding-top: 8px;
+    border-bottom: 1px solid #424242;
+  }
+}
+
+.add-subject-icon {
+  /*float: right;*/
+  /*margin-right: 50px;*/
+  margin-top: 8px;
+  cursor: pointer;
+}
+
+.question-main-tabs {
+  padding-top: 8px;
+  padding-right: 15px;
+  height: 100%;
+}
+
+iframe {
+  .wrapper {
+    max-width: 1400px !important;
+  }
+}
+
+.test-paper-analysis {
+  color: white;
+  cursor: pointer;
+  float: right;
+  margin-right: 50px;
+
+  &:hover {
+    color: aqua;
+  }
+}

File diff suppressed because it is too large
+ 1822 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/CreatePaper.vue


+ 138 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/DfPage.less

@@ -0,0 +1,138 @@
+@main-bgColor: rgb(40, 40, 40); //主背景颜色
+@borderColor: var(--border-color);
+@primary-textColor: var(--primary-text-color); //文本主颜色
+@second-textColor: #a5a5a5; //文本副级颜色
+@primary-fontSize: 14px;
+@second-fontSize: 16px;
+
+.df-container {
+    width: 100%;
+    height: 100%;
+    position: relative;
+
+    .new-exercise-header {
+        width: 100%;
+        height: 45px;
+        border-bottom: 1px solid @borderColor;
+        margin-bottom: 15px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        .ev-title {
+            height: 45px;
+            line-height: 45px;
+            color: @primary-textColor;
+            padding-left: 20px;
+            font-size: 16px;
+            display: inline-flex;
+        }
+
+        .btn-save {
+            margin-top: 6px;
+            margin-right: 20px;
+            height: 28px;
+            line-height: 28px;
+        }
+    }
+
+    .exercise-box {
+        display: flex;
+        height: calc(100% - 60px);
+
+        .point-tree {
+            width: calc(30% - 20px);
+            height: 800px;
+            margin: 0 10px;
+            background: #fff;
+
+            .el-tree-node__label {
+                font-size: 16px;
+            }
+        }
+    }
+
+    .create-body {
+        width: 70%;
+        margin: 0px auto;
+        height: 800px;
+        // display: flex;
+
+        .ivu-page {
+            display: flex;
+            flex-direction: row;
+            justify-content: center;
+            margin: 20px 0;
+        }
+
+        .filter-wrap {
+            margin-bottom: 15px;
+        }
+
+        .content-wrap {
+            position: relative;
+            width: 100%;
+            height: auto;
+            display: flex;
+            flex-direction: column;
+
+            .exercise-item {
+                position: relative;
+                width: 100%;
+                height: auto;
+                padding: 10px 20px 10px 20px;
+                margin-bottom: 10px;
+                font-size: 14px;
+                background: #fff;
+                border: 2px solid transparent;
+                cursor: pointer;
+
+                &:hover {
+                    .item-tools-bind {
+                        display: unset;
+                    }
+                }
+
+                .item-tools-t {
+                    height: 100%;
+                    padding: 0 15px;
+                    float: left;
+                    color: white;
+                    cursor: pointer;
+                    font-size: 14px;
+                }
+            }
+        }
+
+        .no-data-text {
+            width: 100%;
+            padding: 30px;
+            background: #fff;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            margin-top: 10px;
+            font-size: 16px;
+        }
+    }
+
+    .question-shopping-car {
+        position: fixed;
+        right: 50px;
+        bottom: 27px;
+        z-index: 1;
+
+        .ivu-poptip-popper {
+            width: 360px !important;
+        }
+    }
+
+    /*横向垂直水平居中*/
+    .flex-row-center {
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+    }
+}

File diff suppressed because it is too large
+ 1602 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/DfPage.vue


+ 357 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/TestPaper.less

@@ -0,0 +1,357 @@
+.paper-container {
+    position: relative;
+
+    .back-to-top {
+        position: fixed;
+        right: -98%;
+        bottom: 60px;
+        height: 48px;
+        width: 50px;
+        background: #595959;
+        z-index: 99999;
+        border-radius: 50%;
+        cursor: pointer;
+    }
+
+    .back-to-top:hover {
+        background: rgb(128, 128, 128);
+    }
+
+    .back-to-top .ivu-icon {
+        font-size: 26px;
+        color: white;
+    }
+
+
+    .paper-title {
+        font-size: 30px;
+        margin: 30px 0;
+        font-weight: bold;
+        vertical-align: middle;
+        text-align: center;
+        cursor: pointer;
+    }
+
+    .paper-subTitle {
+        font-size: 16px;
+        font-weight: bold;
+        margin: 15px;
+        text-align: center;
+        vertical-align: middle;
+    }
+
+    .paper-info {
+        font-size: 14px;
+        vertical-align: middle;
+    }
+
+    .paper-toolbar {
+        background: #fff;
+        padding: 20px;
+
+        .filter-title {
+            font-size: 14px;
+        }
+    }
+}
+
+.paper-main-wrap {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    margin-top: 10px;
+    padding: 20px 0;
+
+    .paper-analysis {
+        position: sticky;
+        top: 0;
+        background: White;
+        width: 20%;
+        padding: 20px 10px;
+
+        .ivu-radio-group {
+            margin: 15px 0 0 0;
+        }
+
+        .analysis-btns {
+            width: 100%;
+            .flex-row-center;
+            justify-content: space-around;
+
+            .ivu-btn {
+                width: 30%;
+                height: 40px;
+            }
+        }
+
+        .analysis-infos {
+            margin-top: 15px;
+            border-top: 1px dotted #cfcfcf;
+            padding: 0 20px 0 20px;
+
+            .analysis-title {
+                font-size: 18px;
+                margin: 30px 0 10px 0;
+                font-weight: bold;
+
+                &::before {
+                    content: "";
+                    height: 14px;
+                    width: 5px;
+                    margin-top: 2px;
+                    margin-right: 8px;
+                    border-radius: 5px;
+                    background: #01b4ef;
+                    display: inline-block;
+                    vertical-align: baseline;
+                }
+            }
+
+            .flex-row-center {
+                justify-content: space-between;
+            }
+
+            .analysis-info {
+                font-weight: bold;
+                color: #69baec;
+                margin: 0 5px;
+            }
+
+            .analysis-index-wrap {
+                display: flex;
+                flex-wrap: wrap;
+            }
+
+            .exercise-index-item {
+                width: 34px;
+                height: 34px;
+                line-height: 34px;
+                text-align: center;
+                margin-right: 10px;
+                margin-top: 10px;
+                background: #69BAEC;
+                color: #fff;
+                border-radius: 5px;
+                cursor: pointer;
+                .flex-row-center;
+
+                &:hover {
+                    background: #0097F4;
+                }
+            }
+        }
+    }
+}
+
+.paper-content {
+    width: 100%;
+    height: ~"calc(100% - 5px)";
+    min-height: 1552px;
+    background: #fff;
+    padding: 10px;
+    display: flex;
+    flex-direction: row;
+    // min-height: 73vh;
+
+    .paper-body {
+        width: 98%;
+        padding-left: 20px;
+
+        .paper-header {
+            margin-top: 20px;
+        }
+    }
+
+    .paper-base-info {
+        position: sticky;
+        top: 0;
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        background-color: #fff;
+        border-bottom: 1px dashed #cfcfcf;
+        z-index: 99;
+        padding: 10px 0 20px 0;
+
+        .paper-info-items {
+            display: flex;
+            flex-direction: column;
+            font-weight: bold;
+            font-size: 16px;
+            justify-content: space-between;
+
+            .ivu-rate {
+                margin-bottom: 5px;
+            }
+
+            &:first-child {
+                height: 55px;
+                margin-top: 5px;
+            }
+
+            &:not(:first-child) {
+                margin-left: 40px;
+                height: 50px;
+            }
+        }
+
+        // .base-info-item:not(:first-child) {
+        //     margin-left: 30px;
+        // }
+
+        .base-info-btn {
+            padding: 0 8px;
+            margin-bottom: 8px;
+        }
+
+        .base-info-btn:not(:last-child) {
+            margin-right: 10px;
+        }
+
+        .analysis-info {
+            font-weight: bold;
+            color: #69baec;
+            margin: 0 5px;
+        }
+    }
+
+    .paper-part {
+        padding: 15px;
+    }
+
+    .exercise-item {
+        position: relative;
+        font-size: 14px !important;
+        margin-top: 0 !important;
+        padding: 5px 10px !important;
+        box-sizing: border-box;
+        background: transparent;
+        border: 1px solid rgba(1, 1, 1, 0);
+
+        .item-concept {
+            margin-top: 10px;
+        }
+
+        .item-tools {
+            position: absolute;
+            right: -2px;
+            top: -30px;
+            width: auto;
+            height: 30px;
+            margin: 0;
+            padding: 0;
+            background: #01b4ef;
+            display: none;
+        }
+
+        .item-tools-t {
+            height: 100%;
+            padding: 0 15px;
+            float: left;
+            color: white;
+            cursor: pointer;
+
+            &:hover {
+                background: #4a5ae6;
+            }
+
+            .ivu-icon {
+                color: white;
+                font-size: 14px;
+                margin: 0 3px;
+            }
+        }
+
+        &:hover {
+            box-shadow: none !important;
+            border-color: #cfcfcf;
+        }
+    }
+
+    .paper-content-section {
+        font-size: 18px;
+        font-weight: bold;
+        margin-top: 20px;
+
+        span {
+            font-size: 14px;
+            font-weight: 500;
+            margin-left: 10px;
+        }
+    }
+}
+
+/*.paper-content .paper-part:hover {
+            background: #bfcdad45;
+        }*/
+.paper-content .item-answer,
+.paper-content .item-explain {
+    line-height: 26px;
+}
+
+.paper-tools {
+    width: 100%;
+    height: 60px;
+    margin-top: 10px;
+    border-top: 1px dashed #cfcfcf;
+    padding: 10px 0;
+
+    .paper-tools-title {
+        font-size: 14px;
+        font-weight: bold;
+        margin-right: 10px;
+    }
+
+    .ivu-checkbox-wrapper {
+        font-size: 14px;
+        margin-left: 10px;
+    }
+
+    .ivu-checkbox {
+        margin: 0 4px;
+        vertical-align: text-bottom;
+    }
+
+    .ivu-checkbox-checked {
+        .ivu-checkbox-inner {
+            background: #01b4ef;
+            border-color: #01b4ef;
+        }
+    }
+}
+
+.rules-modal {
+
+    .ivu-modal-body {
+
+        .rule-item {
+            display: flex;
+            flex-direction: column;
+
+            &-title {
+                margin: 10px 0;
+                font-weight: bold;
+            }
+        }
+
+    }
+
+}
+
+
+/*横向垂直水平居中*/
+.flex-row-center {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+}
+
+/*向垂直水平居中*/
+.flex-col-center {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}

+ 790 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/TestPaper.vue

@@ -0,0 +1,790 @@
+<template>
+  <div class="paper-container">
+    <div class="back-to-top flex-col-center" :title="$t('evaluation.backToTop')" v-if="isShowBackToTop" @click="handleBackToTop">
+      <Icon type="ios-arrow-up" />
+    </div>
+    <div class="paper-main-wrap">
+      <!-- 试卷内容 -->
+      <div class="paper-content">
+        <div class="paper-body">
+          <!-- 试卷基础信息 -->
+          <div class="paper-base-info" v-show="!isPreview &&  !isPreviewItems">
+            <div style="display: flex;">
+              <div class="paper-info-items">
+                <span class="base-info-item">{{$t('evaluation.paperList.paperScore')}}:<span class="analysis-info" style="cursor: pointer;">{{ paperInfo.score }}</span>{{$t('evaluation.paperList.score')}}</span>
+                <span class="base-info-item" @click="onShowScoreTable" style="cursor: pointer;">{{$t('evaluation.paperList.alreadyScore')}}:<span class="analysis-info">{{ allocatedScore || 0 }}</span>{{$t('evaluation.paperList.score')}}</span>
+              </div>
+              <div class="paper-info-items">
+                <span class="base-info-item">{{$t('evaluation.filter.diff')}}:
+                  <Rate allow-half v-model="paperInfo.item.length ? +paperDiff : 0" disabled />
+                </span>
+                <span class="base-info-item">{{$t('evaluation.paperList.itemCount')}}:<span class="analysis-info">{{ paperInfo.item ? paperInfo.item.length : 0 }}</span></span>
+              </div>
+            </div>
+            <div v-if="isShowBaseInfo && !isExamPaper && !isPreviewItems">
+              <Button class="base-info-btn" type="info" @click="showPaperAttachments" v-if="isHiteachPaper">{{ $t('evaluation.quickPaper.attachments') }}</Button>
+              <Button class="base-info-btn" type="info" @click="addNewModal = true" v-show="!isShowAnalysis">{{ $t('evaluation.index.addExercise') }}</Button>
+              <Button class="base-info-btn" type="info" @click="onHandleToggle" v-show="paperInfo.item.length && !isShowAnalysis">{{ isAllOpen ? $t('evaluation.index.collapseAll') : $t('evaluation.index.openAll')}}</Button>
+              <Button class="base-info-btn" type="info" @click="onSetScoreByType" v-show="paperInfo.item.length && !isShowAnalysis">{{$t('evaluation.exerciseList.typeScore')}}</Button>
+              <Button class="base-info-btn" type="info" @click="onViewModelChange" v-show="paperInfo.item.length && !isShowAnalysis">{{ `${ viewModel === 'type' ? this.$t('evaluation.paperList.sortByOrder') : this.$t('evaluation.paperList.sortByType') }` }}</Button>
+              <Button class="base-info-btn" type="info" @click="isShowAnalysis = !isShowAnalysis" v-show="paperInfo.item.length">{{ isShowAnalysis ? this.$t('evaluation.paperList.paperDetails') : this.$t('evaluation.paperList.paperAnalysis')}}</Button>
+              <Button class="base-info-btn" type="info" v-show="paperInfo.item.length && paperInfo.id" @click="onChooseOrderTemp">{{ $t('evaluation.orderTemp') }}</Button>
+              <Button class="base-info-btn" type="info" @click="downloadSheet" :loading="downLoading" v-show="isMarkModel && paperInfo.item.length && paperInfo.id && paper.sheetNo">{{ $t('evaluation.paperList.goAnswerSheet') }}<span v-if="paperInfo.mode">-{{ paperInfo.mode }}</span></Button>
+              <Button class="base-info-btn" type="info" @click="goAnswerSheet" v-show="isMarkModel && paperInfo.item.length && paperInfo.id">{{ paper.sheetNo ?  $t('evaluation.paperList.reCreateSheet') : $t('evaluation.paperList.createSheet') }}</Button>
+            </div>
+
+            <div v-if="isExamPaper && !isPreviewItems && paperInfo.item.length">
+              <Button class="base-info-btn" type="info" @click="showPaperAttachments" v-show="!isChangePaper" v-if="isHiteachPaper">{{ $t('evaluation.quickPaper.attachments') }}</Button>
+              <Button class="base-info-btn" type="info" @click="showChangePaper" v-show="isExamInfoPaper && !isChangePaper">{{ $t('evaluation.quickPaper.modifyPaper') }}</Button>
+              <!-- <Button class="base-info-btn" type="info" @click="onHandleToggle" v-show="!isShowAnalysis && !isChangePaper">{{ isAllOpen ? $t('evaluation.index.collapseAll') : $t('evaluation.index.openAll')}}</Button> -->
+              <Button class="base-info-btn" type="info" v-show="!isShowAnalysis && !isChangePaper" @click="() => $emit('backToPaperList')">
+                <Icon type="md-arrow-back" size="16" color="white"/>
+              </Button>
+              <Button class="base-info-btn" type="info" @click="onViewModelChange" v-show="!isShowAnalysis && !isChangePaper">{{ `${ viewModel === 'type' ? this.$t('evaluation.paperList.sortByOrder') : this.$t('evaluation.paperList.sortByType') }` }}</Button>
+              <Button class="base-info-btn" type="info" @click="isShowAnalysis = !isShowAnalysis" v-show="!isHideAnalysis && !isChangePaper">{{ isShowAnalysis ? this.$t('evaluation.paperList.paperDetails') : this.$t('evaluation.paperList.paperAnalysis')}}</Button>
+              <Button class="base-info-btn" type="info" @click="downloadSheet" :loading="downLoading" v-show="isMarkModel && paperInfo.id && paper.sheetNo && !hideSheet && !isSharePreview && !isChangePaper">{{ $t('evaluation.paperList.goAnswerSheet') }}<span v-if="paperInfo.mode">-{{ paperInfo.mode }}</span></Button>
+              <Button class="base-info-btn" type="info" @click="goAnswerSheet" v-show="isMarkModel && paperInfo.id && !hideSheet && !isSharePreview && !isChangePaper">{{ paper.sheetNo ?  $t('evaluation.paperList.reCreateSheet') : $t('evaluation.paperList.createSheet') }}</Button>
+              <Button class="base-info-btn" type="info" @click="exitPreview" v-show="paperInfo.id && isSharePreview && !isChangePaper">{{ $t('evaluation.index.backList') }}</Button>
+              <Button class="base-info-btn" type="success" @click="showChangePaper(1)" v-show="isChangePaper">{{ $t('cusMgt.save') }}</Button>
+              <Button class="base-info-btn" @click="showChangePaper" v-show="isChangePaper">{{ $t('evaluation.cancel') }}</Button>
+            </div>
+          </div>
+          <!-- 试卷头部信息 -->
+          <div class="paper-header flex-col-center">
+            <p class="paper-title">{{paperInfo.name}}</p>
+          </div>
+          <!-- 试卷分析部分 -->
+          <ExamPaperAnalysis :testPaper="paperInfo" v-if="isShowAnalysis" :hidePie="hidePie">
+          </ExamPaperAnalysis>
+          <!-- 题目类型及列表 -->
+          <BaseExerciseList :paper="paperInfo" @dataUpdate="onListUpdate" v-show="!isShowAnalysis" ref="exList" :isChangePaper="isChangePaper" :isShowTools="!isPreview" :canFix="canFix" :isExamPaper="isExamPaper || isPreviewItems" @toggleChange="onToggleChange" @scoreUpdate="scoreUpdate"></BaseExerciseList>
+        </div>
+        <!-- <BaseHiteachPaper v-if="isHiteachPaper" :paper="paperInfo"></BaseHiteachPaper> -->
+      </div>
+    </div>
+
+    <!-- 新建试题 -->
+    <Modal v-model="addNewModal" @on-visible-change="onAddNewModalChange" :mask-closable="false" :title="$t('evaluation.index.addExercise')" width="1000px" class="related-point-modal edit-exercise-modal">
+      <Tabs value="name1" name="newExerciseTab" @on-click="onTabChange" :animated="false">
+        <TabPane :label="$t('evaluation.autoCreate')" name="name1" tab="newExerciseTab">
+          <BaseCreateChild @addFinish="onAddNewFinish" ref="newEdit" v-if="addNewModal" :curPeriodIndex="paperInfo.paperPeriod" :curSubjectIndex="paperInfo.paperSubject">
+          </BaseCreateChild>
+        </TabPane>
+        <!-- <TabPane :label="$t('evaluation.quickCreate')" name="name2" tab="newExerciseTab" v-if="isDevEnv">
+          <BasePasteTool v-if="addNewModal" @addFinish="onAddNewFinish"></BasePasteTool>
+        </TabPane> -->
+        <TabPane :label="$t('evaluation.cpTip1')" name="name3" tab="newExerciseTab">
+          <ManualCreate ref="bankPicker" :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode" :isMarkModel="isMarkMode"></ManualCreate>
+        </TabPane>
+        <TabPane :label="$t('evaluation.cpTip2')" name="name4" tab="newExerciseTab">
+          <BasePaperItemPicker v-if="curModalTab === 'name4'" ref='paperPicker' :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode" :isMarkModel="isMarkMode"></BasePaperItemPicker>
+        </TabPane>
+        <TabPane :label="$t('evaluation.cpTip3')" name="name5" tab="newExerciseTab">
+          <ManualCreateNew ref="syllabusPicker" :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode" isAddModel></ManualCreateNew>
+        </TabPane>
+        <TabPane :label="$t('evaluation.quickPaper.title')" name="name6" tab="newExerciseTab" v-if="isMarkMode">
+          <BaseQuickPaper ref="quickPaperRef" :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode" :isMarkModel="isMarkMode" @addFinish="onAddNewFinish" @editLoadingChange="editLoadingChange"></BaseQuickPaper>
+        </TabPane>
+      </Tabs>
+      <div slot="footer">
+        <Button @click="addNewModal = false">{{$t('evaluation.cancel')}}</Button>
+        <Button type="success" :loading="editLoading" @click="doSaveEdit">{{$t('evaluation.confirm')}}</Button>
+      </div>
+    </Modal>
+
+    <!-- 选择题号模板 -->
+    <Modal v-model="orderTempModal" :title="$t('evaluation.orderTempChoos')" width="600px" class="related-point-modal order-temp-modal" @on-ok="onConfirmOrderTemp">
+      <div :class="['order-temp-item',curOrderTempIndex === 0 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(0)">
+        <p>{{ $t('evaluation.order1') }} <span class="order-temp-item-info">({{ $t('evaluation.orderTip1') }})</span> </p>
+      </div>
+      <div :class="['order-temp-item',curOrderTempIndex === 1 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(1)">
+        <p>{{ $t('evaluation.order2') }} <span class="order-temp-item-info">({{ $t('evaluation.orderTip2') }})</span></p>
+      </div>
+      <div :class="['order-temp-item',curOrderTempIndex === 2 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(2)">
+        <p>{{ $t('evaluation.order3') }} <span class="order-temp-item-info">({{ $t('evaluation.orderTip3') }})</span></p>
+      </div>
+      <!-- <div :class="['order-temp-item',curOrderTempIndex === 3 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(3)">
+				<p>中文大写 <span class="order-temp-item-info">(例:壹、贰、叁、肆、伍......)</span></p>
+			</div> -->
+    </Modal>
+
+  </div>
+</template>
+<script>
+import blobTool from '@/utils/blobTool.js'
+import ExamPaperAnalysis from '@/view/learnactivity/ExamPaperAnalysis.vue'
+import ManualCreate from '@/view/learnactivity/ManualCreate.vue'
+import ManualCreateNew from '@/view/learnactivity/ManualCreateNew.vue'
+
+export default {
+  components: {
+    ManualCreate,
+    ExamPaperAnalysis,
+    ManualCreateNew,
+  },
+  props: {
+    paper: {
+      type: Object,
+      default: null
+    },
+    /* 是否阅卷专用 */
+    isMarkMode: {
+      type: Boolean,
+      default: false
+    },
+    isShowTools: {
+      type: Boolean,
+      default: true
+    },
+    isPreview: {
+      type: Boolean,
+      default: false
+    },
+    isShowBaseInfo: {
+      type: Boolean,
+      default: true
+    },
+    isExamPaper: {
+      type: Boolean,
+      default: false
+    },
+    isExamInfoPaper: { //评测页面下的试卷,需调整答案、配分等
+      type: Boolean,
+      default: false
+    },
+    hidePie: {
+      type: Boolean,
+      default: false
+    },
+    isHideAnalysis: {
+      type: Boolean,
+      default: false
+    },
+    isPreviewItems: {
+      type: Boolean,
+      default: false
+    },
+    hideSheet: {
+      type: Boolean,
+      default: false
+    },
+    isSharePreview: {
+      type: Boolean,
+      default: false
+    },
+    canFix: {
+      type: Boolean,
+      default: false
+    },
+    gradeCode: {
+      type: Array,
+      default: () => []
+    },
+    subjectCode: {
+      type: String,
+      default: ''
+    },
+    periodCode: {
+      type: String,
+      default: ''
+    },
+    activityIndex: {
+        type: Number,
+        default: 0
+    },
+    subjectIndex: {
+        type: Number,
+        default: 0
+    },
+    refreshExam: {
+        type: Function,
+        require: true,
+        default: null
+    },
+  },
+  data() {
+    return {
+      isHiteachPaper: false,
+      isMarkModel:false,
+      editLoading: false,
+      hasModify: false,
+      curOrderTempIndex: 0,
+      orderTempModal: false,
+      isShowPasteTool: false,
+      addNewModal: false,
+      scoreTableModal: false,
+      isShowAnswerSheet: false,
+      isShowBackToTop: false,
+      isAllOpen: false,
+      isShowAnalysis: false,
+      isSetPaperName: false,
+      isSetScore: false,
+      isSetRules: false,
+      isShowSave: false,
+      exersicesList: [],
+      allocatedScore: 0,
+      list: [],
+      schoolInfo: {},
+      paperInfo: {
+        name: "",
+        score: 100,
+        answers: [],
+        multipleRule: 1,
+        item: [],
+        paperSubject: 0,
+        paperPeriod: 0
+      },
+      paperInfoOld: {},
+      exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
+      exersicesDiff: this.$GLOBAL.EXERCISE_DIFFS(),
+      exersicesField: this.$GLOBAL.EXERCISE_LEVELS(),
+      diffColors: ['#32CF74', '#E8BE15', '#F19300', '#EB5E00', '#D30000'],
+      filterType: '0',
+      filterDiff: '0',
+      filterSort: '0',
+      pageSize: 5,
+      pageNum: 1,
+      totalNum: 100,
+      allList: [],
+      isShowAnswer: false,
+      isShowPart: false,
+      isShowConcept: false,
+      isShowTitle: true,
+      isShowInfo: false,
+      downLoading: false,
+      analysisTableData: [],
+      viewModel: 'type',
+      paperDiff: 0,
+      curModalTab: '',
+      isChangePaper: false,
+    }
+  },
+  methods: {
+    getSelectedQuestion(val) {
+      // this.onAddNewFinish(val.item)
+      // this.paperInfo.item = this._.cloneDeep(val.item)
+
+    },
+    onAddNewModalChange(val) {
+      if (val) {
+        this.$refs.bankPicker && (this.$refs.bankPicker.shoppingQuestionList = [])
+        this.$refs.bankPicker && (this.$refs.bankPicker.$refs.exList.selectList = [])
+        this.$refs.paperPicker && (this.$refs.paperPicker.checkList = []) && this.$refs.paperPicker.changeCheckAll()
+        this.$refs.syllabusPicker && (this.$refs.syllabusPicker.shoppingQuestionList = [])
+        this.$refs.syllabusPicker && (this.$refs.syllabusPicker.$refs.syllabusTree.selectList = [])
+      }
+    },
+    /* 退出预览 */
+    exitPreview() {
+      this.$emit('exitPreview')
+    },
+    async showPaperAttachments(){
+      let paper = this.paperInfo
+      if(!paper.attachments.length){
+        this.$Message.warning(this.$t('homework.noAttachments'))
+        return
+      }
+      console.error(paper)
+      // let curScope = paper.examScope || paper.scope
+      let curScope = paper.scope // 个人评测使用校本试卷 examScope会是 private 所以改为直接读取 scope
+      let blobHost = curScope === 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+      let sasString = curScope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+      let fullImgArr = paper.attachments?.map(imgName => blobHost + paper.blob + '/' + imgName + sasString.sas)
+      this.$hevueImgPreview({
+					multiple: true,
+					keyboard: true,
+					nowImgIndex: 0,
+					imgList: fullImgArr
+				});
+    },
+    /* 手动保存 */
+    doSaveEdit() {
+      this.editLoading = true
+      if (this.curModalTab === 'name4') {
+        let newItems = this.$refs.paperPicker.checkList
+        this.onAddNewFinish(newItems)
+        this.$refs.paperPicker.changeCheckAll()
+      } else if (this.curModalTab === 'name3') {
+        let newItems = this.$refs.bankPicker.shoppingQuestionList
+        this.onAddNewFinish(newItems)
+      } else if (this.curModalTab === 'name5') {
+        let newItems = this.$refs.syllabusPicker.shoppingQuestionList
+        this.onAddNewFinish(newItems)
+      } else if (this.curModalTab === 'name6') {
+        this.$refs.quickPaperRef.onConfirmSave()
+      } else {
+        this.$refs.newEdit.getContent(this.$refs.newEdit.exersicesType)
+      }
+    },
+    editLoadingChange() {
+      this.editLoading = false
+    },
+    onTabChange(val) {
+      this.curModalTab = val
+      // if (val === 'name1') {
+      // 	this.isShowPasteTool = true
+      // }
+    },
+    onClickOrderTemp(val) {
+      this.curOrderTempIndex = val
+    },
+    onChooseOrderTemp() {
+      this.orderTempModal = true
+    },
+    onConfirmOrderTemp() {
+      this.$refs.exList.onConfirmOrderTemp(this.curOrderTempIndex)
+    },
+    /* 下载Blob答题卡 */
+    async downloadSheet() {
+      this.downLoading = true
+      let examId = this.paperInfo.examId
+      console.error(this.paperInfo)
+      let sheetMode = this.paperInfo.mode ? `(${this.paperInfo.mode})` : ``
+      let fileName = `${this.paperInfo.name}-${this.paperInfo.sheetNo}${sheetMode}.pdf`
+      // let pdfName = `${this.paperInfo.name}-${this.paperInfo.sheetNo}.pdf`
+      let path = examId ? `exam/${examId}/paper/${this.paperInfo.subjectId}/` : `paper/${this.paperInfo.name}/`
+      let curScope = examId ? this.paperInfo.examScope : this.paperInfo.scope
+      let cntr = curScope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+      let sasData = curScope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+      let blobHost = this.$evTools.getBlobHost()
+      let fullPath = blobHost + '/' + cntr + '/' + path + fileName + sasData.sas
+      const downloadRes = async () => {
+        let response = await fetch(fullPath); // 内容转变成blob地址
+        if (response.status !== 200) {
+          this.$Message.warning(this.$t('answerSheet.sheetTip5'))
+        } else {
+          let blob = await response.blob();  // 创建隐藏的可下载链接
+          let objectUrl = window.URL.createObjectURL(blob);
+          let a = document.createElement('a');
+          a.href = objectUrl;
+          a.download = fileName;
+          a.click()
+          a.remove();
+        }
+        this.downLoading = false
+      }
+      downloadRes();
+      console.log(fullPath);
+    },
+    goAnswerSheet() {
+      console.log(this.paperInfo)
+
+      if (this.paperInfo.itemSort === 1) {
+        this.$Message.warning(this.$t('answerSheet.noCreateTip'))
+        return
+      }
+      if (!this.isExamPaper) {
+        localStorage.setItem('c_edit_paper', JSON.stringify(this.paperInfo))
+      }
+      this.$store.commit('clearAllConfig')
+      this.$router.push({
+        name: 'answerSheet',
+        params: {
+          paper: this.paperInfo
+        }
+      })
+    },
+    onShowScoreTable() {
+      this.$refs.exList.scoreTableModal = true
+    },
+    /* 新增题目回调 */
+    onAddNewFinish(item) {
+      sessionStorage.setItem('isSave', 0)
+      if (Array.isArray(item)) {
+        if(this.isMarkMode && item.some(i => i.type === 'compose')){
+          this.$Message.warning(this.$t('evaluation.markMode.tip2'))
+        }
+        let noComposeItems = this.isMarkMode ? item.filter(o => o.type !== 'compose') : item
+        this.paperInfo.item.push(...noComposeItems)
+      } else {
+        if(this.isMarkMode && item.type === 'compose'){
+          this.$Message.warning(this.$t('evaluation.markMode.tip2'))
+        }else{
+          this.paperInfo.item.push(item)
+        }
+      }
+      this.addNewModal = false
+      this.editLoading = false
+    },
+    /**
+     * 标题切换
+     * @param e
+     */
+    titleChange(e) {
+      this.paperInfo.name = e.target.innerHTML
+    },
+
+    handleBackToTop() {
+      console.log(this)
+      this.$EventBus.$emit('onBackToTop')
+    },
+
+    onSetMarkingRules() {
+      this.isSetRules = false
+    },
+
+    onShowAnalysis() {
+      this.isShowAnalysis = true
+    },
+
+    /** 切换全部展开与折叠 */
+    onHandleToggle() {
+      this.$refs.exList.onHandleToggle(this.isAllOpen)
+      this.isAllOpen = !this.isAllOpen
+    },
+
+    /**
+     * exList的collapseList变化
+     * @param list
+     */
+    onToggleChange(list) {
+      this.isAllOpen = list.length !== 0
+    },
+
+    /** 返回试卷库 */
+    onBackToBank() {
+      this.$router.push({
+        name: 'testPaperList',
+        params: {
+          tabName: 'paper'
+        }
+      })
+    },
+
+    /* 清空试卷试题 */
+    onCleanPaper() {
+      this.paperInfo.item = []
+      this.$refs.exList.errorList = []
+      this.$refs.exList.noAnswerList = []
+      this.$EventBus.$emit('onPaperItemChange', [])
+    },
+
+    onSetScore() {
+      this.isSetScore = false
+      this.$refs.exList.exerciseList.map(item => item.score = 0)
+    },
+
+    /**
+     * 组件更新删除题目时的处理
+     * @param list
+     */
+    onListUpdate(list) {
+      this.paperInfo.item = list
+      this.hasModify = true
+      console.log(list);
+    },
+    /** 重新挑题 */
+    goToPickExercise() {
+      this.$router.push({
+        name: 'exercisesList',
+        params: {
+          paperInfo: this.paperInfo
+        }
+      })
+    },
+
+    /** 点击题型配分 */
+    onSetScoreByType() {
+      this.$refs.exList.onSetScoreByType()
+    },
+
+
+    /**
+     * 试卷的视图模式切换
+     * @param val
+     */
+    onViewModelChange(val) {
+      if(this.isMarkMode){
+        this.$Message.warning(this.$t('evaluation.markMode.tip3'))
+        return
+      }
+      this.viewModel = this.viewModel === 'type' ? 'list' : 'type'
+      this.$refs.exList.viewModel = this.viewModel
+      this.$refs.exList.collapseList = []
+      // this.$refs.exList.doRenderMathJax()
+      this.$emit('onViewModelChange', this.viewModel)
+    },
+
+    /**
+     * 计算试卷题目平均难度
+     * @param arr 试题集合
+     */
+    handleDiffCalc(arr) {
+      let levelArr = arr.map(i => i.level)
+      return this._.meanBy(levelArr).toFixed(1)
+    },
+
+    scoreUpdate(score) {
+      this.allocatedScore = this.paperInfo.score - score
+    },
+    async showChangePaper(type) {
+        if(type === 1) {
+            if(this.allocatedScore != this.paperInfo.score) {
+                this.$Message.warning(this.$t('evaluation.paperList.noCompleteScoreTip'))
+                return
+            }
+            // 重新保存试卷 && 调接口保存答案、配分
+            let params = {
+                id: this.paperInfo.examId, //活动ID
+                code: this.paperInfo.examScope === 'school' ? this.paperInfo.examSchool : this.paperInfo.creatorId, //个人ID或者学校编码
+                scode: this.paperInfo.examCode, //原本评测活动的完整code:'Exam-hbcn'
+                paperId: this.paperInfo.id, //试卷ID
+                subjectId: this.paperInfo.examSubject[this.subjectIndex]?.id, //科目ID
+                paperAns: [], //作答记录的完整数组
+                point: [], //完整配分的数组
+                kno: [], //知识点
+                multipleRule: this.paperInfo.multipleRule, //评分规则
+            }
+            this.paperInfo.item.forEach(item => {
+              if(item.type === 'compose') {
+                item.children.forEach(child => {
+                  if(child.type === 'single' || child.type === 'multiple' || child.type === 'judge') {
+                    params.paperAns.push(child.answer)
+                  } else {
+                    params.paperAns.push([])
+                  }
+                  params.point.push(child.score)
+                  params.kno.push(child.knowledge)
+                })
+              } else {
+                if(item.type === 'single' || item.type === 'multiple' || item.type === 'judge') {
+                  params.paperAns.push(item.answer)
+                } else {
+                  params.paperAns.push([])
+                }
+                params.point.push(item.score)
+                params.kno.push(item.knowledge)
+              }
+            })
+            this.$api.learnActivity.updateAns(params).then(async res => {
+              if(res.code === 200) {
+                const itemJsonFile = await this.$evTools.createBlobPaper(this.paperInfo, this.paperInfo.slides);
+                // let blobPaper = await this.$evTools.createBlobPaper(paperItem, res.slides)
+                // 首先保存新题目的JSON文件到Blob 然后返回URL链接
+                let paperFile = new File([JSON.stringify(itemJsonFile)], "index.json");
+                // 获取初始化Blob需要的数据
+                let sasData = this.paperInfo.scope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+                //初始化Blob
+                let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.paperInfo.scope)
+                try {
+                    let promiseArr = []
+                    let blobFile = null
+                    let blobUrl = this.paperInfo.blob.slice(1)
+                    // 放入试题json文件
+                    for (let i = 0; i < this.paperInfo.item.length; i++) {
+                        promiseArr.push(new Promise(async (r, j) => {
+                            try {
+                                let item = this.paperInfo.item[i]
+                                const itemJsonFile = await this.$evTools.createBlobItem(item)
+                                let file = new File([JSON.stringify(itemJsonFile)], item.id + ".json");
+                                containerClient.upload(file, {
+                                    path: blobUrl,
+                                    checkSize: false
+                                }).then(res => {
+                                    r(200)
+                                })
+                            } catch (e) {
+                                console.log(e)
+                            }
+                        }))
+                        if(this.paperInfo.item[i].type === 'compose') {
+                          this.paperInfo.item[i].children.forEach(child => {
+                            promiseArr.push(new Promise(async (r, j) => {
+                              try {
+                                  let item = child
+                                  const itemJsonFile = await this.$evTools.createBlobItem(item)
+                                  let file = new File([JSON.stringify(itemJsonFile)], item.id + ".json");
+                                  containerClient.upload(file, {
+                                      path: blobUrl,
+                                      checkSize: false
+                                  }).then(res => {
+                                      r(200)
+                                  })
+                              } catch (e) {
+                                  console.log(e)
+                              }
+                          }))
+                          })
+                        }
+                    }
+                    // 放入index.json文件
+                    promiseArr.push(new Promise(async (r, j) => {
+                        try {
+                            blobFile = await containerClient.upload(paperFile, {
+                                path: blobUrl,
+                                checkSize: false
+                            })
+                            console.log('上传到试卷目录下', blobFile)
+                            r(blobFile)
+                        } catch (e) {
+                            j(e)
+                            this.$Message.error(e.spaceError)
+                            this.isLoading = false
+                        }
+                    }))
+                    // 不存在多媒体
+
+                    // 进行试卷文件上传Blob 先上传所有题目 再上传index.json文件
+                    Promise.all(promiseArr).then(async result => {
+                        try {
+                            this.refreshExam(this.paperInfo, this.subjectIndex)
+                            if (blobFile.blob) {
+                                // 试卷保存更新后需要重新生成答题卡
+                                /* paperItem.blob = blobFile.blob.split('/index.json')[0]
+
+                                let params = {
+                                    paper: await this.$evTools.createCosmosPaper(paperItem),
+                                    option: this.isEditPaper ? 'update' : 'insert'
+                                }
+                                //  保存试卷到cosmos
+                                this.$api.learnActivity.SaveExamPaper(params).then(res => {
+                                    if (res.error == null) {
+                                        this.$Message.success(this.$t('evaluation.paperList.saveSuc'))
+                                        this.isLoading = false
+                                        this.$Spin.hide()
+                                        // this.savePeriodInfos()
+                                        // 清空已选试题
+                                        this.showQues = false
+                                        this.selectedArr = []
+                                        this.groupList = []
+                                        this.orderList = []
+                                        this.selShowList = []
+                                    } else {
+                                        this.$Message.error(this.$t('evaluation.paperList.saveFail'))
+                                        this.$Spin.hide()
+                                        this.isLoading = false
+                                    }
+                                },err => {
+                                    this.$Message.error(this.$t('evaluation.paperList.saveFail'))
+                                    this.isLoading = false
+                                    this.$Spin.hide()
+                                }) */
+                            } else {
+                                console.error(blobFile)
+                            }
+                        } catch (e) {
+                            console.log(e)
+                        }
+                        this.$Message.success(this.$t("evaluation.editSuc"))
+                        this.isChangePaper = !this.isChangePaper
+                    })
+                } catch (e) {
+                    console.log(e)
+                    this.$Message.error(this.$t('evaluation.paperList.saveItemsFailTip'))
+                    this.isLoading = false
+                    this.isChangePaper = !this.isChangePaper
+                }
+              } else {
+                this.$t('learnActivity.mgtScEv.updErr')
+              }
+            })
+        } else {
+            this.isChangePaper = !this.isChangePaper
+            if(!this.isChangePaper) {
+              this.paperInfo = JSON.parse(JSON.stringify(this.paperInfoOld))
+              this.paper.item = this.paperInfo.item
+              this.$forceUpdate()
+            }
+        }
+    },
+
+
+  },
+  mounted() {
+    // this.isShowSave = window.location.pathname === '/home/evaluation/testPaper'
+    this.$Spin.hide()
+    let paper = this.paper || this.$route.params.paper || JSON.parse(localStorage.getItem('c_edit_paper'))
+    if (!paper || !paper.item) return
+    this.paperInfoOld = JSON.parse(JSON.stringify(paper))
+    console.log('xxxx', paper)
+    this.$EventBus.$emit('getNewPaper', paper)
+    // localStorage.setItem('_paperInfo', JSON.stringify(paper))
+    this.viewModel = paper.itemSort === 1 ? 'list' : 'type'
+    this.paperInfo = paper // 自己页面的值
+    this.paperDiff = paper.item ? this.handleDiffCalc(paper.item) : 0
+    this.curOrderTempIndex = paper.orderTemp || 0
+  },
+  computed: {
+    isAuto() {
+      return this.paperInfo.markConfig ? this.paperInfo.markConfig.auto : false
+    },
+    ruleType() {
+      return this.paperInfo.markConfig ? this.paperInfo.markConfig.type : 0
+    },
+    isSchool() {
+      return this.$route.path.includes('school')
+    },
+    isDevEnv() {
+      return process.env.NODE_ENV === 'development'
+    }
+  },
+  watch: {
+    paper: {
+      handler(newValue, oldValue) {
+        if (newValue.item && newValue.item.length) {
+          this.$nextTick(() => {
+            console.error('new', newValue)
+            this.isHiteachPaper = newValue.qamode === 1 // 是否是快速组卷或者Hiteach的课中试卷
+            this.isMarkModel = newValue.markModel === 1 // 是否是阅卷专用
+            if (!this.isHiteachPaper) {
+              this.$EventBus.$emit('getNewPaper', newValue)
+            }
+            this.viewModel = newValue.itemSort === 1 ? 'list' : 'type'
+            console.log('TestPaper > paper', newValue)
+            if(newValue.createType === 'manual') {
+              this.$EventBus.$emit('onPaperItemChange', newValue.item)
+            }
+          })
+          this.curOrderTempIndex = newValue.orderTemp || 0
+          this.paperInfo = newValue
+        }
+        this.paperDiff = newValue.item ? this.handleDiffCalc(newValue.item) : 4
+      },
+      immediate: true,
+      deep: true
+    },
+    activityIndex: {
+      handler(n, o) {
+        if(this.isExamInfoPaper) this.isChangePaper = false
+        this.paperInfoOld = JSON.parse(JSON.stringify(this.paper))
+      }
+    },
+    subjectIndex: {
+      handler(n, o) {
+        if(this.isExamInfoPaper) this.isChangePaper = false
+        this.paperInfoOld = JSON.parse(JSON.stringify(this.paper))
+      }
+    }
+  }
+}
+</script>
+<style src="../index/TestPaper.less" lang="less" scoped>
+</style>
+<style lang="less">
+.complete-line {
+  padding: 0 45px;
+  border-bottom: 2px solid rgb(128, 128, 128);
+}
+
+.order-temp-modal {
+  .order-temp-item {
+    margin: 10px 0;
+    border: 1px solid #dddddd;
+    padding: 20px 15px;
+    border-radius: 5px;
+    font-size: 16px;
+    font-weight: bold;
+    cursor: pointer;
+    &-info {
+      font-size: 14px;
+      font-weight: 400;
+      margin-left: 15px;
+    }
+
+    &-active {
+      background-color: #72cbff;
+      color: #fff;
+    }
+  }
+}
+</style>

+ 26 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/WxtPage.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="xkw-iframe-container">
+    <iframe id="xkwIframe" :src="iframeSrc" frameborder="0" width="100%" height="100%"></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      iframeSrc: ""
+    }
+  },
+  created() {
+    this.iframeSrc = this.$route.params.iframeSrc
+  }
+}
+</script>
+
+<style>
+.xkw-iframe-container {
+  width: 100%;
+  height: calc(100vh - 60px);
+  overflow: hidden;
+}
+</style>

+ 26 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/WxtPagePreview.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="xkw-iframe-container">
+    <iframe id="xkwIframe" :src="iframeSrc" frameborder="0" width="100%" height="100%"></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      iframeSrc: ""
+    }
+  },
+  created() {
+    this.iframeSrc = 'https://inwxt.zxxk.com' + this.$route.query.wxtpath
+  }
+}
+</script>
+
+<style>
+.xkw-iframe-container {
+  width: 100%;
+  height: calc(100vh - 60px);
+  overflow: hidden;
+}
+</style>

+ 26 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/XkwPage.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="xkw-iframe-container">
+    <iframe id="xkwIframe" :src="iframeSrc" frameborder="0" width="100%" height="100%"></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      iframeSrc: ""
+    }
+  },
+  created() {
+    this.iframeSrc = this.$route.params.iframeSrc
+  }
+}
+</script>
+
+<style>
+.xkw-iframe-container {
+  width: 100%;
+  height: calc(100vh - 60px);
+  overflow: hidden;
+}
+</style>

+ 782 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/htTestPaper.vue

@@ -0,0 +1,782 @@
+<template>
+  <div class="paper-container">
+    <div class="back-to-top flex-col-center" :title="$t('evaluation.backToTop')" v-if="isShowBackToTop" @click="handleBackToTop">
+      <Icon type="ios-arrow-up" />
+    </div>
+    <div class="paper-main-wrap">
+      <!-- 试卷内容 -->
+      <div class="paper-content">
+        <div class="paper-body">
+          <!-- 试卷基础信息 -->
+          <div class="paper-base-info" v-show="!isPreview &&  !isPreviewItems">
+            <div style="display: flex;">
+              <div class="paper-info-items">
+                <span class="base-info-item">{{$t('evaluation.paperList.paperScore')}}:<span class="analysis-info" style="cursor: pointer;">{{ paperInfo.score }}</span>{{$t('evaluation.paperList.score')}}</span>
+                <span class="base-info-item" @click="onShowScoreTable" style="cursor: pointer;">{{$t('evaluation.paperList.alreadyScore')}}:<span class="analysis-info">{{ allocatedScore || 0 }}</span>{{$t('evaluation.paperList.score')}}</span>
+              </div>
+              <div class="paper-info-items">
+                <span class="base-info-item">{{$t('evaluation.filter.diff')}}:
+                  <Rate allow-half v-model="paperInfo.item.length ? +paperDiff : 0" disabled />
+                </span>
+                <span class="base-info-item">{{$t('evaluation.paperList.itemCount')}}:<span class="analysis-info">{{ paperInfo.item ? paperInfo.item.length : 0 }}</span></span>
+              </div>
+            </div>
+            <div v-if="isShowBaseInfo && !isExamPaper && !isPreviewItems">
+              <Button class="base-info-btn" type="info" @click="showPaperAttachments" v-if="isHiteachPaper">{{ $t('evaluation.quickPaper.attachments') }}</Button>
+              <Button class="base-info-btn" type="info" @click="addNewModal = true" v-show="!isShowAnalysis">{{ $t('evaluation.index.addExercise') }}</Button>
+              <Button class="base-info-btn" type="info" @click="onHandleToggle" v-show="paperInfo.item.length && !isShowAnalysis">{{ isAllOpen ? $t('evaluation.index.collapseAll') : $t('evaluation.index.openAll')}}</Button>
+              <Button class="base-info-btn" type="info" @click="onSetScoreByType" v-show="paperInfo.item.length && !isShowAnalysis">{{$t('evaluation.exerciseList.typeScore')}}</Button>
+              <Button class="base-info-btn" type="info" @click="onViewModelChange" v-show="paperInfo.item.length && !isShowAnalysis">{{ `${ viewModel === 'type' ? this.$t('evaluation.paperList.sortByOrder') : this.$t('evaluation.paperList.sortByType') }` }}</Button>
+              <Button class="base-info-btn" type="info" @click="isShowAnalysis = !isShowAnalysis" v-show="paperInfo.item.length">{{ isShowAnalysis ? this.$t('evaluation.paperList.paperDetails') : this.$t('evaluation.paperList.paperAnalysis')}}</Button>
+              <Button class="base-info-btn" type="info" v-show="paperInfo.item.length && paperInfo.id" @click="onChooseOrderTemp">{{ $t('evaluation.orderTemp') }}</Button>
+              <Button class="base-info-btn" type="info" @click="downloadSheet" :loading="downLoading" v-show="isMarkModel && paperInfo.item.length && paperInfo.id && paper.sheetNo">{{ $t('evaluation.paperList.goAnswerSheet') }}<span v-if="paperInfo.mode">-{{ paperInfo.mode }}</span></Button>
+              <Button class="base-info-btn" type="info" @click="goAnswerSheet" v-show="isMarkModel && paperInfo.item.length && paperInfo.id">{{ paper.sheetNo ?  $t('evaluation.paperList.reCreateSheet') : $t('evaluation.paperList.createSheet') }}</Button>
+            </div>
+            <div v-if="isExamPaper && !isPreviewItems && paperInfo.item.length">
+              <Button class="base-info-btn" type="info" @click="showPaperAttachments" v-show="!isChangePaper" v-if="isHiteachPaper">{{ $t('evaluation.quickPaper.attachments') }}</Button>
+              <!-- <Button class="base-info-btn" type="info" @click="showChangePaper" v-show="isExamInfoPaper && !isChangePaper">{{ $t('evaluation.quickPaper.modifyPaper') }}</Button> -->
+              <Button class="base-info-btn" type="info" @click="onHandleToggle" v-show="!isShowAnalysis && !isChangePaper">{{ isAllOpen ? $t('evaluation.index.collapseAll') : $t('evaluation.index.openAll')}}</Button>
+              <Button class="base-info-btn" type="info" @click="onViewModelChange" v-show="!isShowAnalysis && !isChangePaper">{{ `${ viewModel === 'type' ? this.$t('evaluation.paperList.sortByOrder') : this.$t('evaluation.paperList.sortByType') }` }}</Button>
+              <Button class="base-info-btn" type="info" @click="isShowAnalysis = !isShowAnalysis" v-show="!isHideAnalysis && !isChangePaper">{{ isShowAnalysis ? this.$t('evaluation.paperList.paperDetails') : this.$t('evaluation.paperList.paperAnalysis')}}</Button>
+              <Button class="base-info-btn" type="info" @click="downloadSheet" :loading="downLoading" v-show="isMarkModel && paperInfo.id && paper.sheetNo && !hideSheet && !isSharePreview && !isChangePaper">{{ $t('evaluation.paperList.goAnswerSheet') }}<span v-if="paperInfo.mode">-{{ paperInfo.mode }}</span></Button>
+              <Button class="base-info-btn" type="info" @click="goAnswerSheet" v-show="isMarkModel && paperInfo.id && !hideSheet && !isSharePreview && !isChangePaper">{{ paper.sheetNo ?  $t('evaluation.paperList.reCreateSheet') : $t('evaluation.paperList.createSheet') }}</Button>
+              <Button class="base-info-btn" type="info" @click="exitPreview" v-show="paperInfo.id && isSharePreview && !isChangePaper">{{ $t('evaluation.index.backList') }}</Button>
+              <Button class="base-info-btn" type="success" @click="showChangePaper(1)" v-show="isChangePaper">{{ $t('cusMgt.save') }}</Button>
+              <Button class="base-info-btn" @click="showChangePaper" v-show="isChangePaper">{{ $t('evaluation.cancel') }}</Button>
+            </div>
+          </div>
+          <!-- 试卷头部信息 -->
+          <div class="paper-header flex-col-center">
+            <p class="paper-title">{{paperInfo.name}}</p>
+          </div>
+          <!-- 试卷分析部分 -->
+          <ExamPaperAnalysis :testPaper="paperInfo" v-if="isShowAnalysis" :hidePie="hidePie">
+          </ExamPaperAnalysis>
+          <!-- 题目类型及列表 -->
+          <BaseExerciseList :paper="paperInfo" @dataUpdate="onListUpdate" v-show="!isShowAnalysis" ref="exList" :isChangePaper="isChangePaper" :isShowTools="!isPreview" :canFix="canFix" :isExamPaper="isExamPaper || isPreviewItems" @toggleChange="onToggleChange" @scoreUpdate="scoreUpdate"></BaseExerciseList>
+        </div>
+        <!-- <BaseHiteachPaper v-if="isHiteachPaper" :paper="paperInfo"></BaseHiteachPaper> -->
+      </div>
+    </div>
+
+    <!-- 新建试题 -->
+    <Modal v-model="addNewModal" @on-visible-change="onAddNewModalChange" :mask-closable="false" :title="$t('evaluation.index.addExercise')" width="1000px" class="related-point-modal edit-exercise-modal">
+      <Tabs value="name1" name="newExerciseTab" @on-click="onTabChange" :animated="false">
+        <TabPane :label="$t('evaluation.autoCreate')" name="name1" tab="newExerciseTab">
+          <BaseCreateChild @addFinish="onAddNewFinish" ref="newEdit" v-if="addNewModal" :curPeriodIndex="paperInfo.paperPeriod" :curSubjectIndex="paperInfo.paperSubject">
+          </BaseCreateChild>
+        </TabPane>
+        <!-- <TabPane :label="$t('evaluation.quickCreate')" name="name2" tab="newExerciseTab" v-if="isDevEnv">
+          <BasePasteTool v-if="addNewModal" @addFinish="onAddNewFinish"></BasePasteTool>
+        </TabPane> -->
+        <TabPane :label="$t('evaluation.cpTip1')" name="name3" tab="newExerciseTab">
+          <ManualCreate ref="bankPicker" :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode" :isMarkModel="isMarkMode"></ManualCreate>
+        </TabPane>
+        <TabPane :label="$t('evaluation.cpTip2')" name="name4" tab="newExerciseTab">
+          <BasePaperItemPicker v-if="curModalTab === 'name4'" ref='paperPicker' :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode"></BasePaperItemPicker>
+        </TabPane>
+      </Tabs>
+      <div slot="footer">
+        <Button @click="addNewModal = false">{{$t('evaluation.cancel')}}</Button>
+        <Button type="success" :loading="editLoading" @click="doSaveEdit">{{$t('evaluation.confirm')}}</Button>
+      </div>
+    </Modal>
+
+    <!-- 选择题号模板 -->
+    <Modal v-model="orderTempModal" :title="$t('evaluation.orderTempChoos')" width="600px" class="related-point-modal order-temp-modal" @on-ok="onConfirmOrderTemp">
+      <div :class="['order-temp-item',curOrderTempIndex === 0 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(0)">
+        <p>{{ $t('evaluation.order1') }} <span class="order-temp-item-info">({{ $t('evaluation.orderTip1') }})</span> </p>
+      </div>
+      <div :class="['order-temp-item',curOrderTempIndex === 1 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(1)">
+        <p>{{ $t('evaluation.order2') }} <span class="order-temp-item-info">({{ $t('evaluation.orderTip2') }})</span></p>
+      </div>
+      <div :class="['order-temp-item',curOrderTempIndex === 2 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(2)">
+        <p>{{ $t('evaluation.order3') }} <span class="order-temp-item-info">({{ $t('evaluation.orderTip3') }})</span></p>
+      </div>
+      <!-- <div :class="['order-temp-item',curOrderTempIndex === 3 ? 'order-temp-item-active' : '']" @click="onClickOrderTemp(3)">
+				<p>中文大写 <span class="order-temp-item-info">(例:壹、贰、叁、肆、伍......)</span></p>
+			</div> -->
+    </Modal>
+  </div>
+</template>
+<script>
+import blobTool from '@/utils/blobTool.js'
+import ExamPaperAnalysis from '@/view/learnactivity/ExamPaperAnalysis.vue'
+import ManualCreate from '@/view/learnactivity/ManualCreate.vue'
+
+export default {
+  components: {
+    ManualCreate,
+    ExamPaperAnalysis,
+  },
+  props: {    
+    jointGroupId: {
+      type: String,
+      default: ''
+    },
+    jointScheduleId: {
+      type: String,
+      default: ''
+    },
+    paper: {
+      type: Object,
+      default: null
+    },
+    /* 是否阅卷专用 */
+    isMarkMode: {
+      type: Boolean,
+      default: false
+    },
+    isShowTools: {
+      type: Boolean,
+      default: true
+    },
+    isPreview: {
+      type: Boolean,
+      default: false
+    },
+    isShowBaseInfo: {
+      type: Boolean,
+      default: true
+    },
+    isExamPaper: {
+      type: Boolean,
+      default: false
+    },
+    isExamInfoPaper: { //评测页面下的试卷,需调整答案、配分等
+      type: Boolean,
+      default: false
+    },
+    hidePie: {
+      type: Boolean,
+      default: false
+    },
+    isHideAnalysis: {
+      type: Boolean,
+      default: false
+    },
+    isPreviewItems: {
+      type: Boolean,
+      default: false
+    },
+    hideSheet: {
+      type: Boolean,
+      default: false
+    },
+    isSharePreview: {
+      type: Boolean,
+      default: false
+    },
+    canFix: {
+      type: Boolean,
+      default: false
+    },
+    gradeCode: {
+      type: Array,
+      default: () => []
+    },
+    subjectCode: {
+      type: String,
+      default: ''
+    },
+    periodCode: {
+      type: String,
+      default: ''
+    },
+    activityIndex: {
+        type: Number,
+        default: 0
+    },
+    subjectIndex: {
+        type: Number,
+        default: 0
+    },
+    refreshExam: {
+        type: Function,
+        require: true,
+        default: null
+    },
+  },
+  data() {
+    return {
+      isHiteachPaper: false,
+      isMarkModel:false,
+      editLoading: false,
+      hasModify: false,
+      curOrderTempIndex: 0,
+      orderTempModal: false,
+      isShowPasteTool: false,
+      addNewModal: false,
+      scoreTableModal: false,
+      isShowAnswerSheet: false,
+      isShowBackToTop: false,
+      isAllOpen: false,
+      isShowAnalysis: false,
+      isSetPaperName: false,
+      isSetScore: false,
+      isSetRules: false,
+      isShowSave: false,
+      exersicesList: [],
+      allocatedScore: 0,
+      list: [],
+      schoolInfo: {},
+      paperInfo: {
+        name: "",
+        score: 100,
+        answers: [],
+        multipleRule: 1,
+        item: [],
+        paperSubject: 0,
+        paperPeriod: 0
+      },
+      paperInfoOld: {},
+      exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
+      exersicesDiff: this.$GLOBAL.EXERCISE_DIFFS(),
+      exersicesField: this.$GLOBAL.EXERCISE_LEVELS(),
+      diffColors: ['#32CF74', '#E8BE15', '#F19300', '#EB5E00', '#D30000'],
+      filterType: '0',
+      filterDiff: '0',
+      filterSort: '0',
+      pageSize: 5,
+      pageNum: 1,
+      totalNum: 100,
+      allList: [],
+      isShowAnswer: false,
+      isShowPart: false,
+      isShowConcept: false,
+      isShowTitle: true,
+      isShowInfo: false,
+      downLoading: false,
+      analysisTableData: [],
+      viewModel: 'type',
+      paperDiff: 0,
+      curModalTab: '',
+      isChangePaper: false,
+    }
+  },
+  methods: {
+    getSelectedQuestion(val) {
+      // this.onAddNewFinish(val.item)
+      // this.paperInfo.item = this._.cloneDeep(val.item)
+
+    },
+    onAddNewModalChange(val) {
+      if (val) {
+        this.$refs.bankPicker && (this.$refs.bankPicker.shoppingQuestionList = [])
+        this.$refs.bankPicker && (this.$refs.bankPicker.$refs.exList.selectList = [])
+        this.$refs.paperPicker && (this.$refs.paperPicker.checkList = [])
+      }
+    },
+    /* 退出预览 */
+    exitPreview() {
+      this.$emit('exitPreview')
+    },
+    async showPaperAttachments(){
+      let paper = this.paperInfo
+      if(!paper.attachments.length){
+        this.$Message.warning(this.$t('homework.noAttachments'))
+        return
+      }
+      console.error(paper)
+      // let curScope = paper.examScope || paper.scope
+      let curScope = paper.scope // 个人评测使用校本试卷 examScope会是 private 所以改为直接读取 scope
+      let blobHost = curScope === 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+      let sasString = curScope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+      let fullImgArr = paper.attachments?.map(imgName => blobHost + paper.blob + '/' + imgName + sasString.sas)
+      this.$hevueImgPreview({
+					multiple: true,
+					keyboard: true,
+					nowImgIndex: 0,
+					imgList: fullImgArr
+				});
+    },
+    /* 手动保存 */
+    doSaveEdit() {
+      this.editLoading = true
+      if (this.curModalTab === 'name4') {
+        let newItems = this.$refs.paperPicker.checkList
+        this.onAddNewFinish(newItems)
+      } else if (this.curModalTab === 'name3') {
+        let newItems = this.$refs.bankPicker.shoppingQuestionList
+        this.onAddNewFinish(newItems)
+      } else {
+        this.$refs.newEdit.getContent(this.$refs.newEdit.exersicesType)
+      }
+    },
+    onTabChange(val) {
+      this.curModalTab = val
+      // if (val === 'name1') {
+      // 	this.isShowPasteTool = true
+      // }
+    },
+    onClickOrderTemp(val) {
+      this.curOrderTempIndex = val
+    },
+    onChooseOrderTemp() {
+      this.orderTempModal = true
+    },
+    onConfirmOrderTemp() {
+      this.$refs.exList.onConfirmOrderTemp(this.curOrderTempIndex)
+    },
+    /* 下载Blob答题卡 */
+    async downloadSheet() {
+      this.downLoading = true
+      let examId = this.paperInfo.examId
+      console.error(this.paperInfo)
+      let sheetMode = this.paperInfo.mode ? `(${this.paperInfo.mode})` : ``
+      let fileName = `${this.paperInfo.name}-${this.paperInfo.sheetNo}${sheetMode}.pdf`
+      // let pdfName = `${this.paperInfo.name}-${this.paperInfo.sheetNo}.pdf`
+      let path = examId ? `exam/${examId}/paper/${this.paperInfo.subjectId}/` : `paper/${this.paperInfo.name}/`
+      let curScope = examId ? this.paperInfo.examScope : this.paperInfo.scope
+      let cntr = curScope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+      let sasData = curScope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+      let blobHost = this.$evTools.getBlobHost()
+      let fullPath = blobHost + '/' + cntr + '/' + path + fileName + sasData.sas
+      const downloadRes = async () => {
+        let response = await fetch(fullPath); // 内容转变成blob地址
+        if (response.status !== 200) {
+          this.$Message.warning(this.$t('answerSheet.sheetTip5'))
+        } else {
+          let blob = await response.blob();  // 创建隐藏的可下载链接
+          let objectUrl = window.URL.createObjectURL(blob);
+          let a = document.createElement('a');
+          a.href = objectUrl;
+          a.download = fileName;
+          a.click()
+          a.remove();
+        }
+        this.downLoading = false
+      }
+      downloadRes();
+      console.log(fullPath);
+    },
+    goAnswerSheet() {
+      console.log(this.paperInfo)
+
+      if (this.paperInfo.itemSort === 1) {
+        this.$Message.warning(this.$t('answerSheet.noCreateTip'))
+        return
+      }
+      if (!this.isExamPaper) {
+        localStorage.setItem('c_edit_paper', JSON.stringify(this.paperInfo))
+      }
+      this.$store.commit('clearAllConfig')
+      this.$router.push({
+        name: 'answerSheet',
+        params: {
+          paper: this.paperInfo
+        }
+      })
+    },
+    onShowScoreTable() {
+      this.$refs.exList.scoreTableModal = true
+    },
+    /* 新增题目回调 */
+    onAddNewFinish(item) {
+      sessionStorage.setItem('isSave', 0)
+      if (Array.isArray(item)) {
+        if(this.isMarkMode && item.some(i => i.type === 'compose')){
+          this.$Message.warning(this.$t('evaluation.markMode.tip2'))
+        }
+        let noComposeItems = item.filter(o => o.type !== 'compose')
+        this.paperInfo.item.push(...noComposeItems)
+      } else {
+        if(this.isMarkMode && item.type === 'compose'){
+          this.$Message.warning(this.$t('evaluation.markMode.tip2'))
+        }else{
+          this.paperInfo.item.push(item)
+        }
+      }
+      this.addNewModal = false
+      this.editLoading = false
+    },
+    /**
+     * 标题切换
+     * @param e
+     */
+    titleChange(e) {
+      this.paperInfo.name = e.target.innerHTML
+    },
+
+    handleBackToTop() {
+      console.log(this)
+      this.$EventBus.$emit('onBackToTop')
+    },
+
+    onSetMarkingRules() {
+      this.isSetRules = false
+    },
+
+    onShowAnalysis() {
+      this.isShowAnalysis = true
+    },
+
+    /** 切换全部展开与折叠 */
+    onHandleToggle() {
+      this.$refs.exList.onHandleToggle(this.isAllOpen)
+      this.isAllOpen = !this.isAllOpen
+    },
+
+    /**
+     * exList的collapseList变化
+     * @param list
+     */
+    onToggleChange(list) {
+      this.isAllOpen = list.length !== 0
+    },
+
+    /** 返回试卷库 */
+    onBackToBank() {
+      this.$router.push({
+        name: 'testPaperList',
+        params: {
+          tabName: 'paper'
+        }
+      })
+    },
+
+    /* 清空试卷试题 */
+    onCleanPaper() {
+      this.paperInfo.item = []
+      this.$refs.exList.errorList = []
+      this.$refs.exList.noAnswerList = []
+      this.$EventBus.$emit('onPaperItemChange', [])
+    },
+
+    onSetScore() {
+      this.isSetScore = false
+      this.$refs.exList.exerciseList.map(item => item.score = 0)
+    },
+
+    /**
+     * 组件更新删除题目时的处理
+     * @param list
+     */
+    onListUpdate(list) {
+      this.paperInfo.item = list
+      this.hasModify = true
+      console.log(list);
+    },
+    /** 重新挑题 */
+    goToPickExercise() {
+      this.$router.push({
+        name: 'exercisesList',
+        params: {
+          paperInfo: this.paperInfo
+        }
+      })
+    },
+
+    /** 点击题型配分 */
+    onSetScoreByType() {
+      this.$refs.exList.onSetScoreByType()
+    },
+
+
+    /**
+     * 试卷的视图模式切换
+     * @param val
+     */
+    onViewModelChange(val) {
+      if(this.isMarkMode){
+        this.$Message.warning(this.$t('evaluation.markMode.tip3'))
+        return
+      }
+      this.viewModel = this.viewModel === 'type' ? 'list' : 'type'
+      this.$refs.exList.viewModel = this.viewModel
+      this.$refs.exList.collapseList = []
+      // this.$refs.exList.doRenderMathJax()
+      this.$emit('onViewModelChange', this.viewModel)
+    },
+
+    /**
+     * 计算试卷题目平均难度
+     * @param arr 试题集合
+     */
+    handleDiffCalc(arr) {
+      let levelArr = arr.map(i => i.level)
+      return this._.meanBy(levelArr).toFixed(1)
+    },
+
+    scoreUpdate(score) {
+      this.allocatedScore = this.paperInfo.score - score
+    },
+    async showChangePaper(type) {
+        if(type === 1) {// 修改試卷 > 儲存按鈕
+            if(this.allocatedScore != this.paperInfo.score) {
+                this.$Message.warning(this.$t('evaluation.paperList.noCompleteScoreTip'))
+                return
+            }
+            
+            // 活動板修改試卷 先暫停
+            // let params1 = {
+            //     jointEventId: this.$route.params.data.id,
+            //     jointGroupId: this.jointGroupId,
+            //     jointScheduleId: this.jointScheduleId,
+            //     jointExamId: this.paperInfo.examId,                                            
+            //     papers: this.paperInfo
+            // }
+            // 重新保存试卷 && 调接口保存答案、配分
+            let params = {
+                id: this.paperInfo.examId, //活动ID
+                code: this.paperInfo.examScope === 'school' ? this.paperInfo.examSchool : this.paperInfo.creatorId, //个人ID或者学校编码
+                scode: this.paperInfo.examCode, //原本评测活动的完整code:'Exam-hbcn'
+                paperId: this.paperInfo.id, //试卷ID
+                //subjectId: this.paperInfo.examSubject[this.subjectIndex]?.id, //科目ID
+                paperAns: [], //作答记录的完整数组
+                point: [], //完整配分的数组
+                kno: [], //知识点
+                multipleRule: this.paperInfo.multipleRule, //评分规则
+            }
+            this.paperInfo.item.forEach(item => {
+              if(item.type === 'compose') {
+                item.children.forEach(child => {
+                  if(child.type === 'single' || child.type === 'multiple' || child.type === 'judge') {
+                    params.paperAns.push(child.answer)
+                  } else {
+                    params.paperAns.push([])
+                  }
+                  params.point.push(child.score)
+                  params.kno.push(child.knowledge)
+                })
+              } else {
+                if(item.type === 'single' || item.type === 'multiple' || item.type === 'judge') {
+                  params.paperAns.push(item.answer)
+                } else {
+                  params.paperAns.push([])
+                }
+                params.point.push(item.score)
+                params.kno.push(item.knowledge)
+              }
+            })            
+            //this.paperInfo.slides 分數是未更新前的   this.paperInfo.item 分數是更新後的
+            const itemJsonFile111 = await this.$evTools.createBlobPaper(this.paperInfo, this.paperInfo.slides);
+           
+            this.$api.htcommunity.jointExamUpsert(params).then(async res => {
+              if(res.code === 200) {
+                const itemJsonFile = await this.$evTools.createBlobPaper(this.paperInfo, this.paperInfo.slides);
+                // let blobPaper = await this.$evTools.createBlobPaper(paperItem, res.slides)
+                // 首先保存新题目的JSON文件到Blob 然后返回URL链接
+                let paperFile = new File([JSON.stringify(itemJsonFile)], "index.json");
+                // 获取初始化Blob需要的数据
+                let sasData = this.paperInfo.scope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+                //初始化Blob
+                let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.paperInfo.scope)
+                try {
+                    let promiseArr = []
+                    let blobFile = null
+                    let blobUrl = this.paperInfo.blob.slice(1)
+                    // 放入试题json文件
+                    for (let i = 0; i < this.paperInfo.item.length; i++) {
+                        promiseArr.push(new Promise(async (r, j) => {
+                            try {
+                                let item = this.paperInfo.item[i]
+                                const itemJsonFile = await this.$evTools.createBlobItem(item)
+                                let file = new File([JSON.stringify(itemJsonFile)], item.id + ".json");
+                                containerClient.upload(file, {
+                                    path: blobUrl,
+                                    checkSize: false
+                                }).then(res => {
+                                    r(200)
+                                })
+                            } catch (e) {
+                                console.log(e)
+                            }
+                        }))
+                        if(this.paperInfo.item[i].type === 'compose') {
+                          this.paperInfo.item[i].children.forEach(child => {
+                            promiseArr.push(new Promise(async (r, j) => {
+                              try {
+                                  let item = child
+                                  const itemJsonFile = await this.$evTools.createBlobItem(item)
+                                  let file = new File([JSON.stringify(itemJsonFile)], item.id + ".json");
+                                  containerClient.upload(file, {
+                                      path: blobUrl,
+                                      checkSize: false
+                                  }).then(res => {
+                                      r(200)
+                                  })
+                              } catch (e) {
+                                  console.log(e)
+                              }
+                          }))
+                          })
+                        }
+                    }
+                    // 放入index.json文件
+                    promiseArr.push(new Promise(async (r, j) => {
+                        try {
+                            blobFile = await containerClient.upload(paperFile, {
+                                path: blobUrl,
+                                checkSize: false
+                            })
+                            console.log('上传到试卷目录下', blobFile)
+                            r(blobFile)
+                        } catch (e) {
+                            j(e)
+                            this.$Message.error(e.spaceError)
+                            this.isLoading = false
+                        }
+                    }))
+                    // 不存在多媒体
+
+                    // 进行试卷文件上传Blob 先上传所有题目 再上传index.json文件
+                    Promise.all(promiseArr).then(async result => {
+                        try {
+                            this.refreshExam(this.paperInfo, this.subjectIndex)
+                            if (blobFile.blob) {
+                                // 试卷保存更新后需要重新生成答题卡
+                                /* paperItem.blob = blobFile.blob.split('/index.json')[0]
+
+                                let params = {
+                                    paper: await this.$evTools.createCosmosPaper(paperItem),
+                                    option: this.isEditPaper ? 'update' : 'insert'
+                                }
+                                //  保存试卷到cosmos
+                                this.$api.learnActivity.SaveExamPaper(params).then(res => {
+                                    if (res.error == null) {
+                                        this.$Message.success(this.$t('evaluation.paperList.saveSuc'))
+                                        this.isLoading = false
+                                        this.$Spin.hide()
+                                        // this.savePeriodInfos()
+                                        // 清空已选试题
+                                        this.showQues = false
+                                        this.selectedArr = []
+                                        this.groupList = []
+                                        this.orderList = []
+                                        this.selShowList = []
+                                    } else {
+                                        this.$Message.error(this.$t('evaluation.paperList.saveFail'))
+                                        this.$Spin.hide()
+                                        this.isLoading = false
+                                    }
+                                },err => {
+                                    this.$Message.error(this.$t('evaluation.paperList.saveFail'))
+                                    this.isLoading = false
+                                    this.$Spin.hide()
+                                }) */
+                            } else {
+                                console.error(blobFile)
+                            }
+                        } catch (e) {
+                            console.log(e)
+                        }
+                        this.$Message.success(this.$t("evaluation.editSuc"))
+                        this.isChangePaper = !this.isChangePaper
+                    })
+                } catch (e) {
+                    console.log(e)
+                    this.$Message.error(this.$t('evaluation.paperList.saveItemsFailTip'))
+                    this.isLoading = false
+                    this.isChangePaper = !this.isChangePaper
+                }
+              } else {
+                this.$t('learnActivity.mgtScEv.updErr')
+              }
+            })
+        } else {
+            this.isChangePaper = !this.isChangePaper
+            if(!this.isChangePaper) {
+              this.paperInfo = JSON.parse(JSON.stringify(this.paperInfoOld))
+              this.paper.item = this.paperInfo.item
+              this.$forceUpdate()
+            }
+        }
+    },
+
+
+  },
+  mounted() {
+    // this.isShowSave = window.location.pathname === '/home/evaluation/testPaper'
+    this.$Spin.hide()
+    let paper = this.paper || this.$route.params.paper || JSON.parse(localStorage.getItem('c_edit_paper'))
+    if (!paper || !paper.item) return
+    this.paperInfoOld = JSON.parse(JSON.stringify(paper))
+    console.log('xxxx', paper)
+    this.$EventBus.$emit('getNewPaper', paper)
+    // localStorage.setItem('_paperInfo', JSON.stringify(paper))
+    this.viewModel = paper.itemSort === 1 ? 'list' : 'type'
+    this.paperInfo = paper // 自己页面的值
+    this.paperDiff = paper.item ? this.handleDiffCalc(paper.item) : 0
+    this.curOrderTempIndex = paper.orderTemp || 0
+  },
+  computed: {
+    isAuto() {
+      return this.paperInfo.markConfig ? this.paperInfo.markConfig.auto : false
+    },
+    ruleType() {
+      return this.paperInfo.markConfig ? this.paperInfo.markConfig.type : 0
+    },
+    isSchool() {
+      return this.$route.name === 'newSchoolPaper'
+    },
+    isDevEnv() {
+      return process.env.NODE_ENV === 'development'
+    }
+  },
+  watch: {
+    paper: {
+      handler(newValue, oldValue) {
+        if (newValue.item && newValue.item.length) {
+          this.$nextTick(() => {            
+            this.isHiteachPaper = newValue.qamode === 1 // 是否是快速组卷或者Hiteach的课中试卷
+            this.isMarkModel = newValue.markModel === 1 // 是否是阅卷专用
+            if (!this.isHiteachPaper) {
+              this.$EventBus.$emit('getNewPaper', newValue)
+            }
+            this.viewModel = newValue.itemSort === 1 ? 'list' : 'type'
+            console.log('TestPaper > paper', newValue)
+          })
+          this.curOrderTempIndex = newValue.orderTemp || 0          
+          this.paperInfo = newValue
+        }
+        this.paperDiff = newValue.item ? this.handleDiffCalc(newValue.item) : 4
+      },
+      immediate: true,
+      deep: true
+    },
+    activityIndex: {
+      handler(n, o) {
+        if(this.isExamInfoPaper) this.isChangePaper = false
+        this.paperInfoOld = JSON.parse(JSON.stringify(this.paper))
+      }
+    },
+    subjectIndex: {
+      handler(n, o) {
+        if(this.isExamInfoPaper) this.isChangePaper = false
+        this.paperInfoOld = JSON.parse(JSON.stringify(this.paper))
+      }
+    }
+  }
+}
+</script>
+<style src="../index/TestPaper.less" lang="less" scoped>
+</style>
+<style lang="less">
+.complete-line {
+  padding: 0 45px;
+  border-bottom: 2px solid rgb(128, 128, 128);
+}
+
+.order-temp-modal {
+  .order-temp-item {
+    margin: 10px 0;
+    border: 1px solid #dddddd;
+    padding: 20px 15px;
+    border-radius: 5px;
+    font-size: 16px;
+    font-weight: bold;
+    cursor: pointer;
+    &-info {
+      font-size: 14px;
+      font-weight: 400;
+      margin-left: 15px;
+    }
+
+    &-active {
+      background-color: #72cbff;
+      color: #fff;
+    }
+  }
+}
+</style>

+ 218 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/index/index.vue

@@ -0,0 +1,218 @@
+<template>
+    <div class="evaluation">
+        <Loading :top="100" v-show="dataLoading" type="3"></Loading>
+        <vuescroll ref="evScroll" id="evScroll" @handle-scroll="handleScroll">
+            <div class="ev-body">
+                <div class="ev-content">
+                    <router-view />
+                </div>
+            </div>
+        </vuescroll>
+    </div>
+
+</template>
+<script>
+  export default {
+    data() {
+      return {
+        syllabusTitle: ' 评测模块',
+        identitydata: [
+          { 'id': 1, 'name': '成都紫藤小学', 'rolename': '管理员', status: '1' },
+          { 'id': 2, 'name': '成都七中小学', 'rolename': '班主任', status: '2' }
+        ],
+        treeData: [],
+        schoolInfo: {},
+        evSubjectList: [],
+        evPeriodsList: [],
+        evVolumesList: [],
+        evSubjectSelect: '',
+        evPeriodSelect: '',
+        evVolumeSelect: '',
+        dataLoading: false
+      }
+    },
+    created() {
+      
+    },
+    methods: {
+        goRouter(name) {
+            this.$router.push({
+              name: name
+            })
+      },
+	  // 判断容器滚动距离
+	  handleScroll(vertical, horizontal, nativeEvent) {
+		this.$EventBus.$emit('evScroll',vertical.scrollTop)
+		// let anchorBarDom = document.getElementById('AnchorBar')
+		// console.log(anchorBarDom.getBoundingClientRect().top)
+	  },
+
+      
+    },
+    mounted() {
+    }
+
+  }
+</script>
+<style scoped>
+  .evaluation {
+    background:#f6f6f6;
+    width:100%;
+    height:100%;
+    position:relative;
+    overflow:hidden;
+  }
+
+  .ev-body {
+    /*width:98%;*/
+    /* min-width:1200px; */
+    min-height:700px;
+    margin:auto;
+    display:flex;
+    flex-direction:row;
+    justify-content:space-between;
+  }
+    .ev-body .ev-slide {
+      width:20%;
+      max-width:300px;
+      margin-left:20px;
+      height:auto;
+      padding:0;
+      background:#fff;
+      border-radius:5px 5px 0 0;
+    }
+
+    .ev-body .ev-slide .ev-slide-header{
+      width:104%;
+      height:45px;
+      background:#10abe7;
+      text-align:center;
+      line-height:45px;
+      color:white;
+      font-size:16px;
+      border-radius:5px;
+      margin:20px -5px;
+      box-shadow: 0px 1px 8px 5px #dedede;
+    }
+
+      .ev-body .ev-slide .ev-slide-select {
+        padding:0 10px;
+        /*background:#10abe7;*/
+      }
+
+      .ev-body .ev-slide .ev-slide-school {
+          height:40px;
+          line-height:40px;
+          text-align:center;
+          padding-top:10px;
+          font-size:14px;
+          font-weight:bold;
+          padding-right:5px;
+      }
+
+      .ev-slide /deep/ .ev-slide-select .ivu-select-selection {
+        margin-top:8px;
+        height:40px;
+      }
+
+      .ev-slide /deep/ .ev-slide-select .ivu-select-selected-value {
+        line-height:40px;
+      }
+
+      .ev-slide .ev-slide-select-volumes /deep/ .ivu-select-selection {
+          background:transparent;
+          border:none;
+          color:#fff;
+          width:140px;
+          margin:0 auto;
+          padding-right:10px;
+          font-size:14px;
+      }
+    .ev-slide .ev-slide-select-volumes /deep/ .ivu-select-arrow {
+        color: #fff;
+    }
+
+    .ev-slide .ev-slide-select-volumes /deep/ .ivu-select-dropdown {
+        left: unset !important;
+    }
+      .ev-slide .ev-slide-select-volumes /deep/ .ivu-select-placeholder {
+          color:#fff;
+          font-size:14px;
+      }
+      .ev-slide .ev-slide-select-volumes /deep/ .ivu-select-selected-value {
+          color:#fff;
+          font-size:14px;
+      }
+
+  .ev-body .ev-content {
+      width:100%;
+      margin:0 auto;
+      }
+
+  .slide-menu {
+    width:150px;
+    background:#191919;
+    position:fixed;
+    top:100px;
+    left:-140px;
+    border-radius: 0 10px 10px 0;
+    z-index:9999;
+  }
+
+    .slide-menu ul {
+      height:100%;
+      width:100%;
+      padding:20px 0;
+    }
+
+      .slide-menu ul li {
+        width:100%;
+        height:70px;
+        font-size:16px;
+        text-align:center;
+        line-height:70px;
+        color:#fff;
+        font-weight:bold;
+        cursor:pointer;
+        z-index: 1;
+        position: relative;
+        outline: none;
+        border: none;
+        overflow: hidden;
+         transition: color 0.4s ease-in-out;
+      }
+
+    .slide-menu .ivu-icon {
+      position:absolute;
+      top:170px;
+      right:-44px;
+      color:#477fd8;
+      font-size:44px;
+    }
+
+      .slide-menu ul li::before {
+          content: '';
+          z-index: -1;
+          position: absolute;
+          bottom: 100%;
+          right: 100%;
+          width: 1em;
+          height: 1em;
+          border-radius: 50%;
+          background:#a28f5c;
+          transform-origin: center;
+          transform: translate3d(50%, 50%, 0) scale3d(0, 0, 0);
+          transition: transform 0.45s ease-in-out;
+        }
+
+        .slide-menu:hover{
+          cursor: pointer;
+          transform:translate(140px, 0);
+          transition:1s  ease  0s;
+        }
+
+        .slide-menu ul li:hover::before {
+          transform: translate3d(120%, 50%, 0) scale3d(25, 15, 15);
+        }
+
+</style>

+ 96 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseCompletion.vue

@@ -0,0 +1,96 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.complete') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+			<div>
+				<div ref="editor" style="text-align:left"></div>
+			</div>
+		</div>
+		<div class="exersices-option">
+			<IconText :text="$t('evaluation.completeCount')" :color="'#0e9c50'" :icon="'md-compass'"></IconText>
+			</br>
+			<InputNumber v-model="blankCount" :min="1" :editable="false" size="large"></InputNumber>
+		</div>
+		<div class="exersices-option">
+			<IconText :text="$t('evaluation.newExercise.answerTitle')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+			<div style="margin-top:15px;">
+				<div ref="answerEditor" style="text-align:left"></div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+	import E from 'wangeditor'
+	import IconText from '@/components/evaluation/IconText.vue'
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo:{
+				type:Object,
+				default:() => {}
+			},
+			isEdit:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data() {
+			return {
+				stemContent: '',
+				stemEditor: null,
+				answerContent: '',
+				answerEditor: null,
+				blankCount:1
+			}
+		},
+		methods: {
+			doRender(){
+				this.blankCount = this.editInfo.blankCount || this.editInfo.count || 1
+				this.stemContent = this.editInfo.question
+				this.answerContent = this.editInfo.answer[0]
+				this.stemEditor.txt.html(this.editInfo.question)
+				this.answerEditor.txt.html(this.editInfo.answer[0])
+			}
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.editor)
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html
+			}
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor,this)
+			stemEditor.create()
+
+			let answerEditor = new E(this.$refs.answerEditor)
+			answerEditor.config.onchange = (html) => {
+				this.answerContent = html
+			}
+			answerEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(answerEditor,this)
+			answerEditor.create()
+
+			this.stemEditor = stemEditor
+			this.answerEditor = answerEditor
+
+			if (this.editInfo && this.isEdit) { 
+				this.doRender()
+			}
+		},
+		watch:{
+			editInfo:{
+				handler(n){
+					console.log('填空题富文本接收到的数据')
+					console.log(n)
+					if(n){
+						this.doRender()
+					}
+				}
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+	@import"../index/CreateExercises.less";
+</style>

+ 73 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseCompose.vue

@@ -0,0 +1,73 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.compose') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+			<div>
+				<div ref="editor" style="text-align:left"></div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+	import E from 'wangeditor'
+	import IconText from '@/components/evaluation/IconText.vue'
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo:{
+				type:Object,
+				default:() => {}
+			},
+			isEdit:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data() {
+			return {
+				defaultConfig: {
+					uploadImgShowBase64: true,
+					menus: this.$tools.wangEditorMenu
+				},
+				stemContent: '',
+				stemEditor: null
+			}
+		},
+		methods: {
+
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.editor)
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html
+			}
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor,this)
+			stemEditor.create()
+			this.stemEditor = stemEditor
+			
+			if (this.editInfo && this.isEdit) { 
+				console.log('综合题Mount编辑')
+				this.stemContent = this.editInfo.question
+				this.stemEditor.txt.html(this.editInfo.question)
+			}
+		},
+		watch:{
+			editInfo:{
+				handler(n){
+					console.log('主观题富文本接收到的数据')
+					console.log(n)
+					if(n){
+						this.stemContent = this.editInfo.question
+						this.stemEditor.txt.html(this.editInfo.question)
+					}
+				}
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+	@import"../index/CreateExercises.less";
+</style>

+ 92 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseConnector.vue

@@ -0,0 +1,92 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.connector') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+			<div>
+				<div ref="editor" style="text-align:left"></div>
+			</div>
+		</div>
+		<div class="exersices-option">
+			<IconText :text="$t('evaluation.newExercise.answerTitle')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+			<div style="margin-top:15px;">
+				<div ref="answerEditor" style="text-align:left"></div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+	import E from 'wangeditor'
+	import IconText from '@/components/evaluation/IconText.vue'
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo:{
+				type:Object,
+				default:() => {}
+			},
+			isEdit:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data() {
+			return {
+				stemContent: '',
+				stemEditor: null,
+				answerContent: '',
+				answerEditor: null
+
+			}
+		},
+		methods: {
+			doRender(){
+				this.stemContent = this.editInfo.question
+				this.answerContent = this.editInfo.answer
+				this.stemEditor.txt.html(this.editInfo.question)
+				this.answerEditor.txt.html(this.editInfo.answer[0])
+			}
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.editor)
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html
+			}
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor,this)
+			
+			stemEditor.create()
+
+			let answerEditor = new E(this.$refs.answerEditor)
+			answerEditor.config.onchange = (html) => {
+				this.answerContent = html
+			}
+			answerEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(answerEditor,this)
+			
+			answerEditor.create()
+
+			this.stemEditor = stemEditor
+			this.answerEditor = answerEditor
+
+			if (this.editInfo && this.isEdit) { 
+				this.doRender()
+			}
+		},
+		watch:{
+			editInfo:{
+				handler(n){
+					console.log('主观题富文本接收到的数据')
+					console.log(n)
+					if(n){
+						this.doRender()
+					}
+				}
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+	@import"../index/CreateExercises.less";
+</style>

+ 92 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseCorrect.vue

@@ -0,0 +1,92 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.correct') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+			<div>
+				<div ref="editor" style="text-align:left"></div>
+			</div>
+		</div>
+		<div class="exersices-option">
+			<IconText :text="$t('evaluation.newExercise.answerTitle')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+			<div style="margin-top:15px;">
+				<div ref="answerEditor" style="text-align:left"></div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+	import E from 'wangeditor'
+	import IconText from '@/components/evaluation/IconText.vue'
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo:{
+				type:Object,
+				default:() => {}
+			},
+			isEdit:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data() {
+			return {
+				stemContent: '',
+				stemEditor: null,
+				answerContent: '',
+				answerEditor: null
+
+			}
+		},
+		methods: {
+			doRender(){
+				this.stemContent = this.editInfo.question
+				this.answerContent = this.editInfo.answer
+				this.stemEditor.txt.html(this.editInfo.question)
+				this.answerEditor.txt.html(this.editInfo.answer[0])
+			}
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.editor)
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html
+			}
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor,this)
+			
+			stemEditor.create()
+
+			let answerEditor = new E(this.$refs.answerEditor)
+			answerEditor.config.onchange = (html) => {
+				this.answerContent = html
+			}
+			answerEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(answerEditor,this)
+			
+			answerEditor.create()
+
+			this.stemEditor = stemEditor
+			this.answerEditor = answerEditor
+
+			if (this.editInfo && this.isEdit) { 
+				this.doRender()
+			}
+		},
+		watch:{
+			editInfo:{
+				handler(n){
+					console.log('主观题富文本接收到的数据')
+					console.log(n)
+					if(n){
+						this.doRender()
+					}
+				}
+			}
+		}
+	}
+</script>
+<style lang="less" scoped>
+	@import"../index/CreateExercises.less";
+</style>

+ 86 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseJudge.vue

@@ -0,0 +1,86 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.judge') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+			<div>
+				<div ref="editor" style="text-align:left"></div>
+			</div>
+		</div>
+		<div class="exersices-option">
+			<IconText :text="$t('evaluation.newExercise.option')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+			<RadioGroup v-model="trueAnswer" type="button" size="large">
+				<Radio label="A">{{$t('evaluation.isTrue')}}</Radio>
+				<Radio label="B">{{$t('evaluation.isFalse')}}</Radio>
+			</RadioGroup>
+		</div>
+	</div>
+</template>
+<script>
+	import E from 'wangeditor'
+	import IconText from '@/components/evaluation/IconText.vue'
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo:{
+				type:Object,
+				default:() => {}
+			},
+			isEdit:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data() {
+			return {
+				trueAnswer: 'A',
+				defaultConfig: {
+					uploadImgShowBase64: true,
+					menus: this.$tools.wangEditorMenu
+				},
+				stemContent: '',
+				stemEditor: null
+			}
+		},
+		methods: {
+
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.editor)
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html
+			}
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor,this)
+			
+			stemEditor.create()
+			this.stemEditor = stemEditor
+			
+			if (this.editInfo && this.isEdit) { 
+				console.log('判断题Mount编辑')
+				this.stemEditor.txt.html(this.editInfo.question)
+				this.stemContent = this.editInfo.question
+				this.trueAnswer = this.editInfo.answer[0] === 'A' ? 'A' : 'B'
+			}
+
+			// 判断是否为编辑状态
+			// if (Object.keys(this.editInfo).length > 0) {
+			// 	this.stemEditor.txt.html(this.editInfo.question)
+			// 	this.stemContent = this.editInfo.question
+			// 	this.trueAnswer = this.editInfo.answer[0] === 'A' ? '正确' : '错误'
+			// }
+		},
+		watch:{
+			editInfo:{
+				handler(n){
+					if(n){
+						this.stemEditor.txt.html(this.editInfo.question)
+						this.stemContent = this.editInfo.question
+						this.trueAnswer = this.editInfo.answer[0] === 'A' ? 'A' : 'B'
+					}
+				}
+			}
+		}
+	}
+</script>

+ 338 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseMultiple.vue

@@ -0,0 +1,338 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.multiple') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom: 15px"></IconText>
+			<div>
+				<div ref="singleEditor" style="text-align: left"></div>
+			</div>
+		</div>
+		<div class="exersices-option" ref="optionRefs">
+			<IconText :text="$t('evaluation.newExercise.multipleOption')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+			<div v-for="(item, index) in options" :key="index" :ref="'optionBox' + index" :class="'editor-wrap-' + item" style="margin-top: 10px; display: flex">
+				<span class="fl-center option-delete" @click="deleteOption(index, item)"><Icon type="md-remove-circle" /></span>
+				<span class="fl-center option-order" :ref="'optionOrder' + index" :data-index="index">{{ renderIndex(index) }}</span>
+				<div :ref="'singleOption' + index" style="text-align: left" class="option-editor" @click="optionClick(item)"></div>
+				<span :class="['fl-center', 'option-setting', trueArr.indexOf(index) > -1 ? 'option-true' : '']" @click="settingAnswer(index)">{{ trueArr.indexOf(index) > -1 ? $t("evaluation.newExercise.trueAnswer") : $t("evaluation.newExercise.setAnswer") }}</span>
+			</div>
+			<p class="option-add">
+				<span @click="addOption()">+ {{ $t("evaluation.newExercise.addOption") }} </span><span style="color: rgb(60, 196, 82); margin-left: 15px; font-weight: bold">{{ $t("evaluation.newExercise.trueAnswer") }}:{{ multipleAnswers.join("") }}</span>
+			</p>
+		</div>
+	</div>
+</template>
+<script>
+	import E from "wangeditor";
+	import IconText from "@/components/evaluation/IconText.vue";
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo: {
+				type: Object,
+				default: () => {}
+			},
+			isEdit: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				options: [...new Array(4).keys()], // 默认四个选项
+				existOptions: [...new Array(4).keys()],
+				initFlag: true,
+				trueIndex: 0,
+				optionTrueIndex: 0,
+				trueArr: [0],
+				optionTrueArr: [0],
+				multipleAnswers: ["A"],
+				editSingleInfo: {},
+				stemEditor: null,
+				stemContent: "",
+				transferArr: [],
+				optionsContent: [],
+				optionEditors: [],
+				defaultConfig: {
+					uploadImgShowBase64: true,
+					menus: this.$tools.wangEditorMenu
+				}
+			};
+		},
+		created() {},
+		methods: {
+			initEditors() {
+				console.error("initEditors", this.options);
+				// Editor默认配置
+				if (this.options.length > 0) {
+					this.options.forEach((item, i) => {
+						let that = this;
+						let editor = new E(that.$refs["singleOption" + i][0]);
+						// 选项编辑器失焦隐藏工具栏
+						editor.config.onblur = function () {
+							let allToolbars = document.getElementsByClassName("option-editor");
+							for (let i = 0; i < allToolbars.length; i++) {
+								if (allToolbars[i].children.length) {
+									allToolbars[i].children[0].style.visibility = "hidden";
+								}
+							}
+						};
+
+						editor.config.uploadImgShowBase64 = true;
+						this.$editorTools.initMyEditor(editor, this, true);
+
+						// 选项编辑器内容发生变化时
+						editor.config.onchange = (html) => {
+							let key = String.fromCharCode(64 + parseInt(i + 1));
+							let codeArr = this.optionsContent.map((item) => item.code);
+							// 如果已经编辑过则 修改选项内容
+							if (codeArr.indexOf(key) !== -1) {
+								this.optionsContent[codeArr.indexOf(key)].value = html;
+							} else {
+								// 否则创建新选项
+								let option = {
+									code: key,
+									value: html
+								};
+								this.optionsContent.push(option);
+							}
+						};
+						editor.create();
+						this.optionEditors.push(editor);
+						that.$refs["singleOption" + i][0].dataset.editorId = editor.id;
+
+						// 如果是编辑状态 则将选项内容回显
+						if (Object.keys(this.editSingleInfo).length > 0) {
+							editor.txt.html(this.editSingleInfo.option[i].value);
+						}
+					});
+				}
+			},
+
+			/* 添加选项 */
+			addOption() {
+				let that = this;
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName("option-order"));
+				let optionsLength = wraps.length;
+				let newIndex = this.options.length ? parseInt(this.options[this.options.length - 1]) + 1 : 0;
+				// let optionsLength = this.options.length
+				if (optionsLength < 10) {
+					this.options.push(newIndex);
+					this.existOptions.push(newIndex);
+					this.$nextTick(() => {
+						let editor = new E(that.$refs["singleOption" + newIndex][0]);
+						this.$editorTools.initMyEditor(editor, this, true);
+						editor.config.onchange = (html) => {
+							let key = String.fromCharCode(64 + parseInt(newIndex + 1));
+							let codeArr = this.optionsContent.map((item) => item.code);
+							// 如果已经编辑过则 修改选项内容
+							if (codeArr.indexOf(key) !== -1) {
+								this.optionsContent[codeArr.indexOf(key)].value = html;
+								console.log(this.optionsContent.map((item) => item.code));
+								console.log(this.optionsContent.map((item) => item.value));
+							} else {
+								// 否则创建新选项
+								let option = {
+									code: key,
+									value: html
+								};
+								this.optionsContent.push(option);
+							}
+						};
+						editor.create();
+						this.optionEditors.push(editor);
+						this.$refs["singleOption" + newIndex][0].dataset.editorId = editor.id;
+						this.refreshOrder();
+					});
+				} else {
+					this.$Message.warning(this.$t("evaluation.addTip2"));
+				}
+			},
+
+			/* 设置正确答案 */
+			settingAnswer(index) {
+				this.$nextTick(() => {
+					if (this.trueArr.indexOf(index) > -1) {
+						if (this.trueArr.length === 1) {
+							this.$Message.warning(this.$t("evaluation.addTip3"));
+						} else {
+							this.trueArr.splice(this.trueArr.indexOf(index), 1);
+						}
+					} else {
+						this.trueArr.push(index);
+						this.trueArr = this.trueArr.sort(); // 选项排序
+					}
+					this.getAnswerOrder(this.trueArr);
+				});
+			},
+
+			/* 获取最新答案选项 */
+			getAnswerOrder(arr) {
+				let arr2 = [];
+				arr.forEach((i) => {
+					arr2.push(this.getOrderCode(i));
+				});
+				this.multipleAnswers = arr2.sort();
+			},
+
+			/* 根据index获取对应选项字母的值 */
+			getOrderCode(index) {
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName("option-order"));
+				for (let i = 0; i < wraps.length; i++) {
+					let item = wraps[i];
+					if (+index === +item.dataset.index) {
+						return item.innerText;
+						break;
+					}
+				}
+			},
+
+			/* 根据页面上的选项DOM数量,刷新每个选项的Order显示 */
+			refreshOrder() {
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName("option-order"));
+				wraps.forEach((item, index) => {
+					item.innerHTML = this.renderIndex(index);
+				});
+			},
+
+			/* 根据下标渲染对应的字母顺序 */
+			renderIndex(index) {
+				return String.fromCharCode(64 + parseInt(index + 1));
+			},
+
+			/* 删除选项 */
+			deleteOption(index, item) {
+				// 拿到所有选项
+				if (this.existOptions.length > 2) {
+					// 如果删除的是正确答案 则重置正确答案
+					if (this.trueArr.indexOf(index) > -1) {
+						if (this.trueArr.length === 1) {
+							this.$Message.warning(this.$t("evaluation.addTip3"));
+						} else {
+							this.trueArr.splice(this.trueArr.indexOf(index), 1);
+							// 确保当前存在的options保持同步
+							this.existOptions.splice(this.existOptions.indexOf(item), 1);
+							this.optionsContent.splice(index, 1);
+							// 删除操作 移除选项DOM
+							let textWrap = this.$refs["optionBox" + index][0];
+							textWrap.remove();
+							// 刷新选项序号显示
+							this.refreshOrder();
+							this.getAnswerOrder(this.trueArr);
+						}
+					} else {
+						// 确保当前存在的options保持同步
+						this.existOptions.splice(this.existOptions.indexOf(item), 1);
+						this.optionsContent.splice(index, 1);
+						// 删除操作 移除选项DOM
+						let textWrap = this.$refs["optionBox" + index][0];
+						textWrap.remove();
+						// 刷新选项序号显示
+						this.refreshOrder();
+						this.getAnswerOrder(this.trueArr);
+					}
+				} else {
+					this.$Message.warning(this.$t("evaluation.addTip4"));
+				}
+			},
+
+			/* 保存试题 获取最新选项数据 */
+			doSave() {
+				// 拿到当前还剩的选项DOM
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName("option-editor"));
+				let arr = [];
+				wraps.forEach((item, index) => {
+					// 遍历选项 找到对应的 Editor 然后获取编辑器里面的内容
+					let id = item.dataset.editorId;
+					let curEditor = this.optionEditors.filter((i) => i.id === id)[0];
+					if (curEditor) {
+						// 生成新的选项对象
+						let obj = {
+							code: String.fromCharCode(64 + parseInt(index + 1)),
+							value: curEditor.txt.html()
+						};
+						arr.push(obj);
+					}
+				});
+				this.optionsContent = arr;
+			},
+
+			/* 模拟选项聚焦事件 */
+			optionClick(index) {
+				let allToolbars = document.getElementsByClassName("option-editor");
+				let that = this;
+				for (let i = 0; i < allToolbars.length; i++) {
+					allToolbars[i].children[0].style.visibility = "hidden";
+				}
+				setTimeout(function () {
+					let currentToolBar = that.$refs["singleOption" + index][0].children[0];
+					if (currentToolBar.clientHeight > 50) {
+						currentToolBar.style.top = "-90px";
+					}
+					currentToolBar.style.visibility = "visible";
+				}, 100);
+			},
+			/* 渲染多选题 */
+			doRender(editInfo) {
+				console.error("xxxxxxxxx3");
+				console.error(this.options);
+				this.editSingleInfo = editInfo;
+				this.stemContent = editInfo.question;
+				this.optionsContent = editInfo.option;
+				this.multipleAnswers = editInfo.answer;
+				this.trueArr = editInfo.answer.map((item) => item.charCodeAt() - 65);
+				this.options = editInfo.option.map((item, index) => index);
+				this.existOptions = editInfo.option.map((item, index) => index);
+				this.$nextTick(() => {
+					this.initEditors();
+					this.stemEditor.txt.html(editInfo.question);
+				});
+			}
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.singleEditor);
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html;
+			};
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor, this);
+			stemEditor.create();
+			this.stemEditor = stemEditor;
+			if (this.editInfo && Object.keys(this.editInfo).length > 0) {
+				this.doRender(this.editInfo);
+			}
+		},
+		computed: {
+			// 选项顺序转化成字母
+			showTrueAnswers() {
+				let arr = this.trueArr.map((item) => String.fromCharCode(64 + parseInt(item + 1)));
+				this.transferArr = arr;
+				return this.transferArr.sort().join("");
+			}
+		},
+		watch: {
+			editInfo: {
+				handler(newValue, oldValue) {
+					if (newValue) {
+						this.options = [];
+						this.$nextTick(() => {
+							this.editSingleInfo = newValue;
+							if (Object.keys(newValue).length > 0) {
+								this.doRender(newValue);
+							} else {
+								this.options = [...new Array(4).keys()]
+								this.$nextTick(() => {
+									this.initEditors();
+								});
+							}
+						});
+					}
+				},
+				immediate: true
+			}
+		}
+	};
+</script>
+<style lang="less" scoped>
+	@import "../index/CreateExercises.less";
+</style>

+ 302 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseSingle.vue

@@ -0,0 +1,302 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.single') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+			<div>
+				<div ref="singleEditor" style="text-align:left"></div>
+			</div>
+		</div>
+		<div class="exersices-option" ref="optionRefs">
+			<IconText :text="$t('evaluation.newExercise.singleOption')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+			<div v-for="(item,index) in options" :key="index" :ref="'optionBox' + index" :class="'editor-wrap-'+item" style="margin-top:10px;display:flex">
+				<span class="fl-center option-delete" @click="deleteOption(index,item)">
+					<Icon type="md-remove-circle" /></span>
+				<span class="fl-center option-order" :ref="'optionOrder' + index" :data-index="index">{{ renderIndex(index) }}</span>
+				<!-- <span class="fl-center option-order">{{String.fromCharCode(64 + parseInt(index+1))}}</span> -->
+				<div :ref="'singleOption'+index" style="text-align:left" class="option-editor" @click="optionClick(item)"></div>
+				<span :class="['fl-center', 'option-setting', optionTrueIndex === index ? 'option-true':'']" @click="settingAnswer(index)">{{ optionTrueIndex === index ? $t('evaluation.newExercise.trueAnswer') :$t('evaluation.newExercise.setAnswer') }}</span>
+
+			</div>
+			<p class="option-add"><span @click="addOption()">+ {{$t('evaluation.newExercise.addOption')}} </span><span style="color:rgb(60,196,82);margin-left:15px;font-weight:bold">{{ $t('evaluation.newExercise.trueAnswer') }}:{{ renderIndex(trueIndex) }}</span></p>
+		</div>
+	</div>
+</template>
+<script>
+	import E from 'wangeditor'
+	import IconText from '@/components/evaluation/IconText.vue'
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo:{
+				type:Object,
+				default:() => {}
+			},
+			isEdit:{
+				type:Boolean,
+				default:false
+			},
+		},
+		data() {
+			return {
+				options: [...new Array(4).keys()], // 默认四个选项
+				existOptions: [...new Array(4).keys()],
+				initFlag: true,
+				trueIndex: 0,
+				optionTrueIndex: 0,
+				editSingleInfo: {},
+				stemEditor: null,
+				stemContent: '',
+				optionsContent: [],
+				optionEditors: [],
+				defaultConfig: {
+					uploadImgShowBase64: true,
+					menus: this.$tools.wangEditorMenu
+				}
+			}
+		},
+		created() {},
+		methods: {
+			initEditors() {
+				// Editor默认配置
+				if (this.options.length > 0) {
+					this.options.forEach((item, i) => {
+						let that = this
+						let editor = new E(that.$refs['singleOption' + i][0])
+
+						// 选项编辑器失焦隐藏工具栏
+						editor.config.onblur = function() {
+							let allToolbars = document.getElementsByClassName('option-editor')
+							for (let i = 0; i < allToolbars.length; i++) {
+								if (allToolbars[i].children.length) {
+									allToolbars[i].children[0].style.visibility = 'hidden'
+								}
+							}
+						}
+						this.$editorTools.initMyEditor(editor,this,true)
+
+						// 选项编辑器内容发生变化时
+						editor.config.onchange = (html) => {
+							let key = String.fromCharCode(64 + parseInt(i + 1))
+							let codeArr = this.optionsContent.map(item => item.code)
+							// 如果已经编辑过则 修改选项内容
+							if (codeArr.indexOf(key) !== -1) {
+								this.optionsContent[codeArr.indexOf(key)].value = html
+							} else { // 否则创建新选项
+								let option = {
+									code: key,
+									value: html
+								}
+								this.optionsContent.push(option)
+							}
+						}
+						editor.create()
+						this.optionEditors.push(editor)
+						that.$refs["singleOption" + i][0].dataset.editorId = editor.id
+
+						// 如果是编辑状态 则将选项内容回显
+						if (Object.keys(this.editSingleInfo).length > 0) {
+							editor.txt.html(this.editSingleInfo.option[i].value)
+						}
+					})
+				}
+			},
+
+			/* 添加选项 */
+			addOption() {
+				let that = this
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName('option-order'))
+				let optionsLength = wraps.length;
+				let newIndex = this.options.length ? parseInt(this.options[this.options.length - 1]) + 1 : 0
+				// let optionsLength = this.options.length
+				if (optionsLength < 10) {
+					this.options.push(newIndex)
+					this.existOptions.push(newIndex)
+					this.$nextTick(() => {
+						let editor = new E(that.$refs['singleOption' + newIndex][0])
+						this.$editorTools.initMyEditor(editor,this,true)
+						editor.config.onchange = (html) => {
+							let key = String.fromCharCode(64 + parseInt(newIndex + 1))
+							let codeArr = this.optionsContent.map(item => item.code)
+							// 如果已经编辑过则 修改选项内容
+							if (codeArr.indexOf(key) !== -1) {
+								this.optionsContent[codeArr.indexOf(key)].value = html
+								let option = {
+									code: key,
+									value: html
+								}
+								this.optionsContent.push(option)
+							}
+						}
+						editor.create()
+						this.optionEditors.push(editor);
+						this.$refs["singleOption" + newIndex][0].dataset.editorId = editor.id
+						this.refreshOrder()
+					})
+				} else {
+					this.$Message.warning(this.$t('evaluation.addTip2'))
+				}
+			},
+
+
+			/* 设置正确答案 */
+			settingAnswer(index) {
+				this.optionTrueIndex = index
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName('option-order'))
+				for (let i = 0; i < wraps.length; i++) {
+					let item = wraps[i]
+					if (+index === +item.dataset.index) {
+						this.trueIndex = item.innerText.charCodeAt() - 65
+						break;
+					}
+				}
+				// wraps.forEach((item, orderIndex) => {
+				// 	console.log(item)
+				// 	console.log(item.dataset.index)
+
+				// })
+			},
+
+			/* 根据页面上的选项DOM数量,刷新每个选项的Order显示 */
+			refreshOrder() {
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName('option-order'))
+				wraps.forEach((item, index) => {
+					item.innerHTML = this.renderIndex(index)
+				})
+			},
+
+			/* 根据下标渲染对应的字母顺序 */
+			renderIndex(index) {
+				return String.fromCharCode(64 + parseInt(index + 1))
+			},
+
+
+
+			/* 删除选项 */
+			deleteOption(index, item) {
+				// 如果删除的是正确答案前面的选项 则正确答案需要往前排一位
+				if (index < this.optionTrueIndex) {
+					this.trueIndex--
+				}
+				// 拿到所有选项
+				if (this.existOptions.length > 2) {
+					// 确保当前存在的options保持同步
+					this.existOptions.splice(this.existOptions.indexOf(item), 1)
+					this.optionsContent.splice(index, 1)
+					// 删除操作 移除选项DOM
+					let textWrap = this.$refs['optionBox' + index][0]
+					textWrap.remove()
+					// 如果删除的是正确答案 则重置正确答案
+					if (index === this.optionTrueIndex) {
+						this.optionTrueIndex = this.existOptions[0]
+						this.trueIndex = 0
+					}
+					// 刷新选项序号显示
+					this.refreshOrder()
+				} else {
+					this.$Message.warning(this.$t('evaluation.addTip4'))
+				}
+			},
+
+			/* 保存试题 获取最新选项数据 */
+			doSave() {
+				// 拿到当前还剩的选项DOM
+				let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName('option-editor'))
+				let arr = []
+				console.log(wraps)
+				wraps.forEach((item, index) => {
+					// 遍历选项 找到对应的 Editor 然后获取编辑器里面的内容
+					let id = item.dataset.editorId
+					let curEditor = this.optionEditors.filter(i => i.id === id)[0]
+					if (curEditor) {
+						// 生成新的选项对象
+						let obj = {
+							code: String.fromCharCode(64 + parseInt(index + 1)),
+							value: curEditor.txt.html()
+						}
+						arr.push(obj)
+					}
+
+				})
+				this.optionsContent = arr
+			},
+
+			/* 模拟选项聚焦事件 */
+			optionClick(index) {
+				let allToolbars = document.getElementsByClassName('option-editor')
+				let that = this
+				for (let i = 0; i < allToolbars.length; i++) {
+					allToolbars[i].children[0].style.visibility = 'hidden'
+				}
+				setTimeout(function() {
+					let currentToolBar = that.$refs['singleOption' + index][0].children[0]
+					if(currentToolBar.clientHeight > 50){
+						currentToolBar.style.top = '-90px'
+					}
+					currentToolBar.style.visibility = 'visible'
+				}, 100)
+			}
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.singleEditor)
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html
+			} 
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor,this)
+
+			stemEditor.create()
+			this.stemEditor = stemEditor
+			
+			if (this.editInfo && this.isEdit) {
+				console.log('进入单选题Mounted编辑',this.editInfo)
+				this.editSingleInfo = this.editInfo
+				this.stemContent = this.editSingleInfo.question
+				this.optionsContent = this.editSingleInfo.option
+				this.trueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
+				this.optionTrueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
+				this.options = this.editSingleInfo.option.map((item, index) => index)
+				this.existOptions = this.editSingleInfo.option.map((item, index) => index)
+				this.$nextTick(() => {
+					this.initEditors()
+					this.stemEditor.txt.html(this.editSingleInfo.question)
+				})
+			}
+		},
+		watch: {
+			editInfo: {
+				handler(newValue, oldValue) {	
+					if (newValue) {
+						this.options = [];
+						this.$nextTick(() => {
+							this.editSingleInfo = newValue;
+							if (Object.keys(newValue).length > 0) {
+								this.stemContent = this.editSingleInfo.question
+								this.optionsContent = this.editSingleInfo.option
+								this.trueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
+								this.optionTrueIndex = this.editSingleInfo.answer[0].charCodeAt() - 65
+								this.options = this.editSingleInfo.option.map((item, index) => index)
+								this.existOptions = this.editSingleInfo.option.map((item, index) => index)
+								this.$nextTick(() => {
+									this.initEditors()
+									this.stemEditor.txt.html(this.editSingleInfo.question)
+								})
+							} else {
+								this.options = [...new Array(4).keys()]
+								this.$nextTick(() => {
+									this.initEditors();
+								});
+							}
+						});
+					}
+				},
+				immediate:true
+			},
+
+		}
+	}
+</script>
+<style lang="less" scoped>
+	@import"../index/CreateExercises.less";
+</style>

+ 136 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/evaluation/types/BaseSubjective.vue

@@ -0,0 +1,136 @@
+<template>
+	<div>
+		<div class="exersices-content">
+			<IconText :text="$t('evaluation.subjective') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom: 15px"></IconText>
+			<div>
+				<div ref="editor" style="text-align: left"></div>
+			</div>
+		</div>
+		<div class="exersices-content answer-type">
+			<IconText :text="$t('evaluation.newExercise.answerType.types')" :color="'#07ac88'" :icon="'md-chatbubbles'"></IconText>
+			<div class="types">
+				<span :class="['type-item', answerType === 'text' ? 'type-item-active' : '']" @click="answerType = 'text'">{{ $t("evaluation.newExercise.answerType.text") }}</span>
+				<!-- <span :class="['type-item', answerType === 'text_Image' ? 'type-item-active' : '']" @click="answerType = 'text_Image'">{{ $t("evaluation.newExercise.answerType.textImage") }}</span> -->
+				<span :class="['type-item', answerType === 'Image' ? 'type-item-active' : '']" @click="answerType = 'Image'">{{ $t("evaluation.newExercise.answerType.image") }}</span>
+				<span :class="['type-item', answerType === 'audio' ? 'type-item-active' : '']" @click="answerType = 'audio'">{{ $t("evaluation.newExercise.answerType.audio") }}</span>
+				<span :class="['type-item', answerType === 'file' ? 'type-item-active' : '']" @click="answerType = 'file'">{{ $t("evaluation.newExercise.answerType.file") }}</span>
+				<div style="display: inline-block;" v-if="answerType === 'audio'"> 
+					<span style="margin-right: 10px">{{ $t("evaluation.newExercise.answerType.autoScore") }}</span>
+					<i-switch v-model="useAutoScore" />
+					<span style="margin: 10px 20px">{{ $t("evaluation.newExercise.answerType.answerLang") }}</span>
+					<Select v-model="answerLang" style="width: 150px">
+						<Option value="en-US">{{ $t("evaluation.newExercise.answerType.us") }}(en-US)</Option>
+						<Option value="zh">{{ $t("evaluation.newExercise.answerType.zh") }}(zh) </Option>
+						<Option value="ja-JP">{{ $t("evaluation.newExercise.answerType.jp") }}(ja-JP)</Option>
+						<Option value="ko-KR">{{ $t("evaluation.newExercise.answerType.kr") }}(ko-KR)</Option>
+						<Option value="zh-HK">{{ $t("evaluation.newExercise.answerType.hk") }}(zh-HK)</Option>
+					</Select>
+				</div>
+			</div>
+		</div>
+		<div class="exersices-option">
+			<IconText :text="$t('evaluation.newExercise.answerTitle')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+			<div style="margin-top: 15px">
+				<div ref="answerEditor" style="text-align: left"></div>
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+	import E from "wangeditor";
+	import IconText from "@/components/evaluation/IconText.vue";
+	export default {
+		components: {
+			IconText
+		},
+		props: {
+			editInfo: {
+				type: Object,
+				default: () => {}
+			},
+			isEdit: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				answerType: "text",
+				useAutoScore: false,
+				answerLang: "en-US",
+				stemContent: "",
+				stemEditor: null,
+				answerContent: "",
+				answerEditor: null
+			};
+		},
+		methods: {
+			doRender() {
+				this.stemContent = this.editInfo.question;
+				this.answerType = this.editInfo.answerType;
+				this.useAutoScore = this.editInfo.useAutoScore;
+				this.answerLang = this.editInfo.answerLang;
+				this.answerContent = this.editInfo.answer[0];
+				this.answerType = this.editInfo.answerType || "text";
+				this.stemEditor.txt.html(this.editInfo.question);
+				this.answerEditor.txt.html(this.editInfo.answer[0]);
+			}
+		},
+		mounted() {
+			let stemEditor = new E(this.$refs.editor);
+			stemEditor.config.onchange = (html) => {
+				this.stemContent = html;
+			};
+			stemEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(stemEditor, this);
+
+			stemEditor.create();
+
+			let answerEditor = new E(this.$refs.answerEditor);
+			answerEditor.config.onchange = (html) => {
+				this.answerContent = html;
+			};
+			answerEditor.config.uploadImgShowBase64 = true;
+			this.$editorTools.initMyEditor(answerEditor, this);
+
+			answerEditor.create();
+
+			this.stemEditor = stemEditor;
+			this.answerEditor = answerEditor;
+
+			if (this.editInfo && this.isEdit) {
+				this.doRender();
+			}
+		},
+		watch: {
+			editInfo: {
+				handler(n) {
+					console.log("主观题富文本接收到的数据");
+					console.log(n);
+					if (n) {
+						this.doRender();
+					}
+				}
+			}
+		}
+	};
+</script>
+<style lang="less" scoped>
+	@import "../index/CreateExercises.less";
+
+	.answer-type {
+		.type-item {
+			display: inline-block;
+			padding: 4px 18px;
+			border-radius: 5px;
+			margin-right: 15px;
+			margin-top: 20px;
+			border: 1px solid #dddddd;
+			cursor: pointer;
+			&-active {
+				background: #10abe7;
+				color: #fff;
+			}
+		}
+	}
+</style>

+ 78 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/navbar/IdentityNavbar.vue

@@ -0,0 +1,78 @@
+<script>
+export default {
+    props: {
+        identity: {
+            type: String,
+            required: true
+        }
+    },
+    data() {
+        return {
+            hidePaths: ['HTPaperDownload']
+        }
+    },
+    methods: {
+        refresh() {
+            this.$router.go(0);
+        },
+        navigateToSchool() {
+            const path = this.$route.path.replace('private', 'school');
+            const query = this.$route.query;
+            this.$router.push({
+                path,
+                query
+            });
+        },
+        navigateToPrivate() {
+            const path = this.$route.path.replace('school', 'private');
+            const query = this.$route.query;
+            this.$router.push({
+                path,
+                query
+            });
+        }
+    },
+}
+</script>
+
+<template>
+    <div class="identityNavbar" v-show="!hidePaths.includes($route.name)">
+        <button @click="navigateToSchool" :class="{ active: identity === 'school' }" class="navBtn">
+            {{ $t("hiTeachSideMenu.school") }}
+        </button>
+        <button @click="navigateToPrivate" :class="{ active: identity === 'private' }" class="navBtn" style="margin-right: auto;">
+            {{ $t("hiTeachSideMenu.private") }}
+        </button>
+
+        <button @click="refresh">
+            <Icon type="md-refresh" size="24"/>
+        </button>
+    </div>
+</template>
+
+<style scoped>
+button {
+    all: unset;
+    cursor: pointer;
+}
+
+.identityNavbar {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.identityNavbar .navBtn {
+    padding: 1px 5px;
+    border-radius: 4px;
+    color: var(--primary-text-color);
+    border: 1px solid var(--border-color);
+    transition: all 0.3s;
+}
+
+.identityNavbar .navBtn.active {
+    background-color: var(--assist-color-light);
+    color: white;
+    border: 1px solid var(--assist-color-light);
+}
+</style>

+ 60 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/navbar/PathNavbar.vue

@@ -0,0 +1,60 @@
+<script>
+export default {
+    props: {
+        identity: {
+            type: String,
+            required: true
+        }
+    },
+}
+</script>
+
+<template>
+    <div class="pathNavbar">
+        <template v-if="identity === 'school'">
+            <RouterLink :to="`/hiTeachSideMenu/school/syllabus`">
+                {{ $t("hiTeachSideMenu.syllabus") }}
+            </RouterLink>
+            <RouterLink :to="`/hiTeachSideMenu/school/content`">
+                {{ $t("hiTeachSideMenu.content") }}
+            </RouterLink>
+            <RouterLink :to="`/hiTeachSideMenu/school/bank`">
+                {{ $t("hiTeachSideMenu.bank") }}
+            </RouterLink>
+        </template>
+
+        <template v-if="identity === 'private'">
+            <RouterLink :to="`/hiTeachSideMenu/private/syllabus`">
+                {{ $t("hiTeachSideMenu.syllabus") }}
+            </RouterLink>
+            <RouterLink :to="`/hiTeachSideMenu/private/content`">
+                {{ $t("hiTeachSideMenu.content") }}
+            </RouterLink>
+            <RouterLink :to="`/hiTeachSideMenu/private/bank`">
+                {{ $t("hiTeachSideMenu.bank") }}
+            </RouterLink>
+        </template>
+    </div>
+</template>
+
+<style>
+.pathNavbar {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+}
+
+.pathNavbar a {
+    padding: 1px 5px;
+    border-radius: 4px;
+    color: var(--primary-text-color);
+    border: 1px solid var(--border-color);
+    transition: all 0.3s;
+}
+
+.pathNavbar .router-link-active {
+    background-color: var(--assist-color-light);
+    color: white;
+    border: 1px solid var(--assist-color-light);
+}
+</style>

+ 416 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/syllabus/Syllabus.less

@@ -0,0 +1,416 @@
+@primaryColor: #1CC0F3;
+@borderColor: #eeeeee;
+@highlightColor: #1fb06d;
+@second-textColor: var(--second-text-color); //文本副级颜色
+@borderColor: var(--border-color);
+
+.syllabus-container {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	background: #fff;
+	// font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+
+	.syllabus-header {
+		position: relative;
+		width: 100%;
+		// min-height: 45px;
+		box-shadow: 0 2px 5px #e9e9e9;
+		margin-bottom: 5px;
+		// border-bottom: 1px solid @borderColor;
+		display: flex;
+		padding-left: 15px;
+		z-index: 10;
+		padding-top: 5px;
+		padding-bottom: 5px;
+
+		.syllabus-tab-item {
+			margin-right: 50px;
+			font-size: 14px;
+			color: @second-textColor;
+			cursor: pointer;
+
+			&-active {
+				// font-size: 16px;
+				font-weight: bold;
+				padding-bottom: 3px;
+				border-bottom: 2px solid var(--tabs-bottom-color);
+				color: var(--tabs-text-color);
+			}
+		}
+
+		.subject-select {
+			max-width: 80%;
+
+			&-item {
+				margin: 5px 10px;
+				display: inline-block;
+				padding: 2px 10px;
+				border: 1px solid var(--border-color);
+				cursor: pointer;
+				color: var(--primary-text-color);
+				border-radius: 5px;
+			}
+
+			.item-active {
+				background-color: var(--assist-color-light);
+				border-color: var(--assist-color-light);
+				color: white;
+			}
+		}
+
+		.btn-save-modify {
+			position: absolute;
+			right: 20px;
+			top: 5px;
+			background-color: var(--assist-color-light);
+			color: #fff;
+			border: none;
+		}
+	}
+
+	.syllabus-content {
+		height: calc(100% - 50px);
+		display: flex;
+
+		&-header {
+			height: 40px;
+			width: 100%;
+			// border-bottom: 1px solid @borderColor;
+			display: flex;
+			align-items: center;
+			padding-left: 15px;
+			color: var(--primary-text-color);
+			position: relative;
+			// font-weight: bold;
+
+
+
+			.ivu-icon {
+				color: var(--normal-icon-color);
+			}
+
+
+			&::before {
+				content: '';
+				display: inline-block;
+				border: 4px solid var(--assist-color-light);
+				border-radius: 50%;
+				margin-right: 10px;
+				margin-bottom: 3px;
+			}
+
+			&-tools {
+				position: absolute;
+				right: 0;
+				top: 10px;
+
+				.ivu-icon {
+					cursor: pointer;
+					margin-right: 15px;
+				}
+
+				.btn-batch-download {
+					font-weight: 500;
+					margin-right: 10px;
+					color: #6b6b6b;
+					cursor: pointer;
+				}
+
+				.ivu-btn {
+					background: none;
+					border: none;
+					color: #6b6b6b;
+					margin-right: 5px;
+				}
+			}
+		}
+
+		.syllabus-paper {
+			width: 80%;
+		}
+
+		.syllabus-left {
+			height: 100%;
+			width: 30%;
+			border-right: 1px solid @borderColor;
+
+			.volume-list {
+				display: flex;
+				flex-direction: column;
+				overflow: hidden;
+				padding-bottom: 60px;
+
+				.volume-item {
+					position: relative;
+					padding: 10px 0 10px 20px;
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+					border-bottom: 1px solid #efefef;
+					cursor: pointer;
+
+					&-name {
+						color: var(--primary-text-color);
+						font-size: 16px;
+						// font-weight: bold;
+						margin: 10px 0 5px 0;
+						padding-right: 10px;
+						display: flex;
+						align-items: center;
+						justify-content: space-between;
+					}
+
+
+
+					.status-idDel {
+						font-size: 12px;
+						display: inline-block;
+						padding: 0 8px;
+						background-color: @highlightColor;
+						color: #fff;
+						margin-right: 5px;
+						margin-bottom: 1px;
+						vertical-align: text-bottom;
+						border-radius: 4px;
+						zoom: 0.9;
+					}
+
+					.status-compulsory {
+						background-color: transparent;
+						color: #108d6c;
+						margin-left: 5px;
+						border: 1px solid #108d6c;
+					}
+
+					.status-coedit {
+						min-width: 54px;
+						background-color: var(--assist-color-light);
+					}
+
+					&-tools {
+						position: absolute;
+						right: 20px;
+						top: 30%;
+						opacity: 0;
+						display: flex;
+						align-items: center;
+
+						.ivu-icon {
+							font-size: 20px;
+							margin-right: 10px;
+							color: var(--normal-icon-color);
+						}
+					}
+
+					&:hover {
+						.volume-item-tools {
+							opacity: 1;
+						}
+					}
+
+					&-info {
+						margin-top: 5px;
+
+						span {
+							color: var(--second-text-color);
+							font-size: 12px;
+
+							&:nth-child(2) {
+								margin: 0 8px;
+							}
+						}
+
+						.volume-btn-agree {
+							display: inline-flex;
+							justify-content: center;
+							align-items: center;
+							padding: 3px 8px;
+							border-radius: 4px;
+							vertical-align: middle;
+
+							span {
+								display: inline-block;
+								margin: 2px 5px 0 5px;
+							}
+						}
+
+						.volume-btn-refuse {
+							display: inline-flex;
+							justify-content: center;
+							align-items: center;
+							padding: 3px 8px;
+							vertical-align: middle;
+
+							span {
+								display: inline-block;
+								margin: 2px 5px 0 5px;
+							}
+						}
+					}
+
+					&:hover {
+						.volume-active;
+					}
+				}
+			}
+		}
+
+		.syllabus-mid {
+			height: 100%;
+			width: 70%;
+			border-right: 1px solid @borderColor;
+			z-index: 1;
+
+			.syllabus-tree-box {
+				height: 100%;
+				z-index: 0;
+			}
+		}
+
+		.syllabus-right {
+                        height: 100%;
+                        position: absolute;
+                        right: 0;
+                        width: 70%;
+			border-right: 1px solid @borderColor;
+                        background: white;
+                        z-index: 30;
+
+			.syllabus-content-tab {
+				width: 100%;
+				display: flex;
+				border-bottom: 1px solid rgb(240, 240, 240);
+
+				.tab-item {
+					width: 50%;
+					display: flex;
+					justify-content: center;
+					cursor: pointer;
+				}
+
+				.label {
+					display: inline-block;
+					width: 160px;
+					text-align: center;
+					padding-bottom: 10px;
+
+					&-active {
+						border-bottom: 2px solid @primaryColor;
+					}
+				}
+			}
+
+			.syllabus-tree-box {
+				height: 100%;
+				overflow: auto;
+				padding-bottom: 50px;
+			}
+
+			.node-resource-box {
+				display: flex;
+				flex-direction: column;
+				padding: 10px 20px;
+				overflow: auto;
+
+				.node-resource-item {
+					position: relative;
+					// border-bottom: 1px solid var(--border-color);
+					padding: 10px 20px;
+					color: var(--primary-text-color);
+					display: flex;
+					align-items: center;
+					cursor: pointer;
+
+					&:hover {
+						background-color: var(--hover-text-color);
+
+						.node-resource-tools {
+							display: flex;
+						}
+					}
+
+					&:last-child {
+						border-bottom: none;
+					}
+
+					img {
+						width: 25px;
+						height: 25;
+						margin-right: 20px;
+					}
+
+					.node-resource-tools {
+						position: absolute;
+						right: 30px;
+						align-items: center;
+						display: none;
+						font-size: 12px;
+
+						.node-resource-tool {
+							display: flex;
+							align-items: center;
+							margin-right: 15px;
+							cursor: pointer;
+						}
+
+						.ivu-icon {
+							color: var(--normal-icon-color);
+							font-size: 16px;
+							margin-right: 5px;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	.volume-active {
+		/* background-image: -webkit-linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
+	    background-image: -o-linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
+	    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%); */
+		background: var(--active-item-start);
+	}
+
+	.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;
+		}
+	}
+
+	@media screen and (min-width:1280px) and (max-width:1366px) {
+		.volume-item-info {
+			margin-left: 0px !important;
+
+			span {
+				margin: 0px !important;
+			}
+		}
+	}
+}

File diff suppressed because it is too large
+ 3306 - 0
TEAMModelOS/ClientApp/src/components/hiTeachSideMenu/syllabus/index.vue


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

@@ -2118,4 +2118,55 @@ export const routes = [{
     path: '/thirdDetails',
     path: '/thirdDetails',
     component: () => import('@/view/thirdparty/details.vue'),
     component: () => import('@/view/thirdparty/details.vue'),
 },
 },
+{
+    name: 'hiTeachSideMenu',
+    path: '/hiTeachSideMenu',
+    redirect: '/hiTeachSideMenu/school/syllabus',
+    component: () => import('@/view/hiTeachSideMenu/index.vue'),
+    children: [
+        {
+            path: 'school',
+            redirect: '/hiTeachSideMenu/school/syllabus',
+            component: () => import('@/view/hiTeachSideMenu/school.vue'),
+            children: [
+                {
+                    path: 'syllabus',
+                    component: () => import('@/view/hiTeachSideMenu/school/syllabus.vue'),
+                },
+                {
+                    path: 'content',
+                    component: () => import('@/view/hiTeachSideMenu/school/content.vue'),
+                },
+                {
+                    path: 'bank',
+                    component: () => import('@/view/hiTeachSideMenu/school/bank.vue'),
+                }
+            ]
+        },
+        {
+            path: 'private',
+            redirect: '/hiTeachSideMenu/private/syllabus',
+            component: () => import('@/view/hiTeachSideMenu/private.vue'),
+            children: [
+                {
+                    path: 'syllabus',
+                    component: () => import('@/view/hiTeachSideMenu/private/syllabus.vue'),
+                },
+                {
+                    path: 'content',
+                    component: () => import('@/view/hiTeachSideMenu/private/content.vue'),
+                },
+                {
+                    path: 'bank',
+                    component: () => import('@/view/hiTeachSideMenu/private/bank.vue'),
+                }
+            ]
+        },
+        {
+            path: 'paperDownload',
+            name: 'HTPaperDownload',
+            component: () => import('@/view/hiTeachSideMenu/paperDownload.vue')
+        }
+    ]
+}
 ]
 ]

+ 13 - 0
TEAMModelOS/ClientApp/src/utils/callDesktopAppMethods.js

@@ -0,0 +1,13 @@
+export const callDesktopAppMethods = {
+    openLink(url) {
+         window.chrome.webview.hostObjects.bridge.OpenLink(url);
+    },
+
+    downloadResource(containerName, blobFileName) {
+         window.chrome.webview.hostObjects.bridge.DownloadResource(containerName, blobFileName);
+    },
+
+    openExternal(url) {
+         window.chrome.webview.hostObjects.bridge.OpenExternal(url);
+    },
+}

+ 1 - 1
TEAMModelOS/ClientApp/src/utils/excel.js

@@ -21,7 +21,7 @@ function auto_width(ws, data) {
     let result = colWidth[0];
     let result = colWidth[0];
     for (let i = 1; i < colWidth.length; i++) {
     for (let i = 1; i < colWidth.length; i++) {
         for (let j = 0; j < colWidth[i].length; j++) {
         for (let j = 0; j < colWidth[i].length; j++) {
-            if (result[j]['wch'] < colWidth[i][j]['wch']) {
+            if (result[j] && result[j]['wch'] < colWidth[i][j]['wch']) {
                 result[j]['wch'] = colWidth[i][j]['wch'];
                 result[j]['wch'] = colWidth[i][j]['wch'];
             }
             }
         }
         }

+ 2 - 2
TEAMModelOS/ClientApp/src/view/areaMgmt/AreaLayout.vue

@@ -511,7 +511,7 @@
 						child: [],
 						child: [],
 						permission: "",
 						permission: "",
 						menuName: "qingYangGp",
 						menuName: "qingYangGp",
-						isShow: this.isShowGp,
+						isShow: this.isShowGp && false,
 						to: "qingYangGp"
 						to: "qingYangGp"
 					},
 					},
 					// 资源平台
 					// 资源平台
@@ -524,7 +524,7 @@
 						permission: "",
 						permission: "",
 						menuName: "mgtPlatform",
 						menuName: "mgtPlatform",
 						child: [],
 						child: [],
-						isShow: !this.$jsFn.checkJinNiu() && !this.$jsFn.checkTrain()
+						isShow: !this.$jsFn.checkJinNiu() && !this.$jsFn.checkTrain() && false
 					},
 					},
 				];
 				];
 			},
 			},

+ 14 - 5
TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue

@@ -412,6 +412,20 @@ export default {
         setTimeout(() => {
         setTimeout(() => {
           that.dataLoading = false
           that.dataLoading = false
         }, 1000)
         }, 1000)
+      }).then(() => {
+        if (this.$route.query.params) {
+          const params = JSON.parse(this.$route.query.params);
+          const paperId = params.paperId;
+          if (!paperId) {
+            return;
+          }
+
+          const paper = this.paperList.find(p => p.id === paperId);
+          if (!paper) {
+            return; 
+          }
+          this.onPreviewPaper(paper);
+        }
       }).catch(err => {
       }).catch(err => {
         console.log(err)
         console.log(err)
         setTimeout(() => {
         setTimeout(() => {
@@ -761,11 +775,6 @@ export default {
       this.schoolInfo = schoolProfile.school_base
       this.schoolInfo = schoolProfile.school_base
       this.periodList = this.schoolInfo.period
       this.periodList = this.schoolInfo.period
     }
     }
-    // if(!this.isShowSchoolBank){
-    // 	this.$nextTick(() => {
-    // 		this.$refs.baseFilter.filterOriginChange(this.$store.state.userInfo.TEAMModelId)
-    // 	})
-    // }
   },
   },
   computed: {
   computed: {
     isSchool() {
     isSchool() {

+ 9 - 0
TEAMModelOS/ClientApp/src/view/evaluation/bank/index.vue

@@ -127,6 +127,7 @@
 	import BaseImport from "../components/BaseImport";
 	import BaseImport from "../components/BaseImport";
 	import PaperList from "./TestPaperList";
 	import PaperList from "./TestPaperList";
 	import ExerciseList from "./ExerciseList";
 	import ExerciseList from "./ExerciseList";
+
 	export default {
 	export default {
 		name: "schoolBank",
 		name: "schoolBank",
 		components: {
 		components: {
@@ -449,6 +450,14 @@
 			if (this.$route.name === "schoolBank" && this.$route.params.activePeriod) {
 			if (this.$route.name === "schoolBank" && this.$route.params.activePeriod) {
 				this.$EventBus.$emit("showSchoolBank", this.$route.params.activePeriod);
 				this.$EventBus.$emit("showSchoolBank", this.$route.params.activePeriod);
 			}
 			}
+
+			if (this.$route.query.params) {
+				const params = JSON.parse(this.$route.query.params);
+				const tab = params.tab;
+
+				this.tabName = tab;
+				this.onTabClick(tab);
+			}
 		},
 		},
 		computed: {
 		computed: {
 			isSchool() {
 			isSchool() {

File diff suppressed because it is too large
+ 537 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/index.vue


+ 13 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/paperDownload.vue

@@ -0,0 +1,13 @@
+<script>
+import PaperDownload from '@/components/hiTeachSideMenu/evaluation/bank/PaperDownload.vue'
+
+export default {
+  components: {
+    PaperDownload
+  }
+}
+</script>
+
+<template>
+  <PaperDownload />
+</template>

+ 3 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private.vue

@@ -0,0 +1,3 @@
+<template>
+    <RouterView />
+</template>

+ 14 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private/bank.vue

@@ -0,0 +1,14 @@
+<script>
+import Bank from '@/components/hiTeachSideMenu/evaluation/bank/index.vue'
+
+export default {
+    components: {
+        Bank
+    }
+}
+</script>
+
+<template>
+    <Bank />
+</template>
+

+ 13 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private/content.vue

@@ -0,0 +1,13 @@
+<script>
+import Content from '@/components/hiTeachSideMenu/content/index.vue'
+
+export default {
+    components: {
+        Content
+    }
+}
+</script>
+
+<template>
+    <Content />
+</template>

+ 13 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/private/syllabus.vue

@@ -0,0 +1,13 @@
+<script>
+import Syllabus from '@/components/hiTeachSideMenu/syllabus/index.vue'
+
+export default {
+    components: {
+        Syllabus,
+    }
+}
+</script>
+
+<template>
+    <Syllabus />
+</template>

+ 3 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school.vue

@@ -0,0 +1,3 @@
+<template>
+    <RouterView />
+</template>

+ 14 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school/bank.vue

@@ -0,0 +1,14 @@
+<script>
+import Bank from '@/components/hiTeachSideMenu/evaluation/bank/index.vue'
+
+export default {
+    components: {
+        Bank
+    }
+}
+</script>
+
+<template>
+    <Bank />
+</template>
+

+ 14 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school/content.vue

@@ -0,0 +1,14 @@
+<script>
+import Content from '@/components/hiTeachSideMenu/content/index.vue'
+
+export default {
+    components: {
+        Content
+    }
+}
+</script>
+
+<template>
+    <Content />
+</template>
+

+ 13 - 0
TEAMModelOS/ClientApp/src/view/hiTeachSideMenu/school/syllabus.vue

@@ -0,0 +1,13 @@
+<script>
+import Syllabus from '@/components/hiTeachSideMenu/syllabus/index.vue'
+
+export default {
+    components: {
+        Syllabus,
+    },
+}
+</script>
+
+<template>
+    <Syllabus />
+</template>

+ 25 - 11
TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue

@@ -417,6 +417,7 @@ export default {
   },
   },
   data() {
   data() {
     return {
     return {
+      isFirstLoadList: false,
       curSemesterTimeRange:null,
       curSemesterTimeRange:null,
       importNodes: null,
       importNodes: null,
       excelData: null,
       excelData: null,
@@ -2875,17 +2876,30 @@ export default {
   },
   },
   watch: {
   watch: {
     volumeList: {
     volumeList: {
-      handler(n, o) {
-        if (n.length === 0) {
-          this.treeOrigin = []
-          this.nodeItems = []
-          this.tabResources = []
-          this.curNode = {
-            id: '',
-            rnodes: []
-          }
-        }
-      },
+         handler(n, o) {
+             if (n.length === 0) {
+                 this.treeOrigin = []
+                 this.nodeItems = []
+                 this.tabResources = []
+                 this.curNode = {
+                     id: '',
+                     rnodes: []
+                 }
+             } else {
+                 const params = this.$route.query.params;
+
+                 if (!this.isFirstLoadList && params) {
+                     this.isFirstLoadList = true;
+                     let { subjectIndex, volumeIndex } = JSON.parse(params);
+
+                     subjectIndex = parseInt(subjectIndex) || 0;
+                     volumeIndex = parseInt(volumeIndex) || 0;
+                     this.activeSubjectIndex = subjectIndex;
+                     this.onSubjectChange(null, subjectIndex);
+                     this.onVolumeClick(this.volumeList[volumeIndex], volumeIndex);
+                 }
+             }
+          },
       immediate: true
       immediate: true
     },
     },
     '$store.state.user.curPeriod': {
     '$store.state.user.curPeriod': {