Browse Source

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

Li 3 năm trước cách đây
mục cha
commit
aea9f9d2dc
100 tập tin đã thay đổi với 22439 bổ sung666 xóa
  1. 132 0
      TEAMModelOS/ClientApp/src/api/ability.js
  2. 6 1
      TEAMModelOS/ClientApp/src/api/index.js
  3. 98 0
      TEAMModelOS/ClientApp/src/api/jyzx.js
  4. 52 0
      TEAMModelOS/ClientApp/src/api/train.js
  5. 26 3
      TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html
  6. 7 3
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css
  7. 1 1
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js
  8. 7 0
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.json
  9. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf
  10. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff
  11. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2
  12. BIN
      TEAMModelOS/ClientApp/src/assets/image/area_exam.png
  13. BIN
      TEAMModelOS/ClientApp/src/assets/image/area_study.png
  14. BIN
      TEAMModelOS/ClientApp/src/assets/image/area_survey.png
  15. BIN
      TEAMModelOS/ClientApp/src/assets/image/area_vote.png
  16. BIN
      TEAMModelOS/ClientApp/src/assets/image/bad.png
  17. BIN
      TEAMModelOS/ClientApp/src/assets/image/good.png
  18. BIN
      TEAMModelOS/ClientApp/src/assets/image/jyzx_app.png
  19. BIN
      TEAMModelOS/ClientApp/src/assets/image/normal.png
  20. BIN
      TEAMModelOS/ClientApp/src/assets/image/ok_point.png
  21. BIN
      TEAMModelOS/ClientApp/src/assets/image/school_exam.png
  22. BIN
      TEAMModelOS/ClientApp/src/assets/image/school_study.png
  23. BIN
      TEAMModelOS/ClientApp/src/assets/image/school_survey.png
  24. BIN
      TEAMModelOS/ClientApp/src/assets/image/school_vote.png
  25. BIN
      TEAMModelOS/ClientApp/src/assets/image/study_point.png
  26. 87 16
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  27. 3 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/stuAccount.js
  28. 12 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/system.js
  29. 3 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/stuAccount.js
  30. 12 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/system.js
  31. 3 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/stuAccount.js
  32. 12 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/system.js
  33. 868 598
      TEAMModelOS/ClientApp/src/router/routes.js
  34. 113 37
      TEAMModelOS/ClientApp/src/static/Global.js
  35. 86 0
      TEAMModelOS/ClientApp/src/static/course.json
  36. 22 0
      TEAMModelOS/ClientApp/src/static/diagnosis.json
  37. 3164 0
      TEAMModelOS/ClientApp/src/static/judge.json
  38. 443 0
      TEAMModelOS/ClientApp/src/static/policy.json
  39. 410 0
      TEAMModelOS/ClientApp/src/static/video.json
  40. 23 0
      TEAMModelOS/ClientApp/src/utils/time.js
  41. 42 0
      TEAMModelOS/ClientApp/src/view/ability/Ability.less
  42. 395 0
      TEAMModelOS/ClientApp/src/view/ability/Ability.vue
  43. 225 0
      TEAMModelOS/ClientApp/src/view/ability/Review.less
  44. 525 0
      TEAMModelOS/ClientApp/src/view/ability/Review.vue
  45. 115 0
      TEAMModelOS/ClientApp/src/view/ability/TestPaper.less
  46. 478 0
      TEAMModelOS/ClientApp/src/view/ability/TestPaper.vue
  47. 328 0
      TEAMModelOS/ClientApp/src/view/abilityMgmt/Index.less
  48. 2012 0
      TEAMModelOS/ClientApp/src/view/abilityMgmt/Index.vue
  49. 18 0
      TEAMModelOS/ClientApp/src/view/abilityUpload/AbilityUpload.less
  50. 96 0
      TEAMModelOS/ClientApp/src/view/abilityUpload/AbilityUpload.vue
  51. 111 0
      TEAMModelOS/ClientApp/src/view/areaMgmt/HourProg.vue
  52. 69 0
      TEAMModelOS/ClientApp/src/view/areaMgmt/Index.less
  53. 53 0
      TEAMModelOS/ClientApp/src/view/areaMgmt/Index.vue
  54. 427 0
      TEAMModelOS/ClientApp/src/view/areaMgmt/SchoolDetail.vue
  55. 266 0
      TEAMModelOS/ClientApp/src/view/areatrain/Create.less
  56. 873 0
      TEAMModelOS/ClientApp/src/view/areatrain/Create.vue
  57. 201 0
      TEAMModelOS/ClientApp/src/view/areatrain/PhoneSign.vue
  58. 158 0
      TEAMModelOS/ClientApp/src/view/areatrain/SurveyDetail.less
  59. 223 0
      TEAMModelOS/ClientApp/src/view/areatrain/SurveyDetail.vue
  60. 179 0
      TEAMModelOS/ClientApp/src/view/areatrain/TrainDetail.less
  61. 1025 0
      TEAMModelOS/ClientApp/src/view/areatrain/TrainDetail.vue
  62. 64 0
      TEAMModelOS/ClientApp/src/view/areatrain/TrainMgt.less
  63. 142 0
      TEAMModelOS/ClientApp/src/view/areatrain/TrainMgt.vue
  64. 146 0
      TEAMModelOS/ClientApp/src/view/jyzx/AllPoint.vue
  65. 197 0
      TEAMModelOS/ClientApp/src/view/jyzx/DoExam.vue
  66. 154 0
      TEAMModelOS/ClientApp/src/view/jyzx/DoSurvey.less
  67. 186 0
      TEAMModelOS/ClientApp/src/view/jyzx/DoSurvey.vue
  68. 537 0
      TEAMModelOS/ClientApp/src/view/jyzx/Question.vue
  69. 453 0
      TEAMModelOS/ClientApp/src/view/jyzx/Vote.vue
  70. 393 0
      TEAMModelOS/ClientApp/src/view/jyzx/application.vue
  71. 393 0
      TEAMModelOS/ClientApp/src/view/jyzx/classMemoir.vue
  72. 486 0
      TEAMModelOS/ClientApp/src/view/jyzx/discuss.vue
  73. 297 0
      TEAMModelOS/ClientApp/src/view/jyzx/index.less
  74. 1079 0
      TEAMModelOS/ClientApp/src/view/jyzx/index.vue
  75. 259 0
      TEAMModelOS/ClientApp/src/view/jyzx/offline.less
  76. 546 0
      TEAMModelOS/ClientApp/src/view/jyzx/offline.vue
  77. 587 0
      TEAMModelOS/ClientApp/src/view/jyzx/relatedTopic.vue
  78. 124 0
      TEAMModelOS/ClientApp/src/view/jyzx/review.vue
  79. BIN
      TEAMModelOS/ClientApp/src/view/jyzx/test.pdf
  80. 386 0
      TEAMModelOS/ClientApp/src/view/jyzx/topicPublish.vue
  81. 1 1
      TEAMModelOS/ClientApp/src/view/knowledge-point/index/Index.vue
  82. 146 0
      TEAMModelOS/ClientApp/src/view/policy/DiagDetail.vue
  83. 135 0
      TEAMModelOS/ClientApp/src/view/policy/Diagnosis.vue
  84. 248 0
      TEAMModelOS/ClientApp/src/view/policy/Leadership.vue
  85. 228 0
      TEAMModelOS/ClientApp/src/view/policy/LeadershipDetail.vue
  86. 164 0
      TEAMModelOS/ClientApp/src/view/policy/Literacy.vue
  87. 161 0
      TEAMModelOS/ClientApp/src/view/policy/LiteracyDetail.vue
  88. 61 0
      TEAMModelOS/ClientApp/src/view/policy/Policy.less
  89. 139 0
      TEAMModelOS/ClientApp/src/view/policy/Policy.vue
  90. 157 0
      TEAMModelOS/ClientApp/src/view/policy/Probation.vue
  91. 116 0
      TEAMModelOS/ClientApp/src/view/statistics/AbilityPoint.vue
  92. 332 0
      TEAMModelOS/ClientApp/src/view/statistics/GroupData.vue
  93. 47 0
      TEAMModelOS/ClientApp/src/view/statistics/Hour.less
  94. 214 0
      TEAMModelOS/ClientApp/src/view/statistics/Hour.vue
  95. 153 0
      TEAMModelOS/ClientApp/src/view/statistics/Percent.vue
  96. 51 0
      TEAMModelOS/ClientApp/src/view/statistics/Result.less
  97. 188 0
      TEAMModelOS/ClientApp/src/view/statistics/Result.vue
  98. 26 0
      TEAMModelOS/ClientApp/src/view/statistics/Statistics.less
  99. 34 0
      TEAMModelOS/ClientApp/src/view/statistics/Statistics.vue
  100. 0 0
      TEAMModelOS/ClientApp/src/view/statistics/Training.less

+ 132 - 0
TEAMModelOS/ClientApp/src/api/ability.js

@@ -0,0 +1,132 @@
+import { post } from '@/api/http'
+export default {
+    // 更新册别
+    SaveOrUpdateVolume: function(data) {
+        return post('/research/ability/upsert', data)
+    },
+	FindVolumes:function(data) {
+        return post('/research/ability/find', data)
+    },
+	FindAbilityById:function(data) {
+	    return post('/research/ability/find-id', data)
+	},
+	DeleteVolume:function(data) {
+	    return post('/research/ability/delete', data)
+	},
+	GetTreeByVolume:function(data) {
+	    return post('/research/ability-task/find-id', data)
+	},
+	UpsertTree:function(data) {
+	    return post('/research/ability-task/upsert-tree', data)
+	},
+	DeleteTree:function(data) {
+	    return post('/research/ability-task/delete', data)
+	},
+	ShareTree:function(data) {
+	    return post('/teacher/share/to', data)
+	},
+	FindShare:function(data) {
+	    return post('/teacher/share/find', data)
+	},
+	ViewShare:function(data) {
+	    return post('/teacher/share/view-share', data)
+	},
+	ShareAgree:function(data) {
+	    return post('/teacher/share/agree-share', data)
+	},
+	CheckLink:function(data) {
+	    return post('/common/syllabus/check-link', data)
+	},
+	// 查找知识块数量
+	FindBlockCount: function (data) {
+		return post('/knowledges/find-count', data)
+	},
+	/* 问卷相关 */
+	getSurveyList:function (data) {
+		return post('/common/TrSurvey/find', data)
+	},
+	getSurveySummary:function (data) {
+		return post('/common/TrSurvey/find-summary', data)
+	},
+	saveSurvey:function (data) {
+		return post('/common/TrSurvey/save', data)
+	},
+	deleteSurvey:function (data) {
+		return post('/common/TrSurvey/delete', data)
+	},
+	/* 投票相关 */
+	getVoteList:function (data) {
+		return post('/common/TrVote/find', data)
+	},
+	getVoteSummary:function (data) {
+		return post('/common/TrVote/find-summary', data)
+	},
+	saveVote:function (data) {
+		return post('/common/TrVote/save', data)
+	},
+	deleteVote:function (data) {
+		return post('/common/TrVote/delete', data)
+	},
+	/* 评测相关 */
+	getExamList:function (data) {
+		return post('/common/TrExam/find', data)
+	},
+	getExamSummary:function (data) {
+		return post('/common/TrExam/find-summary', data)
+	},
+	saveExam:function (data) {
+		return post('/common/TrExam/save', data)
+	},
+	deleteExam:function (data) {
+		return post('/common/TrExam/delete', data)
+	},
+    
+	//数据统计API
+	FindTotalData: function (data) {
+		return post('/research/ability/get-subs-statistics', data)
+	},
+	//校本研修审核学时
+	AuditStudy: function (data) {
+		return post('/common/study/audit', data)
+	},
+	getSchList: function (data) {
+		return post('/school/area/find', data)
+	},
+	
+	//根据区id查询学校及分组情况
+	findAreaGroup(data){
+		return post('/school/area/find-group', data)
+	},
+	
+	//根据区id查询学校及分组情况
+	saveAreaVote(data){
+		return post('/school/area/save-vote', data)
+	},
+	
+	getAreaVotes(data){
+		return post('/school/area/find-all-vote', data)
+	},
+	
+	//根据区id查询学校及分组情况
+	saveAreaSurveys(data){
+		return post('/school/area/save-survey', data)
+	},
+	
+	getAreaSurveys(data){
+		return post('/school/area/find-all-survey', data)
+	},
+	
+	getAreaVoteTeachers(data){
+		return post('/school/area/find-all-vote-teachers', data)
+	},
+	
+	getAreaSurveyTeachers(data){
+		return post('/school/area/find-all-survey-teachers', data)
+	},
+	
+	
+	
+	
+	
+	
+}

+ 6 - 1
TEAMModelOS/ClientApp/src/api/index.js

@@ -31,6 +31,9 @@ import mark from './mark'
 import openMgmt from './openMgmt'
 import service from './service'
 import notice from './notice'
+import ability from './ability';
+import jyzx from './jyzx'
+import train from './train';
 
 export default {
     accessToken,
@@ -63,7 +66,9 @@ export default {
     openMgmt,
 	service,
     notice,
-
+	 jyzx,
+	ability,
+	train,
     // 获取登录跳转链接
     getLoginLink: function (data) {
         return post('api/login/login', data)

+ 98 - 0
TEAMModelOS/ClientApp/src/api/jyzx.js

@@ -0,0 +1,98 @@
+import { fetch, post } from '@/api/http'
+export default {
+    /**
+     * 教研中心
+     * 
+     */
+    //发表话题
+    topicInitiate: function (data) {
+        return post('/school/debate/upsert', data)
+    },
+    //所有话题
+    totalTopic: function (data) {
+        return post('/school/debate/find', data)
+    },
+    //回复话题
+    replyTopic: function (data) {
+        return post('/school/debate/reply', data)
+    },
+    //显示回复详细
+    replyDetail: function (data) {
+        return post('/school/debate/find-id', data)
+    },
+    // 删除话题
+    delReply: function (data) {
+        return post("/school/debate/delete", data)
+    },
+    // 已有的能力点
+    getPoint: function (data) {
+        return post("/research/ability/get-subs", data)
+    },
+    // 已有的能力点
+    getOtherSubs: function (data) {
+        return post("/research/ability/get-group-subs", data)
+    },
+    // 针对能力点的操作
+    pointAction: function (data) {
+        return post("/research/ability/sub-opt", data)
+    },
+    //获取能力点树状
+    getztree: function (data) {
+        return post('/research/ability-task/find-id', data)
+    },
+    // 勾选能力点
+    checkPoint: function (data) {
+        return post("/research/ability/save-subs", data)
+    },
+    //获取上传的课堂实录
+    getmemoir: function (data) {
+        return post('/research/class-video/opt', data)
+    },
+    //获取上传的课堂实录
+    getAllVideo: function (data) {
+        return post('/research/class-video/get-all', data)
+    },
+    saveVideoAppraise: function (data) {
+        return post('/research/appraise/opt', data)
+    },
+    //获取所有能力点
+    getAlltrait: function (data) {
+        return post('/research/ability/find', data)
+    },
+    // 获取学时
+    getStudyTime: function (data) {
+        return post("/research/ability/statistics-self", data)
+    },
+    //获取老师的问卷调查
+    getquestion: function (data) {
+        return post('/common/TrSurvey/find-by-teacher', data)
+    },
+    //获取老师当前问卷信息
+    getcurrents: function (data) {
+        return post('/common/TrSurvey/find-summary-by-teacher', data)
+    },
+    //提交问卷
+    submitquestionnaire: function (data) {
+        return post('/common/TrSurvey/record-in', data)
+    },
+    //提交评测
+    submitExam: function (data) {
+        return post('/common/TrExam/record-in',data)
+    },
+    // 投票列表
+    getVoteList: function (data) {
+        return post("/common/TrVote/find-by-teacher", data)
+    },
+    // 投票信息
+    getSurveyList: function (data) {
+        return post("/common/TrVote/find-summary-by-teacher", data)
+    },
+    // 投票
+    setVote: function (data) {
+        return post("/common/TrVote/record-in", data)
+    },
+    //同组课堂实录
+    getTeamclass: function (data) {
+        return post('/research/class-video/get-group',data)
+    },
+}

+ 52 - 0
TEAMModelOS/ClientApp/src/api/train.js

@@ -0,0 +1,52 @@
+import { post } from '@/api/http'
+export default {
+    // 保存研修活动
+    saveTrain: function (data) {
+        return post('/common/study/save', data)
+    },
+    // 删除研修活动
+    delTrain: function (data) {
+        return post('/common/study/delete', data)
+    },
+    // 查询研修活动
+    findTrainList: function (data) {
+        return post('/common/study/find', data)
+    },
+    // 查询研修活动
+    findSummaryTrain: function (data) {
+        return post('/common/study/find-summary', data)
+    },
+    // 查询研修活动
+    findTrainByTeacher: function (data) {
+        return post('/common/study/find-by-teacher', data)
+    },
+    // 根据活动id 查询信息
+    findTrainById: function (data) {
+        return post("/common/study/find-summary-by-teacher", data)
+    },
+    // 上传作业
+    uploadHw: function (data) {
+        return post("/common/study/upload", data)
+    },
+    // 签到
+    signIn: function (data) {
+        return post("/common/study/sign-in", data)
+    },
+    //区级研修
+	saveAreaTrain: function (data) {
+		return post('/school/area/save-study', data)
+	},
+	findAreaTrain: function (data) {
+		return post('/school/area/find-all-study', data)
+	},
+    //查看区级活动对应的所有学校活动
+    findAreaAllTrain: function (data) {
+		return post('/school/area/find-all-study-teachers', data)
+	},
+    //删除区级活动
+    delAreaTrain: function (data) {
+		return post('/school/area/delete', data)
+	},
+
+}
+

+ 26 - 3
TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html

@@ -54,6 +54,12 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe607;</span>
+                <div class="name">教研</div>
+                <div class="code-name">&amp;#xe607;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe64f;</span>
                 <div class="name">公告</div>
@@ -912,9 +918,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1629253044790') format('woff2'),
-       url('iconfont.woff?t=1629253044790') format('woff'),
-       url('iconfont.ttf?t=1629253044790') format('truetype');
+  src: url('iconfont.woff2?t=1630551723231') format('woff2'),
+       url('iconfont.woff?t=1630551723231') format('woff'),
+       url('iconfont.ttf?t=1630551723231') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -940,6 +946,15 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont icon-train"></span>
+            <div class="name">
+              教研
+            </div>
+            <div class="code-name">.icon-train
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont icon-notify"></span>
             <div class="name">
@@ -2227,6 +2242,14 @@
       <div class="content symbol">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-train"></use>
+                </svg>
+                <div class="name">教研</div>
+                <div class="code-name">#icon-train</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-notify"></use>

+ 7 - 3
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2000444 */
-  src: url('iconfont.woff2?t=1629253044790') format('woff2'),
-       url('iconfont.woff?t=1629253044790') format('woff'),
-       url('iconfont.ttf?t=1629253044790') format('truetype');
+  src: url('iconfont.woff2?t=1630551723231') format('woff2'),
+       url('iconfont.woff?t=1630551723231') format('woff'),
+       url('iconfont.ttf?t=1630551723231') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,10 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-train:before {
+  content: "\e607";
+}
+
 .icon-notify:before {
   content: "\e64f";
 }

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 1
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js


+ 7 - 0
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.json

@@ -5,6 +5,13 @@
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "6662454",
+      "name": "教研",
+      "font_class": "train",
+      "unicode": "e607",
+      "unicode_decimal": 58887
+    },
     {
       "icon_id": "4437595",
       "name": "公告",

BIN
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf


BIN
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff


BIN
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2


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


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


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


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


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


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


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


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


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


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


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


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


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


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


+ 87 - 16
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -333,72 +333,72 @@ export default {
                 },
                 // 研修中心
                 {
-                    icon: 'iconfont icon-activityS',
-                    name: '研修中心',
+                    icon: 'iconfont icon-train',
+                    name: this.$t('system.menu.train'),
                     router: '',
                     role: 'admin',
                     permission: '',
-                    subName: 'train',
+                    subName: 'scTrain',
                     child: [
                         {
                             icon: 'iconfont icon-test',
-                            name: '研修数据',
+                            name: this.$t('system.menu.trainData'),
                             router: '',
                             tag: '',
                             role: 'admin',
                             permission: '',
                             menuName: '',
-                            isShow: true
+                            isShow: false
                         },
                         {
                             icon: 'iconfont icon-test',
-                            name: '微能力点',
-                            router: '',
+                            name: this.$t('system.menu.abilityPoint'),
+                            router: '/home/abilityMgmt',
                             tag: '',
                             role: 'admin',
                             permission: '',
-                            menuName: '',
+                            menuName: 'abilityMgmt',
                             isShow: true
                         },
                         {
                             icon: 'iconfont icon-test',
-                            name: '校本研修',
+                            name: this.$t('system.menu.scTrain'),
                             router: '',
                             tag: '',
                             role: 'admin',
                             permission: '',
                             menuName: '',
-                            isShow: true
+                            isShow: false
                         },
                         {
                             icon: 'iconfont icon-test',
-                            name: '应用能力',
+                            name: this.$t('system.menu.appAbility'),
                             router: '',
                             tag: '',
                             role: 'admin',
                             permission: '',
                             menuName: '',
-                            isShow: true
+                            isShow: false
                         },
                         {
                             icon: 'iconfont icon-test',
-                            name: '课堂实录',
+                            name: this.$t('system.menu.classRecord'),
                             router: '',
                             tag: '',
                             role: 'admin',
                             permission: '',
                             menuName: '',
-                            isShow: true
+                            isShow: false
                         },
                         {
                             icon: 'iconfont icon-test',
-                            name: '资源中心',
+                            name: this.$t('system.menu.resourceCenter'),
                             router: '',
                             tag: '',
                             role: 'admin',
                             permission: '',
                             menuName: '',
-                            isShow: true
+                            isShow: false
                         }
                     ]
                 },
@@ -556,6 +556,77 @@ export default {
                         // }
                     ]
                 },
+                // 个人研修
+                {
+                    icon: 'iconfont icon-train',
+                    name: this.$t('system.menu.train'),
+                    router: '',
+                    role: 'teacher|admin',
+                    permission: '',
+                    subName: 'privTrain',
+                    child: [
+                        {
+                            icon: 'iconfont icon-test',
+                            name: this.$t('system.menu.trainCount'),
+                            router: '',
+                            tag: '',
+                            role: 'teacher|admin',
+                            permission: '',
+                            menuName: '',
+                            isShow: false
+                        },
+                        {
+                            icon: 'iconfont icon-test',
+                            name: this.$t('system.menu.onlineTrain'),
+                            router: '',
+                            tag: '',
+                            role: 'teacher|admin',
+                            permission: '',
+                            menuName: '',
+                            isShow: false
+                        },
+                        {
+                            icon: 'iconfont icon-test',
+                            name: this.$t('system.menu.scTrain'),
+                            router: '',
+                            tag: '',
+                            role: 'teacher|admin',
+                            permission: '',
+                            menuName: '',
+                            isShow: false
+                        },
+                        {
+                            icon: 'iconfont icon-test',
+                            name: this.$t('system.menu.appAssassment'),
+                            router: '',
+                            tag: '',
+                            role: 'teacher|admin',
+                            permission: '',
+                            menuName: '',
+                            isShow: false
+                        },
+                        {
+                            icon: 'iconfont icon-test',
+                            name: this.$t('system.menu.classRecord'),
+                            router: '',
+                            tag: '',
+                            role: 'teacher|admin',
+                            permission: '',
+                            menuName: '',
+                            isShow: false
+                        },
+                        {
+                            icon: 'iconfont icon-test',
+                            name: this.$t('system.menu.discuss'),
+                            router: '',
+                            tag: '',
+                            role: 'teacher|admin',
+                            permission: '',
+                            menuName: '',
+                            isShow: false
+                        }
+                    ]
+                },
 
                 // 任务列表(暂时隐藏)
                 {

+ 3 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/stuAccount.js

@@ -86,7 +86,7 @@ export default {
   importInfo4: 'No corresponding classroom:',
   importInfo5: 'The account already exists in the system:',
   importInfo6: 'Can be imported:',
-  passwordTips: '* Items with no password found will default to the same password as the account/student ID',
+  passwordTips: 'Items with no password found will default to the same password as the account/student ID',
   submitList: 'Create Account',
   lackAttr:'Incomplete Excel Column:',
   noSetNo:'No Seat Number:',
@@ -99,6 +99,8 @@ export default {
   setNoErr:"Error: Seat number has been repeated within the school",
   downloadText:'(Download List Sample)',
   idRepErr:'Account already exists, will overwrite the original account',
+  stuYearErr:'學生學級數據錯誤',
+  classYearErr:'班級年級錯誤',
 
   // Authorization.vue
   authTitle: 'Service Authorization Management',

+ 12 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/system.js

@@ -46,7 +46,18 @@ export default {
         homework:'Assignmnet',
         acRecord: 'Activity Record',
         cusMgt:'Course',
-        taskList:'Task List'
+        taskList:'Task List',
+        train:'研修中心',
+        trainData:'研修數據',
+        abilityPoint:'微能力點',
+        scTrain:'校本研修',
+        appAbility:'應用能力',
+        classRecord:'課堂實錄',
+        resourceCenter:'資源中心',
+        trainCount:'研修統計',
+        onlineTrain:'線上研修',
+        appAssassment:'應用考核',
+        discuss:'討論中心'
     },
     compt: {
         cusWare: 'Teaching Material',

+ 3 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/stuAccount.js

@@ -86,7 +86,7 @@ export default {
   importInfo4: '没有对应教室:',
   importInfo5: '系统已存在账号:',
   importInfo6: '可导入:',
-  passwordTips: '*未找到密码的项目将默认密码与账号相同',
+  passwordTips: '未导入密码的学生将默认密码与账号相同',
   submitList: '建立账号',
   lackAttr:'Excel栏位有缺:',
   noSetNo:'沒有座号:',
@@ -99,6 +99,8 @@ export default {
   setNoErr:"错误:座位号已在校內重复",
   downloadText:'(下载名单模板)',
   idRepErr:'账号已存在,将覆盖原有账号',
+  stuYearErr:'学生学级数据错误',
+  classYearErr:'班级年级错误',
 
   // Authorization.vue
   authTitle: '服务授权管理',

+ 12 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/system.js

@@ -47,7 +47,18 @@ export default {
         homework:'作业活动',
         acRecord: '活动记录',
         cusMgt:'课程管理',
-        taskList:'任务列表'
+        taskList:'任务列表',
+        train:'研修中心',
+        trainData:'研修数据',
+        abilityPoint:'微能力点',
+        scTrain:'校本研修',
+        appAbility:'应用能力',
+        classRecord:'课堂实录',
+        resourceCenter:'资源中心',
+        trainCount:'研修统计',
+        onlineTrain:'线上研修',
+        appAssassment:'应用考核',
+        discuss:'讨论中心'
     },
     compt: {
         cusWare: '课件',

+ 3 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/stuAccount.js

@@ -86,7 +86,7 @@ export default {
   importInfo4: '沒有對應教室:',
   importInfo5: '系統已存在帳號:',
   importInfo6: '可匯入:',
-  passwordTips: '*未找到密碼的項目將預設密碼與帳號相同',
+  passwordTips: '未導入密碼的學生將默認密碼與賬號相同',
   submitList: '建立帳號',
   lackAttr:'Excel欄位有缺:',
   noSetNo:'沒有座號:',
@@ -99,6 +99,8 @@ export default {
   setNoErr:"錯誤:座位號已在校內重複",
   downloadText:'(下載名單模板)',
   idRepErr:'帳號已存在,將覆蓋原有帳號',
+  stuYearErr:'學生學級數據錯誤',
+  classYearErr:'班級年級錯誤',
 
   //Authorization.vue
   authTitle: '服務授權管理',

+ 12 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/system.js

@@ -46,7 +46,18 @@ export default {
         homework: '工作活動',
         acRecord: '活動記錄',
         cusMgt: '課程管理',
-        taskList: '任務清單'
+        taskList: '任務清單',
+        train:'研修中心',
+        trainData:'研修數據',
+        abilityPoint:'微能力點',
+        scTrain:'校本研修',
+        appAbility:'應用能力',
+        classRecord:'課堂實錄',
+        resourceCenter:'資源中心',
+        trainCount:'研修統計',
+        onlineTrain:'線上研修',
+        appAssassment:'應用考核',
+        discuss:'討論中心'
     },
     compt: {
         cusWare: '教材',

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 868 - 598
TEAMModelOS/ClientApp/src/router/routes.js


+ 113 - 37
TEAMModelOS/ClientApp/src/static/Global.js

@@ -33,49 +33,46 @@ const EXAM_TYPE = () => {
 }
 //系统固定评测类型
 const EV_TYPE = () => {
-	return [
-		{
-			value: 'regular',
-			label: i18n.t('global.evType1')
-		},
-		{
-			value: 'simulation',
-			label: i18n.t('global.evType2')
-		},
-		{
-			value: 'normal',
-			label: i18n.t('global.evType3')
-		}
+	return [{
+		value: 'regular',
+		label: i18n.t('global.evType1')
+	},
+	{
+		value: 'simulation',
+		label: i18n.t('global.evType2')
+	},
+	{
+		value: 'normal',
+		label: i18n.t('global.evType3')
+	}
 	]
 }
 //系统固定评测类型
 const EV_MODE = () => {
-	return [
-		{
-			value: '0',
-			label: i18n.t('global.evMode1')
-		},
-		{
-			value: '1',
-			label: i18n.t('global.evMode2')
-		},
-		{
-			value: '2',
-			label: i18n.t('global.evMode3')
-		}
+	return [{
+		value: '0',
+		label: i18n.t('global.evMode1')
+	},
+	{
+		value: '1',
+		label: i18n.t('global.evMode2')
+	},
+	{
+		value: '2',
+		label: i18n.t('global.evMode3')
+	}
 	]
 }
 //系统固定评测发布方式
 const PUBLISH_TYPE = () => {
-	return [
-		{
-			value: '0',
-			label: i18n.t('global.publishType1')
-		},
-		{
-			value: '1',
-			label: i18n.t('global.publishType2')
-		}
+	return [{
+		value: '0',
+		label: i18n.t('global.publishType1')
+	},
+	{
+		value: '1',
+		label: i18n.t('global.publishType2')
+	}
 	]
 }
 
@@ -116,6 +113,81 @@ const EXERCISE_LEVELS = () => {
 	]
 }
 
+/* 维度定义 */
+const DIMENSIONS = () => {
+	return [{
+		"code": "2438e72f-4de8-4ccb-8cae-3f1dce89a769",
+		"val": "学情分析"
+	},
+	{
+		"code": "44e25d11-101f-49bf-b60a-e41c833f0fa1",
+		"val": "教学设计"
+	},
+	{
+		"code": "7371e4fb-a470-49dd-8bf1-8c74516a8630",
+		"val": "学法指导"
+	},
+	{
+		"code": "430b9157-b771-4322-a5f1-e1737df9461f",
+		"val": "学业评价"
+	}
+	]
+}
+
+/* 维度定义 */
+const VIDEO_DIMENSIONS = () => {
+	return [{
+		"code": "24D43BAE-71A4-BC19-882E-D995260517FE",
+		"val": "教学设计"
+	},
+	{
+		"code": "49221BDE-561A-9CBB-236B-0DDA58D25D95",
+		"val": "教学过程"
+	},
+	{
+		"code": "2A144531-EC47-5405-0BC2-DE39C6C592E8",
+		"val": "融合创新"
+	},
+	{
+		"code": "BD6B7AC6-F492-32BC-A9C983636E744A97",
+		"val": "技术应用"
+	},
+	{
+		"code": "0B3D04A1-4C23-225F-B839-FE2D8FAC3921",
+		"val": "教学效果"
+	}
+	]
+}
+
+const TRAIN_TYPE = () => {
+	return [
+		{
+			label: '信息化教学案例展示与分享',
+			value: 1
+		},
+		{
+			label: '专家专题培训',
+			value: 2
+		},
+		{
+			label: '同课同构',
+			value: 3
+		},
+		{
+			label: '同课异构',
+			value: 4
+		},
+		{
+			label: '校本2.0培训',
+			value: 5
+		},
+		{
+			label: '自定义活动',
+			value: 6
+		}
+	]
+}
+
 const GLOBAL = {
 	CONTENT_TYPES,
 	PRIVATE_SPACE,
@@ -128,7 +200,9 @@ const GLOBAL = {
 	EV_TYPE,
 	EV_MODE,
 	PUBLISH_TYPE,
-	NotSupport
+	DIMENSIONS,
+	VIDEO_DIMENSIONS,
+	TRAIN_TYPE
 }
 
 const install = (Vue, options) => {
@@ -140,4 +214,6 @@ export default {
 	install
 
 }
-export { GLOBAL }
+export {
+	GLOBAL
+}

+ 86 - 0
TEAMModelOS/ClientApp/src/static/course.json

@@ -0,0 +1,86 @@
+[
+  {
+    "link": "/jyzx/a5125c2d-3131-9514-7b29-b50066959a91/1.1案例导入.mp4",
+    "title": "1.1案例导入",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/1.jpg"
+  },
+  {
+    "link": "/jyzx/d1f8519f-dd44-3604-4962-8730a351e151/1.2 关键词“以校为本”.mp4",
+    "title": "1.2 关键词“以校为本”",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/2.jpg"
+  },
+  {
+    "link": "/jyzx/9612794f-599c-088b-bdf6-27b812221a1e/1.3 关键词“基于课堂”.mp4",
+    "title": "1.3 关键词“基于课堂”",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/3.jpg"
+  },
+  {
+    "link": "/jyzx/246b745e-9cb6-141a-611b-518c4b1b6fcd/1.4 关键词“应用驱动”.mp4",
+    "title": "1.4 关键词“应用驱动”",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/4.jpg"
+  },
+  {
+    "link": "/jyzx/35d4b310-05c5-ef88-d6e8-4d9eac44ec25/1.5 关键词“精准测评”(上).mp4",
+    "title": "1.5 关键词“精准测评”(上)",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/5.jpg"
+  },
+  {
+    "link": "/jyzx/b5b7461f-25d2-a49a-8e60-0894b066576f/1.6 关键词“精准测评”(下).mp4",
+    "title": "1.6 关键词“精准测评”(下)",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/6.jpg"
+  },
+  {
+    "link": "/jyzx/328be38b-cd85-aed6-aa20-83142b94d75f/2.1 认识测评体系 1.mp4",
+    "title": "2.1 认识测评体系 1",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/7.jpg"
+  },
+  {
+    "link": "/jyzx/328be38b-cd85-aed6-aa20-83142b94d75f/2.1 认识测评体系 2.mp4",
+    "title": "2.1 认识测评体系 2",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/8.jpg"
+  },
+  {
+    "link": "/jyzx/328be38b-cd85-aed6-aa20-83142b94d75f/2.1.1多媒体教学环境(上.mp4",
+    "title": "2.1.1多媒体教学环境(上)",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/9.jpg"
+  },
+  {
+    "link": "/jyzx/328be38b-cd85-aed6-aa20-83142b94d75f/2.1.2多媒体教学环境(下).mp4",
+    "title": "2.1.2多媒体教学环境(下)",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/10.jpg"
+  },
+  {
+    "link": "/jyzx/c687e4df-b74b-882d-4bd1-7f7342625987/2.2.1混合学习环境(上).mp4",
+    "title": "2.2.1混合学习环境(上",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/11.jpg"
+  },
+  {
+    "link": "/jyzx/c687e4df-b74b-882d-4bd1-7f7342625987/2.2.2混合学习环境(下).mp4",
+    "title": "2.2.2混合学习环境(下)",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/12.jpg"
+  },
+  {
+    "link": "/jyzx/c687e4df-b74b-882d-4bd1-7f7342625987/2.3智慧学习环境.mp4",
+    "title": "2.3智慧学习环境",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/13.jpg"
+  },
+  {
+    "link": "/jyzx/d7766778-67bd-5544-2402-35fedf21f0d7/2.4测评参与建议.mp4",
+    "title": "2.4测评参与建议",
+    "time": "2021-01-22",
+    "poster":"https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/14.jpg"
+  }
+]

+ 22 - 0
TEAMModelOS/ClientApp/src/static/diagnosis.json

@@ -0,0 +1,22 @@
+[
+  {
+    "link": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E3%80%8A%E5%AD%A6%E6%A0%A1%E4%BF%A1%E6%81%AF%E5%8C%96%E8%AF%8A%E6%96%AD%E4%B8%8E%E8%A7%84%E5%88%92%E5%88%B6%E5%AE%9A%E3%80%8B%E8%AF%BE%E7%A8%8B%E4%B8%80%EF%BC%88%E7%8E%AF%E5%A2%83%E4%B8%8E%E6%94%BF%E7%AD%96%E7%AF%87%EF%BC%89.mp4",
+    "poster": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E5%9B%BE1%EF%BC%9A%E7%8E%AF%E5%A2%83%E4%B8%8E%E6%94%BF%E7%AD%96%E7%AF%87%E5%B0%81%E9%9D%A2.png",
+    "title": "《学校信息化诊断与规划制定》课程一(环境与政策篇)"
+  },
+  {
+    "link": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E3%80%8A%E5%AD%A6%E6%A0%A1%E4%BF%A1%E6%81%AF%E5%8C%96%E8%AF%8A%E6%96%AD%E4%B8%8E%E8%A7%84%E5%88%92%E5%88%B6%E5%AE%9A%E3%80%8B%E8%AF%BE%E7%A8%8B%E4%BA%8C%EF%BC%88%E5%9B%A2%E9%98%9F%E4%BA%BA%E5%91%98%E7%AF%87%EF%BC%89.mp4",
+    "poster": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E5%9B%BE2%EF%BC%9A%E5%9B%A2%E9%98%9F%E4%BA%BA%E5%91%98%E7%AF%87%E5%B0%81%E9%9D%A2.png",
+    "title": "《学校信息化诊断与规划制定》课程二(团队人员篇)"
+  },
+  {
+    "link": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E3%80%8A%E5%AD%A6%E6%A0%A1%E4%BF%A1%E6%81%AF%E5%8C%96%E8%AF%8A%E6%96%AD%E4%B8%8E%E8%A7%84%E5%88%92%E5%88%B6%E5%AE%9A%E3%80%8B%E8%AF%BE%E7%A8%8B%E4%B8%89%EF%BC%88%E5%8F%91%E5%B1%95%E8%A7%84%E5%88%92%E7%AF%87%EF%BC%89.mp4",
+    "poster": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E5%9B%BE3%EF%BC%9A%E5%8F%91%E5%B1%95%E8%A7%84%E5%88%92%E7%AF%87%E5%B0%81%E9%9D%A2.png",
+    "title": "《学校信息化诊断与规划制定》课程三(发展规划篇)"
+  },
+  {
+    "link": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E3%80%8A%E5%AD%A6%E6%A0%A1%E4%BF%A1%E6%81%AF%E5%8C%96%E8%AF%8A%E6%96%AD%E4%B8%8E%E8%A7%84%E5%88%92%E5%88%B6%E5%AE%9A%E3%80%8B%E8%AF%BE%E7%A8%8B%E5%9B%9B%EF%BC%88%E2%80%9D%E6%A0%A1%E6%9C%AC%E7%A0%94%E4%BF%AE%E4%B8%8E%E8%80%83%E6%A0%B8%E2%80%9C%E4%B8%A4%E6%A1%88%E7%AF%87%EF%BC%89.mp4",
+    "poster": "https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/video/%E8%AF%8A%E6%96%AD/%E5%9B%BE4%EF%BC%9A%E4%B8%A4%E6%A1%88%E7%AF%87%E5%B0%81%E9%9D%A2.png",
+    "title": "《学校信息化诊断与规划制定》课程四(”校本研修与考核“两案篇)"
+  }
+]

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 3164 - 0
TEAMModelOS/ClientApp/src/static/judge.json


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 443 - 0
TEAMModelOS/ClientApp/src/static/policy.json


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 410 - 0
TEAMModelOS/ClientApp/src/static/video.json


+ 23 - 0
TEAMModelOS/ClientApp/src/utils/time.js

@@ -0,0 +1,23 @@
+export function formatDate(date, fmt) {
+    if (/(y+)/.test(fmt)) {
+        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
+    }
+    let o = {
+        'M+': date.getMonth() + 1,
+        'd+': date.getDate(),
+        'h+': date.getHours(),
+        'm+': date.getMinutes(),
+        's+': date.getSeconds()
+    }
+    for (let k in o) {
+        if (new RegExp(`(${k})`).test(fmt)) {
+            let str = o[k] + ''
+            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str))
+        }
+    }
+    return fmt
+}
+
+function padLeftZero(str) {
+    return ('00' + str).substr(str.length)
+}

+ 42 - 0
TEAMModelOS/ClientApp/src/view/ability/Ability.less

@@ -0,0 +1,42 @@
+.ability-container {
+		padding: 20px;
+		height: 100vh;
+		overflow: auto;
+		padding-bottom: 100px;
+		font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+
+		.table-filter {
+			margin: 10px 0;
+
+			.ivu-select {
+				margin-right: 10px;
+			}
+		}
+		
+		.btn-export{
+			float: right;
+			margin-right: 20px;
+			color: #707070;
+			cursor: pointer;
+		}
+
+		.ivu-table {
+			font-size: 14px;
+		}
+		
+		.appraise-detail{
+			padding: 20px;
+			
+			.detail-title{
+				font-size: 20px;
+				font-weight: bold;
+				margin-bottom: 20px;
+				
+				.ivu-icon{
+					font-size: 24px;
+					margin-right: 10px;
+					cursor: pointer;
+				}
+			}
+		}
+	}

+ 395 - 0
TEAMModelOS/ClientApp/src/view/ability/Ability.vue

@@ -0,0 +1,395 @@
+<template>
+    <div class="ability-container">
+        <div v-if="!isShowDetail && !isShowReview">
+            <div class="table-filter">
+                <Select v-model="curAbility" style="width:250px" @on-change="onAbilityFilter">
+                    <Option v-for="(item,index) in abilityList" :value="index" :key="index">{{ item.no }} {{ item.name }}</Option>
+                </Select>
+                <Select v-model="curGroup" style="width:200px" @on-change="onGroupFilter">
+                    <Option v-for="(item,index) in groupList" :value="item.groupName" :key="index">{{ item.groupName }}</Option>
+                </Select>
+                <Input v-model="searchVal" suffix="ios-search" placeholder="搜索姓名..." style="width: auto" @on-change="onSearch" />
+                <span class="btn-export" @click="exportData()">
+                    <Icon type="md-download" /> 测评结果下载
+                </span>
+            </div>
+            <Table :columns="ablitiesColumn" :data="origin" border :span-method="handleSpan" :loading="tableLoading" ref="table">
+                <template slot-scope="{ row, index }" slot="schoolAppraise">
+                    <span v-show="row.schoolAppraise === 2" style="color: #007700;">优秀</span>
+                    <span v-show="row.schoolAppraise === 1" style="color: orangered;">合格</span>
+                    <span v-show="row.schoolAppraise === 0" style="color: red;">不合格</span>
+                    <span v-show="row.schoolAppraise === '-'">-</span>
+                </template>
+                <template slot-scope="{ row, index }" slot="action">
+                    <Button type="info" size="small" style="margin-right: 5px;font-size: 12px;" @click="goAppraiseDetail(row)">评价详情</Button>
+                    <Button type="primary" size="small" style="margin-right: 5px;font-size: 12px;" @click="goReview(row)">评审</Button>
+                </template>
+            </Table>
+        </div>
+        <div class="animated appraise-detail" v-if="isShowDetail">
+            <div class="detail-title">
+                <Icon type="md-arrow-back" @click="isShowDetail = false" />
+                <span>{{ curData.tmdname }} 【 {{ curData.ability }} 】</span>
+            </div>
+            <Table :columns="appraiseColumns" :data="appraiseArr" border>
+                <template slot-scope="{ row, index }" slot="result">
+                    <span v-show="row.result === '优秀'" style="color: #007700;">优秀</span>
+                    <span v-show="row.result === '合格'" style="color: orangered;">合格</span>
+                    <span v-show="row.result === '不合格'" style="color: red;">不合格</span>
+                </template>
+            </Table>
+        </div>
+        <Review v-if="isShowReview" :mode="curReviewMode" :reviewData="curData" @goBack="goBack"></Review>
+
+    </div>
+</template>
+<script>
+import excel from '@/utils/excel.js'
+import Review from './Review.vue'
+import { mapGetters } from 'vuex'
+export default {
+    components: { Review },
+    data() {
+        return {
+            tableLoading: false,
+            spanb: [],
+            searchVal: '',
+            abilityList: [],
+            groupList: [],
+            origin: [],
+            ablitiesColumn: [
+                {
+                    title: '教师姓名',
+                    key: 'tmdname',
+                    width: 140
+                },
+                {
+                    title: '能力点名称',
+                    key: 'ability'
+                },
+                {
+                    title: '维度',
+                    key: 'dimension',
+                    width: 140
+                },
+                {
+                    title: '所属环境',
+                    key: 'env',
+                    width: 180
+                },
+                {
+                    title: '教研组',
+                    key: 'groupName',
+                    width: 140
+                },
+                {
+                    title: '校评结果',
+                    slot: 'schoolAppraise',
+                    key: 'schoolAppraise',
+                    width: 140
+                },
+                {
+                    title: '专家抽查',
+                    key: 'teacherAppraise',
+                    width: 140
+                },
+                {
+                    title: '操作',
+                    slot: 'action',
+                    key: 'action'
+                }
+            ],
+            appraiseColumns: [
+                {
+                    title: '评价人',
+                    key: 'name'
+                },
+                {
+                    title: '评价类型',
+                    key: 'type'
+                },
+                {
+                    title: '评价结果',
+                    slot: 'result',
+                },
+                {
+                    title: '评价内容',
+                    key: 'content'
+                },
+                {
+                    title: '评价时间',
+                    key: 'time'
+                },
+            ],
+            appraiseArr: [],
+            curAbility: 0,
+            curGroup: '全部教研组',
+            isShowDetail: false,
+            isShowReview: false,
+            curData: false,
+            groupData: [],
+            curReviewMode: '',
+            originList: []
+        }
+    },
+    created() {
+        //由DB取得該校所有使用者並放入state.schoolUserList
+        this.$store.dispatch('user/getSchoolTeacher').then(
+            res => {
+                this.handelGroup()
+                if (res.code == 0) {
+                    this.$Message.error('無法取得使用者資料')
+                }
+            },
+            err => {
+                this.$Message.error('user/setSchoolTeacher API error!')
+            }
+        )
+
+        this.getAllSubs()
+    },
+    methods: {
+        goBack() {
+            this.isShowReview = false
+            this.searchVal = ''
+            this.getAllSubs()
+        },
+        exportData(type) {
+            let downloadData = JSON.parse(JSON.stringify(this.origin))
+            downloadData.forEach(i => {
+                if (i.schoolAppraise !== '-') {
+                    i.schoolAppraise = ['不合格', '合格', '优秀'][i.schoolAppraise]
+                }
+            })
+            const params = {
+                title: this.ablitiesColumn.map(i => i.title).slice(0, this.ablitiesColumn.length - 1),
+                key: this.ablitiesColumn.map(i => i.key).slice(0, this.ablitiesColumn.length - 1),
+                data: downloadData,
+                autoWidth: true,
+                filename: '评审结果统计'
+            }
+            excel.export_array_to_excel(params)
+        },
+        getAllSubs() {
+            this.origin = []
+            this.tableLoading = true
+            this.$api.jyzx.getOtherSubs({
+                "tmdid": this.$store.state.userInfo.TEAMModelId,
+                "school": this.$store.state.userInfo.schoolCode,
+                "all": "1"
+            }).then(res => {
+                res.groupMembers.forEach(i => {
+                    if (i.sub.uploads.length) {
+                        let ability = res.abilities.find(j => j.id === i.sub.abilityId)
+                        i.dimension = this.$GLOBAL.DIMENSIONS().find(j => j.code === res.abilities.find(k => k.id === i.sub.abilityId).dimension).val
+                        i.ability = ability.no + ' ' + ability.name
+                        i.time = i.sub.uploads.length ? this.$tools.formatTime(i.sub.uploads[0].time) : '-'
+                        i.env = ability.env
+                        i.name = i.tmdname
+                        i.id = ability.id
+                        i.teacherAppraise = '未抽查'
+                        i.schoolAppraise = i.sub.otherScore.find(i => i.roleType === 'school') ? i.sub.otherScore.find(i => i.roleType === 'school').score : '-'
+                        this.origin.push(i)
+                    }
+                })
+                this.tableLoading = false
+                this.originList = JSON.parse(JSON.stringify(this.origin))
+                console.log(res.groupMembers);
+                console.log(this.origin)
+                console.log(res.groupMembers.map(i => i.sub.uploads))
+                this.doGroup()
+            })
+        },
+        //转分组模式数据结构
+        handelGroup() {
+            this.teachers.forEach(item => {
+                item.groupName = item.groupName || '默认组别'
+            })
+            let groupRes = this.$jsFn.groupBy(this.teachers, 'groupName')
+            this.groupData.length = 0
+            for (let index in groupRes) {
+                this.groupData.push({
+                    groupName: groupRes[index][0].groupName || '默认组别',
+                    groupId: groupRes[index][0].groupId,
+                    teachers: groupRes[index]
+                })
+            }
+            this.groupData.sort((a, b) => {
+                return parseInt(a.groupId) - parseInt(b.groupId)
+            })
+            this.groupData.unshift({
+                groupName: '全部教研组',
+                groupId: '-1',
+                teachers: []
+            })
+            this.groupList = this.groupData
+            console.log('处理分组数据', this.groupData)
+        },
+        goAppraiseDetail(data) {
+            this.appraiseArr = data.sub.otherScore.map(i => {
+                return {
+                    name: i.tmdname,
+                    result: ['不合格', '合格', '优秀'][i.score],
+                    content: i.replyIds[0],
+                    type: '互评',
+                    time: this.$tools.formatTime(i.time)
+                }
+            })
+            this.appraiseArr.unshift({
+                name: data.tmdname,
+                result: ['不合格', '合格', '优秀'][data.sub.self],
+                content: '-',
+                type: '自评',
+                time: this.$tools.formatTime(data.sub.uploads[0].time)
+            })
+            this.curData = data
+            this.isShowDetail = true
+        },
+        goReview(data) {
+            if (data.tmdid === this.$store.state.userInfo.TEAMModelId) {
+                this.$Message.warning('您自己的材料只能由其它管理员进行校评!')
+                return
+            }
+            // this.curData = data
+            let reviewData = {
+                id: data.sub.abilityId,
+                uploads: data.sub.uploads,
+                targetId: data.tmdid,
+                targetName: data.tmdname
+            }
+            this.curData = reviewData
+            this.isShowReview = true
+            this.curReviewMode = 'school'
+        },
+        getAllAbilities() {
+            return new Promise((r, j) => {
+                let findParams = {
+                    "scope": "school",
+                    "code": this.$store.state.userInfo.schoolCode,
+                    "dimension": "",
+                    "status": 1
+                }
+                this.$api.ability.FindVolumes(findParams).then(res => {
+                    if (!res.error) {
+                        res.abilities.unshift({
+                            name: '全部能力点',
+                            no: '',
+                            id: ''
+                        })
+                        this.abilityList = res.abilities.sort(function(s, t) {
+							var a = s.no.toLowerCase();
+							var b = t.no.toLowerCase();
+							if (a.length === 2) {
+								a = a.slice(0, a.length - 1) + '0' + a.slice(-1)
+							}
+							if (b.length === 2) {
+								b = b.slice(0, b.length - 1) + '0' + b.slice(-1)
+							}
+
+							if (a < b) return -1;
+							if (a > b) return 1;
+							return 0;
+						});
+                    } else {
+                        this.$Message.warning(res.error);
+                    }
+                }).catch(err => {
+                    this.$Message.error(err);
+                })
+            })
+        },
+        handleSpan({
+            row,
+            column,
+            rowIndex,
+            columnIndex
+        }) {
+            if (columnIndex === 0) {
+                if (this.spanb[rowIndex]) {
+                    return {
+                        rowspan: this.spanb[rowIndex],
+                        colspan: 1
+                    }
+                } else {
+                    return {
+                        rowspan: 0,
+                        colspan: 1
+                    }
+                }
+            }
+
+        },
+        doGroup() {
+            let contactDotb = 0;
+            let spanb = [];
+            this.origin.forEach((item, index) => {
+                if (index === 0) {
+                    console.log(spanb)
+                    spanb.push(1)
+                } else {
+                    if (item.name === this.origin[index - 1].name) {
+                        spanb[contactDotb] += 1;
+                        spanb.push(0)
+                    } else {
+                        contactDotb = index
+                        spanb.push(1)
+                    }
+                }
+            })
+            this.spanb = spanb;
+        },
+
+        onAbilityFilter(val) {
+            if (val === 0) {
+                if (this.curGroup === '全部教研组') {
+                    this.origin = this.originList
+                } else {
+                    this.origin = this.originList.filter(i => i.groupName === this.curGroup)
+                }
+            } else {
+                let curAbilityId = this.abilityList[val].id
+                if (this.curGroup === '全部教研组') {
+                    this.origin = this.originList.filter(i => i.id === curAbilityId)
+                } else {
+                    this.origin = this.originList.filter(i => i.id === curAbilityId && i.groupName === this.curGroup)
+                }
+            }
+            this.doGroup()
+        },
+
+        onGroupFilter(val) {
+            if (val === '全部教研组') {
+                if (this.curAbility === 0) {
+                    this.origin = this.originList
+                } else {
+                    let curAbilityId = this.abilityList[this.curAbility].id
+                    this.origin = this.originList.filter(i.id === curAbilityId)
+                }
+            } else {
+                if (this.curAbility === 0) {
+                    this.origin = this.originList.filter(i => i.groupName === val)
+                } else {
+                    let curAbilityId = this.abilityList[this.curAbility].id
+                    this.origin = this.originList.filter(i => i.groupName === val && i.id === curAbilityId)
+                }
+            }
+            this.doGroup()
+        },
+
+        onSearch(val) {
+            this.origin = this.originList.filter(i => i.name.indexOf(this.searchVal) > -1)
+        },
+    },
+    mounted() {
+
+
+        this.getAllAbilities()
+    },
+    computed: {
+        ...mapGetters({
+            teachers: 'user/getSchoolUserJoined', // 取得已加入此學校的使用者
+        })
+    },
+}
+</script>
+<style lang="less" src="./Ability.less" scoped></style>

+ 225 - 0
TEAMModelOS/ClientApp/src/view/ability/Review.less

@@ -0,0 +1,225 @@
+.review-container{
+	padding: 0 20px;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+	overflow: auto;
+	height: 100vh;
+	padding-bottom: 120px;
+	
+	.ivu-btn{
+		margin-left: 40px;
+	}
+	
+	.upload-btn{
+		background-color: #00c3c3;
+		border-color: #00c3c3;
+		margin: 10px;
+	}
+	
+	.review-header{
+		position: sticky;
+		top: 0;
+		padding: 15px 0;
+		background: #f7f7f7;
+		z-index: 9;
+		border-bottom: 1px solid #e2e2e2;
+	}
+	
+	.review-title{
+		font-size: 20px;
+		font-weight: bold;
+		margin-bottom: 20px;
+		
+		.ivu-icon{
+			font-size: 24px;
+			margin-right: 10px;
+			cursor: pointer;
+		}
+	}
+	.ability-des{
+		margin-left: 35px;
+		font-weight: bold;
+		color: #838383;
+	}
+	
+	.unit-list{
+		margin-left: 35px;
+		margin-top: 20px;
+		.unit-item{
+			margin-bottom: 20px;
+			padding-bottom: 20px;
+			border-bottom: 1px solid #e8e8e8;
+			
+			.task-radio-group{
+				span{
+					margin:10px 6px;
+					display: inline-block;
+					background: #d4dae3;
+					padding: 2px 10px;
+					color: #828282;
+					cursor: pointer;
+				}
+				
+				.active{
+					background-color: #00c3c3;
+					color: #fff;
+				}
+			}
+			
+			&-name{
+				font-size: 16px;
+				font-weight: bold;
+			}
+			&-des{
+				margin: 10px;
+				color: #838383;
+			}
+			&-files{
+				.unit-file-item{
+					display: inline-flex;
+					align-items: center;
+					background-color: #eaeaea;
+					padding: 20px;
+					margin-left: 10px;
+					border-radius: 6px;
+					
+					img{
+						width: 40px;
+						height: 40px;
+					}
+					
+					.file-info{
+						display: flex;
+						flex-direction: column;
+						justify-content: space-between;
+						margin-left: 20px;
+					}
+					
+					.file-name{
+						font-weight: bold;
+					}
+					
+					.file-size{
+						color: #969696;
+						font-size: 12px;
+					}
+					
+					.file-action{
+						margin-left: 50px;
+						
+						span{
+							margin-right: 10px;
+							color: #0098ff;
+							font-weight: bold;
+							cursor: pointer;
+						}
+					}
+				}
+			}
+			
+			.appraise-title{
+				margin: 20px 0 10px 10px;
+				color: #505050;
+				
+				&::before {
+					content: '';
+					display: inline-block;
+					border: 4px solid #78797c;
+					border-radius: 50%;
+					margin-right: 10px;
+					margin-bottom: 3px;
+				}
+			}
+			
+			.unit-item-appraise{
+				margin-left: 10px;
+				display: flex;
+				
+				.ivu-radio-group-item{
+					margin-right: 10px;
+					border-radius: 5px;
+				}
+				
+				.appraise-block{
+					background: #f1f1f1;
+					padding: 10px;
+					border-radius: 6px;
+					margin-right: 20px;
+				}
+				
+				.title{
+					font-size: 18px;
+					font-weight: bold;
+					margin-bottom: 10px;
+					
+					.ivu-icon{
+						margin-right: 10px;
+						font-size: 20px;
+						vertical-align: revert;
+						cursor: pointer;
+					}
+				}
+				
+				.ivu-checkbox-group{
+					display: inline-flex;
+					flex-direction: column;
+					
+					.ivu-checkbox-group-item{
+						margin-bottom: 5px;
+						font-size: 14px;
+					}
+				}
+			}
+			
+			.appraise-result{
+				margin: 30px 0 0 10px;
+				
+				
+			}
+		}
+	}
+	.good{
+		font-size: 18px;
+		font-weight: bold;
+		color: #00af00;
+	}
+	.normal{
+		font-size: 18px;
+		font-weight: bold;
+		color: #e87237;
+	}
+	.bad{
+		font-size: 18px;
+		font-weight: bold;
+		color: #949494;
+	}
+	
+	
+	.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;
+	    }
+	}
+}

+ 525 - 0
TEAMModelOS/ClientApp/src/view/ability/Review.vue

@@ -0,0 +1,525 @@
+<template>
+	<div class="review-container">
+		<Spin size="large" fix v-if="isLoading"></Spin>
+		<div class="review-header">
+			<div class="review-title">
+				<Icon type="md-arrow-back" @click="goBack" />
+				<span>{{ data.targetName }} 【 {{ data.no}} {{ data.name }} 】</span>
+			</div>
+			<div class="ability-des">
+				<p>{{ data.desc }}</p>
+			</div>
+		</div>
+		<div class="unit-list">
+			<div class="unit-item" v-for="(std,index) in data.stds" :key="index">
+				<p class="unit-item-name">{{ index + 1 }}.{{ std.std }}</p>
+				<span class="task-radio-group" v-if="std.task.length > 1 && mode === 'self'">
+					<span v-for="(radio,radioIndex) in std.task" :class="[curTaskIndexArr[index] === radioIndex ? 'active' : '']" @click="onChangeTask(radioIndex,index)">任务 {{ radioIndex + 1 }}</span>
+				</span>
+				<div class="task-item" v-for="(task,taskIndex) in std.task" :key="taskIndex">
+					<div v-if="curTaskIndexArr[index] === taskIndex">
+						<p class="unit-item-des">{{ task.stddesc }}</p>
+						<!-- 上传按钮 -->
+						<div class="task-upload">
+							<Button type="info" icon="md-cloud-upload" class="upload-btn" @click="onUpload(std,index)" v-show="!stdFileArr[index].length && mode === 'self'">上传资料</Button>
+						</div>
+						<!-- 文件清单 -->
+						<div class="unit-item-files">
+							<div class="unit-file-item" v-for="(file,fileIndex) in stdFileArr[index]" :key="fileIndex">
+								<img :src="getTypeIcon(file)" width="30">
+								<div class="file-info">
+									<span class="file-name">{{ file.name }}</span>
+									<span class="file-size">{{ getSizeByBytes(file.size) }}</span>
+								</div>
+								<div class="file-action">
+									<span @click="onPreview(file)">预览</span>
+									<span @click="onDownload(file)">下载</span>
+									<span @click="onDelete(file,fileIndex,index)" v-if="mode === 'self'">删除</span>
+								</div>
+							</div>
+						</div>
+						<p class="appraise-title"><span style="font-size: 16px;font-weight: bold;">{{ mode === 'self' ? '请自我评价' : '请评价'}}</span> (请勾选下方评分项进行评价)</p>
+						<div class="unit-item-appraise">
+							<!-- 优秀 -->
+							<div class="appraise-block" v-if="task.titles.length">
+								<p class="title">
+									<Icon type="ios-square-outline" size="18" @click="onCheckAll(index,'1',task.titles)" v-if="appraiseResultArr[index] !== 'good'"/>
+									<Icon type="ios-checkbox" size="20" color="#2d8cf0" @click="onCheckAll(index,'1',task.titles)" v-if="appraiseResultArr[index] === 'good'"/>
+									优秀
+								</p>
+								<CheckboxGroup v-model="appraiseList[index]" @on-change="onCheckChange(index,'1',task.titles)">
+									<Checkbox 
+									v-for="(point,pointIndex) in task.titles.filter(i => i.value === '1')" 
+									:key="pointIndex" 
+									:label="point.id">{{ point.title }}</Checkbox>
+								</CheckboxGroup>
+							</div>
+							<!-- 合格 -->
+							<div class="appraise-block" v-if="task.titles.length">
+								<p class="title">
+									<Icon type="ios-square-outline" size="18" @click="onCheckAll(index,'2',task.titles)" v-if="!isAllNormalArr[index]"/>
+									<Icon type="ios-checkbox" size="20" color="#2d8cf0" @click="onCheckAll(index,'2',task.titles)" v-if="isAllNormalArr[index]"/>
+									合格</p>
+								<CheckboxGroup v-model="appraiseList[index]" @on-change="onCheckChange(index,'2',task.titles)">
+									<Checkbox 
+									v-for="(point,pointIndex) in task.titles.filter(i => i.value === '2')" 
+									:key="pointIndex" 
+									:label="point.id">{{ point.title }}</Checkbox>
+								</CheckboxGroup>
+							</div>
+							<div v-if="!task.titles.length">
+								<RadioGroup type="button" v-model="appraiseResultArr[index]" @on-change="getFinalResult()">
+									<Radio label="good" border>优秀</Radio>
+									<Radio label="normal" border>合格</Radio>
+									<Radio label="bad" border>不合格</Radio>
+								</RadioGroup>
+							</div>
+						</div>
+						<div class="appraise-result">
+							<span>评价结果:</span>
+							<span class="good" v-if="appraiseResultArr[index] === 'good'"><Icon type="md-happy" size="22" />优秀</span>
+							<span class="normal" v-else-if="appraiseResultArr[index] === 'normal'">合格</span>
+							<span class="bad" v-else><Icon type="md-sad" size="22"/>不合格</span>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		
+		<div style="margin-left: 40px;margin-bottom: 20px;">
+			<p style="margin: 5px;margin-bottom: 20px;">总评价结果:
+				<span class="good" v-if="finalResult === 2"><Icon type="md-happy" size="22" />优秀</span>
+				<span class="normal" v-else-if="finalResult === 1">合格</span>
+				<span class="bad" v-else><Icon type="md-sad" size="22"/>不合格</span>
+			</p>
+			<Input v-model="appraiseContent" type="textarea" :rows="4" placeholder="输入评价内容..." v-if="mode !== 'self'"/>
+		</div>
+		<!--文件预览-->
+		<div v-if="previewStatus" class="image-viewer">
+			<div style="width:fit-content;position:relative;margin:auto;">
+				<Icon type="md-close" class="close-icon" @click="previewStatus = false" />
+				<video v-if="previewFile.type == 'video'" id="previewVideo" :src="previewFile.link" width="870"
+					controls="controls" style="max-height: 800px;">
+					{{$t('teachContent.tips8')}}
+				</video>
+				<audio v-else-if="previewFile.type == 'audio'" controls>
+					<source :src="previewFile.link">
+					{{$t('teachContent.notAudio')}}
+				</audio>
+				<img v-else-if="previewFile.type == 'image'" :src="previewFile.link"
+					style="border-radius: 5px;max-height: 800px;max-width:870px;" />
+				<!-- <embed v-else-if="previewFile.type == 'doc'" :src="previewFile.link" width="870" height="720" /> -->
+				<iframe v-else :src="'https://view.officeapps.live.com/op/view.aspx?src=' + previewFile.link"
+					width='870' height='700' frameborder='1'></iframe>
+			</div>
+		</div>
+		
+		<Button type="success" size="large" :loading="isBtnLoading" icon="ios-paper-plane" @click="onSubmit">提交评审结果</Button>
+		
+		<!--上传文件-->
+		<Modal v-model="uploadModal" width="800" footer-hide class="add-volume-modal">
+			<div class="modal-header" slot="header">
+				<span>{{ $t('syllabus.upload') }}</span>
+			</div>
+			<BaseUpload :auth="curSas" :acceptTypes="['doc','docx','xls','xlsx','ppt','pptx','mp4','jpg','png','pdf','mp3','wav']" :scope="'school'" mode="modal" :prefix="curPrefix" @uploadFinish="uploadFinish"></BaseUpload>
+		</Modal>
+	</div>
+</template>
+
+<script>
+	export default {
+		props: {
+			mode:{
+				type:String,
+				default:'self'
+			},
+			reviewData:{
+				type:Object,
+				default:() => {
+					id:''
+				}
+			}
+		},
+		data() {
+			return {
+				finalResult:0,
+				isBtnLoading:false,
+				curPrefix:'',
+				appraiseContent:'',
+				curTaskIndexArr:[],
+				previewStatus:false,
+				isLoading:false,
+				indeterminate: true,
+				checkAll: false,
+				previewFile:null,
+				checkAllGroup: [],
+				fileArr:[],
+				appraiseResultArr:[],
+				appraiseList:[],
+				isAllNormalArr:[],
+				uploadModal:false,
+				stdFileArr:[],
+				curUploadIndex:0,
+				curSas: {
+					sas: '',
+					url: '',
+					name: ''
+				},
+				data:{
+					name:''
+				},
+			}
+		},
+		methods: {
+			goBack() {
+				this.$emit('goBack')
+			},
+			/* 切换任务 */
+			onChangeTask(taskIndex,stdIndex){
+				this.$set(this.curTaskIndexArr,stdIndex,taskIndex)
+				this.appraiseList[stdIndex] = []
+				this.appraiseResultArr[stdIndex] = 'bad'
+				this.isAllNormalArr[stdIndex] = false
+				this.fileArr[stdIndex] = []
+			},
+			/* 转换size */
+			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'
+			},
+			/* 提交评审 */
+			onSubmit(){
+				this.isBtnLoading = true
+				console.log(this.finalUpload)
+				console.log(this.appraiseResultArr)
+				console.log(this.appraiseResultArr.every(i => i === 'good') ? 2 : (this.appraiseResultArr.every(i => i !== 'bad') ? 1 : 0))
+				if(this.finalUpload.some(i => i.urls.length === 0)){
+					this.$Message.warning('存在未提交资料的任务!')
+					this.isBtnLoading = false
+				}else if(this.mode === 'self' && this.finalResult === 0){
+					this.$Message.warning('当前自评结果为不合格,无法提交!请修改自评结果!')
+					this.isBtnLoading = false
+				}else{
+					let appraiseParams = {
+						"tmdid": this.$store.state.userInfo.TEAMModelId,
+						"abilityId": this.reviewData.id,
+						"school": this.$store.state.userInfo.schoolCode,
+						"opt": "Upload",
+						"self": this.finalResult,
+						"uploads": this.finalUpload
+					}
+					// 如果不是自评的话
+					if(this.mode !== 'self'){
+						appraiseParams.tmdid = this.data.targetId
+						appraiseParams.opt = "YouRateSomone"
+						appraiseParams.otherScore = {
+							roleType:this.mode === 'school' ? 'school' : 'member',
+							tmdid:this.$store.state.userInfo.TEAMModelId,
+							tmdname:this.$store.state.userInfo.name,
+							atTmdname:this.data.targetName,
+							score:this.finalResult,
+							reply: this.appraiseContent,
+							scoreUploads:this.finalOtherResult
+						}
+						delete appraiseParams.upload
+					}
+					this.$api.jyzx.pointAction(appraiseParams).then(res => {
+						this.isBtnLoading = false
+						this.goBack()
+						this.$Message.success('提交成功!')
+					})
+				}
+				
+			},
+			/* 执行上传操作 */
+			async onUpload(std,index){
+				this.uploadModal = true
+				this.curUploadIndex = index
+				this.curSas = await this.$tools.getSchoolSas()
+				this.curPrefix = this.$store.state.userInfo.TEAMModelId + '/' + std.id
+			},
+			/* 上传结束回调 */
+			uploadFinish(res){
+				this.$set(this.stdFileArr,this.curUploadIndex,this.stdFileArr[this.curUploadIndex].concat(res))
+				this.uploadModal = false
+				console.log(this.stdFileArr)
+			},
+			/* 预览文件 */
+			async onPreview(file){
+				this.curSas = await this.$tools.getSchoolSas()
+				let fullFilePath = file.url + this.curSas.sas
+				let suffix = this.getSuffix(file.name)
+				console.log(file)
+				console.log(fullFilePath)
+				let isImg = ['jpg','png'].includes(suffix)
+				let isDoc = ['doc','docx','xls','xlsx','ppt','pptx'].includes(suffix)
+				let isPdf = suffix === 'pdf'
+				let isVideo = suffix === 'mp4'
+				let isAudio = ['mp3','wav'].includes(suffix)
+				let type = isImg ? 'image' : isVideo ? 'video' : isAudio ? 'audio' : 'doc'
+				switch (type) {
+					case 'doc':
+						if(isPdf){
+							this.openPdf(fullFilePath, file.name)
+						}else{
+							this.previewFile = JSON.parse(JSON.stringify(file))
+							this.previewFile.link = escape(fullFilePath)
+							this.previewStatus = true
+						}
+						break;
+					default:
+						this.previewFile = JSON.parse(JSON.stringify(file))
+						this.previewFile.link = fullFilePath
+						this.previewFile.type = type
+						this.previewStatus = true
+						break;
+				}
+			},
+			/* 获取文件后缀名 */
+			getSuffix(name) {
+				return name.substr(name.lastIndexOf(".") + 1).toLowerCase()
+			},
+			/* 打开PDF文件进行预览 */
+			openPdf(url) {
+				window.open('/web/viewer.html?file=' + encodeURIComponent(url));
+			},
+			/* 下载文件 */
+			async onDownload(file){
+				this.isLoading = true
+				this.curSas = await this.$tools.getSchoolSas()
+				let fullFilePath = file.url + this.curSas.sas
+				/* 下载模板制作详情说明文件 */
+				const downloadRes = async () => {
+				  let response = await fetch(fullFilePath); // 内容转变成blob地址
+				  let blob = await response.blob();  // 创建隐藏的可下载链接
+				  let objectUrl = window.URL.createObjectURL(blob);
+				  let a = document.createElement('a');
+				  a.href = objectUrl;
+				  a.download = file.name;
+				  a.click()
+				  a.remove(); 
+				  this.isLoading = false
+			   }
+			  downloadRes();
+			},
+			/* 删除文件 */
+			onDelete(file,index,stdIndex){
+				console.log(file);
+				this.isLoading = true
+				this.deleteBlobPrefix(file.blob).then(r => {
+					setTimeout(() => {
+						this.$Message.success('删除成功!')
+						this.stdFileArr[stdIndex].splice(index,1)
+						this.isLoading = false
+					},1000)
+				}).catch(err => {
+					this.$Message.error(err)
+				}).finally(() => {
+					// this.isLoading = false
+				})
+			},
+			/* 删除blob指定试题目录下所有 */
+			deleteBlobPrefix(prefix) {
+				return new Promise((resolve, reject) => {
+					this.$api.blob.deletePrefix({
+						"cntr": this.$store.state.userInfo.schoolCode,
+						"prefix": prefix.substring(1)
+					}).then(
+						(res) => {
+							if (!res.error) {
+								resolve(200)
+							} else {
+								resolve(500)
+							}
+						},
+						(err) => {
+							reject(err)
+						}
+					)
+				})
+			},
+			/* 评价结果 */
+			onCheckChange(stdIndex,type,allIds){
+				let curStdChooseIds = this.appraiseList[stdIndex]
+				let allGoodIds = allIds.filter(j => j.value === '1').map(k => k.id)
+				let allNormalIds = allIds.filter(j => j.value === '2').map(k => k.id)
+				// 优秀的判断就是所有优秀的评价ID都在选择内
+				let isAllGood = allGoodIds.every(i => curStdChooseIds.includes(i))
+				let isAllNormal = allNormalIds.every(i => curStdChooseIds.includes(i))
+				// 未通过的判断就是当前未选择任何标准
+				let isAllBad = curStdChooseIds.length === 0
+				// 其余则为合格
+				this.appraiseResultArr[stdIndex] = isAllGood ? 'good' : isAllNormal ? 'normal' : 'bad'
+				this.isAllNormalArr[stdIndex] = !isAllGood && allNormalIds.every(i => curStdChooseIds.includes(i))
+				this.getFinalResult()
+			},
+			/* 全选操作 */
+			onCheckAll(stdIndex,type,allIds){
+				let curStdChooseIds = this.appraiseList[stdIndex]
+				let allGoodIds = allIds.filter(j => j.value === '1').map(k => k.id)
+				let allNormalIds = allIds.filter(j => j.value === '2').map(k => k.id)
+				let isAllGood = allGoodIds.every(i => curStdChooseIds.includes(i))
+				if(type === '1'){
+					if(this.appraiseResultArr[stdIndex] === 'good'){
+						this.$set(this.appraiseList,stdIndex,this.appraiseList[stdIndex].filter(i => !allGoodIds.includes(i)))
+						this.appraiseResultArr[stdIndex] = allNormalIds.every(i => curStdChooseIds.includes(i)) ? 'normal' : 'bad'
+					}else{
+						let addGoodIdsRes = [...new Set(this.appraiseList[stdIndex].concat(allGoodIds))]
+						this.$set(this.appraiseList,stdIndex,addGoodIdsRes)
+						this.appraiseResultArr[stdIndex] ='good'
+					}
+				}else{
+					// 如果合格 点击全选时 处于已全选状态 则设置为全空
+					 if(allNormalIds.every(i => curStdChooseIds.includes(i))){
+						this.$set(this.appraiseList,stdIndex,this.appraiseList[stdIndex].filter(i => !allNormalIds.includes(i)))
+						this.isAllNormalArr[stdIndex] = false
+						if(this.appraiseList[stdIndex].length === 0 || !allGoodIds.every(i => curStdChooseIds.includes(i))){
+							this.appraiseResultArr[stdIndex] ='bad'
+						}
+						
+					}else{
+						// 如果合格 点击全选时 处于未全选状态 则设置为全选
+						let addNormalIdsRes = [...new Set(this.appraiseList[stdIndex].concat(allNormalIds))]
+						this.$set(this.appraiseList,stdIndex,addNormalIdsRes)
+						this.isAllNormalArr[stdIndex] = true
+						if(!allGoodIds.every(i => curStdChooseIds.includes(i))){
+							this.appraiseResultArr[stdIndex] ='normal'
+						}else{
+							this.appraiseResultArr[stdIndex] ='good'
+						}
+					}
+				}
+				this.getFinalResult()
+				
+				// this.isAllNormal = !isAllGood && allNormalIds.every(i => curStdChooseIds.includes(i))
+				console.log(this.appraiseList[stdIndex]);
+			},
+			/* 获取能力点详情 */
+			async getDetailById(id){
+				let abilityDetail = await this.$evTools.getAbilityDetailById(id)
+				console.log(abilityDetail)
+				this.appraiseResultArr = abilityDetail.stds.map(i => 'bad')
+				this.appraiseList = abilityDetail.stds.map(i => [])
+				this.isAllNormalArr = abilityDetail.stds.map(i => false)
+				this.stdFileArr = abilityDetail.stds.map(i => [])
+				this.curTaskIndexArr = abilityDetail.stds.map(i => 0)
+				abilityDetail.uploads = this.reviewData.uploads
+				console.log(abilityDetail.uploads)
+				if(this.mode !== 'self'){
+					this.stdFileArr = abilityDetail.uploads.map(i => i.urls)
+					abilityDetail.targetId = this.reviewData.targetId
+					abilityDetail.targetName = this.reviewData.targetName
+				}
+				return abilityDetail
+			},
+			/* 根据类型换取ICON图标 */
+			getTypeIcon(file){
+				let suffix = file.name.split('.')[file.name.split('.').length - 1].toLowerCase()
+				if(['jpg','png'].includes(suffix)){
+					return require('../../assets/icon/pic50.png')
+				}
+				if(['doc','docx'].includes(suffix)){
+					return require('../../assets/icon/word50.png')
+				}
+				if(['xls','xlsx'].includes(suffix)){
+					return require('../../assets/icon/xls50.png')
+				}
+				if(['ppt','pptx'].includes(suffix)){
+					return require('../../assets/icon/ppt50.png')
+				}
+				if(suffix === 'pdf'){
+					return require('../../assets/icon/pdf50.png')
+				}
+				if(['mp3','wav'].includes(suffix)){
+					return require('../../assets/icon/video50.png')
+				}
+				if(['mp4'].includes(suffix)){
+					return require('../../assets/icon/video50.png')
+				}
+			},
+			
+			getFinalResult(){
+				this.finalResult = this.appraiseResultArr.every(i => i === 'good') ? 2 : this.appraiseResultArr.some(i => i === 'bad') ? 0 : 1
+				console.log(this.finalResult)
+			}
+			
+		},
+		computed:{
+			finalUpload(){
+				return this.stdFileArr.map((i,stdIndex) => {
+					return {
+						"stdid": this.data.stds[stdIndex].id,
+						"taskid": this.data.stds[stdIndex].task[0].id,
+						"urls": i.map(j => {
+							return {
+								url:j.url,
+								name:j.name,
+								size:j.size
+							}
+						}),
+						"titleIds":this.appraiseList[stdIndex]
+					}
+				})
+			},
+			finalOtherResult(){
+				return this.stdFileArr.map((i,stdIndex) => {
+					return {
+						"stdid": this.data.stds[stdIndex].id,
+						"taskid": this.data.stds[stdIndex].task[0].id,
+						"score": this.appraiseResultArr[stdIndex] === 'good' ? 2 : this.appraiseResultArr[stdIndex] === 'normal' ? 1 : 0,
+						"titleIds":this.appraiseList[stdIndex]
+					}
+				})
+			}
+		},
+		watch:{
+			reviewData:{
+				async handler(n,o){
+					if(n){
+						this.data = await this.getDetailById(n.id)
+						console.log(this.data)
+					}
+				},
+				immediate:true,
+				deep:true
+			}
+		}
+	}
+</script>
+
+<style lang="less" src="./Review.less" scoped></style>
+<style lang="less">
+	
+	.ivu-spin-fix{
+		background-color: rgba(255,255,255,.6) !important;
+	}
+	
+	/* 修改iview Modal样式 */
+	.add-volume-modal {
+		font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+	}
+	
+	
+	.add-volume-modal .ivu-modal-header {
+		border-bottom: none;
+		font-size: 18px;
+		font-weight: bold;
+		padding: 25px;
+	
+		.modal-header {
+			display: inline-block;
+		}
+	
+		&::before {
+			content: '';
+			display: inline-block;
+			border: 4px solid #2e97ff;
+			border-radius: 50%;
+			margin-right: 10px;
+			margin-bottom: 2px;
+		}
+	}
+	
+	.add-volume-modal .modal-content {
+		padding: 10px 30px;
+	}
+</style>

+ 115 - 0
TEAMModelOS/ClientApp/src/view/ability/TestPaper.less

@@ -0,0 +1,115 @@
+.test-paper-container{
+	padding: 0 20px;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+	overflow: auto;
+	height: 100vh;
+	padding-bottom: 200px;
+	
+	.review-header{
+		position: sticky;
+		top: 0;
+		padding: 15px 0;
+		background: #f7f7f7;
+		z-index: 9;
+		border-bottom: 1px solid #e2e2e2;
+	}
+	
+	.review-title{
+		font-size: 20px;
+		font-weight: bold;
+		margin-bottom: 20px;
+		
+		.ivu-icon{
+			font-size: 24px;
+			margin-right: 10px;
+			cursor: pointer;
+		}
+	}
+	.ability-des{
+		margin-left: 35px;
+		font-weight: bold;
+		color: #838383;
+	}
+	
+	.questions-wrap{
+		padding: 30px;
+		
+		.question-item{
+			margin-bottom: 40px;
+			
+			&-stem{
+				display: inline-block;
+				font-size: 16px;
+				margin-left: 5px;
+				font-weight: bold;
+			}
+			
+			&-tools{
+				margin-left: 15px;
+				.ivu-icon{
+					margin-left: 5px;
+					font-size: 16px;
+					color: #797979;
+					cursor: pointer;
+				}
+			}
+			
+			&-options{
+				margin-top: 20px;
+				
+				.ivu-radio-wrapper{
+					border-radius: 4px;
+					border-color: transparent;
+					margin-top: 10px;
+					background: #eaeaea;
+					box-shadow: none;
+					display: inline-block;
+					width: fit-content;
+				}
+				
+				.ivu-radio-group-button .ivu-radio-wrapper-checked{
+					background-color: #16c18e;
+					color: #fff;
+				}
+				
+				.ivu-radio-group-button .ivu-radio-wrapper:after{
+					width: 0;
+				}
+				
+				.ivu-radio-group,.ivu-checkbox-group{
+					display: flex;
+					flex-direction: column;
+				}
+				
+				.ivu-checkbox-wrapper{
+					border-radius: 4px;
+					border-color: transparent;
+					margin-right: 10px;
+					margin-bottom: 10px;
+					background: #eaeaea;
+					box-shadow: none;
+					display: inline-block;
+					width: fit-content;
+				}
+				
+				.ivu-checkbox-border{
+					height: 36px;
+					line-height: 36px;
+				}
+				
+				.ivu-checkbox-inner{
+					display: none !important;
+				}
+				
+				.ivu-checkbox-wrapper-checked.ivu-checkbox-border{
+					background-color: #16c18e;
+					color: #fff;
+				}
+				
+				.ivu-checkbox-group-button .ivu-checkbox-wrapper:after{
+					width: 0;
+				}
+			}
+		}
+	}
+}

+ 478 - 0
TEAMModelOS/ClientApp/src/view/ability/TestPaper.vue

@@ -0,0 +1,478 @@
+<template>
+	<div class="test-paper-container">
+		<div class="review-header" v-if="!isNewPaper">
+			<div class="review-title">
+				<Icon type="md-arrow-back" @click="goBack" />
+				<span>{{ data.targetName }} 【 {{ data.no}} {{ data.name }} 】</span>
+			</div>
+			<div class="ability-des">
+				<p>{{ data.desc }}</p>
+			</div>
+		</div>
+		
+		<div class="questions-wrap">
+			<div v-if="!items.length">
+				<EmptyData textContent="当前能力点下暂无检测试题数据"></EmptyData>
+			</div>
+			<div v-else>
+				<div class="question-item" v-for="(item,index) in items" :key="index">
+					<span>{{ index + 1 }}. </span>
+					<p v-html="item.question" class="question-item-stem"></p>
+					<span> ({{ item.type === 'single' ? '单选' : item.type === 'multiple' ? '多选' : '判断'}})</span>
+					<span class="question-item-tools" v-if="isNewPaper &&  $access.can('area.*')">
+						<Icon type="md-create" title="编辑试题" @click="onEditQues(item,index)"/>
+						<Icon type="md-trash" title="删除试题" @click="onDeleteQues(item,index)"/>
+					</span>
+					<div class="question-item-options">
+						<RadioGroup v-model="isNewPaper ? answerArr[index][0] : answerArr[index]" type="button" button-style="solid" v-if="item.type !== 'multiple'">
+							<Radio v-for="(option,optionIndex) in item.options" :key="optionIndex" :label="option.code" :disabled="isNewPaper">{{ option.code + ' . ' + getSimpleText(option.value) }}</Radio>
+						</RadioGroup>
+						<CheckboxGroup v-model="answerArr[index]" v-if="item.type === 'multiple'">
+							<Checkbox v-for="(option,optionIndex) in item.options" :key="optionIndex" :label="option.code" border  :disabled="isNewPaper">{{ option.code + ' . ' + getSimpleText(option.value) }}</Checkbox>
+						</CheckboxGroup>
+					</div>
+					<Divider />
+				</div>
+			</div>
+			
+		</div>
+		
+		<Button v-if="!isNewPaper && items.length" type="success" size="large" :loading="isBtnLoading" style="margin-left: 30px;background-color: #16c18e;border: none;" icon="ios-paper-plane" @click="onSubmit">提交答案</Button>
+		<Button v-if="isNewPaper && hasModify" type="success" :loading="isBtnLoading" style="margin-left: 30px;background-color: #16c18e;border: none;" icon="ios-paper-plane" @click="onSavePaper">保存试卷</Button>
+	
+		<!-- 新建题目弹窗 -->
+		<Modal v-model="addModal" width="800" footer-hide class="related-modal survey-modal" @on-visible-change="onModalChange">
+			<div class="modal-header" slot="header">{{ $t('survey.questionaire.add') }}{{ typeList[curType] }}{{ $t('survey.questionaire.item') }}</div>
+			<template v-if="isUpdate">
+				<!-- <BaseAddItem ref="addItem" :type="curType" :currentQn="curItem"></BaseAddItem> -->
+				<BaseSingle v-if="curType==='single'" ref="addItem" :editInfo="curItem" @onSave="onSave" needAnswer></BaseSingle>
+				<BaseMultiple v-if="curType==='multiple'" ref="addItem" :editInfo="curItem" @onSave="onSave" needAnswer></BaseMultiple>
+				<BaseJudge v-if="curType==='judge'" ref="addItem" :editInfo="curItem" @onSave="onSave" needAnswer></BaseJudge>
+				<Button class="modal-btn" @click="onConfirmAdd">{{ $t('survey.questionaire.confirm') }}</Button>
+			</template>
+		</Modal>
+		
+		<!-- 新建题目弹窗 -->
+		<Modal v-model="importModal" width="800" footer-hide class="related-modal">
+			<div class="modal-header" slot="header">试题导入</div>
+			<BaseUpload :acceptTypes="['xls','xlsx']" mode="import" @uploadFinish="uploadFinish"></BaseUpload>
+		</Modal>
+	</div>
+</template>
+
+<script>
+	import blobTool from "@/utils/blobTool.js";
+	import BaseSingle from "@/components/questionnaire/BaseSingle.vue"
+	import BaseMultiple from "@/components/questionnaire/BaseMultiple.vue"
+	import BaseJudge from "@/components/questionnaire/BaseJudge.vue"
+	export default{
+		components:{
+			BaseSingle,
+			BaseMultiple,
+			BaseJudge
+		},
+		props: {
+			mode:{
+				type:String,
+				default:'self'
+			},
+			abilityData:{
+				type:Object,
+				default:() => {
+					id:''
+				}
+			},
+			isNewPaper:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data(vm) {
+			return {
+				importModal:false,
+				hasModify:false,
+				curItem:null,
+				typeList: {
+					'single': vm.$t('survey.questionaire.single'),
+					'multiple': vm.$t('survey.questionaire.multiple'),
+					'judge':vm.$t('survey.questionaire.judge'),
+					'subjective': vm.$t('survey.questionaire.subjective')
+				},
+				curType:'',
+				addModal:false,
+				isBtnLoading:false,
+				data:{
+					name:''
+				},
+				items:[],
+				answerArr:[],
+				trueAnswerArr:[],
+				editIndex:null,
+				isUpdate:false,
+			}
+		},
+		created() {
+			this.answerArr = this.isNewPaper ? this.items.map(i => i.answer) : []
+		},
+		methods:{
+			/* 返回上级 */
+			goBack() {
+				this.$emit('goBack')
+			},
+			// 重置所有富文本编辑器内容 初始化
+			onModalChange(val){
+				this.isUpdate = val
+			},
+			/* 新增试题 */
+			onChooseType(type){
+				console.log(type);
+				if(type === 'import'){
+					this.importModal = true
+				}else{
+					this.curItem = null
+					this.editIndex = null
+					this.addModal = true
+					this.curType = type
+				}
+			},
+			/* 导入试题 */
+			uploadFinish(res){
+				this.items.push(...res)
+				this.answerArr = this.items.map(i => i.answer)
+				this.importModal = false
+				this.$Message.success('导入成功!')
+				this.hasModify = true
+			},
+			/* 编辑试卷题目 */
+			onEditQues(item,index){
+				item.option = item.options
+				this.curItem = item
+				this.editIndex = index
+				this.curType = item.type
+				this.addModal = true
+			},
+			/* 删除试卷题目 */
+			onDeleteQues(item,index){
+				this.$Modal.confirm({
+					title: this.$t('survey.questionaire.confirmTitle'),
+					content: this.$t('survey.questionaire.confirmText'),
+					onOk: () => {
+						this.items.splice(index, 1)
+						this.hasModify = true
+					}
+				})
+			},
+			// 确认添加题目
+			onConfirmAdd() {
+				this.$refs.addItem.doSave()
+			},
+			/* 获取指定路径试卷下的所有文件 */
+			getPaperFiles(path) {
+				return new Promise(async (r, j) => {
+					// 获取初始化Blob需要的数据
+					let s = this.$store.state.user.osblob_uri
+					let sasData = {
+						sas: '?' + this.$store.state.user.osblob_sas,
+						name: 'teammodelos',
+						url: s.split(s.substring(s.lastIndexOf('/')))[0]
+					}
+					//初始化Blob
+					let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, 'school')
+					// 等待blob的返回结果
+					containerClient.listBlob({
+						prefix: path
+					}).then(
+						(res) => {
+							r(res)
+						},
+						(err) => {
+							this.$Message.error('API Error')
+						}
+					)
+				})
+			},
+			/* 删除试卷 */
+			onDeleteBlobPaper(files) {
+				return new Promise(async (r, j) => {
+					// 获取初始化Blob需要的数据
+					let s = this.$store.state.user.osblob_uri
+					let sasData = {
+						sas: '?' + this.$store.state.user.osblob_sas,
+						name: 'teammodelos',
+						url: s.split(s.substring(s.lastIndexOf('/')))[0]
+					}
+					//初始化Blob
+					let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, 'school')
+					// 等待blob的返回结果
+					containerClient.deleteBlobBatch(files).then(
+						(res) => {
+							r(res)
+						},
+						(err) => {
+							this.$Message.error('API Error')
+						}
+					)
+				})
+			},
+			/* 保存自我评测试卷 */
+			async onSavePaper(){
+				console.log(this.items)
+				console.log(this.data)
+				this.isBtnLoading = true;
+				try{
+					let qnBaseInfo = this.data
+					let qnItems = this.items
+					if(qnBaseInfo.blob){
+						let blobList = await this.getPaperFiles(`yxpt/${this.curStandard}/train/` + qnBaseInfo.name + '/')
+						let files = blobList.blobList.map(i => i.blob)
+						await this.onDeleteBlobPaper(files)
+					}
+					qnBaseInfo.blob = await this.doUploadBlob(qnBaseInfo, qnItems);
+					// 开始保存问卷
+					this.$api.ability.SaveOrUpdateVolume(qnBaseInfo).then(res => {
+						if(!res.error){
+							this.$Message.success(this.$t('survey.doSuc'));
+							this.hasModify = false
+						}
+					}).catch((error) => {
+						this.$Message.error(`${error}`);
+					}).finally(data => {
+						this.isBtnLoading = false;
+					});
+				}catch(e){
+					this.isBtnLoading = false;
+				}
+			},
+			/* 上传index.json */
+			async doUploadItems(qnBaseInfo,items){
+				return new Promise(async (resolve, reject) => {
+					let promiseArr = []
+					// 获取初始化Blob需要的数据
+					let s = this.$store.state.user.osblob_uri
+					let sasData = {
+						sas: '?' + this.$store.state.user.osblob_sas,
+						name: 'teammodelos',
+						url: s.split(s.substring(s.lastIndexOf('/')))[0]
+					}
+					//初始化Blob
+					let containerClient = new blobTool(
+						sasData.url,
+						sasData.name,
+						sasData.sas,
+						'school'
+					);
+					
+					for (let item of items) {
+						let curId = item.id || this.$tools.guid()
+						promiseArr.push(new Promise(async (r, j) => {
+							// let itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
+							let file = new File([JSON.stringify(item)], curId + ".json");
+							try {
+								// 等待上传blob的返回结果
+								let blobFile = await containerClient.upload(file, `yxpt/${this.curStandard}/train/` + qnBaseInfo.name);
+								if (blobFile.blob) {
+									console.log('上传Blob成功', blobFile)
+									r(`/train/${qnBaseInfo.name}/${curId}.json`)
+								} else {
+									this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+								}
+							} catch (e) {
+								j(e)
+								this.isBtnLoading = false;
+								this.$Message.error(e.spaceError);
+							}
+						}))
+					}
+					Promise.all(promiseArr).then(result => {
+						resolve(result)
+					})
+				})
+			},
+			/* 保存问卷题目到Blob */
+			doUploadBlob(qnBaseInfo, items) {
+				return new Promise(async (resolve,reject) => {
+					try {
+						if(items.length === 0){
+							resolve('')
+						}
+						let itemUrls = await this.doUploadItems(qnBaseInfo, items)
+						qnBaseInfo.slides = itemUrls
+						// 获取初始化Blob需要的数据
+						let s = this.$store.state.user.osblob_uri
+						let sasData = {
+							sas: '?' + this.$store.state.user.osblob_sas,
+							name: 'teammodelos',
+							url: s.split(s.substring(s.lastIndexOf('/')))[0]
+						}
+						//初始化Blob
+						let containerClient = new blobTool(
+							sasData.url,
+							sasData.name,
+							sasData.sas,
+							'school'
+						);
+						let file = new File([JSON.stringify(qnBaseInfo)], "index.json");
+						// 等待上传blob的返回结果
+						let blobFile = await containerClient.upload(file, `yxpt/${this.curStandard}/train/` + qnBaseInfo.name);
+						if (blobFile.blob) {
+							delete qnBaseInfo.slides
+							resolve(blobFile.blob)
+						} else {
+							this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+						}
+					} catch (e) {
+						this.isBtnLoading = false;
+						reject(e);
+					}
+				})
+				
+			},
+			// 保存新增试题
+			onSave(addItem){
+				console.log(addItem)
+				// 判断获取到的新题是否规范
+				if ( this.getSimpleText(addItem.stemContent) && this.checkOptionNull(addItem.optionsContent)){
+					let newItem = {
+						question: addItem.stemContent,
+						options: addItem.optionsContent,
+						type: this.editIndex === null ? this.curType : this.curItem.type,
+						answer:addItem.answer
+					}
+					console.log(newItem)
+					if(this.editIndex === null){
+						this.items.push(newItem)
+						console.log(this.items)
+					}else{
+						this.$set(this.items, this.editIndex, newItem)
+					}
+					this.addModal = false
+					this.answerArr = this.items.map(i => i.answer)
+					this.hasModify = true
+				}else{
+					this.$Message.warning(this.$t('survey.questionaire.noCompleteTip'))
+				}
+			},
+			// 提取富文本内容中的文本
+			getSimpleText(html) {
+				html = html + ''
+				var r = /<(?!img|video|audio).*?>/g;
+				return html.replace(r, "").replace(/&nbsp;/g, ' ');
+			},
+			// 检测是否有空选项
+			checkOptionNull(arr) {
+				let flag = true
+				for (let i = 0; i < arr.length; i++) {
+					if (this.getSimpleText(arr[i].value + '') === '') {
+						flag = false
+					}
+				}
+				return flag
+			},
+			// 提交作答结果
+			onSubmit(){
+				let myAnswer = this.answerArr.map(i => Array.isArray(i) ? i.sort().join(',') : i)
+				let trueAnswer =  this.trueAnswerArr.map(i => Array.isArray(i) ? i.sort().join(',') : i)
+				console.log(myAnswer);
+				console.log(trueAnswer);
+				let exerciseScore = 1
+				trueAnswer.forEach((i,index) => {
+					if(myAnswer[index] !== i){
+						exerciseScore = 0
+					}
+				})
+				this.$api.jyzx.pointAction({
+					"tmdid": this.$store.state.userInfo.TEAMModelId,
+					"abilityId": this.data.id,
+					"school": this.$store.state.userInfo.schoolCode,
+					"opt": "ExerciseScore",
+					"exercise":exerciseScore
+				}).then(res => {
+					this.isBtnLoading = false
+					this.goBack()
+					this.$Message.success('提交成功!自评结果为' + (exerciseScore ? '通过!' : '未通过!'))
+				})
+			},
+			/* 获取能力点详情 */
+			async getDetailById(id){
+				let abilityDetail = await this.$evTools.getAbilityDetailById(id)
+				console.log(abilityDetail)
+				if(abilityDetail.blob){
+					console.log(abilityDetail.blob)
+					this.items = await this.getBlobItems(abilityDetail)
+					this.trueAnswerArr = this.items.map(i => i.answer)
+					this.answerArr = this.isNewPaper ? this.items.map(i => i.answer) : []
+				}else{
+					this.items = []
+					this.answerArr = []
+				}
+				this.hasModify = false
+				return abilityDetail
+			},
+			// 获取blob里的试题数据
+			getBlobItems(qnItem){
+				return new Promise(async (resolve,reject) => {
+					let blobHost = this.$store.state.user.osblob_uri
+					// 根据试卷的Blob地址 去读取JSON文件
+					let sasString = '?' + this.$store.state.user.osblob_sas
+					let promiseArr = []
+					let indexJson = await this.getBlobJsonFile(qnItem.scope,qnItem.blob)
+					let basePath = qnItem.blob.split('/train')[0]
+					console.log(basePath);
+					if(indexJson.slides.length){
+						for (let item of indexJson.slides) {
+							promiseArr.push(new Promise(async (r,j) => {
+								try{
+									let itemJson = JSON.parse(await this.$tools.getFile(blobHost + basePath + item + sasString))
+									r(itemJson)
+								}catch(e){
+									j(e)
+								}
+							}))
+						}
+						Promise.all(promiseArr).then(result => {
+							resolve(result)
+						}).catch(err => {
+							reject(err)
+						})
+					}else{
+						resolve([])
+					}
+					
+				})
+			},
+			// 获取blob里的试题数据
+			getBlobJsonFile(scope,url){
+				return new Promise(async (resolve,reject) => {
+					let blobHost = this.$store.state.user.osblob_uri
+					// 根据试卷的Blob地址 去读取JSON文件
+					let sasString =  '?' + this.$store.state.user.osblob_sas
+					try{
+						let itemJson = JSON.parse(await this.$tools.getFile(blobHost + url + sasString))
+						resolve(itemJson)
+					}catch(e){
+						reject(e)
+					}
+				})
+			},
+		},
+		computed:{
+			curStandard(){
+				return this.$store.state.user.schoolProfile.school_base.standard
+			}
+		},
+		watch:{
+			abilityData:{
+				async handler(n,o){
+					if(n && n.id){
+						this.data = await this.getDetailById(n.id)
+						console.log(this.data)
+					}
+				},
+				immediate:true,
+				deep:true
+			}
+		}
+	}
+</script>
+
+<style lang="less" src="./TestPaper.less"></style>

+ 328 - 0
TEAMModelOS/ClientApp/src/view/abilityMgmt/Index.less

@@ -0,0 +1,328 @@
+@primaryColor: #1CC0F3;
+@borderColor: #e6e6e6;
+@second-textColor: #c7c7c7; //文本副级颜色
+.ability-mgmt-container {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+
+	.syllabus-header {
+		position: relative;
+		height: 50px;
+		width: 100%;
+		border-bottom: 1px solid @borderColor;
+		display: flex;
+		align-items: center;
+		padding-left: 5px;
+		z-index: 10;
+
+		.syllabus-tab-item {
+			margin-right: 40px;
+			font-size: 14px;
+			color: @second-textColor;
+			cursor: pointer;
+
+			&-active {
+				font-size: 16px;
+				font-weight: bold;
+				padding-bottom: 3px;
+				border-bottom: 1px solid #fff;
+			}
+		}
+
+		.subject-select {
+			&-item {
+				margin-left: 10px;
+				display: inline-block;
+				padding: 4px 14px;
+				background-color: #ababab;
+				color: #fff;
+				cursor: pointer;
+			}
+
+			.item-active {
+				background-color: #1fb06d;
+			}
+		}
+
+		.btn-save-modify {
+			position: absolute;
+			right: 20px;
+			background-color: #1fb06d;
+			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;
+			position: relative;
+			font-weight: bold;
+
+
+			&::before {
+				content: '';
+				display: inline-block;
+				border: 4px solid #16C18E;
+				border-radius: 50%;
+				margin-right: 10px;
+				margin-bottom: 3px;
+			}
+
+			&-tools {
+				position: absolute;
+				right: 0;
+
+				.ivu-icon {
+					cursor: pointer;
+					margin-right: 15px;
+				}
+
+				.ivu-btn {
+					background: none;
+					border: none;
+					color: #6b6b6b;
+					margin-right: 5px;
+				}
+			}
+		}
+
+		.syllabus-paper{
+			width: 80%;
+		}
+		
+		.syllabus-left {
+			height: 100%;
+			width: 20%;
+			border-right: 1px solid @borderColor;
+
+			.volume-list {
+				display: flex;
+				flex-direction: column;
+				overflow: hidden;
+				padding-bottom: 40px;
+
+				.volume-item {
+					padding: 10px 0 10px 20px;
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+					border-bottom: 1px solid @borderColor;
+					cursor: pointer;
+
+					&-name {
+						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: #16C18E;
+						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 {
+						background-color: #006f3c;
+					}
+
+					&-info {
+						margin-left: 35px;
+						margin-top: 5px;
+
+						span {
+							color: #838383;
+							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: 35%;
+			border-right: 1px solid @borderColor;
+			z-index: 1;
+
+			.syllabus-tree-box {
+				height: 100%;
+				z-index: 0;
+			}
+		}
+
+		.syllabus-right {
+			height: 100%;
+			width: 45%;
+			border-right: 1px solid @borderColor;
+
+			.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 #e5e5e5;
+					padding: 10px 20px;
+					display: flex;
+					align-items: center;
+
+					&:hover {
+						background-color: #d6d6d6;
+
+						.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 {
+							font-size: 16px;
+							margin-right: 5px;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	.volume-active {
+		background: #e8e8e8;
+	}
+
+	.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;
+			}
+		}
+	}
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 2012 - 0
TEAMModelOS/ClientApp/src/view/abilityMgmt/Index.vue


+ 18 - 0
TEAMModelOS/ClientApp/src/view/abilityUpload/AbilityUpload.less

@@ -0,0 +1,18 @@
+.upload-container{
+	padding: 10px;
+	font-weight: bold;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+	
+	.upload-box{
+		width: 500px;
+		margin-left: 10px;
+	}
+	
+	.ivu-upload{
+		margin-top: 20px;
+	}
+	
+	.ivu-btn{
+		margin-top: 20px;
+	}
+}

+ 96 - 0
TEAMModelOS/ClientApp/src/view/abilityUpload/AbilityUpload.vue

@@ -0,0 +1,96 @@
+<template>
+	<div class="upload-container">
+		<Tabs value="name1" @on-click="onTabChange">
+			<TabPane label="整校实施方案上传" name="name1">
+				<div class="upload-box">
+					<p style="margin: 20px 0;"><span style="color: red;vertical-align: middle;">*</span> 学校名称</p>
+					<Input v-model="schoolName" placeholder="请输入学校名称..." />
+					<p style="margin: 20px 0;"><span style="color: red;vertical-align: middle;">*</span> 实施方案</p>
+					<p style="margin: 0 10px;color: #838383;font-size: 12px;margin-bottom: 20px;">支持Word、PDF格式上传,文件大小需小于50M</p>
+					<BaseUpload :auth="curSas" mode="outer" :acceptTypes="['pdf','doc','docx']" :maxSize="52428800" @uploadFinish="uploadFinish" ref="upload1"></BaseUpload>
+					<Button type="info" icon="ios-paper-plane" @click="onSubmit('0')" :loading="btnLoading">提交</Button>
+				</div>
+			</TabPane>
+			<TabPane label="信息化规划上传" name="name2">
+				<div class="upload-box">
+					<p style="margin: 20px 0;"><span style="color: red;vertical-align: middle;">*</span> 学校名称</p>
+					<Input v-model="schoolName2" placeholder="请输入学校名称..." />
+					<p style="margin: 20px 0;"><span style="color: red;vertical-align: middle;">*</span> 信息化规划</p>
+					<p style="margin: 0 10px;color: #838383;font-size: 12px;">支持Word、PDF格式上传,文件大小需小于50M</p>
+					<BaseUpload :auth="curSas" mode="outer" :acceptTypes="['pdf','doc','docx']" :maxSize="52428800" @uploadFinish="uploadFinish" ref="upload2"></BaseUpload>
+					<Button type="info" icon="ios-paper-plane" @click="onSubmit('1')" :loading="btnLoading">提交</Button>
+				</div>
+			</TabPane>
+		</Tabs>
+	</div>
+</template>
+
+<script>
+	import BlobTool from '@/utils/blobTool.js'
+	export default {
+		data(){
+			return {
+				btnLoading:false,
+				schoolName:'',
+				schoolName2:'',
+				curSas: {
+					sas: '',
+					url: '',
+					name: ''
+				},
+				curTab:'name1'
+			}
+		},
+		created() {
+			this.getSas()
+		},
+		methods:{
+			onTabChange(val){
+				this.curTab = val
+			},
+			async getSas(){
+				this.curSas = await this.$tools.getSchoolSas()
+			},
+			onSubmit(){
+				if(this.curTab === 'name1' && this.$refs.upload1.fileArr.length === 0){
+					this.$Message.warning('请先上传方案文件!')
+					return
+				}
+				if(this.curTab === 'name2' && this.$refs.upload2.fileArr.length === 0){
+					this.$Message.warning('请先上传方案文件!')
+					return
+				}
+				if(this.curTab === 'name1' && this.schoolName === ''){
+					this.$Message.warning('学校名称不能为空!')
+					return
+				}
+				if(this.curTab === 'name2' && this.schoolName2 === ''){
+					this.$Message.warning('学校名称不能为空!')
+					return
+				}
+				
+				
+				this.btnLoading = true
+				// this.$refs.upload1.onConfirmUpload()
+				this.schoolName = ''
+				this.schoolName2 = ''
+				setTimeout(() => {
+					this.$refs.upload1.fileArr = []
+					this.$refs.upload2.fileArr = []
+					this.$Message.success('上传成功!')
+					this.btnLoading = false
+				},1000)
+			},
+			
+			uploadFinish(res){
+				this.schoolName = ''
+				this.schoolName2 = ''
+				this.$refs.upload1.fileArr = []
+				this.$refs.upload2.fileArr = []
+				this.$Message.success('上传成功!')
+			}
+		}
+	}
+</script>
+
+<style lang="less" src="./AbilityUpload.less" scoped></style>

+ 111 - 0
TEAMModelOS/ClientApp/src/view/areaMgmt/HourProg.vue

@@ -0,0 +1,111 @@
+<template>
+    <div class="hour-prog-pie" :id="'hour-prog-pie'+id"></div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        count: {
+            type: Array,
+            default: () => {
+                return [0, 0, 0]
+            }
+        }
+    },
+    data() {
+        return {
+            id: '',
+            myData: [],
+            techScoreGau: undefined,
+            option: {
+                tooltip: {
+                    trigger: 'item'
+                },
+                series: [
+                    {
+                        type: 'pie',
+                        radius: ['30%', '70%'],
+                        label: {
+                            show: false,
+                            position: 'center'
+                        },
+                        labelLine: {
+                            show: false
+                        },
+                        data: [
+                            {
+                                value: 1048,
+                                name: '已完成',
+                                itemStyle: {
+                                    color: '#19be6b'
+                                }
+                            },
+                            {
+                                value: 735,
+                                name: '进行中',
+                                itemStyle: {
+                                    color: '#ff9900'
+                                }
+                            },
+                            {
+                                value: 580,
+                                name: '未开始',
+                                itemStyle: {
+                                    color: '#ed4014'
+                                }
+                            }
+                        ]
+                    }
+                ]
+            }
+        }
+    },
+    mounted() {
+        this.techScoreGau = this.$echarts.init(document.getElementById('hour-prog-pie' + this.id))
+        this.techScoreGau.setOption(this.option)
+        this.erd = elementResizeDetectorMaker()
+        this.erd.listenTo(document.getElementById("hour-prog-pie" + this.id), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.techScoreGau.resize()
+            })
+        })
+    },
+    created() {
+        this.id = this.id || this.$jsFn.getBtwRandom(10000, 99999)
+    },
+    methods: {
+    },
+    watch: {
+        count: {
+            handler(n, o) {
+                if (this.count && this.count.length) {
+                    console.log('统计数据', this.count)
+                    this.option.series[0].data.forEach((item, index) => {
+                        item.value = this.count[index]
+                    })
+                    if (this.techScoreGau) {
+                        this.techScoreGau.setOption(this.option)
+                    } else {
+                        this.id = this.id || this.$jsFn.getBtwRandom(10000, 99999)
+                        this.$nextTick(() => {
+                            this.techScoreGau = this.$echarts.init(document.getElementById('hour-prog-pie' + this.id))
+                            this.techScoreGau.setOption(this.option)
+                        })
+                    }
+                }
+            },
+            immediate: true,
+            deep: true
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.hour-prog-pie {
+    width: 400px;
+    height: 240px;
+}
+</style>
+<style>
+</style>

+ 69 - 0
TEAMModelOS/ClientApp/src/view/areaMgmt/Index.less

@@ -0,0 +1,69 @@
+.areaMgmt-container{
+	padding: 10px;
+	height: 100vh;
+	background-color: #fff;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+	.header{
+		padding: 10px 0;
+		.title{
+			font-size:18px;
+			font-weight:  bold;
+			padding: 10px;
+			
+			
+			&::before {
+				content: '';
+				display: inline-block;
+				border: 2px solid #0fa4c1;
+				height: 12px;
+				margin-right: 10px;
+			}
+		}
+		
+		.info{
+			
+		}
+	}
+	
+	.content{
+		display: flex;
+		flex-wrap: wrap;
+		.sch-item{
+			margin: 10px;
+			border-radius: 8px;
+			background-color: #f9f9f9;
+			width: 270px;
+			height: 220px;
+			transition: all 0.2s ease 0s;
+			cursor: pointer;
+			
+			&:hover {
+			    box-shadow: 0 26px 40px -24px #aaa;
+			    transform: translateY(-8px);
+			}
+			
+			&-img{
+				width: 100%;
+				height: 150px;
+				margin-bottom: 8px;
+				border-radius: 8px 8px 0 0;
+				background-size: contain;
+				background-repeat: no-repeat;
+				background-position: center;
+				margin-top: 5px;
+			}
+			
+			&-name{
+				font-size: 18px;
+				font-weight: bold;
+				margin-left: 10px;
+				margin-bottom: 5px;
+				padding-left: 10px;
+			}
+			&-info{
+				margin-left: 10px;
+				padding-left: 10px;
+			}
+		}
+	}
+}

+ 53 - 0
TEAMModelOS/ClientApp/src/view/areaMgmt/Index.vue

@@ -0,0 +1,53 @@
+<template>
+    <div class="areaMgmt-container">
+        <div class="header">
+            <span class="title">所有学校</span>
+            <span class="info">(共建 {{ schList.length }} 个试点校,参训人数 {{ totalTeachers }} 人)</span>
+        </div>
+        <div class="content">
+            <div class="sch-item" v-for="(item,index) in schList" :key="index" @click="toSchoolDetail(index)">
+                <div class="sch-item-img" :style="{backgroundImage: 'url('+ item.picture +')' }">
+                </div>
+                <p class="sch-item-name">{{ item.name }}</p>
+                <p class="sch-item-info">参训人数:{{ item.count }}</p>
+            </div>
+
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+    data() {
+        return {
+            schList: []
+        }
+    },
+    created() {
+        this.getSchList(sessionStorage.getItem('areaId'))
+    },
+    methods: {
+        toSchoolDetail(index) {
+            console.log(this.schList[index])
+            this.$router.push({
+                name: "schoolDetail",
+                params: this.schList[index]
+            })
+        },
+        getSchList(areaId) {
+            this.$api.ability.getSchList({
+                id: areaId
+            }).then(res => {
+                this.schList = res.info
+            })
+        }
+    },
+    computed: {
+        totalTeachers() {
+            return this.schList.reduce((a, b) => a + b.count, 0)
+        }
+    }
+}
+</script>
+
+<style lang="less" src="./Index.less" scoped></style>

+ 427 - 0
TEAMModelOS/ClientApp/src/view/areaMgmt/SchoolDetail.vue

@@ -0,0 +1,427 @@
+<template>
+    <div class="school-detail-container">
+        <Loading v-show="isLoading"></Loading>
+        <vuescroll>
+            <!-- 学校基础信息 -->
+            <div class="school-base-info" v-if="schoolInfo">
+                <img :src="schoolInfo.picture" class="school-img">
+                <div class="school-info-wrap">
+                    <h3 class="school-name">{{schoolInfo.name}}</h3>
+                    <p class="school-info-item">
+                        <span class="info-label">教师人数:</span>
+                        <span class="info-value">{{schoolInfo.count}}人</span>
+                    </p>
+                    <p class="school-info-item">
+                        <span class="info-label">信息化负责人:</span>
+                        <span class="info-value">罗老师</span>
+                    </p>
+                </div>
+                <span class="return-list" @click="returnList">
+                    <Icon type="md-arrow-back" size="16" />
+                    返回列表
+                </span>
+            </div>
+            <!-- 学时数据统计 -->
+            <div class="online-train-wrap">
+                <h4 class="block-title">学时统计</h4>
+                <div class="hour-data-wrap">
+                    <div class="hour-data-item" v-for="(item,index) in hourData" :key="index">
+                        <p class="hour-data-title">
+                            {{item.name}}
+                            <span class="hour-value">({{item.total}}学时)</span>
+                        </p>
+                        <div class="hour-chart">
+                            <HourProg :count="[item.complete,item.going,item.uncomplete]"></HourProg>
+                            <p class="legend-item">
+                                <span>
+                                    未开始:{{item.uncomplete}}人
+                                </span>
+                            </p>
+                            <p class="legend-item">
+                                <span>
+                                    进行中:{{item.going}}人
+                                </span>
+                            </p>
+                            <p class="legend-item">
+                                <span>
+                                    已完成:{{item.complete}}人
+                                </span>
+                            </p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <!-- 网络研修数据统计 -->
+            <div class="online-train-wrap">
+                <h4 class="block-title">网络研修统计</h4>
+                <div class="online-data-wrap">
+                    <div class="percent-wrap">
+                        <Percent :count="dimension"></Percent>
+                    </div>
+                    <div class="point-wrap">
+                        <AbilityPoint :count="abilityPoint"></AbilityPoint>
+                    </div>
+                </div>
+            </div>
+            <!-- 校本研修数据统计 -->
+            <div class="online-train-wrap">
+                <h4 class="block-title">校本研修统计</h4>
+                <div class="offline-data-wrap">
+                    <div class="offline-data-item" v-for="(item,index) in trainType" :key="index">
+                        <img class="offline-img" :src="trainImgs[index]">
+                        <div style="margin-left:20px;height:fit-content;">
+                            <p class="offline-label" :title="item.label">{{item.label}}</p>
+                            <p class="offline-value"><b>{{item.count || 0}}</b></p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <!-- 教研组数据统计 -->
+            <!-- <div class="online-train-wrap">
+                <h4 class="block-title">教研组</h4>
+            </div> -->
+        </vuescroll>
+    </div>
+</template>
+<script>
+import Percent from '@/view/statistics/Percent.vue'
+import AbilityPoint from '@/view/statistics/AbilityPoint.vue'
+import HourProg from './HourProg.vue'
+export default {
+    components: {
+        Percent, AbilityPoint, HourProg
+    },
+    data() {
+        return {
+            schoolInfo: undefined,
+            isLoading: false,
+            dimension: [],
+            abilityPoint: [],
+            offlineData: [
+                {
+                    label: '教研组',
+                    value: 0
+                }
+            ],
+            trainType: [],
+            trainImgs:[
+                'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E4%BF%A1%E6%81%AF%E5%8C%96%E6%95%99%E5%AD%A6%E6%A1%88%E4%BE%8B.png',
+                'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E4%B8%93%E5%AE%B6%E4%B8%93%E9%A2%98%E5%9F%B9%E8%AE%AD.png',
+                'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E5%90%8C%E8%AF%BE%E5%BC%82%E6%9E%84.png',
+                'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E5%90%8C%E8%AF%BE%E5%90%8C%E6%9E%84.png',
+                'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E6%A0%A1%E6%9C%AC2.0%E5%9F%B9%E8%AE%AD.png'
+            ],
+            hourData: [
+                {
+                    name: '线上研修',
+                    total: 20,
+                    complete: 0,
+                    going: 0,
+                    uncomplete: 0
+                },
+                {
+                    name: '校本研修',
+                    total: 10,
+                    complete: 0,
+                    going: 0,
+                    uncomplete: 0
+                },
+                {
+                    name: '应用考核',
+                    total: 15,
+                    complete: 0,
+                    going: 0,
+                    uncomplete: 0
+                },
+                {
+                    name: '课堂实录',
+                    total: 5,
+                    complete: 0,
+                    going: 0,
+                    uncomplete: 0
+                }
+            ]
+        }
+    },
+    methods: {
+        returnList() {
+            this.$router.push({
+                name: 'areaMgmt'
+            })
+        },
+        findTotalData() {
+            this.isLoading = true
+            let params = {
+                school: this.schoolInfo.id
+            }
+            this.$api.ability.FindTotalData(params).then(
+                res => {
+                    if (!res.error) {
+                        let deData = this.$GLOBAL.DIMENSIONS()
+                        // 能力点统计
+                        res.abilityCount.forEach(item => {
+                            item.percent = (item.count * 100 / res.subCount).toFixed(1) + '%'
+                            let d = deData.find(i => {
+                                return i.code == item.dimension
+                            })
+                            if (d) {
+                                item.dimensionName = d.val
+                            }
+                        })
+                        this.abilityPoint = res.abilityCount
+
+                        // 能力维度统计
+                        res.dimensionCount.forEach(item => {
+                            let d = deData.find(i => {
+                                return i.code == item.dimension
+                            })
+                            if (d) {
+                                item.name = d.val
+                            }
+                        })
+                        this.dimension = res.dimensionCount
+
+                        //学时数据统计
+                        res.groupMembers.forEach(item => {
+                            // 线上研修
+                            if (item.onlineTime == 0) {
+                                this.hourData[0].uncomplete++
+                            } else if (item.onlineTime >= 20) {
+                                this.hourData[0].complete++
+                            } else {
+                                this.hourData[0].going++
+                            }
+                            // 校本研修
+                            if (item.offlinelTime == 0) {
+                                this.hourData[1].uncomplete++
+                            } else if (item.onlineTime >= 10) {
+                                this.hourData[1].complete++
+                            } else {
+                                this.hourData[1].going++
+                            }
+                            // 应用考核
+                            if (item.schoolScoreTime == 0) {
+                                this.hourData[2].uncomplete++
+                            } else if (item.onlineTime >= 15) {
+                                this.hourData[2].complete++
+                            } else {
+                                this.hourData[2].going++
+                            }
+                            // 课堂实录
+                            if (item.classVideoTime == 0) {
+                                this.hourData[3].uncomplete++
+                            } else if (item.onlineTime >= 5) {
+                                this.hourData[3].complete++
+                            } else {
+                                this.hourData[3].going++
+                            }
+                        })
+                    }
+                },
+                err => {
+
+                }
+            ).finally(() => {
+                this.isLoading = false
+            })
+        },
+        findTrainList() {
+            let params = {
+                code: this.schoolInfo.id
+            }
+            this.$api.train.findTrainList(params).then(
+                res => {
+                    console.log(res)
+                    this.trainType = this.$GLOBAL.TRAIN_TYPE()
+                    res.studies.forEach(item => {
+                        this.trainType[item.type - 1].count = this.trainType[item.type - 1].count || 0
+                        this.trainType[item.type - 1].count++
+                    })
+                    this.trainType.splice(this.trainType.length - 1, 1)
+                },
+                err => {
+                }
+            )
+        }
+
+    },
+    created() {
+
+    },
+    watch: {
+        '$route': {
+            handler(n, o) {
+                this.schoolInfo = n.params
+                if (this.schoolInfo && this.schoolInfo.id) {
+                    this.findTotalData()
+                    this.findTrainList()
+                }
+            },
+            deep: true,
+            immediate: true
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.hour-chart {
+    // height: 200px;
+    text-align: center;
+    .legend-item {
+        font-size: 12px;
+        margin-top: 5px;
+        &::before {
+            content: " ";
+            display: inline-block;
+            width: 15px;
+            height: 8px;
+            // background: red;
+            border-radius: 2px;
+            margin-right: 10px;
+            margin-left: 15px;
+        }
+        &:nth-child(2)::before {
+            background: #ed4014;
+        }
+        &:nth-child(3)::before {
+            background: #ff9900;
+        }
+        &:nth-child(4)::before {
+            background: #19be6b;
+        }
+    }
+}
+.hour-data-item {
+    width: 23%;
+}
+.hour-data-wrap {
+    display: flex;
+    justify-content: space-evenly;
+    margin-top: 20px;
+    font-size: 16px;
+    .hour-value {
+        font-weight: bold;
+        color: #1e1f24;
+        font-size: 16px;
+    }
+    .hour-data-title {
+        text-align: center;
+        background: #f9f9f9;
+        padding: 10px 0px;
+    }
+}
+.offline-data-wrap {
+    margin-bottom: 20px;
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+}
+.offline-data-item {
+    margin: 10px;
+    display: flex;
+    align-items: center;
+    .offline-label {
+        width: 120px;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+        color: #1e1f24;
+        cursor: pointer;
+    }
+    .offline-value {
+        margin-top: 5px;
+        font-size: 30px;
+        font-weight: bold;
+        color: #1e1f24;
+    }
+}
+.offline-img {
+    width: 80px;
+    height: 80px;
+    border-radius: 10px;
+}
+.school-detail-container {
+    width: 100%;
+    height: 100%;
+    padding: 15px;
+}
+.school-base-info {
+    border-radius: 5px;
+    width: 100%;
+    background: white;
+    padding: 25px 15px;
+    // box-shadow: 0px 4px 4px 1px #f0f0f0;
+    display: flex;
+    position: relative;
+    align-items: center;
+}
+.return-list {
+    position: absolute;
+    right: 80px;
+    top: 35px;
+    color: #1cc0f3;
+    font-weight: bold;
+    cursor: pointer;
+}
+.school-img {
+    margin-left: 30px;
+    margin-right: 30px;
+    width: 150px;
+    &::after {
+        content: " ";
+        width: 1px;
+        height: 100px;
+        background: #a5a5a5;
+    }
+}
+.school-info-wrap {
+    margin-left: 30px;
+    .school-name {
+        font-size: 26px;
+        margin-bottom: 22px;
+        line-height: 33px;
+    }
+}
+.school-info-item {
+    margin-top: 10px;
+    .info-label {
+        color: #a0a0a0;
+    }
+    .info-value {
+        color: #5cadff;
+        font-weight: 600;
+    }
+}
+.online-train-wrap {
+    border-radius: 5px;
+    width: 100%;
+    background: white;
+    padding: 30px 20px;
+    box-shadow: 0px 4px 4px 1px #f0f0f0;
+    margin-top: 10px;
+}
+.block-title {
+    border-left: 6px solid #1cc0f3;
+    padding-left: 15px;
+    font-size: 18px;
+    line-height: 16px;
+    color: #414749;
+    margin-bottom: 30px;
+}
+.online-data-wrap {
+    display: flex;
+}
+.percent-wrap {
+    width: fit-content;
+    padding: 30px 0px;
+    background: white;
+    margin-left: 30px;
+}
+.point-wrap {
+    width: fit-content;
+    padding: 30px 0px;
+    background: white;
+    margin-left: 20px;
+    flex: 1;
+}
+</style>

+ 266 - 0
TEAMModelOS/ClientApp/src/view/areatrain/Create.less

@@ -0,0 +1,266 @@
+.create-train-container{
+    width: 100%;
+    height: 100%;
+    padding: 10px;
+}
+.create-form-wrap{
+    background: white;
+    width: 80%;
+    min-height: 800px;
+    margin: auto;
+    margin-top: 10px;
+    padding: 20px;
+    box-shadow: 0px 0px 10px #ccc;
+    border-radius: 5px;
+    position: relative;
+    h1{
+        font-size: 22px;
+        text-align: center;
+        margin-top: 50px;
+        margin-bottom: 10px;
+    }
+    
+}
+.train-form{
+    width: 600px;
+    margin: auto;
+    .img-box{
+        width: 100%;
+        height: 240px;
+        background-size: cover;
+        background-repeat: no-repeat;
+    }
+    .title{
+        font-size: 22px;
+        text-align: center;
+        margin-top: 50px;
+        margin-bottom: 10px;
+    }
+    .hour{
+        margin-left: 10px;
+        display: inline-block;
+        font-size: 12px;
+        color: #ff9900;
+        border: 1px solid #ff9900;
+        padding: 0px 5px;
+        border-radius: 4px;
+        vertical-align: middle;
+    }
+}
+.save-btn{
+    width: 500px;
+    margin-top: 20px;
+    margin-left: 100px;
+}
+.step-wrap{
+    width:80%;
+    margin:20px auto 30px auto;
+}
+.check-item{
+    display: block;
+    margin-bottom: 20px;
+}
+.base-info-wrap{
+    display: flex;
+    .info-label{
+        color: #a0a0a0;
+    }
+}
+.infos{
+    margin-left: 5px;
+    margin-top: 15px;
+    
+    .info-item{
+        margin-top: 3px;
+    }
+}
+.tarin-img{
+    width: 240px;
+}
+.published-icon{
+    color: #19be6b;
+    font-size: 99px;
+    display: block;
+    margin-top: 150px;
+}
+.success-text{
+    text-align: center;
+    margin-top: 10px;
+    color: #19be6b;
+    font-size: 20px;
+    font-weight: 600;
+}
+.link-text{
+    text-align: center;
+    text-decoration: underline;
+    font-weight: 400;
+    font-size: 14px;
+    color: #909090;
+    margin-top: 30px;
+    cursor: pointer;
+}
+.train-content{
+    // background: #f6f6f6;
+    padding: 10px 5px;
+    font-size: 15px;
+    border-radius: 5px;
+    margin-top: 10px;
+}
+.to-train-mgt{
+    position: absolute;
+    z-index: 999;
+    cursor: pointer;
+    text-align: center;
+    padding: 5px 10px;
+    box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.2);
+    border-radius: 5px;
+    color: #1cc0f3;
+    font-size: 12px;
+    &:hover{
+        background: #f0f0f0;
+    }
+}
+.step-icon-wrap{
+    background: #1cc0f3;
+    color: white;
+    border-radius: 50%;
+    box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.2);
+    display: inline-block;
+}
+.last-step{
+    left: 40px;
+    top: 350px;
+    .step-icon-wrap{
+        padding: 6px 7px 6px 5px;
+    }
+}
+.next-step{
+    right: 40px;
+    top: 350px;
+    .step-icon-wrap{
+        padding: 6px 5px 6px 7px;
+    }
+}
+.qu-action-wrap{
+    display: flex;
+    justify-content: space-evenly;
+    .qu-action-item{
+        margin-top: 20px;
+        border: 1px solid #eee;
+        padding: 40px 50px;
+        color: #1cc0f3;
+        cursor: pointer;
+        text-align: center;
+        user-select: none;
+        &:hover{
+            background: #1cc0f3;
+            color: white;
+        }
+    }
+}
+.qu-type-select{
+    &-item{
+        margin-left: 10px;
+        display: inline-block;
+        padding: 4px 14px;
+        background-color: #ababab;
+        color: #fff;
+        cursor: pointer;
+    }
+    
+    .item-active{
+        background-color: #1fb06d;
+    }
+}
+.qn-item{
+    &:first-child{
+        margin-top: 0px;
+    }
+    &:last-child{
+        margin-bottom: 0px;
+    }
+    position: relative;
+    display: flex;
+    margin: 10px 0;
+    font-size: 16px;
+    font-weight: bold;
+    padding: 20px;
+    user-select: none;
+    cursor: move;
+    
+    &-content{
+        flex: 1;
+    }
+    
+    &-charts{
+        width: 20%;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-end;
+    }
+    
+    .qn-stem p{
+        display: inline-block;
+    }
+    
+    &:hover{
+        background: #ececec;
+        
+        .qn-tools{
+            visibility: visible;
+        }
+    }
+    
+    .ivu-radio-wrapper{
+        margin: 20px 40px 20px 10px;
+        
+        .ivu-radio{
+            margin-right: 5px;
+        }
+    }
+    
+    .ivu-checkbox-wrapper{
+         margin: 20px 40px 20px 10px;
+        
+        .ivu-checkbox{
+            margin-right: 5px;
+        }
+        
+    }
+    
+    .ivu-checkbox-disabled+span{
+        color:inherit !important;
+    }
+    
+    .qn-tools{
+        position: absolute;
+        display: flex;
+        top: -30px;
+        right:0;
+        font-size: 14px;
+        background: #00ae9b;
+        color:#fff;
+        visibility: hidden;
+        
+        .qn-tools-item{
+            padding: 0px 10px;
+            cursor: pointer;
+            
+            &:not(:last-child){
+                margin-right: 15px;
+            }
+            
+            &:hover{
+                background: #2597ae;
+            }
+            
+            span{
+                margin-left: 5px;
+            }
+        }
+    }
+}
+.qn-box{
+    border: 1px solid #eeeeee;
+    margin-bottom: 10px;
+}

+ 873 - 0
TEAMModelOS/ClientApp/src/view/areatrain/Create.vue

@@ -0,0 +1,873 @@
+<template>
+    <div class="create-train-container">
+        <vuescroll>
+            <div class="create-form-wrap">
+                <!-- 上一步 -->
+                <div v-show="step > 0" class="to-train-mgt last-step" @click="lastStep()">
+                    <span class="step-icon-wrap">
+                        <Icon type="ios-arrow-back" size="24" color="white" />
+                    </span>
+                    <p style="margin-top:2px">上一步</p>
+                </div>
+                <!-- 下一步 -->
+                <div v-show="step < 3" class="to-train-mgt next-step" @click="nextStep()">
+                    <span class="step-icon-wrap">
+                        <Icon type="ios-arrow-forward" size="24" color="white" />
+                    </span>
+                    <p style="margin-top:2px">下一步</p>
+                </div>
+                <Steps :current="step" class="step-wrap">
+                    <Step title="基础信息" @click.native="step = 0"></Step>
+                    <Step title="详情信息"></Step>
+                    <Step title="高级设置"></Step>
+                    <Step title="发布活动"></Step>
+                </Steps>
+                <Form ref="baseInfo" :model="baseInfo" :rules="baseRule" :label-width="100" class="train-form" label-colon v-show="step == 0">
+                    <h1>基础信息</h1>
+                    <FormItem label="类型" prop="type" style="margin-top:80px">
+                        <Select v-model="baseInfo.type">
+                            <Option v-for="item in typeList" :value="item.value" :key="item.value">{{ item.label }}</Option>
+                        </Select>
+                    </FormItem>
+                    <FormItem label="学时" prop="hour" style="margin-top:30px">
+                        <InputNumber :max="10" :min="1" style="width:100%" v-model="baseInfo.hour" placeholder="请输入学时"></InputNumber>
+                    </FormItem>
+                    <FormItem label="对象" prop="target" style="margin-top:30px">
+                        <el-cascader size="small" :show-all-levels="false" clearable filterable v-model="baseInfo.target" :options="csOptions" :props="props" @change="treeChange" style="width:100%;">
+                            <template slot="empty">
+                                <p>
+                                    学校暂未分组,勾选学校则为学校所有老师
+                                </p>
+                            </template>
+                        </el-cascader>
+                    </FormItem>
+                </Form>
+                <Form ref="trainInfo" :model="trainInfo" :rules="trainRule" :label-width="100" class="train-form" label-colon v-show="step == 1">
+                    <h1>详细信息</h1>
+                    <FormItem label="上传封面">
+                        <Upload type="drag" action="/blob/public-upload" :format="['jpg','png','jpeg']" :on-format-error="handleFormatError" :on-success="success">
+                            <div style="padding: 10px 0">
+                                <Icon type="ios-cloud-upload" size="52" style="color: #3399ff"></Icon>
+                                <p>1、格式支持:jpg、png、jpeg,大小不超过3M;</p>
+                                <p>2、图片最佳比例400 * 300;</p>
+                            </div>
+                        </Upload>
+                    </FormItem>
+                    <FormItem label="主讲人" prop="presenter">
+                        <Input v-model="trainInfo.presenter" placeholder="请输入主讲人"></Input>
+                    </FormItem>
+                    <FormItem label="培训主题" prop="topic">
+                        <Input v-model="trainInfo.topic" placeholder="请输入培训主题"></Input>
+                    </FormItem>
+                    <FormItem label="培训时间" prop="time">
+                        <DatePicker :editable="false" @on-change="getTimeInfo" type="datetimerange" format="yyyy-MM-dd HH:mm" placeholder="请设置培训时间" style="width: 500px"></DatePicker>
+                    </FormItem>
+                    <FormItem label="培训地点" prop="address">
+                        <Input v-model="trainInfo.address" placeholder="请输入培训地点"></Input>
+                    </FormItem>
+                    <FormItem label="培训内容">
+                        <Input v-model="trainInfo.desc" type="textarea" placeholder="请输入活动描述" maxlength="200" show-word-limit :autosize="{minRows: 5, maxRows: 8}"></Input>
+                    </FormItem>
+                </Form>
+                <Form ref="highInfo" :model="trainInfo" :rules="trainRule" :label-width="100" class="train-form" label-colon v-show="step == 2">
+                    <h1>高级设置</h1>
+                    <CheckboxGroup v-model="trainInfo.setting" @on-change="initStatus">
+                        <!-- <Checkbox label="notice" class="check-item">
+                            <span>发送通知</span>
+                        </Checkbox> -->
+                        <Checkbox label="sign" class="check-item">
+                            <span>现场签到</span>
+                        </Checkbox>
+                        <Checkbox label="hw" class="check-item">
+                            <span>布置作业</span>
+                        </Checkbox>
+                        <Checkbox label="survey" class="check-item">
+                            <span>问卷反馈</span>
+                        </Checkbox>
+                        <Checkbox label="exam" class="check-item">
+                            <span>评测反馈</span>
+                        </Checkbox>
+                    </CheckboxGroup>
+                    <Tabs v-model="tabName">
+                        <TabPane v-for="(item,index) in tabListShow" :key="index" :label="item.label" :name="item.name">
+                            <div v-if="item.name == 'hw'">
+                                <FormItem label="作业名称" prop="hwName">
+                                    <Input v-model="trainInfo.hwName" placeholder="请输入作业名称"></Input>
+                                </FormItem>
+                                <FormItem label="作业内容" prop="hwDesc">
+                                    <Input v-model="trainInfo.hwDesc" type="textarea" placeholder="请输入活动描述" maxlength="200" show-word-limit :autosize="{minRows: 6, maxRows: 8}"></Input>
+                                </FormItem>
+                                <FormItem label="截止时间" prop="hwTime">
+                                    <DatePicker type="datetime" v-model="trainInfo.hwTime" placeholder="作业截止时间" format="yyyy-MM-dd HH:mm" style="width: 100%" @on-change="getHwTime"></DatePicker>
+                                </FormItem>
+                            </div>
+                            <div v-else-if="item.name == 'survey'">
+                                <FormItem label="问卷名称" prop="quName">
+                                    <Input v-model="trainInfo.quName" placeholder="请输入问卷名称"></Input>
+                                </FormItem>
+                                <FormItem label="问卷描述" prop="quDesc">
+                                    <Input v-model="trainInfo.quDesc" type="textarea" placeholder="请输入活动描述" maxlength="200" show-word-limit :autosize="{minRows: 6, maxRows: 8}"></Input>
+                                </FormItem>
+                                <!-- 问卷题目区域 -->
+                                <FormItem label="问卷题目" prop="items">
+                                    <div class="qn-box">
+                                        <div class="qn-item" v-for="(item,index) in trainInfo.quItems" :key="index">
+                                            <div class="qn-item-content">
+                                                <!-- 题干 -->
+                                                <p class="qn-stem">
+                                                    <span style="color: red;" v-show="item.required">* </span>
+                                                    <span>{{ index + 1 }}. </span>
+                                                    <span v-html="item.stemContent" style="display: inline-block;"></span>
+                                                    <span>( {{ getQuType(item.type) }} )</span>
+                                                </p>
+                                                <!-- 单选题-选项 -->
+                                                <RadioGroup v-if="item.type === 'single' || item.type === 'judge'">
+                                                    <Radio v-for="(option,optionIndex) in item.optionsContent" :key="optionIndex">
+                                                        <span v-html="option.value" style="display: inline-block;"></span>
+                                                    </Radio>
+                                                </RadioGroup>
+                                                <!-- 多选题-选项 -->
+                                                <CheckboxGroup v-if="item.type === 'multiple'">
+                                                    <Checkbox v-for="(option,optionIndex) in item.optionsContent" :key="optionIndex">
+                                                        <span v-html="option.value" style="display: inline-block;"></span>
+                                                    </Checkbox>
+                                                </CheckboxGroup>
+                                                <!-- 工具栏 -->
+                                                <div class="qn-tools">
+                                                    <!-- <div class="qn-tools-item" @click="onItemEdit(item,index)">
+                                                        <Icon type="md-create" />
+                                                        <span>{{ $t('survey.questionaire.edit') }}</span>
+                                                    </div> -->
+                                                    <div class="qn-tools-item" @click="onItemDelete(index)">
+                                                        <Icon type="md-trash" />
+                                                        <span>{{ $t('survey.questionaire.remove') }}</span>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <EmptyData v-show="!trainInfo.quItems" textContent="请添加问卷题目"></EmptyData>
+                                    </div>
+                                    <Button type="info" @click="toAddQu">
+                                        <Icon type="md-add" />
+                                        添加题目
+                                    </Button>
+                                </FormItem>
+                            </div>
+                            <div v-else-if="item.name == 'exam'">
+                                <FormItem label="评测名称" prop="examName">
+                                    <Input v-model="trainInfo.examName" placeholder="请输入评测名称"></Input>
+                                </FormItem>
+                                <FormItem label="评测描述" prop="examDesc">
+                                    <Input v-model="trainInfo.examDesc" type="textarea" placeholder="请输入评测描述" maxlength="200" show-word-limit :autosize="{minRows: 6, maxRows: 8}"></Input>
+                                </FormItem>
+                                <!-- 评测题目区域 -->
+                                <FormItem label="评测题目" prop="items">
+                                    <div class="qn-box">
+                                        <div class="qn-item" v-for="(item,index) in trainInfo.examItems" :key="index">
+                                            <div class="qn-item-content">
+                                                <!-- 题干 -->
+                                                <p class="qn-stem">
+                                                    <span style="color: red;" v-show="item.required">* </span>
+                                                    <span>{{ index + 1 }}. </span>
+                                                    <span v-html="item.stemContent" style="display: inline-block;"></span>
+                                                    <span>( {{ getQuType(item.type) }} )</span>
+                                                </p>
+                                                <!-- 单选题-选项 -->
+                                                <RadioGroup v-if="item.type === 'single' || item.type === 'judge'">
+                                                    <Radio disabled v-for="(option,optionIndex) in item.optionsContent" :key="optionIndex">
+                                                        <span v-html="option.value" style="display: inline-block;"></span>
+                                                    </Radio>
+                                                </RadioGroup>
+                                                <!-- 多选题-选项 -->
+                                                <CheckboxGroup v-if="item.type === 'multiple'">
+                                                    <Checkbox disabled v-for="(option,optionIndex) in item.optionsContent" :key="optionIndex">
+                                                        <span v-html="option.value" style="display: inline-block;"></span>
+                                                    </Checkbox>
+                                                </CheckboxGroup>
+                                                <!-- 工具栏 -->
+                                                <div class="qn-tools">
+                                                    <!-- <div class="qn-tools-item" @click="onItemEdit(item,index)">
+                                                        <Icon type="md-create" />
+                                                        <span>{{ $t('survey.questionaire.edit') }}</span>
+                                                    </div> -->
+                                                    <div class="qn-tools-item" @click="onItemDelete(index)">
+                                                        <Icon type="md-trash" />
+                                                        <span>{{ $t('survey.questionaire.remove') }}</span>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <EmptyData v-show="!trainInfo.quItems" textContent="请添加问卷题目"></EmptyData>
+                                    </div>
+                                    <Button type="info" @click="toAddQu">
+                                        <Icon type="md-add" />
+                                        添加题目
+                                    </Button>
+                                </FormItem>
+                            </div>
+                        </TabPane>
+                    </Tabs>
+                </Form>
+                <div class="train-form" v-show="step == 3 && !published">
+                    <h1 class="title">
+                        {{trainInfo.topic}}
+                        <span class="hour">
+                            {{baseInfo.hour}}学时
+                        </span>
+                    </h1>
+                    <div class="img-box" :style="{backgroundImage: `url(${trainInfo.img || defImg})`}"></div>
+                    <div class="base-info-wrap">
+                        <div class="infos">
+                            <div class="info-item">
+                                <span class="info-label">
+                                    主讲老师:
+                                </span>
+                                <span>
+                                    {{trainInfo.presenter}}
+                                </span>
+                            </div>
+                            <div class="info-item">
+                                <span class="info-label">
+                                    培训主题:
+                                </span>
+                                <span>
+                                    {{trainInfo.topic}}
+                                </span>
+                            </div>
+                            <div class="info-item">
+                                <span class="info-label">
+                                    培训地点:
+                                </span>
+                                <span>
+                                    {{trainInfo.address}}
+                                </span>
+                            </div>
+                            <div class="info-item">
+                                <span class="info-label">
+                                    培训时间:
+                                </span>
+                                <span>
+                                    {{trainInfo.time.join(' 至 ')}}
+                                </span>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="train-content">
+                        <p class="info-item">培训内容:</p>
+                        <p>{{trainInfo.desc}}</p>
+                    </div>
+                    <!-- <Button type="success" class="save-btn" style="margin-left:180px;width:240px;margin-top:60px" @click="lastStep">取消发布</Button> -->
+                    <Button type="primary" class="save-btn" style="margin-left:180px;width:240px" @click="publish">确认发布</Button>
+                </div>
+                <div v-show="published">
+                    <Icon type="md-checkmark-circle-outline" class="published-icon" />
+                    <p class="success-text">
+                        发布成功
+                    </p>
+                    <p class="link-text" @click="toTrainMgt">查看信息</p>
+                </div>
+            </div>
+        </vuescroll>
+        <Modal v-model="addQuStatus" title="添加题目" width="700" @on-ok="doSaveItem" class-name="add-item-modal" :loading="modalLoading">
+            <div class="qu-type-select">
+                <template v-for="(item,index) in quTypeList">
+                    <span :key="index" v-show="index < 3 || (index == 3 && tabName == 'survey')" :class="['qu-type-select-item', index == activeQuIndex ? 'item-active' : '', ]" @click="activeQuIndex = index">{{ item.label }}</span>
+                </template>
+                <BaseSingle v-if="activeQuIndex == 0" ref="addItem" @onSave="onSave" :needAnswer="tabName == 'exam'"></BaseSingle>
+                <BaseMultiple v-if="activeQuIndex==1" ref="addItem" @onSave="onSave" :needAnswer="tabName == 'exam'"></BaseMultiple>
+                <BaseJudge v-if="activeQuIndex==2" ref="addItem" @onSave="onSave" :needAnswer="tabName == 'exam'"></BaseJudge>
+                <BaseSubjective v-if="activeQuIndex==3 && tabName == 'survey'" ref="addItem" @onSave="onSave"></BaseSubjective>
+            </div>
+        </Modal>
+    </div>
+</template>
+<script>
+import blobTool from "@/utils/blobTool.js";
+import { mapGetters } from 'vuex'
+import BaseSingle from "@/components/questionnaire/BaseSingle.vue"
+import BaseMultiple from "@/components/questionnaire/BaseMultiple.vue"
+import BaseJudge from "@/components/questionnaire/BaseJudge.vue"
+import BaseSubjective from "@/components/questionnaire/BaseSubjective.vue"
+export default {
+    components: {
+        BaseSingle, BaseMultiple, BaseJudge, BaseSubjective
+    },
+    data() {
+        const hwTimeV = (rule, value, callback) => {
+            console.log(value)
+            if (value) {
+                callback()
+            } else {
+                callback(new Error('请设置截止时间'))
+            }
+        }
+        return {
+            modalLoading: true,
+            props: {
+                multiple: true,
+                value: 'id',
+                label: 'name',
+                // checkStrictly:true,
+                // emitPath:false
+            },
+            csOptions: [],
+            areaId: sessionStorage.getItem('areaId'),
+            tabList: [
+                {
+                    name: 'hw',
+                    label: '作业设置'
+                },
+                {
+                    name: 'survey',
+                    label: '问卷设置'
+                },
+                {
+                    name: 'exam',
+                    label: '评测设置'
+                }
+            ],
+            tabListShow: [],
+            surveyList: [],
+            activeQuIndex: 0,
+            addQuStatus: false,
+            quAction: 'create',
+            tabName: 'hw',
+            hwTime: 0,
+            groupData: [],
+            published: false,
+            baseRule: {
+                name: [
+                    { required: true, message: '请输入活动名称', trigger: 'change' }
+                ],
+                type: [
+                    { required: true, type: 'number', message: '请选择活动类型', trigger: 'change' }
+                ],
+                hour: [
+                    { required: true, type: 'number', message: '请输入活动学时', trigger: 'change' }
+                ],
+                target: [
+                    { required: true, type: 'array', message: '请选择学习对象', trigger: 'change' }
+                ]
+            },
+            trainRule: {
+                presenter: [
+                    { required: true, message: '请输入主讲人', trigger: 'change' }
+                ],
+                topic: [
+                    { required: true, message: '请输入培训主题', trigger: 'change' }
+                ],
+                time: [
+                    { required: true, type: 'array', message: '请设置培训时间', trigger: 'change' }
+                ],
+                address: [
+                    { required: true, message: '请输入培训地点', trigger: 'change' }
+                ],
+                hwName: [
+                    { required: true, message: '请输入作业名称', trigger: 'change' }
+                ],
+                hwDesc: [
+                    { required: true, message: '请输入作业内容', trigger: 'change' }
+                ],
+                hwType: [
+                    { required: true, message: '请设置作业类型', trigger: 'change' }
+                ],
+                hwTime: [
+                    { required: true, validator: hwTimeV, trigger: 'change' }
+                ],
+                quName: [
+                    { required: true, message: '请输入问卷名称', trigger: 'change' }
+                ],
+                quDesc: [
+                    { required: true, message: '请输入问卷描述', trigger: 'change' }
+                ],
+                examName: [
+                    { required: true, message: '请输入评测名称', trigger: 'change' }
+                ],
+                examDesc: [
+                    { required: true, message: '请输入评测描述', trigger: 'change' }
+                ]
+            },
+            defImg: 'https://www.habook.com.cn/data/editor/images/img/20210429%2001/001.png',
+            step: 0,
+            baseInfo: {
+                name: '',
+                target: [],
+                time: '',
+                hour: 1,
+                type: ''
+            },
+            trainInfo: {
+                img: '',
+                topic: '',
+                startTime: '',
+                endTime: '',
+                time: [],
+                presenter: '',//主讲人
+                address: '',
+                desc: '',
+                setting: [],
+                hwName: '',
+                hwTime: '',
+                hwDesc: '',
+                surveyId: '',
+                quName: '',
+                quDesc: '',
+                quItems: [],
+                examId: '',
+                examName: '',
+                examDesc: '',
+                examItems: []
+            },
+            typeList: [],
+            quTypeList: [
+                {
+                    label: '单选题',
+                    value: 0,
+                    type: 'single'
+                },
+                {
+                    label: '多选题',
+                    value: 1,
+                    type: 'multiple'
+                },
+                {
+                    label: '判断题',
+                    value: 2,
+                    type: 'judge'
+                },
+                {
+                    label: '问答题',
+                    value: 3,
+                    type: 'subjective'
+                }
+
+            ]
+        }
+    },
+    computed: {
+        ...mapGetters({
+            teachers: 'user/getSchoolUserJoined', // 取得已加入此學校的使用者
+        })
+    },
+    methods: {
+        treeChange(data) {
+            console.log('选择数据', data)
+        },
+        getTargets() {
+            console.log('获取分组信息')
+            this.$api.ability.findAreaGroup({
+                id: this.areaId
+            }).then(
+                res => {
+                    console.log(res)
+                    if (res.gr && res.gr.length) {
+                        this.csOptions = []
+                        res.gr.forEach(sItem => {
+                            let i = {
+                                id: sItem.id,
+                                name: sItem.sname,
+                                sas: '?' + sItem.sas,
+                                url: sItem.uri.substring(0, sItem.uri.lastIndexOf('/')),
+                                children: []
+                            }
+                            sItem.name.forEach(gItem => {
+                                i.children.push({
+                                    id: gItem,
+                                    name: gItem
+                                })
+                            })
+                            if (!i.children.length) {
+                                i.children.push({
+                                    id: 'default-all',
+                                    name: '所有老师(未分组)'
+                                })
+                            }
+                            this.csOptions.push(i)
+                        })
+                        console.log('111', this.csOptions)
+                    }
+
+                },
+                err => {
+                    console.log(err)
+                }
+            )
+        },
+        onItemDelete(index) {
+            if (this.tabName == 'survey') {
+                this.trainInfo.quItems.splice(index, 1)
+            } else if (this.tabName == 'exam') {
+                this.trainInfo.examItems.splice(index, 1)
+            }
+        },
+        getQuType(type) {
+            return this.quTypeList.find(item => {
+                return item.type == type
+            }).label
+        },
+        getSimpleText(html) {
+            var r = /<(?!img|video|audio).*?>/g;
+            return html.replace(r, "").replace(/&nbsp;/g, ' ');
+        },
+        doSaveItem() {
+            this.$refs.addItem.doSave()
+        },
+        handleFormatError(file) {
+            this.$Message.warning('请上传正确格式的封面图片!');
+        },
+        // 检测是否有空选项
+        checkOptionNull(arr) {
+            let flag = true
+            for (let i = 0; i < arr.length; i++) {
+                if (this.getSimpleText(arr[i].value) === '') {
+                    flag = false
+                }
+            }
+            return flag
+        },
+        onSave(item) {
+            console.log('题目数据:', item)
+            //检查信息是否完整
+            if (!this.getSimpleText(item.stemContent) || !this.checkOptionNull(item.optionsContent)) {
+                this.$Message.warning(this.$t('survey.questionaire.noCompleteTip'))
+                this.modalLoading = false
+                setTimeout(() => {
+                    this.modalLoading = true
+                }, 0)
+                return
+            }
+            if (!item.id) {
+                item.id = this.$jsFn.uuid()
+                item.type = this.quTypeList[this.activeQuIndex].type
+                if (this.tabName == 'survey') {
+                    this.trainInfo.quItems.push(item)
+                } else if (this.tabName == 'exam') {
+                    this.trainInfo.examItems.push(item)
+                }
+            } else {
+                let index = this.trainInfo.quItems.findIndex(i => {
+                    return i.id == item.id
+                })
+                if (index > -1) {
+                    if (this.tabName == 'survey') {
+                        this.trainInfo.quItems.splice(index, 1, item)
+                    } else if (this.tabName == 'exam') {
+                        this.trainInfo.examItems.splice(index, 1, item)
+                    }
+                }
+            }
+            // 初始化数据
+            this.activeQuIndex = -1
+            setTimeout(() => {
+                this.activeQuIndex = 0
+            }, 0)
+        },
+        toAddQu() {
+            this.addQuStatus = true
+        },
+        initStatus(data) {
+            this.tabListShow = this.tabList.filter(item => {
+                return data.includes(item.name)
+            })
+            if (this.trainInfo.setting.includes('survey')) {
+                this.quAction = ''
+            } else {
+                this.trainInfo.surveyId = ''
+            }
+            this.tabName = this.trainInfo.setting[data.length - 1] == 'sign' && this.trainInfo.setting.length > 1 ? this.trainInfo.setting[data.length - 2] : this.trainInfo.setting[data.length - 1]
+        },
+        getHwTime(date) {
+            let d = new Date(date)
+            this.hwTime = d.getTime()
+        },
+        //转分组模式数据结构
+        handelGroup() {
+            this.teachers.forEach(item => {
+                item.groupName = item.groupName || '默认组别'
+            })
+            let groupRes = this.$jsFn.groupBy(this.teachers, 'groupName')
+            this.groupData.length = 0
+            for (let index in groupRes) {
+                this.groupData.push({
+                    groupName: groupRes[index][0].groupName || '默认组别',
+                    groupId: groupRes[index][0].groupId,
+                    teachers: groupRes[index]
+                })
+            }
+            this.groupData.sort((a, b) => {
+                return parseInt(a.groupId) - parseInt(b.groupId)
+            })
+            console.log('处理分组数据', this.groupData, groupRes)
+        },
+        toTrainMgt() {
+            this.$router.push({
+                name: 'TrainAreaMgt'
+            })
+        },
+        publish() {
+            let studyInfo = {
+                name: this.trainInfo.topic,
+                settings: this.trainInfo.setting,
+                creatorId: this.$store.state.userInfo.TEAMModelId,
+                type: this.baseInfo.type,
+                hour: this.baseInfo.hour,
+                detail: {
+                    startTime: this.trainInfo.startTime,
+                    endTime: this.trainInfo.endTime,
+                    topic: this.trainInfo.topic,
+                    desc: this.trainInfo.desc,
+                    address: this.trainInfo.address,
+                    presenter: this.trainInfo.presenter,
+                    img: this.trainInfo.img,
+                    hwName: this.trainInfo.hwName,
+                    hwDesc: this.trainInfo.hwDesc,
+                    hwTime: this.hwTime
+                }
+            }
+
+            let examInfo, surveyInfo
+            if (this.trainInfo.setting.includes('survey')) {
+                surveyInfo = {
+                    name: this.trainInfo.quName,
+                    description: this.trainInfo.quDesc,
+                    creatorId: this.$store.state.userInfo.TEAMModelId,
+                    sType: 'train'
+                }
+            }
+
+            if (this.trainInfo.setting.includes('exam')) {
+                examInfo = {
+                    name: this.trainInfo.examName,
+                    description: this.trainInfo.examDesc,
+                    creatorId: this.$store.state.userInfo.TEAMModelId,
+                    sType: 'train'
+                }
+            }
+            //处理发布对象
+            let para = this.handleTarget(this.baseInfo.target)
+            let params = {
+                id: this.areaId,
+                para: para,
+                study: studyInfo,
+                exam: examInfo,
+                survey: surveyInfo
+            }
+
+            console.log('数据', this.baseInfo, params)
+            this.$api.train.saveAreaTrain(params).then(
+                res => {
+                    this.$Message.success('发布成功')
+                    this.published = true
+                    // TODO 保存blob
+                    // if (res.ids && res.ids.length) {
+                    //     res.ids.forEach(item => {
+                    //         let schoolInfo = this.csOptions.find(cs => {
+                    //             return cs.id == item.school
+                    //         })
+                    let examItems = this.trainInfo.examItems.map(qItem => {
+                        return {
+                            id: qItem.id,
+                            type: qItem.type,
+                            question: qItem.stemContent,
+                            option: qItem.optionsContent,
+                            answer: qItem.answer
+                        }
+                    })
+                    let surveyItems = this.trainInfo.quItems.map(qItem => {
+                        return {
+                            id: qItem.id,
+                            type: qItem.type,
+                            question: qItem.stemContent,
+                            option: qItem.optionsContent
+                        }
+                    })
+
+                    //         if (schoolInfo) {
+                    // 现在不用每个学校上传资源,直接传到区级容器
+                    console.log('返回数据', res)
+                    let uri = this.$store.state.user.osblob_uri
+                    let sas = this.$store.state.user.osblob_sas
+                    let areaBlob = {
+                        url: uri.substring(0, uri.lastIndexOf('/')),
+                        id: uri.substring(uri.lastIndexOf('/') + 1),
+                        sas: '?' + sas
+                    }
+                    if (this.trainInfo.setting.includes('exam')) {
+                        // examInfo.id = item.examId
+                        examInfo.id = res.acId
+                        // examInfo.school = item.school
+                        // this.doUploadBlob(examInfo, examItems, schoolInfo)
+                        this.doUploadBlob(examInfo, examItems, areaBlob, 'exam')
+                    }
+                    if (this.trainInfo.setting.includes('survey')) {
+                        // surveyInfo.id = item.surveyId
+                        surveyInfo.id = res.acId
+                        // surveyInfo.school = item.school
+                        // this.doUploadBlob(surveyInfo, surveyItems, schoolInfo)
+                        this.doUploadBlob(surveyInfo, surveyItems, areaBlob, 'survey')
+                    }
+                    //         } else {
+                    //             this.$Message.error('未找到对应学校的授权')
+                    //         }
+                    //     })
+                    // }
+
+                },
+                err => {
+                    this.$Message.error('发布失败')
+                }
+            )
+        },
+        //处理发布对象数据格式
+        handleTarget(treeData) {
+            let d = []
+            treeData.forEach(item => {
+                let s = d.find(i => {
+                    return i.sId == item[0]
+                })
+                if (s) {
+                    s.gName.push(item[1])
+                } else {
+                    d.push({
+                        sId: item[0],
+                        gName: [item[1]]
+                    })
+                }
+            })
+            d.forEach(item => {
+                if (item.gName.includes('default-all')) {
+                    item.gName = []
+                }
+            })
+            return d
+        },
+
+        /* 上传单个题目 */
+        /**
+         * @param type survey || exam
+         */
+        async doUploadItems(qnBaseInfo, items, blobInfo, type) {
+            return new Promise(async (resolve, reject) => {
+                let promiseArr = []
+                //初始化Blob
+                let containerClient = new blobTool(
+                    blobInfo.url,
+                    blobInfo.id,
+                    blobInfo.sas,
+                    'school'
+                )
+
+                for (let item of items) {
+                    let curId = item.id || this.$tools.guid()
+                    promiseArr.push(new Promise(async (r, j) => {
+                        let file = new File([JSON.stringify(item)], curId + ".json");
+                        try {
+                            // 等待上传blob的返回结果
+                            // let blobFile = await containerClient.upload(file, `train/${qnBaseInfo.id}`);
+                            console.log('11', containerClient)
+                            let blobFile = await containerClient.upload(file, `${this.areaId}/${type}/${qnBaseInfo.id}`, {}, true, `${this.areaId}/`);
+                            if (blobFile.blob) {
+                                r(blobFile.blob)
+                            } else {
+                                this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+                            }
+                        } catch (e) {
+                            console.log(e)
+                            this.$Message.error('upload error');
+                        }
+                    }))
+                }
+                Promise.all(promiseArr).then(result => {
+                    resolve(result)
+                })
+            })
+        },
+
+        /* 保存问卷题目到Blob */
+        /***
+         * @param blobInfo : url,name,sas
+         */
+        async doUploadBlob(qnBaseInfo, items, blobInfo, type) {
+            return new Promise(async (r, j) => {
+                console.log('blobInfo', JSON.stringify(blobInfo))
+                let info = JSON.parse(JSON.stringify(qnBaseInfo))
+                let itemUrls = await this.doUploadItems(info, items, blobInfo, type)
+                info.slides = itemUrls
+                //初始化Blob
+                let containerClient = new blobTool(
+                    blobInfo.url,
+                    blobInfo.id,
+                    blobInfo.sas,
+                    'school'
+                );
+                let file = new File([JSON.stringify(info)], "index.json");
+                try {
+                    // 等待上传blob的返回结果
+                    let blobFile = await containerClient.upload(file, `${this.areaId}/${type}/${qnBaseInfo.id}`, {}, true, `${this.areaId}/`);
+                    if (blobFile.blob) {
+                        delete info.slides
+                        r(blobFile.blob)
+                    } else {
+                        this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+                    }
+                } catch (e) {
+                    j(e)
+                }
+            })
+        },
+
+        getTimeInfo(data) {
+            this.trainInfo.time = data.filter(item => {
+                return item
+            })
+            if (this.trainInfo.time.length > 1) {
+                this.trainInfo.startTime = (new Date(this.trainInfo.time[0])).getTime()
+                this.trainInfo.endTime = (new Date(this.trainInfo.time[1])).getTime()
+            }
+        },
+        nextStep() {
+            let form = ''
+            switch (this.step) {
+                case 0:
+                    form = 'baseInfo'
+                    break
+                case 1:
+                    form = 'trainInfo'
+                    break
+                case 2:
+                    form = 'highInfo'
+                    break
+            }
+            if (form) {
+                this.$refs[form].validate((valid) => {
+                    if (valid) {
+                        this.step++
+                    } else {
+                        this.$Message.error('请完善信息!')
+                    }
+                })
+            } else {
+                this.step++
+            }
+        },
+        lastStep() {
+            this.step--
+        },
+        success(response, file, fileList) {
+            this.trainInfo.img = response.url
+        }
+    },
+    created() {
+        this.typeList = this.$GLOBAL.TRAIN_TYPE()
+        this.getTargets()
+    }
+}
+</script>
+<style lang="less" scoped>
+@import "./Create.less";
+</style>
+<style lang="less">
+.add-item-modal .ivu-modal-footer {
+    margin-top: 40px;
+}
+</style>

+ 201 - 0
TEAMModelOS/ClientApp/src/view/areatrain/PhoneSign.vue

@@ -0,0 +1,201 @@
+<template>
+    <div class="join-wrap">
+        <div class="join-main-box" v-show="!isSign && isOpen">
+            <p class="join-title">
+                <span>
+                    扫码签到
+                </span>
+            </p>
+            <p class="course-name">{{topic || $t('cusMgt.join.errorInfo')}}</p>
+            <div style="width:fit-content;margin: auto;">
+                <p class="info-item">
+                    <span class="info-lable">
+                        主讲人:
+                    </span>
+                    <span class="info-value">
+                        {{presenter}}
+                    </span>
+                </p>
+                <p class="info-item">
+                    <span class="info-lable">
+                        地点:
+                    </span>
+                    <span class="info-value">
+                        {{address}}
+                    </span>
+                </p>
+                <p class="info-item">
+                    <span class="info-lable">
+                        时间:
+                    </span>
+                    <span class="info-value">
+                        {{time}}
+                    </span>
+                </p>
+            </div>
+            <div class="join-btn" @click="signIn()">
+                立即签到
+            </div>
+        </div>
+        <div v-show="isSign" class="join-main-box">
+            <p class="join-title">
+                <span>
+                    扫码签到
+                </span>
+            </p>
+            <Icon type="md-checkmark-circle-outline" color="#19be6b" size="120" />
+            <p class="success-tips" style="font-size:24px;color:#19be6b">签到成功</p>
+        </div>
+        <div v-show="!isOpen" class="join-main-box">
+            <p class="join-title">
+                <span>
+                    扫码签到
+                </span>
+            </p>
+            <Icon type="md-close-circle" color="#ed4014" size="120" />
+            <p class="success-tips" style="font-size:24px;color:#ed4014">未开始签到</p>
+        </div>
+    </div>
+</template>
+<script>
+import jwtDecode from 'jwt-decode'
+export default {
+    data() {
+        return {
+            isOpen: false,
+            isSign: false,
+            id: '',
+            school: '',
+            presenter: '',
+            address: '',
+            time: '',
+            topic: '',
+            code: '',
+            userId: '',
+            p:{}
+        }
+    },
+    methods: {
+        signIn() {
+            let params = {
+                id: this.id,
+                code: this.school,
+                tId: this.userId
+            }
+            this.p = params
+            this.$api.train.signIn(params).then(
+                res => {
+                    this.$Message.success('签到成功')
+
+                },
+                err => {
+
+                }
+            ).finally(() => {
+                this.isSign = true
+            })
+        }
+    },
+    created() {
+        this.id = this.$route.query.id //活动id
+        this.code = this.$route.query.code  //登录成功返回的code
+        this.school = this.$route.query.school
+        this.presenter = this.$route.query.presenter
+        this.address = this.$route.query.address
+        this.time = this.$route.query.time
+        this.topic = this.$route.query.topic
+        this.isOpen = this.$route.query.isOpen == 'true'
+
+        //获取登录信息
+        let srvAdr = this.$store.state.config.srvAdr
+        let host = srvAdr == 'Global' ? this.$store.state.config.Global.coreAPIUrl : this.$store.state.config.China.coreAPIUrl
+        let clientId = srvAdr == 'Global' ? this.$store.state.config.Gloabl.clientID : this.$store.state.config.China.clientID
+        this.$api.service.getToken(host, {
+            grant_type: "authorization_code",
+            client_id: clientId,
+            code: this.code
+        }).then(
+            res => {
+                if (!res.error) {
+                    let tokenData = jwtDecode(res.id_token)
+                    if (tokenData) {
+                        this.userId = tokenData.sub
+                        console.log(this.userId)
+                    } else {
+                        this.$Message.error(this.$t('cusMgt.join.parseErr'))
+                    }
+                } else {
+                    this.$Message.error(this.$t('cusMgt.join.getErr'))
+                }
+            },
+            err => {
+                this.$Message.error(this.$t('cusMgt.join.getErr'))
+            }
+        )
+    }
+}
+</script>
+<style scoped lang="less">
+.success-tips {
+    color: white;
+    font-size: 16px;
+    margin-top: 20px;
+}
+.join-wrap {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-evenly;
+    align-items: center;
+    width: 100%;
+    height: 100%;
+    background-image: url("../../assets/image/bak_light.jpg");
+}
+.join-btn {
+    cursor: pointer;
+    width: 100%;
+    margin: auto;
+    margin-top: 60px;
+    text-align: center;
+    border: 1px solid rgba(25, 190, 107, 0.5);
+    // color: rgba(25, 190, 107, 1);
+    color: white;
+    padding: 4px 30px;
+    border-radius: 5px;
+    font-size: 16px;
+    letter-spacing: 2px;
+    font-weight: 400;
+    user-select: none;
+    background: rgba(25, 190, 107, 0.5);
+}
+.course-name {
+    color: white;
+    margin-bottom: 15px;
+    font-size: 30px;
+    // font-family: cursive;
+}
+.join-main-box {
+    max-width: 90%;
+    width: fit-content;
+    text-align: center;
+    .info-item {
+        margin-top: 20px;
+        font-size: 15px;
+        width: fit-content;
+    }
+    .info-lable {
+        color: #a5a5a5;
+    }
+    .info-value {
+        color: #eeeeee;
+    }
+}
+.join-title {
+    position: absolute;
+    top: 15px;
+    text-align: center;
+    width: 100%;
+    left: 0px;
+    border-bottom: 1px dashed;
+    padding-bottom: 8px;
+}
+</style>

+ 158 - 0
TEAMModelOS/ClientApp/src/view/areatrain/SurveyDetail.less

@@ -0,0 +1,158 @@
+@main-bgColor: rgb(40,40,40); //主背景颜色
+@borderColor: #424242;
+@primary-color:#1CC0F3;
+@primary-textColor: #fff; //文本主颜色
+@second-textColor: #CBCBCB; //文本副级颜色
+@primary-fontSize: 14px;
+@second-fontSize: 16px;
+
+.component-questionnaire {
+	position: relative;
+    width: 100%;
+    height: 100%;
+    padding: 30px 6%;
+	padding-bottom: 100px;
+	.tools-bar{
+		width: 100%;
+		border-bottom: 1px dashed  #CCCCCC;
+		margin-bottom: 20px;
+		padding-bottom: 20px;
+		display: flex;
+		align-items: center;
+		
+		.ivu-btn:not(:first-child){
+			margin-left: 20px;
+		}
+	}
+	
+	.no-data-text {
+	    width: 100%;
+	    padding: 30px;
+	    display: flex;
+	    flex-direction: column;
+	    justify-content: center;
+	    align-items: center;
+	    margin-top: 10px;
+	    font-size: 16px;
+	}
+    
+    .qn-title{
+        text-align: center;
+        font-size: 26px;
+        font-weight: bold;
+    }
+
+    .qn-description{
+        font-size: 14px;
+        font-weight: bold;
+        margin-top: 20px;
+        color:#909090;
+		text-indent: 30px;
+    }
+
+    .qn-item-box{
+		
+		
+        .qn-item{
+			position: relative;
+			display: flex;
+            margin: 10px 0;
+            font-size: 16px;
+            font-weight: bold;
+			padding: 20px;
+			user-select: none;
+			cursor: move;
+			
+			&-content{
+				flex: 1;
+			}
+			
+			&-charts{
+				width: 20%;
+				display: flex;
+				flex-direction: column;
+				justify-content: flex-end;
+			}
+			
+			.qn-stem p{
+				display: inline-block;
+			}
+			
+			&:hover{
+				background: #ececec;
+				
+				.qn-tools{
+					visibility: visible;
+				}
+			}
+			
+            .ivu-radio-wrapper{
+                margin: 20px 40px 20px 10px;
+				
+				.ivu-radio{
+					margin-right: 5px;
+				}
+            }
+			
+			.ivu-checkbox-wrapper{
+			     margin: 20px 40px 20px 10px;
+				
+				.ivu-checkbox{
+					margin-right: 5px;
+				}
+				
+			}
+			
+			.ivu-checkbox-disabled+span{
+				color:inherit !important;
+			}
+			
+			.qn-tools{
+				position: absolute;
+				display: flex;
+				top: -30px;
+				right:0;
+				font-size: 14px;
+				background: #00ae9b;
+				color:#fff;
+				visibility: hidden;
+				
+				.qn-tools-item{
+					padding: 5px 10px;
+					cursor: pointer;
+					
+					&:not(:last-child){
+						margin-right: 15px;
+					}
+					
+					&:hover{
+						background: #2597ae;
+					}
+					
+					span{
+						margin-left: 5px;
+					}
+				}
+			}
+        }
+    }
+	
+	.indexFade-enter-active, .indexFade-leave-active {
+	  transition: all .4s;
+	}
+	.indexFade-enter, .indexFade-leave-active {
+	  transform: translate3d(0, 3rem, 0);
+	  opacity: 0;
+	}
+}
+
+.answer-status-icon{
+	position: absolute;
+	left: -20px;
+	top: 22px;
+	font-size: 20px;
+}
+.standard-answer{
+	font-size: 12px;
+	color: #a5a5a5;
+}

+ 223 - 0
TEAMModelOS/ClientApp/src/view/areatrain/SurveyDetail.vue

@@ -0,0 +1,223 @@
+<template>
+    <div class="component-questionnaire">
+        <!-- 问卷的基本操作栏 -->
+        <p class="qn-title">{{currentQn.name}}</p>
+        <p class="qn-description" v-html="currentQn.description"></p>
+        <div class="qn-item-box">
+            <div v-if="items.length === 0" class="no-data-text">
+                <img src="@/assets/icon/no_data.svg" width="120" />
+                <span style="margin-top:15px;color:#808080">{{ $t('survey.questionaire.noItemData') }}</span>
+            </div>
+            <!-- 问卷题目单元 -->
+            <draggable class="list-group" tag="div" v-model="items" v-bind="dragOptions" @start="drag = true" @end="onDragEnd" v-else>
+                <transition-group type="transition" :name="!drag ? 'flip-list' : null">
+                    <div class="qn-item" v-for="(item,index) in items" :key="index">
+                        <Icon v-if="isExam" :type="answerStatus[index] ? 'md-checkmark' : 'md-close'" :color="answerStatus[index] ? '#19be6b' : '#ed4014'" class="answer-status-icon" />
+                        <div class="qn-item-content">
+                            <!-- 题干 -->
+                            <p class="qn-stem">
+                                <span style="color: red;" v-show="item.required">* </span>
+                                <span>{{ index + 1 }}. </span>
+                                <span v-html="item.question"></span>
+                                <span>( {{ typeList[item.type] }} )</span>
+                            </p>
+                            <!-- 单选题-选项 -->
+                            <span v-if="answer[index]">
+                                <RadioGroup v-model="answer[index][0]" v-if="item.type === 'single' || item.type === 'judge'">
+                                    <Radio v-for="(option,optionIndex) in item.option" :key="optionIndex" :label="option.code" disabled>
+                                        <span>
+                                            {{getSimpleText(option.value)}}
+                                        </span>
+                                    </Radio>
+                                </RadioGroup>
+                                <!-- 多选题-选项 -->
+                                <CheckboxGroup v-model="answer[index]" v-else-if="item.type === 'multiple'">
+                                    <Checkbox v-for="(option,optionIndex) in item.option" :key="optionIndex" :label="option.code" disabled>
+                                        <span>
+                                            {{getSimpleText(option.value)}}
+                                        </span>
+                                    </Checkbox>
+                                </CheckboxGroup>
+                                <!-- 客观题 -->
+                                <div v-else-if="item.type === 'subjective'">
+                                    <p v-if="answer[index]">{{answer[index].join('')}}</p>
+                                </div>
+                            </span>
+                            <p v-show="isExam" v-if="item.answer" class="standard-answer">参考答案:{{item.answer.join('')}}</p>
+                            <!-- <span v-else>
+                                <RadioGroup v-model="answer[index]" v-if="item.type === 'single' || item.type === 'judge'">
+                                    <Radio v-for="(option,optionIndex) in item.option" :key="optionIndex" :label="option.code" disabled>
+                                        <span>
+                                            {{getSimpleText(option.value)}}
+                                        </span>
+                                    </Radio>
+                                </RadioGroup>
+                            </span> -->
+
+                        </div>
+                        <!-- <div class="qn-item-charts" v-if="currentQn.progress === 'finish' && item.type !== 'subjective'">
+							<BaseMiniBar :barId="'minibar' + index" :barData="item.result || []"></BaseMiniBar>
+						</div> -->
+                    </div>
+                </transition-group>
+            </draggable>
+        </div>
+    </div>
+</template>
+<script>
+import draggable from "vuedraggable"
+export default {
+    components: {
+        draggable,
+    },
+    props: ["survey", "answer", "schoolInfo"],
+    data(vm) {
+        return {
+            isExam: false,
+            answerStatus: [],
+            curIndexList: [],
+            curItem: null,
+            isUpdate: false,
+            barData: [],
+            editIndex: null,
+            editable: false,
+            isShowAnalysis: false,
+            isShowAllAnalysis: false,
+            addModal: false,
+            drag: false,
+            radioModel: '',
+            multipleModel: [],
+            items: [],
+            itemAnswers: [],
+            curType: '',
+            typeList: {
+                'single': vm.$t('survey.questionaire.single'),
+                'multiple': vm.$t('survey.questionaire.multiple'),
+                'judge': vm.$t('survey.questionaire.judge'),
+                'subjective': vm.$t('survey.questionaire.subjective')
+            }
+        };
+    },
+    created() { },
+    methods: {
+        // 拖拽完成之后
+        onDragEnd() {
+            this.drag = false
+        },
+
+        async getFullSurvey(surveyItem) {
+            this.items = await this.getBlobItems(surveyItem)
+            console.log(this.items)
+            // 如果是评测需要判断作答对错(只有客观题)
+            if (this.survey.code.includes('TrExam') && this.answer) {
+                this.isExam = true
+                this.answerStatus = new Array(this.items.length)
+                this.items.forEach((item, index) => {
+                    let flag = true
+                    if (item.answer.length == this.answer[index].length) {
+                        this.answer[index].forEach(a => {
+                            if (!item.answer.includes(a)) {
+                                flag = false
+                            }
+                        })
+                    } else {
+                        flag = false
+                    }
+                    this.answerStatus[index] = flag
+                })
+            }
+        },
+
+        // 提取富文本内容中的文本
+        getSimpleText(html) {
+            var r = /<(?!img|video|audio).*?>/g;
+            return html.replace(r, "").replace(/&nbsp;/g, ' ');
+        },
+
+
+        // 获取blob里的试题数据
+        getBlobItems(qnItem) {
+            return new Promise(async (resolve, reject) => {
+                let promiseArr = []
+                let blobHost = qnItem.owner === 'area' ? this.$store.state.user.osblob_uri : this.schoolInfo.uri
+                let sas = qnItem.owner === 'area' ?  ('?' + this.$store.state.user.osblob_sas) : ('?' + this.schoolInfo.sas)
+
+                let indexJson = JSON.parse(await this.$tools.getFile(blobHost + qnItem.blob + sas))
+                if (indexJson.slides.length) {
+                    for (let item of indexJson.slides) {
+                        promiseArr.push(new Promise(async (r, j) => {
+                            try {
+                                let itemJson = JSON.parse(await this.$tools.getFile(blobHost + item + sas))
+                                r(itemJson)
+                            } catch (e) {
+                                j(e)
+                            }
+                        }))
+                    }
+                    Promise.all(promiseArr).then(result => {
+                        resolve(result)
+                    }).catch(err => {
+                        reject(err)
+                    })
+                } else {
+                    resolve([])
+                }
+
+            })
+        },
+
+        // 获取blob里的试题数据
+        getBlobJsonFile(scope, url) {
+            return new Promise(async (resolve, reject) => {
+                // let blobHost = scope === 'private' ? JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+                // 根据试卷的Blob地址 去读取JSON文件
+                let sasString = scope === 'private' ? await this.$tools.getPrivateSas() : await this.$tools.getSchoolSas()
+                try {
+                    let itemJson = JSON.parse(await this.$tools.getFile(this.schoolInfo.uri + url + '?' + this.schoolInfo.sas))
+                    resolve(itemJson)
+                } catch (e) {
+                    reject(e)
+                }
+            })
+        },
+    },
+    computed: {
+        dragOptions() {
+            return {
+                animation: 200,
+                group: "description",
+                disabled: this.currentQn.progress !== 'pending',
+                ghostClass: "ghost"
+            };
+        }
+    },
+    watch: {
+        survey: {
+            handler(n, o) {
+                if (n) {
+                    console.log('问卷或评测数据', n)
+                    this.currentQn = n
+                    this.getFullSurvey(n)
+                }
+            },
+            immediate: true,
+            deep: true
+        },
+        answer: {
+            handler(n, o) {
+                if (n) {
+                    console.log('作答数据', n)
+                }
+            },
+            deep: true,
+            immediate: true
+        }
+
+    }
+};
+</script>
+
+<style lang="less" scope>
+@import "./SurveyDetail.less";
+</style>
+

+ 179 - 0
TEAMModelOS/ClientApp/src/view/areatrain/TrainDetail.less

@@ -0,0 +1,179 @@
+.train-detail-container{
+    width: 100%;
+    height: 100%;
+    padding: 15px;
+}
+.detail-wrap{
+    display: flex;
+    flex-direction: row;
+    justify-content: space-around;
+    padding-bottom: 10px;
+}
+.my-card-content{
+    display: flex;
+    flex-direction: row;
+    justify-content: space-around;
+    align-items: center;
+    padding-bottom: 10px;
+}
+.detail-left{
+    width: 70%;
+    min-height: 700px;
+}
+.detail-right{
+    width: 28%;
+    min-height: 700px;
+}
+.select-school-wrap{
+    background: white;
+    width: 100%;
+    padding: 15px 10px;
+    margin-top: 20px;
+    position: relative;
+    border-bottom: 1px solid #eee;
+}
+.train-top-info{
+    background: white;
+    width: 100%;
+    min-height: 240px;
+    padding: 10px;
+    display: flex;
+    align-items: center;
+    position: relative;
+    &:hover{
+        box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
+        border-color: #eee;
+    }
+    .img-box{
+        width: 380px;
+        height: 200px;
+        background-size: cover;
+        background-repeat: no-repeat;
+    }
+    .infos{
+        margin-left: 20px;
+        padding: 10px;
+        .title{
+            font-size: 16px;
+            font-weight: 600;
+            margin-bottom: 20px;
+        }
+        .hour{
+            margin-left: 10px;
+            display: inline-block;
+            font-size: 12px;
+            color: #ff9900;
+            border: 1px solid #ff9900;
+            padding: 0px 5px;
+            border-radius: 4px;
+        }
+        .info-item{
+            margin-top: 10px;
+        }
+        .info-label{
+            color: #a5a5a5;
+        }
+    }
+}
+
+.train-data-wrap{
+    // margin-top: 20px;
+    width: 100%;
+    min-height: 400px;
+    max-height: 800px;
+    margin-bottom: 10px;
+    padding-bottom: 10px;
+    background: white;
+    &:hover{
+        box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
+        border-color: #eee;
+    }
+}
+.tab-pane-wrap{
+    padding: 0px 10px;
+}
+.num-info{
+    font-size: 26px;
+    margin-right: 5px;
+    display: inline-block;
+    width: 80px;
+}
+.success-color{
+    color: #19be6b;
+}
+.warning-color{
+    color: #ff9900;
+}
+.error-color{
+    color: #ed4014;
+}
+.to-train-mgt{
+    position: fixed;
+    right: 50px;
+    bottom: 50px;
+    z-index: 999;
+    cursor: pointer;
+    background: white;
+    padding: 10px;
+    border-radius: 50%;
+    box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.2);
+    &:hover{
+        color: #2d8cf0;
+        background: #f9f9f9;
+    }
+}
+.train-detail-text{
+    color: #2d8cf0;
+    position: absolute;    
+    right: 30px;
+    top: 15px;
+    cursor: pointer;
+    user-select: none;
+}
+.back-item{
+    background: white;
+    width: 100%;
+    height: 35px;
+    line-height: 35px;
+    margin-bottom: 20px;
+    padding-left: 10px;
+    cursor: pointer;
+    &:hover{
+        box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
+        border-color: #eee;
+    }
+}
+.qr-code-item{
+    margin: 10px 0px;
+}
+.qr-code-label{
+    font-size: 16px;
+    display: inline-block;
+    width: 120px;
+}
+.qr-code-wrap {
+    position: fixed;
+    left: 0px;
+    right: 0px;
+    top: 0px;
+    bottom: 0px;
+    background: rgba(103, 103, 103, 0.27);
+    z-index: 99999;
+    display:flex;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.qr-code-title{
+    margin-bottom: 10px;
+    text-align: center;
+    font-size: 22px;
+    // color: #2b85e4;
+}
+.tea-info-item{
+    margin-top: 5px;
+}
+.tea-info-label{
+    color: #a5a5a5;
+    margin-top: 10px;
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1025 - 0
TEAMModelOS/ClientApp/src/view/areatrain/TrainDetail.vue


+ 64 - 0
TEAMModelOS/ClientApp/src/view/areatrain/TrainMgt.less

@@ -0,0 +1,64 @@
+.train-mgt-container{
+    width: 100%;
+    height: 100%;
+    padding: 15px;
+    background: #fff;
+}
+.train-block-box{
+    // background: white;
+    width: 100%;
+    // min-height: 800px;
+    padding: 10px;
+    display: flex;
+    flex-wrap: wrap;
+}
+.train-item{
+    width: 300px;
+    background: #fcfcfc;
+    border: 1px solid #f0f0f0;
+    padding-bottom: 10px;
+    border-radius: 5px;
+    overflow: hidden;
+    margin-bottom: 30px;
+    margin-right: 30px;
+    cursor: pointer;
+    height: fit-content;
+    transition: all .2s ease 0s;
+    .info-label{
+        color: #a5a5a5;
+    }
+    &:hover{
+        box-shadow: 0 26px 40px -24px #aaa;
+        transform: translateY(-4px);
+    }
+    &:hover .train-img{
+        // transform: scale(1.1);
+    }
+    .train-img-box{
+        width: 300px;
+        height: 150px;
+        overflow: hidden;   
+    }
+    .train-img{
+        transition: all 0.3s;
+        width: 300px;
+        height: 150px;
+        background-size: cover;
+        background-repeat: no-repeat;
+    }
+    .info-item{
+        margin-top: 5px;
+        width: 100%;
+        padding-left: 5px;
+    }
+}
+.train-mgt-top{
+    width: ~"calc(100% - 20px)";
+    height: 40px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-left: 10px;
+    padding-left: 10px;
+    padding-right: 20px;
+    margin-bottom: 10px;
+    line-height: 40px;
+}

+ 142 - 0
TEAMModelOS/ClientApp/src/view/areatrain/TrainMgt.vue

@@ -0,0 +1,142 @@
+<template>
+    <div class="train-mgt-container">
+        <div class="train-mgt-top">
+            <span>活动类型:</span>
+            <Select v-model="filter.type" style="width:200px" @on-change="filterByType">
+                <Option v-for="item in typeList" :value="item.value" :key="item.value">{{ item.label }}</Option>
+            </Select>
+            <Input v-model="filter.keyword" suffix="ios-search" placeholder="搜索" style="width: 200px;float:right;margin-top:4px" @on-change="searchTrain" />
+        </div>
+        <vuescroll>
+            <div class="train-block-box">
+                <div class="train-item" v-for="(item,index) in trainListShow" :key="index" @click="toDetailPage(index)">
+                    <div class="train-img-box">
+                        <div class="train-img" :style="{backgroundImage: `url(${item.img})`}"></div>
+                    </div>
+                    <div class="info-item">
+                        <span class="info-label">主讲人:</span>
+                        <span>{{item.presenter}}</span>
+                    </div>
+                    <div class="info-item">
+                        <span class="info-label">主题:</span>
+                        <span>{{item.topic}}</span>
+                    </div>
+                    <div class="info-item">
+                        <span class="info-label">地点:</span>
+                        <span>{{item.address}}</span>
+                    </div>
+                    <div class="info-item">
+                        <span class="info-label">时间:</span>
+                        <span>{{item.startTime + ' 至 ' + item.endTime}}</span>
+                    </div>
+                </div>
+            </div>
+            <EmptyData textContent="暂无校本研修活动" v-show="!trainListShow.length" :top="100"></EmptyData>
+        </vuescroll>
+    </div>
+</template>
+<script>
+export default {
+    data() {
+        return {
+            acId:'',
+            areaId: sessionStorage.getItem('areaId'),
+            trainList: [],
+            trainListShow: [],
+            filter: {
+                type: 0,
+                keyword: ''
+            },
+            typeList: [
+                {
+                    label: '全部',
+                    value: 0
+                },
+                {
+                    label: '信息化教学案例展示与分享',
+                    value: 1
+                },
+                {
+                    label: '专家专题培训',
+                    value: 2
+                },
+                {
+                    label: '同课同构',
+                    value: 3
+                },
+                {
+                    label: '同课异构',
+                    value: 4
+                },
+                {
+                    label: '校本2.0培训',
+                    value: 5
+                },
+                {
+                    label: '自定义活动',
+                    value: 6
+                }
+            ]
+        }
+    },
+    methods: {
+        filterByType() {
+            this.trainListShow = this.trainList.filter(item => {
+                if (this.filter.type == 0) {
+                    return true
+                } else {
+                    return item.type == this.filter.type
+                }
+            })
+        },
+        searchTrain() {
+            this.trainListShow = this.trainList.filter(item => {
+                return JSON.stringify(item).includes(this.filter.keyword)
+            })
+        },
+        toDetailPage(index) {
+            this.$router.push({
+                name: 'TrainAreaDetail',
+                query: {
+                    id: this.trainList[index].id
+                }
+            })
+        },
+        dateFormat(timestamp) {
+            let date = new Date(timestamp)
+            let Y = date.getFullYear()
+            let M = date.getMonth()
+            let D = date.getDate()
+            let H = date.getHours()
+            let MIN = date.getMinutes()
+            return `${Y}-${M < 9 ? '0' + (M + 1) : M + 1}-${D} ${H < 10 ? '0' + H : H}:${MIN < 10 ? '00' : MIN}`
+        },
+        findTrainList() {
+            let params = {
+                id: this.areaId
+            }
+            this.$api.train.findAreaTrain(params).then(
+                res => {
+                    console.log(res)
+                    this.trainList = res.studies.reverse()
+                    console.log('列表', this.trainList)
+                    this.trainList.forEach(item => {
+                        item.img = item.img || 'https://www.habook.com.cn/data/editor/images/img/20210429%2001/001.png'
+                        item.startTime = this.dateFormat(item.startTime)
+                        item.endTime = this.dateFormat(item.endTime)
+                    })
+                    this.trainListShow = this.trainList
+                },
+                err => {
+                }
+            )
+        }
+    },
+    created() {
+        this.findTrainList()
+    }
+}
+</script>
+<style lang="less" scoped>
+@import "TrainMgt.less";
+</style>

+ 146 - 0
TEAMModelOS/ClientApp/src/view/jyzx/AllPoint.vue

@@ -0,0 +1,146 @@
+<template>
+    <div>
+        <div style="margin-bottom: 10px">
+            <p class="studyTime">您已学习: <span>15</span>/50学时</p>
+            <Select
+                v-model="selDimen"
+                style="width: 200px; margin-right: 10px"
+                @on-change="selDimens"
+                label-in-value
+            >
+                <Option
+                    v-for="(item, index) in dimension"
+                    :value="index"
+                    :key="index"
+                    >{{ item }}</Option
+                >
+            </Select>
+            <Select
+                v-model="selEnvi"
+                style="width: 200px"
+                @on-change="selEnvir"
+                label-in-value
+            >
+                <Option
+                    v-for="(item, index) in environment"
+                    :value="index"
+                    :key="index"
+                    >{{ item }}</Option
+                >
+            </Select>
+        </div>
+
+        <Table :columns="allPointCol" :data="showPoint" ref="selection"></Table>
+    </div>
+</template>
+
+<script>
+export default {
+    name: "AllPoint",
+    data() {
+        return {
+            allPointCol: [
+                {
+                    type: "selection",
+                    width: 120,
+                    align: "center",
+                },
+                {
+                    title: "标号",
+                    key: "no",
+                    align: "center",
+                },
+                {
+                    title: "能力点名称",
+                    key: "point",
+                    align: "center",
+                },
+                {
+                    title: "维度",
+                    key: "dimension",
+                    align: "center",
+                },
+                {
+                    title: "所属环境",
+                    key: "environ",
+                    align: "center",
+                },
+            ],
+            allPoint: [
+                {
+                    no: "A1",
+                    point: "技术支持的学情分析",
+                    dimension: "学情分析",
+                    environ: "多媒体教学环境",
+                },
+                {
+                    no: "B3",
+                    point: "探究型学习活动设计",
+                    dimension: "教学设计",
+                    environ: "混合学习环境",
+                },
+                {
+                    no: "A1",
+                    point: "技术支持的学情分析",
+                    dimension: "学情分析",
+                    environ: "多媒体教学环境",
+                },
+                {
+                    no: "B3",
+                    point: "探究型学习活动设计",
+                    dimension: "教学设计",
+                    environ: "混合学习环境",
+                },
+                {
+                    no: "C1",
+                    point: "跨学科学习活动设计",
+                    dimension: "教学设计",
+                    environ: "智慧学习环境",
+                },
+            ],
+            showPoint: [],
+            dimension: ["全部维度", "教学设计", "学法指导", "学情分析"], //维度
+            selDimen: 0, //选择的维度
+            environment: ["全部环境", "混合学习环境", "多媒体教学环境"], //环境
+            selEnvi: 0, //选择的环境
+        }
+    },
+    methods: {
+        selDimens(value) {
+            if (value.value == 0) {
+                this.showPoint = this.allPoint
+            } else {
+                let arr = []
+                for (let i in this.allPoint) {
+                    if (this.allPoint[i].dimension == value.label) {
+                        arr.push(this.allPoint[i])
+                    }
+                }
+                console.log(arr)
+                this.showPoint = arr
+            }
+            console.log(value)
+        },
+        selEnvir(value) {
+            let arr = []
+            if (!this.selDimen && !this.selEnvi) {
+                this.showPoint = this.allPoint
+            } else if (this.selEnvi && !this.selDimen) {
+                this.showPoint = []
+            }
+        },
+    },
+}
+</script>
+
+<style lang="less" scoped>
+.studyTime {
+    float: right;
+    font-size: 17px;
+
+    span {
+        font-size: 20px;
+        font-weight: bold;
+    }
+}
+</style>

+ 197 - 0
TEAMModelOS/ClientApp/src/view/jyzx/DoExam.vue

@@ -0,0 +1,197 @@
+<template>
+    <div class="component-questionnaire">
+        <!-- 问卷的基本操作栏 -->
+        <p class="qn-title">{{currentQn.name}}</p>
+
+        <p class="qn-description" v-html="currentQn.description"></p>
+        <div class="qn-item-box">
+            <div v-if="items.length === 0" class="no-data-text">
+                <img src="@/assets/icon/no_data.svg" width="120" />
+                <span style="margin-top:15px;color:#808080">{{ $t('survey.questionaire.noItemData') }}</span>
+            </div>
+            <!-- 问卷题目单元 -->
+            <div v-else>
+                <transition-group type="transition" :name="!drag ? 'flip-list' : null">
+                    <div class="qn-item" v-for="(item,index) in items" :key="index">
+                        <div class="qn-item-content">
+                            <!-- 题干 -->
+                            <p class="qn-stem">
+                                <span style="color: red;" v-show="item.required">* </span>
+                                <span>{{ index + 1 }}. </span>
+                                <span v-html="item.question"></span>
+                                <span>( {{ typeList[item.type] }} )</span>
+                            </p>
+                            <!-- 单选题-选项 -->
+                            <RadioGroup v-model="answer[index]" v-if="item.type === 'single' || item.type === 'judge'">
+                                <Radio v-for="(option,optionIndex) in item.option" :key="optionIndex" :label="option.code">
+                                    <span v-html="getSimpleText(option.value)"></span>
+                                </Radio>
+                            </RadioGroup>
+                            <!-- 多选题-选项 -->
+                            <CheckboxGroup v-model="answer[index]" v-if="item.type === 'multiple'">
+                                <Checkbox v-for="(option, optionIndex) in item.option" :key="optionIndex" :label="option.code">
+                                    <span v-html="getSimpleText(option.value)"></span>
+                                </Checkbox>
+                            </CheckboxGroup>
+						</div>
+                        <!-- <div class="qn-item-charts" v-if="currentQn.progress === 'finish' && item.type !== 'subjective'">
+							<BaseMiniBar :barId="'minibar' + index" :barData="item.result || []"></BaseMiniBar>
+						</div> -->
+                    </div>
+                </transition-group>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    props: ["exam"],
+    data(vm) {
+        return {
+            answer: [],
+            curIndexList: [],
+            curItem: null,
+            isUpdate: false,
+            barData: [],
+            editIndex: null,
+            editable: false,
+            isShowAnalysis: false,
+            isShowAllAnalysis: false,
+            addModal: false,
+            drag: false,
+            radioModel: '',
+            multipleModel: [],
+            items: [],
+            itemAnswers: [],
+            curType: '',
+            typeList: {
+                'single': vm.$t('survey.questionaire.single'),
+                'multiple': vm.$t('survey.questionaire.multiple'),
+                'judge': vm.$t('survey.questionaire.judge'),
+                'subjective': vm.$t('survey.questionaire.subjective')
+            },
+            rightAns: [],
+            haveAns: [],
+        };
+    },
+    created() { },
+    methods: {
+        async getFullSurvey(examItem) {
+            this.rightAns = []
+            this.items = await this.getBlobItems(examItem)
+            this.items.forEach((item, index) => {
+                item.answer = item.answer.sort()
+                this.rightAns.push(item.answer)
+            })
+            if(this.haveAns.length) {
+                this.$emit('have-answer', {
+                    answer: this.haveAns,
+                    rightAns: this.rightAns,
+                })
+            }
+        },
+
+        // 提取富文本内容中的文本
+        getSimpleText(html) {
+            var r = /<(?!img|video|audio).*?>/g;
+            return html.replace(r, "").replace(/&nbsp;/g, ' ');
+        },
+
+
+        // 获取blob里的试题数据
+        getBlobItems(qnItem) {
+            return new Promise(async (resolve, reject) => {
+                let blobHost = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+                // 根据试卷的Blob地址 去读取JSON文件
+                let sasString = await this.$tools.getSchoolSas()
+                // 如果是区级
+                if(qnItem.owner === 'area'){
+					blobHost = this.$store.state.user.osblob_uri
+					sasString = { sas : '?' + this.$store.state.user.osblob_sas }
+				}
+                let promiseArr = []
+                let indexJson = JSON.parse(await this.$tools.getFile(blobHost + qnItem.blob + sasString.sas))
+                if (indexJson.slides.length) {
+                    for (let item of indexJson.slides) {
+                        promiseArr.push(new Promise(async (r, j) => {
+                            try {
+                                let itemJson = JSON.parse(await this.$tools.getFile(blobHost + item + sasString.sas))
+                                r(itemJson)
+                            } catch (e) {
+                                j(e)
+                            }
+                        }))
+                    }
+                    Promise.all(promiseArr).then(result => {
+                        resolve(result)
+                    }).catch(err => {
+                        reject(err)
+                    })
+                } else {
+                    resolve([])
+                }
+
+            })
+        },
+
+        // 获取blob里的试题数据
+        getBlobJsonFile(scope, url) {
+            return new Promise(async (resolve, reject) => {
+                let blobHost = scope === 'private' ? JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+                // 根据试卷的Blob地址 去读取JSON文件
+                let sasString = scope === 'private' ? await this.$tools.getPrivateSas() : await this.$tools.getSchoolSas()
+                try {
+                    let itemJson = JSON.parse(await this.$tools.getFile(blobHost + url + sasString.sas))
+                    resolve(itemJson)
+                } catch (e) {
+                    reject(e)
+                }
+            })
+        },
+    },
+    computed: {
+        dragOptions() {
+            return {
+                animation: 200,
+                group: "description",
+                disabled: this.currentQn.progress !== 'pending',
+                ghostClass: "ghost"
+            };
+        }
+    },
+    watch: {
+        exam: {
+            handler(n, o) {
+                if (n) {
+                    this.haveAns = []
+                    this.currentQn = n
+                    if(n.teachers.length) {
+                        let teacherAr = n.teachers.find(item => {
+                            return item.id == this.$store.state.userInfo.TEAMModelId
+                        })
+                        this.haveAns = teacherAr.answer
+                        
+                    }
+                    this.getFullSurvey(n)
+                }
+            },
+            immediate: true,
+            deep: true
+        },
+        answer: {
+            handler(n, o) {
+                this.$emit('answer-change', {
+                    answer: this.answer,
+                    count: this.items.length,
+                    rightAns: this.rightAns
+                })
+            }
+        }
+    }
+};
+</script>
+
+<style lang="less" scope>
+@import "./DoSurvey.less";
+</style>
+

+ 154 - 0
TEAMModelOS/ClientApp/src/view/jyzx/DoSurvey.less

@@ -0,0 +1,154 @@
+@main-bgColor: rgb(40,40,40); //主背景颜色
+@borderColor: #424242;
+@primary-color:#1CC0F3;
+@primary-textColor: #fff; //文本主颜色
+@second-textColor: #CBCBCB; //文本副级颜色
+@primary-fontSize: 14px;
+@second-fontSize: 16px;
+
+.component-questionnaire {
+	position: relative;
+    width: 100%;
+    height: 100%;
+    padding: 30px 6%;
+	padding-bottom: 100px;
+	.tools-bar{
+		width: 100%;
+		border-bottom: 1px dashed  #CCCCCC;
+		margin-bottom: 20px;
+		padding-bottom: 20px;
+		display: flex;
+		align-items: center;
+		
+		.ivu-btn:not(:first-child){
+			margin-left: 20px;
+		}
+	}
+	
+	.no-data-text {
+	    width: 100%;
+	    padding: 30px;
+	    display: flex;
+	    flex-direction: column;
+	    justify-content: center;
+	    align-items: center;
+	    margin-top: 10px;
+	    font-size: 16px;
+	}
+    
+    .qn-title{
+        text-align: center;
+        font-size: 26px;
+        font-weight: bold;
+    }
+
+    .qn-description{
+        font-size: 14px;
+        font-weight: bold;
+        margin-top: 20px;
+        color:#909090;
+		text-indent: 30px;
+    }
+
+    .qn-item-box{
+		
+		
+        .qn-item{
+			position: relative;
+			display: flex;
+            margin: 10px 0;
+            font-size: 16px;
+            font-weight: bold;
+			padding: 20px;
+			user-select: none;
+			cursor: move;
+			
+			&-content{
+				flex: 1;
+				width: 100%;
+				word-break: break-all;
+
+				img{
+					max-width: 50vh !important;
+					display: block;
+				}
+			}
+			
+			&-charts{
+				width: 20%;
+				display: flex;
+				flex-direction: column;
+				justify-content: flex-end;
+			}
+			
+			.qn-stem p{
+				display: inline-block;
+			}
+			
+			&:hover{
+				background: #ececec;
+				
+				.qn-tools{
+					visibility: visible;
+				}
+			}
+			
+            .ivu-radio-wrapper{
+                margin: 20px 40px 20px 10px;
+				
+				.ivu-radio{
+					margin-right: 5px;
+				}
+            }
+			
+			.ivu-checkbox-wrapper{
+			     margin: 20px 40px 20px 10px;
+				
+				.ivu-checkbox{
+					margin-right: 5px;
+				}
+				
+			}
+			
+			.ivu-checkbox-disabled+span{
+				color:inherit !important;
+			}
+			
+			.qn-tools{
+				position: absolute;
+				display: flex;
+				top: -30px;
+				right:0;
+				font-size: 14px;
+				background: #00ae9b;
+				color:#fff;
+				visibility: hidden;
+				
+				.qn-tools-item{
+					padding: 5px 10px;
+					cursor: pointer;
+					
+					&:not(:last-child){
+						margin-right: 15px;
+					}
+					
+					&:hover{
+						background: #2597ae;
+					}
+					
+					span{
+						margin-left: 5px;
+					}
+				}
+			}
+        }
+    }
+	
+	.indexFade-enter-active, .indexFade-leave-active {
+	  transition: all .4s;
+	}
+	.indexFade-enter, .indexFade-leave-active {
+	  transform: translate3d(0, 3rem, 0);
+	  opacity: 0;
+	}
+}

+ 186 - 0
TEAMModelOS/ClientApp/src/view/jyzx/DoSurvey.vue

@@ -0,0 +1,186 @@
+<template>
+    <div class="component-questionnaire">
+        <!-- 问卷的基本操作栏 -->
+        <p class="qn-title">{{currentQn.name}}</p>
+
+        <p class="qn-description" v-html="currentQn.description"></p>
+        <div class="qn-item-box">
+            <div v-if="items.length === 0" class="no-data-text">
+                <img src="@/assets/icon/no_data.svg" width="120" />
+                <span style="margin-top:15px;color:#808080">{{ $t('survey.questionaire.noItemData') }}</span>
+            </div>
+            <!-- 问卷题目单元 -->
+            <draggable class="list-group" tag="div" v-model="items" v-bind="dragOptions" @start="drag = true" @end="onDragEnd" v-else>
+                <transition-group type="transition" :name="!drag ? 'flip-list' : null">
+                    <div class="qn-item" v-for="(item,index) in items" :key="index">
+                        <div class="qn-item-content">
+                            <!-- 题干 -->
+                            <p class="qn-stem">
+                                <span style="color: red;" v-show="item.required">* </span>
+                                <span>{{ index + 1 }}. </span>
+                                <span v-html="item.question"></span>
+                                <span>( {{ typeList[item.type] }} )</span>
+                            </p>
+                            <!-- 单选题-选项 -->
+                            <RadioGroup v-model="answer[index]" v-if="item.type === 'single' || item.type === 'judge'">
+                                <Radio v-for="(option,optionIndex) in item.option" :key="optionIndex" :label="option.code">
+                                    <p v-html="getSimpleText(option.value)"></p>
+                                </Radio>
+                            </RadioGroup>
+                            <!-- 多选题-选项 -->
+                            <CheckboxGroup v-model="answer[index]" v-if="item.type === 'multiple'">
+                                <Checkbox v-for="(option,optionIndex) in item.option" :key="optionIndex" :label="option.code">
+                                    <p v-html="getSimpleText(option.value)"></p>
+                                </Checkbox>
+                            </CheckboxGroup>
+							<Input v-model="answer[index]" type="textarea" :rows="6" placeholder="输入您的回答内容..." v-if="item.type === 'subjective'" style="margin-top: 10px;"/>
+                        </div>
+                        <!-- <div class="qn-item-charts" v-if="currentQn.progress === 'finish' && item.type !== 'subjective'">
+							<BaseMiniBar :barId="'minibar' + index" :barData="item.result || []"></BaseMiniBar>
+						</div> -->
+                    </div>
+                </transition-group>
+            </draggable>
+        </div>
+    </div>
+</template>
+<script>
+import draggable from "vuedraggable"
+export default {
+    components: {
+        draggable,
+    },
+    props: ["survey"],
+    data(vm) {
+        return {
+            answer: [],
+            curIndexList: [],
+            curItem: null,
+            isUpdate: false,
+            barData: [],
+            editIndex: null,
+            editable: false,
+            isShowAnalysis: false,
+            isShowAllAnalysis: false,
+            addModal: false,
+            drag: false,
+            radioModel: '',
+            multipleModel: [],
+            items: [],
+            itemAnswers: [],
+            curType: '',
+            typeList: {
+                'single': vm.$t('survey.questionaire.single'),
+                'multiple': vm.$t('survey.questionaire.multiple'),
+                'judge': vm.$t('survey.questionaire.judge'),
+                'subjective': vm.$t('survey.questionaire.subjective')
+            }
+        };
+    },
+    created() { },
+    methods: {
+        // 拖拽完成之后
+        onDragEnd() {
+            this.drag = false
+        },
+
+        async getFullSurvey(surveyItem) {
+            this.items = await this.getBlobItems(surveyItem)
+        },
+
+        // 提取富文本内容中的文本
+        getSimpleText(html) {
+            var r = /<(?!img|video|audio).*?>/g;
+            return html.replace(r, "").replace(/&nbsp;/g, ' ');
+        },
+
+
+        // 获取blob里的试题数据
+        getBlobItems(qnItem) {
+            return new Promise(async (resolve, reject) => {
+                let blobHost = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+                // 根据试卷的Blob地址 去读取JSON文件
+                let sasString = await this.$tools.getSchoolSas()
+				// 如果是区级
+				if(qnItem.owner === 'area'){
+					blobHost = this.$store.state.user.osblob_uri
+					sasString = { sas : '?' + this.$store.state.user.osblob_sas }
+				}
+                let promiseArr = []
+                let indexJson = JSON.parse(await this.$tools.getFile(blobHost + qnItem.blob + sasString.sas))
+                if (indexJson.slides.length) {
+                    for (let item of indexJson.slides) {
+                        promiseArr.push(new Promise(async (r, j) => {
+                            try {
+                                let itemJson = JSON.parse(await this.$tools.getFile(blobHost + item + sasString.sas))
+                                r(itemJson)
+                            } catch (e) {
+                                j(e)
+                            }
+                        }))
+                    }
+                    Promise.all(promiseArr).then(result => {
+                        resolve(result)
+                    }).catch(err => {
+                        reject(err)
+                    })
+                } else {
+                    resolve([])
+                }
+
+            })
+        },
+
+        // 获取blob里的试题数据
+        getBlobJsonFile(scope, url) {
+            return new Promise(async (resolve, reject) => {
+                let blobHost = scope === 'private' ? JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+                // 根据试卷的Blob地址 去读取JSON文件
+                let sasString = scope === 'private' ? await this.$tools.getPrivateSas() : await this.$tools.getSchoolSas()
+                try {
+                    let itemJson = JSON.parse(await this.$tools.getFile(blobHost + url + sasString.sas))
+                    resolve(itemJson)
+                } catch (e) {
+                    reject(e)
+                }
+            })
+        },
+    },
+    computed: {
+        dragOptions() {
+            return {
+                animation: 200,
+                group: "description",
+                disabled: this.currentQn.progress !== 'pending',
+                ghostClass: "ghost"
+            };
+        }
+    },
+    watch: {
+        survey: {
+            handler(n, o) {
+                if (n) {
+                    this.currentQn = n
+                    this.getFullSurvey(n)
+                }
+            },
+            immediate: true,
+            deep: true
+        },
+        answer: {
+            handler(n, o) {
+                // 我的作答数据:this.answer
+                this.$emit('answer-change', {
+                    answer: this.answer,
+                    count: this.items.length
+                })
+            }
+        }
+    }
+};
+</script>
+
+<style lang="less" scope>
+@import "./DoSurvey.less";
+</style>
+

+ 537 - 0
TEAMModelOS/ClientApp/src/view/jyzx/Question.vue

@@ -0,0 +1,537 @@
+<template>
+    <div class="jyzx-ques">
+        <div
+            style="
+                width: 50%;
+                background-color: #fff;
+                border-right: 1px solid rgba(0, 0, 0, 0.1);
+            "
+        >
+            <vuescroll>
+                <EmptyData v-if="!listShow.length" :top="100"></EmptyData>
+                <div style="margin-bottom: 50px;" v-else>
+                    <div
+                        :id="`event${item.id}`"
+                        class="list-new"
+                        v-for="(item, index) in listShow"
+                        :key="index"
+						@click="sentSelectedEventTitle(item,index)"
+						:class="{
+						    'list-item-selected': activeIndex == index,
+						}"
+                    >
+                        <div class="list-new-icon">
+                            <svg-icon icon-class="quesnaire" />
+                        </div>
+                        <div class="list-new-test">
+                            <p>
+                                <span class="list-item-title"
+                                    style="display: inline-block; background: #a2b02e;"
+                                    v-show="item.owner == 'area'"
+                                >区</span>
+                                <span class="list-item-title"
+                                    style="display: inline-block; background: #FF9900;"
+                                    v-show="item.owner == 'school'"
+                                >校</span>
+                                <span style="font-size: 16px;">{{ item.name }}</span>
+                            </p>
+                            <p class="list-item-time" style="color: #9d9d9d;font-weight: 500;margin: 5px 0;">
+                                发起时间: {{ dateFormat(item.createTime) }}
+                                <!--{{ dateFormat(item.endTime) }}-->
+                            </p>
+                        </div>
+                        <div class="list-new-type">
+                            <div class="list-new-unDone isAllowRetry"  v-if="item.time ==0"  style="background-color: #19b177 ">
+                                <!--<span>{{ $t("studentWeb.public.going") }}</span>-->
+                                <span>未完成</span>
+                            </div>
+                            <div class="list-new-unDone isAllowRetry" v-if="item.time !=0" style="background-color:#969696">
+                                <!--<span>{{ $t("studentWeb.public.going") }}</span>-->
+                                <span >已完成</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </vuescroll>
+        </div>
+        <div class="ques-option">
+            <div
+                style="height: 100%"
+				  v-if="listShow.length"
+            >
+                <vuescroll>
+                    <EmptyData v-if="!Object.keys(surveyInfo).length" :top="-100"></EmptyData>
+                    <div style="margin-bottom: 100px" v-else>
+                        <p class="servey-title">
+                            <span class="school-gade" style="background: #a2b02e" v-show="surveyInfo.owner == 'area'">区级</span>
+                            <span class="school-gade" style="background: #ff9900" v-show="surveyInfo.owner == 'school'">校级</span>
+                            {{ surveyInfo.name }}
+                        </p>
+                        <!-- 描述 -->
+                        <div class="title-rect-group">
+                            <h2 class="title-rect-name">
+                                {{ $t("studentWeb.billboard.description") }}
+                            </h2>
+                        </div>
+                        <div class="dec">
+                            <p>
+                                <span v-html="surveyInfo.description"></span>
+                            </p>
+                        </div>
+                        <!-- 内容 -->
+                        <div class="title-rect-group">
+                            <!-- <div class="title-rect" /> -->
+                            <h2 class="title-rect-name">
+                                {{ $t("survey.studentWeb.content") }}
+                            </h2>
+                        </div>
+                        <br />
+                        <!-- 问卷内容 -->
+                        <div
+                            v-for="(item, index) in surveyInfo.items"
+                            :key="index"
+                            class="survey-item"
+                        >
+                            <!-- <br /> -->
+                            <div style="display: flex; font-weight: bold">
+                                <span>{{ index + 1 }}. </span>
+                                <span v-html="item.question"></span>({{typeList[item.type]}})<span></span>
+                            </div>
+                            <!-- 非问答 -->
+                            <div v-if="item.type !== 'subjective' && submitArr.length">
+                                <div
+                                    class="unitTestBtn"
+                                    v-for="(
+                                        option, optionIndex
+                                    ) in item.option"
+                                    :key="optionIndex"
+                                >
+                                    <div
+                                        class="unitTestbg"
+                                        @click="
+                                            onOptionClick(
+                                                item,
+                                                index,
+                                                option.code
+                                            )
+                                        "
+                                          :style="{
+                                            backgroundColor: submitArr[
+                                                index
+                                            ].includes(option.code)
+                                                ? '#24b880'
+                                                : 'transparent',
+											color: submitArr[
+											    index
+											].includes(option.code)
+											    ? '#ffffff'
+											    : '#000',	
+                                        }"
+                                    >
+                                        {{ option.code }}.
+                                        <span
+                                            v-html="option.value"
+                                            style="margin-left: 10px"
+                                        ></span>
+                                    </div>
+                                </div>
+                            </div>
+                            <!-- 问答 -->
+                            <div v-if="item.type === 'subjective' && submitArr.length">
+                                <Input
+                                    v-model="submitArr[index][0]"
+                                    type="textarea"
+									:disabled="curSurvey.time !== 0"
+                                    :rows="8"
+                                    :placeholder="
+                                        $t('studentWeb.exam.inputAnswers')
+                                    "
+                                />
+                            </div>
+                            <Divider
+                                v-if="index != surveyInfo.items.length - 1"
+                            />
+                        </div>
+                        <br />
+                        <br />
+                        <button class="uploadBtn" @click="submitMessage()" v-if="curSurvey && curSurvey.time === 0">
+                            <svg-icon
+                                icon-class="quesnaire"
+                                class="uloadBtn-icon"
+                            />
+                            <span>{{ $t("survey.studentWeb.submit") }}</span>
+                        </button>
+                    </div>
+                </vuescroll>
+            </div>
+            <!-- 活动结束 -->
+            <!--<div
+                v-if="!alreadyAnswered "
+                style="text-align: center; padding-top: 15%"
+            >
+                <Icon type="md-stopwatch" color="#00ad6c" size="80" />
+                <p
+                    style="
+                        font-size: 30px;
+                        color: #00ad6c;
+                        font-weight: 600;
+                        margin: 20px;
+                    "
+                >
+                    {{ $t("survey.studentWeb.overtime") }}
+                </p>
+            </div>-->
+        </div>
+    </div>
+</template>
+
+<script>
+    import BlobTool from '@/utils/blobTool.js';
+export default {
+    data(vm) {
+        return {
+            typeList: {
+                single: vm.$t("survey.questionaire.single"),
+                multiple: vm.$t("survey.questionaire.multiple"),
+                judge: vm.$t("survey.questionaire.judge"),
+                subjective: vm.$t("survey.questionaire.subjective"),
+            },
+            listShow: [],
+            surveyInfo: {
+				name:'',
+				description:'',
+                items: [{
+                    id: '',
+                    question: '',
+                    option: [],
+                    type:'single'
+                }]
+            },
+			activeIndex:0,
+            submitArr: [], //填写的数据
+            questionnaireId:'',
+			curSurvey:null
+        }
+    },
+        created() {
+            this.getquestionnaire()
+            //this.sentSelectedEventTitle(this.listShow[0])
+    },
+    methods: {
+        // 展示当前活动
+        async sentSelectedEventTitle(item,index) {
+            console.log(item,'888888888888888888888888888')
+			this.activeIndex = index
+            var users = this.$store.state.userInfo
+            this.questionnaireId = item.id
+            //this.surveyInfo = item
+			this.curSurvey = item
+            this.submitArr = []
+            //var sasinfo = await this.$tools.getSchoolSas()
+            //var bHost = this.$evTools.getBlobHost()
+            var blobss = ''
+            var promiseArr = []
+            var sss=[]
+            this.$api.jyzx.getcurrents(
+                {
+                    "id": item.id,
+                    "tId": users.TEAMModelId,
+                    "code": users.schoolCode
+                }
+            ).then(
+                async res => {
+                        console.log(res, '详细返回')
+						this.surveyInfo = res.survey[0]
+                        blobss = res.survey[0].blob
+						let qnItem = this.surveyInfo
+						let blobHost = qnItem.scope === 'private' ? JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+						// 根据试卷的Blob地址 去读取JSON文件
+						let sasString = qnItem.scope === 'private' ? await this.$tools.getPrivateSas() : await this.$tools.getSchoolSas()
+						let promiseArr = []
+						// 如果是区级
+						if(qnItem.owner === 'area'){
+							blobHost = this.$store.state.user.osblob_uri
+							sasString = { sas : '?' + this.$store.state.user.osblob_sas }
+						}
+                        let indexJson = JSON.parse(await this.$tools.getFile(blobHost + qnItem.blob + sasString.sas))
+                        console.log(indexJson)
+                        if (indexJson.slides.length) {
+                            for (let item of indexJson.slides) {
+                                promiseArr.push(new Promise(async (r, j) => {
+                                    try {
+                                        let itemJson = JSON.parse(await this.$tools.getFile(blobHost + item + sasString.sas))
+                                        console.log(itemJson)
+                                        r(itemJson)
+                                    } catch (e) {
+                                        j(e)
+                                    }
+                                }))
+                            }
+                            Promise.all(promiseArr).then(result => {
+                                // this.surveyInfo.items = result
+								this.$set(this.surveyInfo,'items',result)
+								this.$forceUpdate()
+								console.log(this.surveyInfo)
+                                // this.surveyInfo.items.forEach((i) => {
+                                //     this.submitArr.push([])
+                                // })
+								if(item.time === 0){
+									this.submitArr = this.surveyInfo.items.map(i => [])
+								}else{
+									this.submitArr = this.surveyInfo.teachers.find(i => i.id === this.$store.state.userInfo.TEAMModelId).answer
+								}
+                                // console.log(JSON.stringify(this.surveyInfo.items), '333333333333')
+                            }).catch(err => {
+                            })
+                        }
+                },
+                error => {
+                }
+            )
+        },
+        // 提交问卷
+        submitMessage() {
+            console.log(this.submitArr)
+			if(this.submitArr.some(i => i === '' || i.length === 0)){
+				this.$Message.warning('存在未作答的题目!')
+				return
+			}
+            var answerData = this.submitArr
+            var usersInfo = this.$store.state.userInfo
+            this.$api.jyzx.submitquestionnaire(
+                {
+                    "tId": usersInfo.TEAMModelId,
+                    "code": usersInfo.schoolCode,
+                    "id": this.questionnaireId,
+                    "ans": answerData
+                }
+            ).then(
+                res => {
+                    console.log(res,'提交后的返回')
+					this.$Message.success('提交成功!')
+					this.getquestionnaire()
+                },
+                error => {
+                }
+            )
+        },
+        // 非问答的答案
+        onOptionClick(item, index, code) {
+            console.log(item,index,code)
+			if(this.curSurvey.time !== 0){
+				this.$Message.warning('已作答的问卷无法修改!')
+				return
+			}
+            if (this.submitArr[index].includes(code)) {
+                this.submitArr[index].splice(
+                    this.submitArr[index].indexOf(code),
+                    1
+                )
+            } else {
+                if (item.type !== "multiple") {
+                    this.submitArr[index] = []
+                    this.submitArr[index].push(code)
+                } else {
+                    this.submitArr[index].push(code)
+                }
+            }
+            this.$forceUpdate()
+        },
+        // 获取年月日
+        dateFormat(timestamp) {
+            let date = new Date(timestamp)
+            let Y = date.getFullYear()
+            let M = date.getMonth()
+            let D = date.getDate()
+            let H = date.getHours()
+            let MIN = date.getMinutes()
+            return `${Y}-${M < 9 ? "0" + (M + 1) : M + 1}-${D} ${
+                H < 10 ? "0" + H : H
+            }:${MIN < 10 ? "00" : MIN}`
+        },
+        //获取当前教师的问卷名称
+        getquestionnaire() {
+           var userData = this.$store.state.userInfo
+            this.$api.jyzx.getquestion(
+                {
+                    "code": userData.schoolCode,
+                    "tId": userData.TEAMModelId
+                }
+            ).then(
+                res => {
+                    this.listShow = res.survey.filter(i => i.sType !== 'train').reverse()
+                    console.log(this.listShow, '数据!!')
+                    if (this.listShow.length != 0) {
+                        this.sentSelectedEventTitle(this.listShow[0])
+                        this.activeIndex = 0
+                    }
+                },
+            )
+        },
+        //获取blob题目信息
+        getcurrent(scope, url) {
+            console.log(url)
+            return new Promise(async (resolve, reject) => {
+                let blobHost = scope === 'private' ? JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+                // 根据试卷的Blob地址 去读取JSON文件
+                let sasString = scope === 'private' ? await this.$tools.getSchoolSas() : await this.$tools.getSchoolSas()
+                try {
+                    let itemJson = JSON.parse(await this.$tools.getFile(blobHost + url + sasString.sas))
+                    resolve(itemJson)
+                } catch (e) {
+                    reject(e)
+                }
+            })
+        },
+    },
+}
+</script>
+
+<style lang="less" scoped>
+.jyzx-ques {
+    display: flex;
+    height: 100%;
+	// font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+
+    .school-gade{
+        vertical-align: middle;
+        font-size: 14px;
+        color: #fff;
+        // background-color: #F97C57;
+        padding: 3px 5px;
+        border-radius: 8px;
+        margin-right: 5px;
+        font-weight: normal;
+    }
+
+    .list-item-selected {
+        background: linear-gradient(-270deg, #fafafa, #d4ede1);
+        color: #03966a;
+        cursor: pointer;
+        width: 100%;
+    }
+    .list-new {
+        display: flex;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+        padding: 15px 20px 15px 25px;
+        cursor: pointer;
+
+        .list-item-title{
+            display: inline-block;
+            width: 20px;
+            height: 20px;
+            line-height: 20px;
+            border-radius: 10px;
+            // background-color: #F97C57;
+            font-size: 12px;
+            text-align: center;
+            color: #fff;
+            margin-right: 5px;
+        }
+
+        &-icon {
+            width: 10%;
+            margin-right: 30px;
+            .svg-icon {
+                width: 40px;
+                height: 40px;
+            }
+        }
+        &-test {
+            width: 70%;
+            font-weight: bolder;
+            font-size: 14px;
+            line-height: 22px;
+            margin-right: 20px;
+            clear: both;
+        }
+        &-type {
+            width: 15%;
+            display: flex;
+            justify-content: center;
+            flex-direction: column;
+
+            .isAllowRetry {
+                color: #fff;
+                background-color: #64ae16;
+                border: none;
+            }
+
+            .list-new-unDone {
+                font-size: 10px;
+                font-weight: bolder;
+                padding: 5px;
+                border-radius: 4px;
+                text-align: center;
+                border: 1px solid;
+            }
+        }
+    }
+
+    .ques-option {
+        width: 100%;
+        background-color: #fff;
+        padding-left: 50px;
+        padding-top: 40px;
+
+        .servey-title{
+            font-size: 25px;
+            font-weight: bold;
+        }
+
+        .survey-item {
+            margin: 0% 3%;
+            .ivu-input {
+                margin: 10px 0;
+                border-color: #9c9c9c;
+            }
+        }
+
+        .unitTestBtn {
+            display: block !important;
+            margin: 0px;
+            width: 100% !important;
+
+            .unitTestbg {
+                cursor: pointer;
+                padding: 10px;
+                background-color: #fff;
+                border-radius: 4px;
+                border: 1px solid #8f8f8f;
+                margin-top: 10px;
+                display: flex;
+            }
+
+            input[type="checkbox"],
+            input[type="radio"] {
+                visibility: hidden;
+                margin-left: -10px;
+            }
+
+            input[type="checkbox"]:checked ~ .unitTestbg,
+            input[type="radio"]:checked ~ .unitTestbg {
+                background-color: #24b880 !important;
+                border: none;
+                color: #fff;
+                font-weight: bolder;
+            }
+        }
+
+        .option-group {
+            display: flex;
+            flex-direction: column;
+
+            .option-wrapper {
+                margin-bottom: 15px;
+                display: flex;
+                align-items: center;
+                // font-weight: 600;
+
+                .ivu-checkbox {
+                    margin-right: 5px;
+                }
+            }
+        }
+    }
+}
+</style>

+ 453 - 0
TEAMModelOS/ClientApp/src/view/jyzx/Vote.vue

@@ -0,0 +1,453 @@
+<template>
+    <div class="jyzx-vote">
+        <div
+            style="
+                width: 35%;
+                background-color: #fff;
+                border-right: 1px solid rgba(0, 0, 0, 0.1);
+            "
+        >
+            <vuescroll>
+                <EmptyData v-if="!listShow.length" :top="100"></EmptyData>
+                <div style="margin-bottom: 50px" v-else>
+                    <div
+                        :id="`event${item.id}`"
+                        class="list-new"
+                        @click="sentSelectedEventTitle(item, index)"
+                        :class="{
+                            'list-item-selected': item.id == voteInfo.id,
+                        }"
+                        v-for="(item, index) in listShow"
+                        :key="index"
+                    >
+                        <div class="list-new-icon">
+                            <svg-icon icon-class="vote" />
+                        </div>
+                        <div class="list-new-test">
+                            <p>
+                                <span class="list-item-title"
+                                    style="display: inline-block; background: #a2b02e;"
+                                    v-show="item.owner == 'area'"
+                                >区</span>
+                                <span class="list-item-title"
+                                    style="display: inline-block; background: #FF9900;"
+                                    v-show="item.owner == 'school'"
+                                >校</span>
+                                <span>{{ item.name }}</span>
+                            </p>
+                            <!-- <p class="list-item-time">
+                                {{ dateFormat(item.startTime) }} ~
+                                {{ dateFormat(item.endTime) }}
+                            </p> -->
+                        </div>
+                        <div class="list-new-type">
+                            <div
+                                class="list-new-unDone isAllowRetry"
+                                v-show="!item.time"
+                            >
+                                <span>{{ $t("studentWeb.public.going") }}</span>
+                            </div>
+                            <div class="list-new-unDone" v-show="item.time">
+                                <span class="isOvertime">{{
+                                    $t("studentWeb.public.finish")
+                                }}</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </vuescroll>
+        </div>
+        <div style="width: 75%; background-color: #fff;overflow: auto;padding-bottom: 40px;">
+            <EmptyData v-if="!Object.keys(voteInfo).length" :top="-100"></EmptyData>
+            <div class="vote-option" v-else>
+                <div class="billboard-and-LightBox">
+                    <p>
+                        <span class="school-gade" style="background: #a2b02e" v-show="voteInfo.owner == 'area'">区级</span>
+                        <span class="school-gade" style="background: #ff9900" v-show="voteInfo.owner == 'school'">校级</span>
+                        {{ voteInfo.name }}
+                    </p>
+                    <div class="title-rect-group">
+                        <h2 class="title-rect-name">
+                            {{ $t("studentWeb.billboard.description") }}
+                        </h2>
+                    </div>
+                    <div class="dec">
+                        <p>
+                            <span v-html="voteInfo.description"></span>
+                        </p>
+                    </div>
+                    <div class="title-rect-group">
+                        <h2 class="title-rect-name">投票区</h2>
+                        <span v-show="voteInfo.voteNum > 1"
+                            >可投票数:{{ haveVoteNum }}</span
+                        >
+                    </div>
+                </div>
+                <!-- 单选 -->
+                <RadioGroup
+                    v-model="radioCheck"
+                    v-if="voteInfo.voteNum <= 1"
+                    class="option-group"
+                >
+                    <Radio
+                        :disabled="listShow[voteIndex].time !== 0"
+                        :label="item.code"
+                        v-for="(item, index) in voteInfo.options"
+                        :key="index"
+                        border
+                        class="option-wrapper"
+                        :class="'true-value' ? 'active-option' : ''"
+                    >
+                        <span v-html="item.value"></span>
+                    </Radio>
+                </RadioGroup>
+                <!-- 多票 不重复 -->
+                <CheckboxGroup
+                    v-model="voteChecked"
+                    v-if="voteInfo.voteNum > 1"
+                    class="option-group"
+                >
+                    <Checkbox
+                        border
+                        :disabled="listShow[voteIndex].time !== 0"
+                        :label="item.code"
+                        v-for="(item, index) in voteInfo.options"
+                        :key="index"
+                        class="option-wrapper"
+                    >
+                        <span v-html="item.value"></span>
+                    </Checkbox>
+                </CheckboxGroup>
+                <Button
+                    :disabled="
+                        !isVote ||
+                        (voteInfo.voteNum < voteChecked.length &&
+                            voteInfo.voteNum > 1)
+                    "
+                    size="large"
+                    type="success"
+                    @click="submitMessage()"
+					style="margin-top: 20px;"
+                >
+                    <svg-icon icon-class="vote" class="uploadBtn-icon" />
+                    <span style="margin-left: 5px">{{
+                        $t("studentWeb.vote.submitBVote")
+                    }}</span>
+                </Button>
+                <span
+                    class="warnText"
+                    v-show="
+                        voteInfo.voteNum < voteChecked.length &&
+                        voteInfo.voteNum > 1
+                    "
+                    >投票数已超出</span
+                >
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+    data() {
+        return {
+			radioCheck:'',
+            listShow: [], //列表
+            voteIndex: 0,
+            voteInfo: {}, //投票
+            voteChecked: [], //选择的
+            isVote: false,
+            haveVoteNum: 0,
+        }
+    },
+    created() {
+        this.getList()
+    },
+    methods: {
+        // 获取投票列表
+        getList() {
+            let req = {
+                tId: this.$store.state.userInfo.TEAMModelId,
+                code: this.$store.state.userInfo.schoolCode,
+            }
+            this.$api.jyzx.getVoteList(req).then((res) => {
+                if (res.votes.length) {
+                    res.votes.reverse()
+                    this.listShow = res.votes
+                    this.sentSelectedEventTitle(
+                        this.listShow[this.voteIndex],
+                        0
+                    )
+                }
+            })
+        },
+        // 展示当前活动
+        sentSelectedEventTitle(item, index) {
+            this.isVote = item.time !== 0 ? false : true
+            this.voteIndex = index
+            this.voteInfo = {}
+            this.voteChecked = []
+			this.radioCheck = ''
+            let req = {
+                tId: this.$store.state.userInfo.TEAMModelId,
+                code: this.$store.state.userInfo.schoolCode,
+                id: item.id,
+            }
+            this.$api.jyzx.getSurveyList(req).then((res) => {
+                if (res.votes.length) {
+                    this.voteInfo = res.votes[0]
+                    this.haveVoteNum = this.voteInfo.voteNum
+					console.log(this.voteInfo)
+					if(!this.isVote){
+						let myId = this.$store.state.userInfo.TEAMModelId
+						if(this.voteInfo.voteNum === 1){
+							this.radioCheck = this.voteInfo.teachers.find(i => i.id === myId).answer[0]
+						}else{
+							this.voteChecked = this.voteInfo.teachers.find(i => i.id === myId).answer
+						}
+						console.log(this.radioCheck)
+						console.log(this.voteChecked)
+					}
+                }
+            })
+        },
+        //获取投票结果
+        getVote(data) {
+            if (this.voteInfo.voteNum <= 1) {
+                this.voteChecked.length = 0
+                this.voteChecked.push(data)
+            }
+        },
+
+        // 确认投票
+        submitMessage() {
+            if ((!this.voteChecked.length && this.voteInfo.voteNum > 1) || (this.radioCheck === '' && this.voteInfo.voteNum === 1)) {
+                this.$Message.warning("您还未投票")
+            } else {
+                let ans = []
+                if (this.voteInfo.voteNum === 1) {
+                    ans.push(this.radioCheck)
+                } else {
+                    ans.push(...this.voteChecked)
+                }
+
+                let req = {
+                    tId: this.$store.state.userInfo.TEAMModelId,
+                    code: this.$store.state.userInfo.schoolCode,
+                    id: this.voteInfo.id,
+                    ans:ans
+                }
+                this.$api.jyzx.setVote(req).then((res) => {
+                    if (res.code == 200) {
+                        this.$Message.success("投票成功")
+                        this.getList()
+						this.radioCheck = ''
+						this.voteChecked = []
+                    }
+                })
+            }
+        },
+        dateFormat(timestamp) {
+            let date = new Date(timestamp)
+            let Y = date.getFullYear()
+            let M = date.getMonth()
+            let D = date.getDate()
+            let H = date.getHours()
+            let MIN = date.getMinutes()
+            return `${Y}-${M < 9 ? "0" + (M + 1) : M + 1}-${D} ${
+                H < 10 ? "0" + H : H
+            }:${MIN < 10 ? "00" : MIN}`
+        },
+    },
+    watch: {
+        voteChecked: {
+            handler(n, o) {
+                if (n.length <= this.voteInfo.voteNum) {
+                    this.haveVoteNum = this.voteInfo.voteNum - n.length
+                }
+            },
+        },
+    },
+}
+</script>
+
+<style lang="less" scoped>
+.jyzx-vote {
+    display: flex;
+    height: 100%;
+
+    .school-gade{
+        vertical-align: middle;
+        font-size: 14px;
+        color: #fff;
+        // background-color: #F97C57;
+        padding: 3px 5px;
+        border-radius: 8px;
+        margin-right: 5px;
+        font-weight: normal;
+    }
+
+    .list-item-selected {
+        background: linear-gradient(-270deg, #fafafa, #d4ede1);
+        color: #03966a;
+        cursor: pointer;
+        width: 100%;
+    }
+    .list-new {
+        display: flex;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+        padding: 15px 20px 15px 25px;
+        cursor: pointer;
+
+        .list-item-title{
+            display: inline-block;
+            width: 20px;
+            height: 20px;
+            line-height: 20px;
+            border-radius: 10px;
+            // background-color: #F97C57;
+            font-size: 12px;
+            text-align: center;
+            color: #fff;
+            margin-right: 5px;
+        }
+
+        &-icon {
+            width: 10%;
+            margin-right: 30px;
+            .svg-icon {
+                width: 40px;
+                height: 40px;
+            }
+        }
+        &-test {
+            width: 70%;
+            font-weight: bolder;
+            font-size: 14px;
+            // line-height: 40px;
+            margin-right: 20px;
+            clear: both;
+        }
+        &-type {
+            width: 20%;
+            display: flex;
+            justify-content: center;
+            flex-direction: column;
+
+            .isAllowRetry {
+                color: #fff;
+                background-color: #19B177;
+                border: none;
+            }
+
+            .list-new-unDone {
+                font-size: 10px;
+                font-weight: bolder;
+                padding: 5px;
+                border-radius: 4px;
+                text-align: center;
+                border: 1px solid;
+            }
+        }
+    }
+
+    .vote-option {
+        width: 100%;
+        background-color: #fff;
+        padding-left: 50px;
+        padding-top: 40px;
+
+        .billboard-and-LightBox > p:first-child{
+            font-size: 25px;
+            font-weight: bold;
+        }
+
+        .title-rect-group {
+            span {
+                margin-left: 10px;
+            }
+        }
+        .checkAnswer {
+            display: block;
+            margin-top: 20px;
+            margin-left: 0px;
+
+            .testBtn {
+                margin: 20px 0px;
+                position: relative;
+                z-index: 2;
+                font-weight: bolder;
+                padding-left: 20px;
+                cursor: pointer;
+
+                input[type="checkbox"] {
+                    visibility: hidden;
+                    margin-left: -10px;
+
+                    &:checked {
+                        ~ .testbg {
+                            background-color: #24b880 !important;
+                            border: none;
+
+                            span {
+                                color: #fff;
+                                font-weight: bolder;
+                            }
+                        }
+                    }
+                }
+            }
+
+            .testbg {
+                cursor: pointer;
+                background-color: #fff;
+                position: relative;
+                height: auto;
+                top: -31px;
+                padding: 10px;
+                z-index: 1;
+                display: flex;
+                border-radius: 4px;
+                border: 1px solid rgb(197, 197, 197);
+            }
+        }
+
+        .option-group {
+            display: flex;
+            flex-direction: column;
+
+            .option-wrapper {
+                margin:15px 0 5px 0;
+                display: flex;
+                align-items: center;
+                padding: 20px 10px;
+                /* border: 1px solid #ccc;
+                border-radius: 5px; */
+
+                & > span {
+                    margin-left: 5px;
+                }
+            }
+        }
+
+        .warnText {
+            color: red;
+            margin-left: 10px;
+        }
+    }
+}
+</style>
+<style lang="less">
+.option-group {
+    .ivu-checkbox,
+    .ivu-radio {
+        display: none;
+    }
+    .ivu-checkbox-wrapper-checked.ivu-checkbox-border,
+    .ivu-radio-wrapper-checked.ivu-radio-border {
+        background: #24b880;
+        border-color: #24b880;
+        color: #fff;
+    }
+}
+</style>

+ 393 - 0
TEAMModelOS/ClientApp/src/view/jyzx/application.vue

@@ -0,0 +1,393 @@
+<template>
+    <div class="application-container">
+        <div v-if="!isReview && !isShowDetail && !isShowPaper" style="height: 100%;">
+            <Card :bordered="false">
+                <Tabs :value="curTab" @on-click="onTabChange">
+                    <TabPane label="成果列表" name="name1">
+                        <EmptyData v-if="!appList.length"></EmptyData>
+                        <div v-else class="app-table">
+                            <Table :columns="appCol" :data="appList" height="730">
+                                <template slot-scope="{ row }" slot="name">
+                                    <p v-if="row.name">{{ row.no }} {{ row.name }}</p>
+                                    <p v-else>-</p>
+                                </template>
+                                <template slot-scope="{ row }" slot="time">
+                                    <p v-if="row.uploads.length">
+                                        {{ $tools.formatTime(row.selfTime) }}
+                                    </p>
+                                    <p v-else>-</p>
+                                </template>
+                                <template slot-scope="{ row }" slot="result">
+                                    <p v-if="row.uploads.length" style="font-weight: bold;">
+                                        <span v-show="row.self === 2" style="color: #007700;">优秀</span>
+                                        <span v-show="row.self === 1" style="color: orangered;">合格</span>
+                                        <span v-show="row.self === 0" style="color: red;">不合格</span>
+                                    </p>
+                                    <p v-else>-</p>
+                                </template>
+								<template slot-scope="{ row }" slot="schoolAppraise">
+								    <p v-if="row.uploads.length" style="font-weight: bold;">
+								        <span v-show="row.schoolAppraise === 2" style="color: #007700;">优秀</span>
+								        <span v-show="row.schoolAppraise === 1" style="color: orangered;">合格</span>
+								        <span v-show="row.schoolAppraise === 0" style="color: red;">不合格</span>
+								    </p>
+								    <p v-else>-</p>
+								</template>
+								<template slot-scope="{ row }" slot="exerciseScore">
+								    <p style="font-weight: bold;">
+								        <span v-show="row.exerciseScore === -1" style="color: #a2a2a2;">-</span>
+								        <span v-show="row.exerciseScore === 0" style="color: red;">未通过</span>
+								        <span v-show="row.exerciseScore === 1" style="color: #007700;">通过</span>
+								    </p>
+								</template>
+                                <template slot-scope="{ row }" slot="action">
+                                    <Button type="success" size="small" @click="doUpload(row)" v-if="!row.uploads.length">上传文件</Button>
+                                    <Button size="small" @click="goAppraiseDetail(row)" v-else>查看评价</Button>
+                                    <Button type="primary" size="small" @click="goTestPaper(row)" style="margin-left: 5px;">{{ row.exerciseScore > -1 ? '重新检测' : '自我检测' }}</Button>
+                                </template>
+                            </Table>
+                        </div>
+                    </TabPane>
+                    <TabPane label="成果互评" name="name2">
+                        <EmptyData textContent="暂无数据" v-if="!otherList.length"></EmptyData>
+                        <div v-else class="app-table">
+                            <Table :columns="comCol" :data="otherList">
+								 <template slot-scope="{ row }" slot="myAppraise">
+                                    <span v-show="row.myAppraise === 2" style="color: #007700;">优秀</span>
+                                    <span v-show="row.myAppraise === 1" style="color: orangered;">合格</span>
+                                    <span v-show="row.myAppraise === 0" style="color: red;">不合格</span>
+                                    <span v-show="row.myAppraise === -1">未评价</span>
+                                </template>
+								<template slot-scope="{ row }" slot="action">
+								    <Button :type="row.myAppraise === -1 ? 'primary':'info'" size="small" @click="appraiseOther(row)">{{ row.myAppraise === -1 ? '前往评分' : '重新评分' }}</Button>
+								</template>
+                            </Table>
+                        </div>
+                    </TabPane>
+                </Tabs>
+            </Card>
+        </div>
+        <div class="animated appraise-detail" v-if="isShowDetail">
+            <div class="detail-title">
+                <Icon type="md-arrow-back" @click="isShowDetail = false" />
+                <span>{{ $store.state.userInfo.name }} 【 {{ curData.name }} 】</span>
+            </div>
+            <Table :columns="appraiseColumns" :data="appraiseArr" border>
+                <template slot-scope="{ row, index }" slot="result">
+                    <span v-show="row.result === '优秀'" style="color: #007700;">优秀</span>
+                    <span v-show="row.result === '合格'" style="color: orangered;">合格</span>
+                    <span v-show="row.result === '不合格'" style="color: red;">不合格</span>
+                </template>
+            </Table>
+        </div>
+        <Review v-if="isReview" :mode="curReviewMode" :reviewData="reviewData" @goBack="goBack"></Review>
+		<TestPaper v-if="isShowPaper" :abilityData="curData" @goBack="goBack"></TestPaper>
+    </div>
+</template>
+
+<script>
+import Review from "@/view/ability/Review.vue"
+import TestPaper from "@/view/ability/TestPaper.vue"
+
+export default {
+    components: {
+        Review,TestPaper
+    },
+    data() {
+        return {
+			isShowPaper:false,
+            isShowDetail: false,
+            curReviewMode: 'self',
+			curTab:'name1',
+            appraiseColumns: [
+                {
+                    title: '评价人',
+                    key: 'name'
+                },
+                {
+                    title: '评价类型',
+                    key: 'type'
+                },
+                {
+                    title: '评价结果',
+                    slot: 'result',
+                },
+                {
+                    title: '评价内容',
+                    key: 'content'
+                },
+                {
+                    title: '评价时间',
+                    key: 'time'
+                },
+            ],
+            appraiseArr: [],
+            appCol: [{
+                title: "能力点名称",
+                key: "name",
+                slot: "name",
+            },
+            {
+                title: "上传时间",
+                slot: "time",
+            },
+            {
+                title: "互评次数",
+                key: "num",
+				width:100
+            },
+            {
+                title: "自评结果",
+                slot: "result",
+				width:100
+            },
+			{
+			    title: "校评结果",
+			    slot: "schoolAppraise",
+				width:100
+			},
+			{
+			    title: "专家抽查",
+			    key: "teacherAppraise",
+				width:100
+			},
+			{
+			    title: "自我检测结果",
+			    slot: "exerciseScore",
+			},
+            {
+                title: "操作",
+                slot: "action",
+            },
+            ],
+            appList: [],
+            comCol: [{
+                title: "教师姓名",
+                key: "tmdname",
+                align: "center",
+            },
+            {
+                title: "维度",
+                key: "dimension",
+                align: "center",
+            },
+            {
+                title: "能力点",
+                key: "abilityName",
+                align: "center",
+            },
+            {
+                title: "上传时间",
+                key: "time",
+                align: "center",
+            },
+			{
+			    title: "评价结果",
+			    slot: "myAppraise",
+			    align: "center",
+			},
+            {
+                title: "操作",
+                slot: "action",
+                align: "center",
+            },
+            ],
+            otherList: [],
+            isDiscuss: false,
+            curData: null,
+            isReview: false,
+            reviewData: null
+        }
+    },
+    methods: {
+        onTabChange(val) {
+            this.curTab = val
+        },
+        goAppraiseDetail(data) {
+            this.isShowDetail = true
+            console.log(data)
+            this.appraiseArr = data.otherScore.map(i => {
+                return {
+                    name: i.roleType === 'school' ? '管理员' : '教师',
+                    result: ['不合格', '合格', '优秀'][i.score],
+                    content: i.replyIds[0],
+                    type: i.roleType === 'school' ? '校评' : '互评',
+                    time: this.$tools.formatTime(i.time)
+                }
+            })
+            this.appraiseArr.unshift({
+                name: this.$store.state.userInfo.name,
+                result: ['不合格', '合格', '优秀'][data.self],
+                content: '-',
+                type: '自评',
+                time: this.$tools.formatTime(data.uploads[0].time)
+            })
+            this.curData = data
+        },
+		
+		goTestPaper(data){
+			this.isShowPaper = true
+			this.curData = data
+		},
+		
+        goBack() {
+            this.isReview = false
+			this.isShowPaper = false
+			this.isShowDetail = false
+            this.getMyAbilities()
+            this.getOtherSubs()
+        },
+		
+        getMyAbilities() {
+            this.$api.jyzx.getPoint({
+                "tmdid": this.$store.state.userInfo.TEAMModelId,
+                "scope": "school",
+                "code": this.$store.state.userInfo.schoolCode
+            }).then(res => {
+                res.rcdSubs.forEach(i => {
+                    i.name = res.hadSubs.find(j => j.id === i.id).name
+                    i.no = res.hadSubs.find(j => j.id === i.id).no
+                    i.num = i.uploads.length ? (i.otherScore.length ? i.otherScore.filter(j => j.roleType === 'member').length : 0) : 0
+					i.schoolAppraise = i.otherScore.length && i.otherScore.filter(j => j.roleType === 'school').length ? i.otherScore.find(j => j.roleType === 'school').score : -1
+					i.teacherAppraise = '未抽查'
+					i.exerciseScore = i.exerciseScore
+                })
+                this.appList = res.rcdSubs.sort(function(s, t) {
+					var a = s.no.toLowerCase();
+					var b = t.no.toLowerCase();
+					if (a.length === 2) {
+						a = a.slice(0, a.length - 1) + '0' + a.slice(-1)
+					}
+					if (b.length === 2) {
+						b = b.slice(0, b.length - 1) + '0' + b.slice(-1)
+					}
+
+					if (a < b) return -1;
+					if (a > b) return 1;
+					return 0;
+				});
+                console.log(this.appList);
+            })
+        },
+
+        getOtherSubs() {
+			this.otherList = []
+            this.$api.jyzx.getOtherSubs({
+                "tmdid": this.$store.state.userInfo.TEAMModelId,
+                "school": this.$store.state.userInfo.schoolCode,
+                "all": "0"
+            }).then(res => {
+				let myId = this.$store.state.userInfo.TEAMModelId
+                res.groupMembers.forEach(i => {
+					if(i.sub.uploads.length){
+						i.dimension = this.$GLOBAL.DIMENSIONS().find(j => j.code === res.abilities.find(k => k.id === i.sub.abilityId).dimension).val
+						i.abilityName = res.abilities.find(j => j.id === i.sub.abilityId).no + ' ' + res.abilities.find(j => j.id === i.sub.abilityId).name
+						i.time = i.sub.uploads.length ? this.$tools.formatTime(i.sub.uploads[0].time) : '-'
+						i.myAppraise = i.sub.otherScore.find(j => j.tmdid === myId && j.roleType === 'member') ? i.sub.otherScore.find(j => j.tmdid === myId && j.roleType === 'member').score : -1
+						this.otherList.push(i)
+					}
+                })
+				console.log(this.otherList)
+            })
+        },
+
+        doUpload(data) {
+            this.reviewData = data
+            this.isReview = true
+            this.curReviewMode = 'self'
+        },
+        appraiseOther(data) {
+            console.log(data)
+            let reviewData = {
+                id: data.sub.abilityId,
+                uploads: data.sub.uploads,
+                targetId: data.tmdid,
+                targetName: data.tmdname
+            }
+            this.reviewData = reviewData
+            this.isReview = true
+            this.curReviewMode = 'other'
+			console.log(this.curTab);
+        },
+
+        delApp() {
+            this.$Modal.confirm({
+                title: "您确实要删除该作业吗?",
+                okText: "删除",
+                cancelText: "取消",
+                onOk: () => {
+                    this.$Message.success("删除成功")
+                },
+            })
+        },
+        disApp(data) {
+            this.curData = data
+            this.isReview = true
+        },
+        cancelMo() {
+            this.isDiscuss = false
+        },
+    },
+    mounted() {
+        this.getMyAbilities()
+        this.getOtherSubs()
+    },
+    computed: {
+
+    }
+}
+</script>
+
+<style lang="less">
+.application-container {
+	height: 100%;
+    .ivu-card {
+        height: 100%;
+
+        .ivu-card-body {
+            height: 100%;
+
+            .ivu-tabs {
+                height: 100%;
+
+                .ivu-tabs-content-animated {
+                    height: 100%;
+                }
+            }
+        }
+    }
+
+    .ivu-tabs .ivu-tabs-tabpane {
+        height: 100%;
+    }
+}
+</style>
+
+<style lang="less" scoped>
+.app-table {
+    height: calc(100vh - 190px);
+    padding: 0 5px;
+    overflow: auto;
+}
+
+.application-container {
+    font-family: "NotoSerif", "微软正黑体", "Microsoft JhengHei UI",
+        "Microsoft JhengHei", Sans-serif;
+}
+
+.appraise-detail {
+    padding: 20px;
+
+    .detail-title {
+        font-size: 20px;
+        font-weight: bold;
+        margin-bottom: 20px;
+
+        .ivu-icon {
+            font-size: 24px;
+            margin-right: 10px;
+            cursor: pointer;
+        }
+    }
+}
+</style>

+ 393 - 0
TEAMModelOS/ClientApp/src/view/jyzx/classMemoir.vue

@@ -0,0 +1,393 @@
+<template>
+    <div class="class-memoir">
+        <Tabs value="name1" v-show="showStatus === false">
+            <TabPane label="我的课堂实录" name="name1">
+                <Card :bordered="false">
+                    <div>
+                        <BaseUpload :auth="curSas" :acceptTypes="['mp4','avi','flv','mkv','rmvb']" :scope="'school'" mode='other' :prefix="uploadId" @uploadFinish="uploadFinish"></BaseUpload>
+                        <Button type="info" size="large" :long="true" @click="confirm">确认上传</Button>
+                    </div>
+                    <div v-if="calssListinfo.length" style="height: calc(100vh - 255px); overflow: auto">
+                        <Table :columns="classCol" :data="calssListinfo" height="680">
+                            <template slot-scope="{ row, index }" slot="action">
+                                <Button size="small" @click="checkevaluate(row,true)" style="margin-right:2%">查看评价</Button>
+                                <Button size="small" @click="delClass(row)">删除</Button>
+                            </template>
+                        </Table>
+                    </div>
+                </Card>
+            </TabPane>
+            <TabPane label="同组课堂实录" name="name2">
+                <div v-if="teamVideo.length" style="height: calc(100vh - 255px); overflow: auto">
+                    <Table :columns="teamclassCol" :data="teamVideo" height="680">
+                        <template slot-scope="{ row, index }" slot="actions">
+                            <Button size="small" @click="checkevaluate(row)">查看课程及评价</Button>
+                        </template>
+                    </Table>
+                </div>
+            </TabPane>
+        </Tabs>
+        <div v-show="showStatus === true">
+            <p><Icon type="md-arrow-back" size="26" @click="showStatus=false" class="backward"/><span class="teachnames">罗老师</span><span class="coursename">【{{courseName}}】</span></p>
+            <Table border :columns="columns1aa" :data="data1"></Table>
+            <!--<div class="appraise">
+                <div>
+                    <span>评价结果:</span>
+                    <RadioGroup v-model="border">
+                        <Radio label="不合格" border></Radio>
+                        <Radio label="合格" border></Radio>
+                        <Radio label="优秀" border></Radio>
+                    </RadioGroup>
+                </div>
+                <div>
+                    <span>评价内容:</span>
+                    <div id="div1"></div>
+                </div>
+            </div>-->
+        </div>
+    </div>
+</template>
+
+<script>
+import BlobTool from '@/utils/blobTool.js'
+import { formatDate } from "../../utils/time.js"
+import E from "wangeditor"
+export default {
+    data() {
+        return {
+            classCol: [
+                {
+                    title: "视频名称",
+                    key: "name",
+                    align: "center",
+                },
+                {
+                    title: "上传时间",
+                    key: "time",
+                    align: "center",
+                },
+                {
+                    title: "文件大小",
+                    key: "size",
+                    align: "center",
+                },
+                {
+                    title: "操作",
+                    slot: "action",
+                    align: "center",
+                },
+            ],
+            teamclassCol: [
+                { title: '视频名称', key: 'vname', align: 'center'},
+                { title: '上传作者', key: 'tmdname', align: 'center' },
+                { title: '大小', key: 'vsize', align: 'center' },
+                { title: '上传时间', key: 'vtime', align: 'center'},
+                { title: '操作', slot: 'actions', align: 'center' },
+            ],
+            classList: [
+                {
+                    name: "XXXXXX的视频",
+                    time: "2021-07-19",
+                    size: 27,
+                },
+                {
+                    name: "第一次上课视频",
+                    time: "2021-07-20",
+                    size: 89,
+                },
+                {
+                    name: "XXXXXX的视频",
+                    time: "2021-07-19",
+                    size: 27,
+                },
+            ],
+            calssListinfo: [],
+            curSas: {
+                sas: '',
+                url: '',
+                name: '',
+            },//验证证书
+            uploadId: '',//上传人ID
+            uploadData: {
+                blob: '',
+                createTime: '',
+                extension: '',
+                name: '',
+                size: '',
+                type: '',
+                url: '',
+            },
+            showStatus: false,
+            teachName:'',
+            courseName: '',
+            border: '',  //评价
+            teamVideo:[],//同组课堂实录
+            //虚拟数据
+            columns1aa: [
+                {
+                    title: '评价人',
+                    key: 'name'
+                },
+                {
+                    title: '评价类型',
+                    key: 'type'
+                },
+                {
+                    title: '评价结果',
+                    key: 'result'
+                },
+                {
+                    title: '评价内容',
+                    key: 'content'
+                },
+                 {
+                    title: '评价时间',
+                    key: 'time'
+                }
+            ],
+            data1: [
+                // {
+                //     name: 'John Brown',
+                //     age: 18,
+                //     address: 'New York No. 1 Lake Park',
+                //     date: '2016-10-03'
+                // },
+                {
+                  name:'李老师',
+                  type:'互评',
+                  result:'合格',
+                  content:'老师讲的很不错',
+                  time:'2021-07-22 15:30:22',
+                  cellClassName:{result:'qualified span'},
+                },
+                  {
+                  name:'罗老师',
+                  type:'自评',
+                  result:'合格',
+                  content:'还需要努力',
+                  time:'2021-07-24 18:32:21',
+                  cellClassName:{result:'qualified span'},
+                },
+                  {
+                  name:'张老师',
+                  type:'校评',
+                  result:'优秀',
+                  content:'讲的很好',
+                  time:'2021-07-26 10:32:21',
+                  cellClassName:{result:'excellent span'},
+                },
+            ]
+        }
+    },
+    created() {
+        this.uploadVerify()
+        this.getvideo('default');
+        this.getteamvideo();
+    },
+    mounted() {
+       let stemEditor = new E("#div1")
+       stemEditor.config.onchange = (html) => {
+           this.stemContent = html
+       }
+       stemEditor.config.uploadImgShowBase64 = true
+       stemEditor.config.zIndex = 500
+       stemEditor.create()
+    },
+    methods: {
+        delClass(row) {
+            console.log(row)
+            this.$Modal.confirm({
+                title: "您确实要删除这条视频吗?",
+                okText: "删除",
+                cancelText: "取消",
+                onOk: () => {
+                    this.getvideo('delete', row.id)
+                    /* this.$Message.success("删除成功")*/
+                },
+            })
+
+
+        },
+        //上传课堂实录相关数据
+        async uploadVerify() {
+            this.curSas = await this.$tools.getSchoolSas()
+            var user = this.$store.state.userInfo
+            this.uploadId = user.TEAMModelId
+        },
+        //upload过后
+        uploadFinish(res) {
+            console.log(res, '回调')
+            if (res[0].blob && res[0].url && res[0].size) {
+                this.uploadData = res[0]
+                this.getvideo('uploadafter', 11)
+            } else {
+                this.$Message.error('上传失败')
+            }
+        },
+        //确认上传
+        confirm() {
+            console.log(this,'指向')
+            var sssinfo = this.$children[0].$children[2].$children[0].$children[0].onConfirmUpload()
+        },
+        //获取自己上传列表
+        getvideo(action, deleId) {
+            console.log(action, deleId, '传来的数据')
+            let user = this.$store.state.userInfo
+            var uploadafterData = {
+                "tmdid": user.TEAMModelId,
+                "school": user.schoolCode,
+                "opt": action === 'uploadafter' ? 'Upload' : action === 'delete' ? 'Delete' : 'Read',
+                "files": [],
+                "ids": [],
+            }
+            if (action === 'uploadafter') {
+                var datainfo = { "url": this.uploadData.url, "name": this.uploadData.name, "size": this.uploadData.size }
+                delete uploadafterData.ids
+                uploadafterData.files.push(datainfo)
+                console.log(uploadafterData)
+                this.$api.jyzx.getmemoir(uploadafterData).then(
+                    res => {
+                        console.log(res)
+                        var timeData = res.classVideo.files
+                        for (var u = 0; u < timeData.length; u++) {
+                            timeData[u].time = this.formatDate(timeData[u].time)
+                            timeData[u].size = this.formatSize(timeData[u].size)
+                        }
+                        this.calssListinfo = timeData
+                        this.$Message.success('上传成功')
+                    },
+                    error => {
+                        this.$Message.error('上传失败')
+                    }
+                )
+            } else if (action === 'delete') {
+                delete uploadafterData.files
+                uploadafterData.ids.push(deleId)
+                this.$api.jyzx.getmemoir(uploadafterData).then(
+                    res => {
+                        console.log(res, '删除后的操作')
+                        var timeData = res.classVideo.files
+                        for (var d = 0; d < timeData.length; d++) {
+                            timeData[d].time = this.formatDate(timeData[d].time)
+                            timeData[d].size = this.formatSize(timeData[d].size)
+                        }
+                        this.calssListinfo = timeData
+                        this.$Message.success('删除成功')
+                    },
+                    error => {
+                        this.$Message.success('删除失败')
+                    }
+                )
+            } else {
+                delete uploadafterData.ids
+                delete uploadafterData.files
+                this.$api.jyzx.getmemoir(uploadafterData).then(
+                    res => {
+                        console.log(res)
+                        var timeData = res.classVideo.files
+                        for (var p = 0; p < timeData.length; p++) {
+                            timeData[p].time = this.formatDate(timeData[p].time)
+                            timeData[p].size = this.formatSize(timeData[p].size)
+                        }
+                        this.calssListinfo = timeData
+                    },
+                    error => {
+
+                    }
+                )
+            }
+        },
+        //同组课程查看评价
+        checkevaluate(data,isSelf){
+            console.log(data)
+            // this.showStatus=true
+            // this.courseName=index.name.substring(0,index.name.lastIndexOf("."));
+			this.$router.push({
+				name:'video',
+				params:{
+					data:data,
+					isSelf:isSelf
+				}
+			})
+			this.$EventBus.$emit('goVideoAppraise',data)
+        },
+        //同组课堂实录
+        getteamvideo() {
+            this.$api.jyzx.getTeamclass(
+                {
+                    "tmdid": this.$store.state.userInfo.TEAMModelId,
+                    "school": this.$store.state.userInfo.schoolCode
+                }
+            ).then(
+                res => {
+                    console.log(res, '同组数据')
+                    if (res.groupMembers.length != 0) {
+                        res.groupMembers.forEach((i) => {
+                            i.vtime = this.formatDate(i.vtime)
+                            i.vsize = this.formatSize(i.vsize)
+                        })
+                        this.teamVideo = res.groupMembers
+                    } else {
+                        this.$Message.info('暂无同组课堂实录')
+                    }
+                   
+                },
+                error => {
+
+                }
+            )
+        },
+        //处理时间戳
+        formatDate(date) {
+            var date = new Date(date);
+            var YY = date.getFullYear() + '-';
+            var MM = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
+            var DD = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate());
+            var hh = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
+            var mm = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
+            var ss = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds());
+            return YY + MM + DD + " " + hh + mm + ss;
+        },
+        //处理文件大小
+        formatSize(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'
+        },
+    },
+}
+</script>
+
+<style lang="less">
+.class-memoir {
+    padding:1%;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+    .ivu-card {
+        height: 100%;
+        margin: 20px;
+    }
+}
+    .teachnames {
+        font-size: 26px;
+        margin-left:10px;
+    }
+    .coursename {
+        font-size:26px;
+    }
+    .backward:hover{
+        cursor:pointer
+    }
+/*    .appraise {
+        width:100%;
+        padding:3%;
+        margin-top:5%;
+        border:1px solid #ccc;
+    }*/
+    .short span {
+        color: red;
+    }
+.qualified span{
+    color:orangered
+}
+.excellent span{
+    color:rgb(0, 119, 0)
+}
+</style>

+ 486 - 0
TEAMModelOS/ClientApp/src/view/jyzx/discuss.vue

@@ -0,0 +1,486 @@
+<template>
+    <div style="background: #eee; padding: 20px; height: 100%" class="discuss1">
+        <Card>
+            <div style="width: 90%; margin: 0% 5%; height: 100%">
+                <topicPublish></topicPublish>
+                <div style="height: 100%">
+                    <vuescroll>
+                        <div
+                            style="
+                                width: 83%;
+                                margin: 0% 9%;
+                                margin-bottom: 130px;
+                            "
+                        >
+                            <div
+                                v-for="(item, index) in discuss"
+                                :key="index"
+                                class="discuss"
+                            >
+                                <div>
+                                    <div class="disContent">
+                                        <p class="inClass" v-if="item.relevance">{{item.relevance}}</p>
+                                        <p class="disName"
+                                           :style="{
+                                                color:
+                                                    item.tmdid ==
+                                                    $store.state.userInfo
+                                                        .TEAMModelId
+                                                        ? '#2d8cf0'
+                                                        : '',
+                                            }">
+                                            {{ item.tmdname }}
+                                        </p>
+                                    </div>
+                                    <p>
+                                        <span style="color: #2d8cf0"
+                                            >话题:</span
+                                        >{{ item.title }}
+                                    </p>
+                                    <div>
+                                        <span style="color: #2d8cf0"
+                                            >内容:</span
+                                        >
+                                        <p v-html="item.comment"></p>
+                                    </div>
+                                    <div class="disAction">
+                                        <div>
+                                            <span
+                                                style="
+                                                    margin-right: 20px;
+                                                    color: #2d8cf0;
+                                                "
+                                                @click="openReply(item.id, 0)"
+                                                v-show="item.replyCount != 0"
+                                                >评论回复({{
+                                                    item.replyCount
+                                                }})</span
+                                            >
+                                            <span
+                                                style="
+                                                    margin-right: 20px;
+                                                    color: #2d8cf0;
+                                                "
+                                                @click="replyOther(item, index)"
+                                                >回复</span
+                                            ><span
+                                                @click="delect(item.id, index)"
+                                                v-if="
+                                                    item.tmdid ==
+                                                    $store.state.userInfo
+                                                        .TEAMModelId
+                                                "
+                                                style="margin-right: 20px"
+                                                >删除</span
+                                            >
+                                            <span @click="report(item.tmdname)"
+                                                >举报</span
+                                            >
+                                        </div>
+                                        <p>{{ item.time | formatDate }}</p>
+                                    </div>
+                                </div>
+                                <!-- 回复框 -->
+                                <div class="replyDisc" v-if="activeIn == index">
+                                    <Input
+                                        v-model="replyDis"
+                                        type="textarea"
+                                        :autosize="{ minRows: 2, maxRows: 5 }"
+                                    ></Input>
+                                    <div>
+                                        <Button
+                                            type="primary"
+                                            size="small"
+                                            @click="
+                                                reply(
+                                                    item.id,
+                                                    item.tmdname,
+                                                    item.tmdid
+                                                )
+                                            "
+                                            >确定</Button
+                                        >
+                                        <Button
+                                            style="margin-left: 8px"
+                                            @click="cancelReply"
+                                            size="small"
+                                            >取消</Button
+                                        >
+                                    </div>
+                                </div>
+                                <!-- 别人的留言 -->
+                                <div
+                                    v-if="replyStatus && item.id === replyId && replyData.length"
+                                    class="disChild"
+                                >
+                                    <div
+                                        v-for="(child, no) in replyData"
+                                        :key="no"
+                                    >
+                                        <div class="disContent">
+                                            <p
+                                                class="disName"
+                                                :style="{
+                                                    color:
+                                                        child.tmdid ==
+                                                        $store.state.userInfo
+                                                            .TEAMModelId
+                                                            ? '#2d8cf0'
+                                                            : '',
+                                                }"
+                                            >
+                                                {{ child.tmdname }}
+                                            </p>
+                                        </div>
+                                        <p v-html="child.comment"></p>
+                                        <div class="disAction">
+                                            <div>
+                                                <span
+                                                    @click="
+                                                        delect(
+                                                            child.pid,
+                                                            index,
+                                                            child.id
+                                                        )
+                                                    "
+                                                    v-if="
+                                                        child.tmdid ==
+                                                            $store.state
+                                                                .userInfo
+                                                                .TEAMModelId ||
+                                                        item.tmdid ==
+                                                            $store.state
+                                                                .userInfo
+                                                                .TEAMModelId
+                                                    "
+                                                    style="margin-right: 20px"
+                                                    >删除</span
+                                                >
+                                                <span
+                                                    @click="
+                                                        report(child.tmdname)
+                                                    "
+                                                    >举报</span
+                                                >
+                                            </div>
+                                            <p>{{ child.time | formatDate }}</p>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </vuescroll>
+                </div>
+            </div>
+        </Card>
+        <Modal v-model="isReport" title="举报" @on-ok="ok">
+            <p>请选择举报类型:</p>
+            <CheckboxGroup v-model="checkReport">
+                <Checkbox
+                    :label="index"
+                    v-for="(item, index) in reportList"
+                    :key="index"
+                >
+                    <span>{{ item }}</span>
+                </Checkbox>
+            </CheckboxGroup>
+        </Modal>
+    </div>
+</template>
+<script>
+import topicPublish from "./topicPublish.vue"
+import { formatDate } from "../../utils/time.js"
+export default {
+    components: { topicPublish },
+    data() {
+        return {
+            activeIn: -1, //-1:没有点击回复
+            replyDis: "",
+            discuss: [],
+            replyDis: "",
+            replyStatus: false,
+            replyOpen: "展开回复",
+            replyData: [],
+            topicId: "",
+            replyId: "",
+            replyIndex: 0,
+            reportList: [
+                "违法信息",
+                "涉黄信息",
+                "泄露隐私",
+                "人身攻击",
+                "垃圾营销",
+            ],
+            isReport: false,
+            checkReport: [],
+        }
+    },
+    created() {
+       
+        },
+        mounted() {
+            this.totaltopic()
+        },
+    methods: {
+        replyOther(data, i) {
+            if (this.activeIn == -1 || this.activeIn != i) {
+                this.activeIn = i
+                this.replyDis = ""
+            }
+        },
+        // 取消回复
+        cancelReply() {
+            this.activeIn = -1
+        },
+        //获取所有话题
+        totaltopic() {
+            let user = this.$store.state.userInfo
+            this.$api.jyzx
+                .totalTopic({
+                    code: this.$store.state.userInfo.schoolCode,
+                    source: "",
+                    comid: "",
+                    tmdid: "",
+                })
+                .then(
+                    (res) => {
+                        var traitData = JSON.parse(localStorage.getItem('trait'))
+                        for (var i = 0; i < res.length; i++) {
+                            let comment = res[i].comment
+                            var commenfrt = comment.replace("<p>", "")
+                            var commenlast = commenfrt.replace("</p>", "")
+                            res[i].comment = commenlast
+                            var comidNmae = res[i].comid
+                            var name = ''
+                            if (traitData) {
+                                var signame = traitData.filter(function (x) {
+                                    return x.comid === comidNmae
+                                })
+                                if (signame.length != 0) {
+                                    name = signame[0].no + signame[0].name
+                                }
+                            }
+                            res[i].relevance = name
+                        }
+                        res.reverse()
+                        this.discuss = res
+                        localStorage.setItem("allTopic", JSON.stringify(res))
+                    },
+                    (error) => {
+                        this.$Message.error("API异常,数据获取失败!")
+                        console.log(error, "获取失败")
+                    }
+                )
+        },
+        // 回复话题
+        reply(topicId, replyName, replyId) {
+            let userme = this.$store.state.userInfo
+            console.log(userme, this.replyDis, "用户信息")
+            this.$api.jyzx
+                .replyTopic({
+                    opt: "add",
+                    debateId: topicId,
+                    debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                    reply: {
+                        id: "",
+                        pid: topicId,
+                        tmdid: userme.TEAMModelId,
+                        tmdname: userme.name,
+                        comment: this.replyDis,
+                        atTmdid: replyId,
+                        atTmdname: replyName,
+                    },
+                })
+                .then(
+                    (res) => {
+                        console.log(res)
+                        this.$Message.success("回复成功")
+                        this.discuss[this.activeIn].replyCount += 1
+                        this.activeIn = -1
+                        this.openReply(res.reply.pid, 1)
+                    },
+                    (error) => {
+                        console.log(error)
+                    }
+                )
+        },
+        //展开回复
+        openReply(topicId, status) {
+            this.topicId = topicId
+            if (!this.replyStatus) {
+                this.replyStatus = true
+                this.activeIn = -1
+            } else {
+                // status:1 回复
+                if (!status) {
+                    if (this.replyId == topicId) {
+                        this.replyStatus = false
+                    }
+                }
+            }
+            this.getReplyArr(topicId)
+        },
+        getReplyArr(topicId) {
+            this.$api.jyzx
+                .replyDetail({
+                    debateId: topicId,
+                    debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                })
+                .then(
+                    (res) => {
+                        this.replyData = res.debate.replies
+                        this.replyId = res.debate.id
+                    },
+                    (error) => {}
+                )
+        },
+        // 举报
+        report(name) {
+            this.isReport = true
+            this.checkReport = []
+        },
+        ok() {
+            if (this.checkReport.length) {
+                this.$Message.success("举报成功")
+            }
+        },
+        // 删除
+        delect(topicId, index, replyId) {
+            let cont = ""
+            if (replyId) {
+                cont = "回复"
+            } else {
+                cont = "话题"
+            }
+            this.$Modal.confirm({
+                title: "删除",
+                content: `您确定要删除该${cont}吗?`,
+                onOk: () => {
+                    // 回复的
+                    if (replyId) {
+                        let req = {
+                            opt: "del",
+                            debateId: topicId,
+                            debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                            reply: {
+                                id: replyId,
+                            },
+                        }
+                        this.$api.jyzx.replyTopic(req).then((res) => {
+                            if (res.status == 0) {
+                                this.$Message.success("删除成功")
+                                this.getReplyArr(topicId)
+                                this.discuss[index].replyCount --
+                            } else {
+                                this.$Message.warning("删除失败")
+                            }
+                        })
+                    }
+                    // 自己的话题
+                    else {
+                        let req = {
+                            debateId: topicId,
+                            debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                        }
+                        // 调另一个api
+                        this.$api.jyzx.delReply(req).then((res) => {
+                            if (res.status == 200) {
+                                this.$Message.success("删除成功")
+                                this.totaltopic()
+                            } else {
+                                this.$Message.warning("删除失败")
+                            }
+                        })
+                    }
+                },
+            })
+        },
+    },
+    //时间处理
+    filters: {
+        formatDate(time) {
+            time = time
+            let date = new Date(time)
+            return formatDate(date, "yyyy-MM-dd hh:mm")
+        },
+    },
+}
+</script>
+
+<style lang="less" scoped>
+.discuss {
+    border-bottom: 1px solid #ccc;
+    padding: 10px;
+
+    .disContent {
+        .disName {
+            font-size: 20px;
+            // color: #2d8cf0;
+        }
+
+        & > p:nth-of-type(2) {
+            margin: 5px 0;
+        }
+    }
+    .disAction {
+        margin-top: 5px;
+        & > p:last-child {
+            color: #878787;
+        }
+        div {
+            float: right;
+            span {
+                cursor: pointer;
+            }
+        }
+    }
+    .inClass {
+        background: #2d8cf0;
+        color: white;
+        border-radius: 10px;
+        padding: 2px 10px;
+        margin: 2px 0;
+        float: right;
+    }
+    .disChild {
+        border: 1px solid #ccc;
+        border-radius: 10px;
+        margin: 15px 40px;
+        padding: 5px 10px;
+
+        & > div {
+            padding: 2px 10px 5px;
+            &:not(:last-child) {
+                border-bottom: 1px solid #ccc;
+            }
+        }
+
+        .disName {
+            font-size: 16px;
+            // color: #515a6e;
+        }
+    }
+
+    .replyDisc {
+        margin: 15px 40px;
+        margin-top: 10px;
+        text-align: right;
+
+        & > div:last-child {
+            margin-top: 10px;
+        }
+    }
+}
+</style>
+
+<style lang="less">
+.discuss1 {
+    .ivu-card-bordered,
+    .ivu-card-body {
+        height: 100%;
+    }
+    .ivu-card-body {
+        overflow: hidden;
+    }
+}
+</style>

+ 297 - 0
TEAMModelOS/ClientApp/src/view/jyzx/index.less

@@ -0,0 +1,297 @@
+// @primaryColor: #1CC0F3;
+@borderColor: #aaaaaa;
+.online-container {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+
+	.online-content {
+		height: 100%;
+		display: flex;
+
+		&-header {
+			height: 35px;
+			width: 100%;
+			border-bottom: 1px solid @borderColor;
+			display: flex;
+			align-items: center;
+			padding-left: 15px;
+			color: #000000;
+			font-weight: bold;
+			position: relative;
+
+
+			&::before {
+				content: '';
+				display: inline-block;
+				border: 4px solid #16c18e;
+				border-radius: 50%;
+				margin-right: 10px;
+				margin-bottom: 3px;
+			}
+
+			&-tools {
+				position: absolute;
+				right: 0;
+
+				.ivu-icon {
+					cursor: pointer;
+					margin-right: 15px;
+				}
+
+				.ivu-btn {
+					background: none;
+					border: none;
+					color: #bdbdbd;
+					margin-right: 5px;
+				}
+			}
+		}
+
+		.online-left {
+			height: 100%;
+			width: 20%;
+			border-right: 1px solid @borderColor;
+
+			.volume-list {
+				display: flex;
+				flex-direction: column;
+				margin-left: 10px;
+				overflow: hidden;
+				padding-bottom: 60px;
+
+				.volume-item {
+					padding: 10px 0 10px 20px;
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+					border-bottom: 1px solid @borderColor;
+					cursor: pointer;
+
+					&-name {
+						font-size: 16px;
+						font-weight: bold;
+						margin: 10px 0 5px 0;
+						padding-right: 10px;
+						overflow: hidden;
+						color: #383838;
+
+						.abli-type{
+							background-color: transparent;
+							color: #108d6c;
+							margin-left: 5px;
+							border: 1px solid #108d6c;
+							border-radius: 4px;
+							font-size: 12px;
+							padding: 0 8px;
+						}
+
+						.status-idDel {
+							font-size: 12px;
+							display: inline-block;
+							padding: 2px 8px;
+							background-color: #16C18E;
+							color: #fff;
+							margin-right: 5px;
+							margin-bottom: 3px;
+							vertical-align: bottom;
+							border-radius: 4px;
+							zoom: 0.9;
+						}
+
+						.status-time {
+							display: inline-block;
+							font-size: 12px;
+							margin-right: 10px;
+							margin-top: 3px;
+							color: #7b7b7b;
+							float: right;
+						}
+					}
+
+
+					&:hover {
+						.volume-active;
+					}
+				}
+			}
+		}
+
+		.online-mid {
+			height: 100%;
+			width: 30%;
+			border-right: 1px solid @borderColor;
+			z-index: 1;
+
+			.online-tree-box {
+				height: 100%;
+				z-index: 0;
+			}
+		}
+		.online-right {
+			height: 100%;
+			width: 50%;
+			border-right: 1px solid @borderColor;
+
+			.online-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 #aaa;
+					padding: 10px 20px;
+					color: #515a6e;
+					font-size: 16px;
+					font-weight: bold;
+					display: flex;
+					// align-items: center;
+					flex-direction: column;
+					//右侧关联资源的样式
+					.detailsboxs {
+						padding: 1%;
+
+						.resourcetitle {
+							width: 100%;
+							text-align: center;
+							margin-bottom: 1%;
+							font-size: 20px;
+						}
+					}
+
+					.node-title {
+						display: flex;
+						margin: 10px 0;
+						font-size: 20px;
+
+						img {
+							width: 30px;
+							height: 30px;
+							margin-right: 20px;
+						}
+					}
+
+					.qieicon {
+						width: 30px;
+						height: 30px;
+						line-height: 30px;
+						border-radius: 15px;
+						font-size: 17px;
+						background: #19be6b;
+						color: #fff;
+						text-align: center;
+						margin-right: 20px;
+					}
+
+					.successicon {
+						position: absolute;
+						right: 1%;
+						height: 30px;
+						line-height: 30px;
+					}
+
+					&-tools {
+						background-color: #16C18E;
+						color: #fff;
+						border-radius: 5px;
+						font-size: 12px;
+						padding: 2px 5px;
+						margin-right: 10px;
+					}
+
+					.iconbtn {
+						position: absolute;
+						right: 5%;
+						font-size: 20px !important;
+						border: 0 none;
+						background: none;
+						outline: 0 !important;
+					}
+
+					&:hover {
+						border-radius: 5px;
+						background-color: #d3d3d3;
+						// color: #fff;
+
+						.node-resource-tools {
+							display: flex;
+						}
+					}
+					/* &:last-child {
+						border-bottom: none;
+					} */
+				}
+			}
+		}
+	}
+
+	.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%);
+	}
+
+	.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;
+		}
+	}
+}
+//关联话题新增样式
+.topicboxinfo {
+	padding: 1% 5%;
+	border-bottom:1px dashed #ccc;
+	.btnicon {
+		margin-top: 1%;
+	}
+
+	.topicscontent{
+		width:100%;
+		padding:1% 5%;
+	}
+}
+
+.studyTime {
+	float: right;
+	font-size: 17px;
+
+	span {
+		font-size: 20px;
+		font-weight: bold
+	}
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1079 - 0
TEAMModelOS/ClientApp/src/view/jyzx/index.vue


+ 259 - 0
TEAMModelOS/ClientApp/src/view/jyzx/offline.less

@@ -0,0 +1,259 @@
+@borderColor: #aaaaaa;
+.offline {
+	width: 100%;
+	height: 100%;
+	background-color: #fff;
+	display: flex;
+	flex-direction: column;
+	font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
+	.detailheader{
+		display: inline-block;
+		width:100%;
+		padding: 1%;
+		border:1px solid #ccc;
+		border-radius: 10px;
+		box-shadow: 3px 3px 5px #d9d9d9;
+	}
+	.img-box {
+		display: inline-block;
+		width: 40%;
+		height: 300px;
+		background-size: 100% 100%;
+		background-repeat: no-repeat;
+		// box-shadow: 10px 10px 5px #888888;
+		border-radius: 15px;
+		float: left;
+	}
+	.activitytitle{
+		display:inline-block;
+		width:58%;
+		float: left;
+		// margin-left:2%;
+		.site{
+			margin-bottom:1%;
+		}
+	}
+	.activitytitleinfo{
+		font-size:16px;
+	}
+	.activitytitlenmame{
+		color:#ba183e;
+	}
+	.participation{
+		font-size:16px;
+		color: #a5a5a5 !important;
+		margin-top:2%;
+	}
+	.info-label {
+		color: #a5a5a5 !important;
+		display: inline-block;
+		width: 80px;
+		margin-left:2%;
+	}
+    .info-labels{
+		width:66px;
+		color: #a5a5a5 !important;
+		display: inline-block;
+	}
+	.parname{
+		color:#ba183e;
+		margin-left:1%;
+	}
+	.basics{
+		width: 100%;
+		padding: 0% 1% 1% 0%;
+		border:1px solid #ccc;
+		margin-top:1%;
+		border-radius: 10px;
+		box-shadow: 3px 3px 5px #d9d9d9;
+	}
+	.headeicon{
+		border-left:8px solid #2D8CF0;
+		color:#fff;
+		padding: 1%;
+		font-size:24px;
+		background: linear-gradient(to right, #5EA7F4, #fff);
+		border-top-left-radius: 10px;
+		margin-bottom: 20px;
+	}
+	.headeicontitle{
+		padding-left: 1%;
+	}
+	.submitarea{
+		border:1px solid #ccc;
+		margin: 15px 0px 15px 0px;
+		border-radius: 10px;
+		box-shadow: 3px 3px 5px #d9d9d9;
+
+		.hw-style{
+			margin-left: 2%;
+		}
+	}
+	.offline-content {
+		height: 100%;
+		display: flex;
+
+		&-header {
+			height: 40px;
+			width: 100%;
+			border-bottom: 1px solid @borderColor;
+			display: flex;
+			align-items: center;
+			padding-left: 15px;
+			color: #000000;
+			font-weight: bold;
+			position: relative;
+
+			&::before {
+				content: '';
+				display: inline-block;
+				border: 4px solid #2D8CF0;
+				border-radius: 50%;
+				margin-right: 10px;
+				margin-bottom: 3px;
+			}
+		}
+
+		.offline-left {
+			height: 100%;
+			width: 30%;
+			border-right: 1px solid @borderColor;
+
+			.volume-list {
+				display: flex;
+				flex-direction: column;
+				margin-left: 10px;
+				overflow: hidden;
+				padding-bottom: 40px;
+
+				.volume-item {
+					padding: 10px 0 10px 20px;
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+					border-bottom: 1px solid @borderColor;
+					cursor: pointer;
+
+					&-name {
+						font-size: 16px;
+						font-weight: bold;
+						margin: 10px 0 5px 0;
+						padding-right: 10px;
+						overflow: hidden;
+						color: #383838;
+
+						& > span {
+							display: inline-block;
+							width: 20px;
+							height: 20px;
+							line-height: 20px;
+							border-radius: 10px;
+							// background-color: #F97C57;
+							font-size: 12px;
+							text-align: center;
+							color: #fff;
+						}
+
+						.status-time {
+							display: inline-block;
+							font-size: 12px;
+							margin-right: 10px;
+							margin-top: 3px;
+							color: #7b7b7b;
+							float: right;
+						}
+					}
+				}
+			}
+		}
+
+		.offline-right {
+			height: 100%;
+			width: 70%;
+			border-right: 1px solid @borderColor;
+
+			
+			.ansShow{
+				font-size: 14px;
+				margin-left: 10px;
+			}
+
+			.node-resource-box {
+				margin-top: 20px;
+				margin-right: 20px;
+			}
+		}
+	}
+
+	.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%);
+	}
+}
+.detailbox {
+	width: 100%;
+	padding: 1%;
+	/*margin: 2% 7%;*/
+	// background-color: #759cc5;
+	border-radius: 10px;
+	// color: #fff;
+	height: calc(100vh - 80px);
+	overflow: auto;
+	font-size: initial;
+	font-weight: bold;
+
+	.detailstitle {
+		color: #2b85e4;
+		font-size: 26px;
+		margin-bottom: 5px;
+		font-weight: bold;
+		text-align:left;
+		margin-left: 3%;
+		position: relative;
+
+		.school-gade{
+			vertical-align: middle;
+			font-size: 14px;
+			color: #fff;
+			// background-color: #F97C57;
+			padding: 3px 5px;
+			border-radius: 8px;
+			margin-right: 10px;
+		}
+	}
+
+	.titlename, .target, .type, .object, .time, .keynote, .totalperiod, .score, .site, .content, .accessory, .successbtn {
+		padding: 10px;
+		// color: #000;
+		& > span:first-child {
+			color: #13457a;
+		}
+	}
+
+	.accessory {
+		display: flex;
+	}
+
+	.successbtn {
+		text-align: right;
+	}
+}
+.hw-area{
+	border: 1px solid;
+	border-radius: 5px;
+	padding: 2px 5px;
+	cursor: pointer;
+	display: inline-block;
+	color: #fff;
+	margin-top:20px
+}
+.hour-tag{
+    display: inline-block;
+    font-size: 12px;
+    color: #ff9900;
+    border: 1px solid #ff9900;
+    padding: 0px 5px;
+    border-radius: 4px;
+}

+ 546 - 0
TEAMModelOS/ClientApp/src/view/jyzx/offline.vue

@@ -0,0 +1,546 @@
+<template>
+    <div class="offline">
+        <Loading v-if="isLoading" hideMask></Loading>
+        <div class="offline-content">
+            <div class="offline-left">
+                <div class="offline-content-header">研修活动</div>
+                <vuescroll>
+                    <EmptyData :top="100" v-if="!activity.length"></EmptyData>
+                    <div class="volume-list">
+                        <div v-for="(item, index) in activity" :key="index" :class="[
+                                'volume-item',
+                                activeIndex === index ? 'volume-active' : '',
+                            ]" @click="getActivityInfo(item, index)">
+                            <p class="volume-item-name">
+                                <span style="display: inline-block; background: #a2b02e;" v-show="item.owner == 'area'">区</span>
+                                <span style="display: inline-block; background: #FF9900;" v-show="item.owner == 'school'">校</span>
+                                {{ item.name }}
+                            </p>
+                            <p class="status-time" style="padding-left:25px">
+                                {{ item.startTime }} - {{ item.endTime }}
+                            </p>
+                        </div>
+                    </div>
+                </vuescroll>
+            </div>
+            <div class="offline-right">
+                <div class="offline-content-header">详细信息</div>
+                <div class="offline-tree-box">
+                    <div class="detailbox" v-if="Object.keys(resource).length">
+                        <div style="margin-bottom: 40px">
+                            <div class="detailheader">
+                                <div class="activitytitle">
+                                    <p class="detailstitle">
+                                        <span class="school-gade" style="background: #a2b02e" v-show="resource.owner == 'area'">区级</span>
+                                        <span class="school-gade" style="background: #ff9900" v-show="resource.owner == 'school'">校级</span>
+                                        {{ resource.name }}
+                                    </p>
+                                    <div class="site">
+                                        <span class="info-label">创建者:</span>
+                                        <span v-if="resource.createdName">{{resource.createdName}}</span>
+                                        <span v-else>无</span>
+                                    </div>
+                                    <div class="site">
+                                        <span class="info-label">参与人数:</span><span>{{ resource.teachers.length}}</span>
+                                    </div>
+                                    <div class="site">
+                                        <span class="info-label">活动时间:</span>
+                                        <span>
+                                            {{ resource.detail.startTime }} -
+                                            {{ resource.detail.endTime }}
+                                        </span>
+                                    </div>
+                                    <!--<div class="site">
+                                        <span class="info-label">参与者:</span>
+                                        <span style="margin-right: 15px" v-for="(iteminfo, index) in resource.teachers" :key="index">{{iteminfo.name}}</span>
+                                    </div>-->
+                                    <div class="site">
+                                        <span class="info-label">学习对象:</span>
+                                        <span v-for="(item, index) in resource.targets" :key="index" style="margin-right: 10px">{{ item }}</span>
+                                    </div>
+                                </div>
+                                <div class="img-box" :style="{backgroundImage: `url(${resource.detail.img || defImg})`}"></div>
+                            </div>
+                            <div class="basics">
+                                <dt class="headeicon"><span class="headeicontitle">基础信息</span></dt>
+                                <div class="target">
+                                    <span class="info-label">研修目标:</span>
+                                    <span>{{ resource.detail.desc }}</span>
+                                </div>
+                                <!--<div class="object">
+                                    <span class="info-label">学习对象:</span>
+                                    <span v-for="(item, index) in resource.targets" :key="index" style="margin-right: 10px">{{ item }}</span>
+                                </div>-->
+                                <div class="site">
+                                    <span class="info-label">培训地点:</span><span>{{ resource.detail.address }}</span>
+                                </div>
+                                <div class="keynote">
+                                    <span class="info-label">主讲人:</span><span>{{ resource.detail.presenter }}</span>
+                                </div>
+                                <div class="type">
+                                    <span class="info-label">类型:</span><span>{{ resource.typeName }}</span>
+                                </div>
+                                <div class="totalperiod">
+                                    <span class="info-label">学时:</span>
+                                    <span class="hour-tag">{{ resource.hour }}学时</span>
+                                </div>
+                                <div class="content">
+                                    <span class="info-label">内容:</span><span>{{ resource.detail.topic }}</span>
+                                </div>
+                            </div>
+                            <!-- <Divider dashed /> -->
+                            <!-- 作业 -->
+                            <div class="submitarea"  v-if="resource.settings.includes('hw')">
+                                <dt class="headeicon"><span class="headeicontitle">作业</span></dt>
+                                <div class="target">
+                                    <span class="info-label">作业名称:</span><span>{{ resource.detail.hwName }}</span>
+                                </div>
+                                <div class="target">
+                                    <span class="info-label">作业描述:</span><span>{{ resource.detail.hwDesc }}</span>
+                                </div>
+                                <div class="target hw-style">
+                                    <Upload
+                                        type="drag"
+                                        action=""
+                                        :before-upload="customUpload"
+                                    >
+                                        <div style="padding: 20px 0">
+                                            <Icon type="ios-cloud-upload" size="52" :style="{color: homeWork ? '#b4b4b4' : '#2d8cf0' }"></Icon>
+                                            <p>
+                                                <span>
+                                                    {{homeWork ? '重新上传' : '上传作业'}}
+                                                </span>
+                                            </p>
+                                        </div>
+                                    </Upload>
+                                    <!-- <Upload action="" :before-upload="customUpload">
+                                        <div class="hw-area" :style="{background: homeWork ? '#19be6b' : '#2d8cf0' }">
+                                            <Icon type="ios-cloud-upload" color="#fff" />
+                                            <span>
+                                                {{homeWork ? '重新上传' : '上传作业'}}
+                                            </span>
+                                        </div>
+                                    </Upload> -->
+                                    <span v-show="homeWork">
+                                        <Icon type="ios-document" />{{
+                                            homeWork
+                                        }}
+                                    </span>
+                                </div>
+                            </div>
+                            <!-- 问卷 -->
+                            <div class="submitarea" v-if="resource.settings.includes('survey') && surveyInfo">
+                                <dt class="headeicon"><span class="headeicontitle">问卷</span></dt>
+                                <div class="target">
+                                    <span class="info-label">问卷名称:</span><span>{{ surveyInfo.name }}</span>
+                                </div>
+                                <div class="target">
+                                    <span class="info-label">问卷描述:</span><span>{{ surveyInfo.description }}</span>
+                                </div>
+                                <div class="target hw-style">
+                                    <Button type="primary" @click="doSurvey" v-if="!isSurvey">
+                                        <Icon type="ios-create" />
+                                        作答问卷
+                                    </Button>
+                                    <Button type="success" @click="doSurvey" v-else>
+                                        <Icon type="md-checkmark-circle-outline" />
+                                        已作答
+                                    </Button>
+                                </div>
+                            </div>
+                            <!-- 评测 -->
+                            <div class="submitarea" v-if="resource.settings.includes('exam') && examInfo">
+                                <dt class="headeicon"><span class="headeicontitle">评测</span></dt>
+                                <div class="target">
+                                    <span class="info-label">评测名称:</span><span>{{ examInfo.name }}</span>
+                                </div>
+                                <div class="target">
+                                    <span class="info-label">评测描述:</span><span>{{ examInfo.description }}</span>
+                                </div>
+                                <!-- <div v-if="resource.detail.hwTime">
+                                    <span class="info-label">结束时间:</span><span>{{ resource.detail.hwTime }}</span>
+                                </div> -->
+                                <div class="target hw-style">
+                                    <Button type="primary" @click="doExam" v-if="!isExam">
+                                        <Icon type="ios-create" />
+                                        作答评测
+                                    </Button>
+                                    <Button type="success" @click="doExam" v-else>
+                                        <Icon type="md-checkmark-circle-outline" />
+                                        已作答
+                                    </Button>
+                                    <span class="ansShow" v-show="isExam">
+                                        答对:{{ rightNum }} 题,
+                                        答错:{{ wrongNum }}题
+                                    </span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <Modal v-model="surveyStatus" title="问卷反馈" width="800" @on-ok="saveSurvey">
+            <DoSurvey v-if="surveyInfo" :survey="surveyInfo" @answer-change="getSurveyData" />
+        </Modal>
+        <Modal v-model="examStatus" title="评测作答" width="800" @on-ok="saveExam">
+            <DoExam v-if="examInfo" :exam="examInfo" @answer-change="getExamData" @have-answer="haveAns" />
+        </Modal>
+    </div>
+</template>
+
+<script>
+import DoSurvey from './DoSurvey.vue'
+import DoExam from './DoExam.vue'
+import BlobTool from "@/utils/blobTool.js"
+import { mapGetters } from 'vuex'
+export default {
+    components: {
+        DoSurvey,
+        DoExam
+    },
+    data() {
+        return {
+            myAnswer: [],
+            itemCount: 0,
+            isSurvey: false, //问卷是否作答
+            surveyStatus: false, //问卷弹框
+            surveyInfo: undefined, //问卷信息
+            isExam: false,
+            examStatus: false,
+            examInfo: undefined,
+            rightNum: 0,
+            wrongNum: 0,
+            rightAns: [],
+            defImg: 'https://www.habook.com.cn/data/editor/images/img/20210429%2001/001.png',
+            isLoading: false,
+            activity: [],
+            resource: {},
+            actType: [],
+            activeIndex: 0,
+            fileDownload: false,
+            successicon: false,
+            homeWork: "", //上传的作业
+            allteach:[],//所有老师
+            ansShow: false, //是否合格:2:不合格  1:合格
+        }
+    },
+    created() {
+        this.actType = this.$GLOBAL.TRAIN_TYPE()
+        this.$store.dispatch('user/getSchoolTeacher').then(
+            res=>{ 
+                this.allteach=this.teachers
+            },
+            error=>{
+                this.$Message.error('获取所有教师API异常')
+            }
+        )
+    },
+     mounted() {
+        this.getAllAct()
+     },
+    methods: {
+        // 作答问卷
+        getSurveyData(data) {
+            // 获取作答数据:data
+            this.myAnswer = data.answer
+            this.itemCount = data.count
+        },
+        saveSurvey() {
+            if(this.myAnswer.length && this.myAnswer.length == this.itemCount && !this.myAnswer.includes(undefined)) {
+                let params = {
+                    id: this.surveyInfo.id,
+                    code: this.$store.state.userInfo.schoolCode,
+                    tId: this.$store.state.userInfo.TEAMModelId,
+                    ans: this.myAnswer.map(i => Array.isArray(i) ? i : [i])
+                }
+                this.$api.jyzx.submitquestionnaire(params).then(res => {
+                    if(res.code == 200) {
+                        this.isSurvey = true
+                        this.$Message.success('保存成功')
+                    } else {
+                        this.$Message.warning('保存失败')
+                    }
+                })
+            } else if(this.myAnswer.length) {
+                this.$Message.warning("问卷未填写完,请继续填写")
+            }
+            
+        },
+        // 作答评测
+        getExamData(data) {
+            // 获取作答数据:data
+            this.myAnswer = data.answer
+            this.itemCount = data.count
+            this.rightAns = data.rightAns
+        },
+        // 已有答案
+        haveAns(data) {
+            this.judgeSub(data.answer, data.rightAns)
+        },
+        // 判断作对几题
+        judgeSub(ans, rightAns) {
+            var rNum = rightAns.length;
+            var hNum = 0;
+            ans.forEach((ha, hi) => {
+                let ii = 0
+                if(rightAns[hi].length == ha.length) {
+                    ha.forEach(hChild => {
+                        rightAns[hi].forEach(rChild => {
+                            if(hChild == rChild) {
+                                ii++
+                            }
+                        })
+                    })
+                    if(ii == rightAns[hi].length) {
+                        hNum++
+                    }
+                }
+            })
+            this.rightNum = hNum
+            this.wrongNum = rNum - hNum
+        },
+        saveExam() {
+            if(this.myAnswer.length && this.myAnswer.length == this.itemCount && !this.myAnswer.includes(undefined)) {
+                this.myAnswer = this.myAnswer.map(i => Array.isArray(i) ? i : [i])
+                this.judgeSub(this.myAnswer, this.rightAns)
+                let params = {
+                    id: this.examInfo.id,
+                    code: this.$store.state.userInfo.schoolCode,
+                    tId: this.$store.state.userInfo.TEAMModelId,
+                    ans: this.myAnswer
+                }
+                this.$api.jyzx.submitExam(params).then(res => {
+                    if(res.code == 200) {
+                        this.$Message.success('保存成功')
+                        this.isExam = true
+                    }
+                })
+            }
+            else if(this.myAnswer.length) {
+                this.$Message.warning("评测未作答完,请继续作答")
+            }
+        },
+        doSurvey() {
+            this.surveyStatus = true
+        },
+        doExam() {
+            this.examStatus = true
+        },
+        // 活动列表
+        getAllAct() {
+            let req = {
+                tId: this.$store.state.userInfo.TEAMModelId,
+                code: this.$store.state.userInfo.schoolCode,
+            }
+            this.$api.train.findTrainByTeacher(req).then((res) => {
+                if (res.studies.length) {
+                    res.studies.map((item) => {
+                        item.startTime = this.dateFormat(item.startTime)
+                        item.endTime = this.dateFormat(item.endTime)
+                    })
+                    this.activity = res.studies.reverse()
+                    this.getApiAct()
+                }
+            })
+        },
+        getActivityInfo(activity, index) {
+            this.activeIndex = index
+            this.getApiAct()
+        },
+        // 查询活动的信息
+        getApiAct() {
+            this.resource={}
+            this.homeWork = ""
+            let req = {
+                code: this.$store.state.userInfo.schoolCode,
+                id: this.activity[this.activeIndex].id,
+            }
+            this.$api.train.findSummaryTrain(req).then((res) => {
+                if (res.studies.length) {
+                    res.studies[0].detail.startTime = this.dateFormat(
+                        res.studies[0].detail.startTime
+                    )
+                    res.studies[0].detail.endTime = this.dateFormat(
+                        res.studies[0].detail.endTime
+                    )
+                    if (res.studies[0].detail.hwTime) {
+                        res.studies[0].detail.hwTime = this.dateFormat(
+                            res.studies[0].detail.hwTime
+                        )
+                    }
+
+                    this.actType.map((item) => {
+                        if (item.value == res.studies[0].type) {
+                            res.studies[0].typeName = item.label
+                        }
+                    })
+                    if (!res.studies[0].detail.desc) {
+                        res.studies[0].detail.desc = "暂无"
+                    }
+                    let hWork = res.studies[0].teachers.filter((item) => {
+                        return item.id == this.$store.state.userInfo.TEAMModelId
+                    })
+                    if (hWork[0].hw) {
+                        this.homeWork = hWork[0].hw.substring(hWork[0].hw.lastIndexOf("/") + 1)
+                    }
+                    
+                    this.resource = res.studies[0]
+                    if (this.resource.settings.includes('survey')) {
+                        this.getSurveyInfo(this.resource.detail.surveyId)
+                    }
+                    if (this.resource.settings.includes('exam')) {
+                        this.getExam(this.resource.detail.examId)
+                    }
+                    this.getcreatd()
+                }
+            })
+        },
+        //处理创建者name和参与人name
+        getcreatd() {
+            var createdId = this.resource.creatorId
+            var detailsDatas = this.resource
+            var allteachs = this.allteach
+            for(let i in allteachs) {
+                if(allteachs[i].id === createdId) {
+                    detailsDatas.createdName = allteachs[i].name
+                }
+            }
+            var teachArr = this.resource.teachers
+            var teachnameArr = []
+            for(var i = 0; i < teachArr.length; i++) {
+                var teachId = teachArr[i].id
+                let crew = allteachs.find((n) => n.id === teachId)
+                if(crew) {
+                    teachnameArr.push(crew)
+                }
+                //console.log(allteachs.find((n)=>n.id===teachId),'添加到数据的人')   
+            }
+            /* for(var r = 0; r < allteachs.length; r++) {
+                if(allteachs[r].id === '1528783259') {
+                    console.log(allteachs[r],'找到了')
+                } else {
+                    console.log('没有找到这个人')
+                }
+            } */
+            detailsDatas.teachers = teachnameArr
+            this.resource = detailsDatas
+        },
+        getSurveyInfo(surveyId) {
+            let params = {
+                id: surveyId,
+                code: this.$store.state.userInfo.schoolCode
+            }
+            this.$api.ability.getSurveySummary(params).then(
+                res => {
+                    if (res.trSurveys && res.trSurveys[0]) {
+                        res.trSurveys[0].teachers.forEach(item => {
+                            item.answer = item.answer || []
+                        })
+                        this.surveyInfo = res.trSurveys[0]
+
+                        let tea = res.trSurveys[0].teachers.find(item => {
+                            return item.id == this.$store.state.userInfo.TEAMModelId
+                        })
+                        if (tea) {
+                            this.isSurvey = !!tea.time
+                        }
+                    }
+                },
+                err => {
+
+                }
+            )
+        },
+        getExam(examId) {
+            let params = {
+                id: examId,
+                code: this.$store.state.userInfo.schoolCode
+            }
+            this.$api.ability.getExamSummary(params).then(
+                res => {
+                    if (res.trExams && res.trExams[0]) {
+                        res.trExams[0].teachers.forEach(item => {
+                            item.answer = item.answer || []
+                        })
+                        this.examInfo = res.trExams[0]
+
+                        let tea = res.trExams[0].teachers.find(item => {
+                            return item.id == this.$store.state.userInfo.TEAMModelId
+                        })
+                        if (tea) {
+                            this.isExam = !!tea.time
+                        }
+                    }
+                },
+                err => {
+
+                }
+            )
+        },
+        // 上传作业
+        customUpload(file) {
+            let sas = this.$store.state.user.schoolProfile.blob_sas
+            let blobUrl = JSON.parse(
+                decodeURIComponent(localStorage.school_profile, "utf-8")
+            ).blob_uri
+            let host = blobUrl.substring(0, blobUrl.lastIndexOf("/"))
+            let cont = blobUrl.substring(blobUrl.lastIndexOf("/") + 1)
+            // 创建一个blob盒子,装文件
+            let blobTool = new BlobTool(host, cont, "?" + sas, "school")
+            let path = `train/${this.activity[this.activeIndex].id}`
+            blobTool.upload(file, path).then(
+                (res) => {
+                    let req = {
+                        id: this.activity[this.activeIndex].id,
+                        code: this.$store.state.userInfo.schoolCode,
+                        tId: this.$store.state.userInfo.TEAMModelId,
+                        hw: res.blob, //文件地址
+                    }
+                    this.$api.train.uploadHw(req).then((resHW) => {
+                        if (resHW.code === 200) {
+                            this.homeWork = res.name
+                            this.$Message.success("上传成功")
+                        } else {
+                            this.$Message.warning("上传失败")
+                        }
+                    })
+                },
+                (err) => { }
+            )
+            return false
+        },
+        dateFormat(timestamp) {
+            let date = new Date(timestamp)
+            let Y = date.getFullYear()
+            let M = date.getMonth()
+            let D = date.getDate()
+            let H = date.getHours()
+            let MIN = date.getMinutes()
+            return `${Y}-${M < 9 ? "0" + (M + 1) : M + 1}-${D} ${H < 10 ? "0" + H : H
+                }:${MIN < 10 ? "00" : MIN}`
+        },
+        },
+
+        computed: {
+            ...mapGetters({
+                teachers: 'user/getSchoolUserJoined', // 取得已加入此學校的使用者
+            })
+        }
+}
+</script>
+
+<style lang="less" scoped>
+@import "./offline.less";
+</style>
+<style lang="less">
+.offline {
+    .ivu-form .ivu-form-item-label,
+    .ivu-input {
+        font-weight: bold;
+    }
+    /* .ivu-upload {
+    display: inline-block;
+} */
+}
+</style>

+ 587 - 0
TEAMModelOS/ClientApp/src/view/jyzx/relatedTopic.vue

@@ -0,0 +1,587 @@
+<template>
+    <div style="height: 100%" class="discuss1">
+        <div style="width: 90%; margin: 0% 5%; height: 100%">
+            <div>
+                <Input v-model="topicname" placeholder="发起新的话题" style="width:100%;padding:1% 0%" @on-focus="topstatus = true" />
+            </div>
+            <div class="topicboxinfo" v-show="topstatus">
+                <div id="divtop"></div>
+                <div class="btnicon">
+                    <div class="submit" @click="submitTopic"><Button type="primary">发布</Button></div>
+                    <div class="canle" @click="topstatus = false"><Button>取消</Button></div>
+                </div>
+            </div>
+            <div style="height: 75vh">
+                <vuescroll>
+                    <div style="margin-bottom: 130px;">
+                        <div
+                            v-for="(item, index) in discuss"
+                            :key="index"
+                            class="discuss"
+                        >
+                            <div>
+                                <div class="disContent">
+                                    <p class="inClass" v-if="item.relevance">{{item.relevance}}</p>
+                                    <p class="disName"
+                                        :style="{
+                                            color:
+                                                item.tmdid ==
+                                                $store.state.userInfo
+                                                    .TEAMModelId
+                                                    ? '#2d8cf0'
+                                                    : '',
+                                        }">
+                                        {{ item.tmdname }}
+                                    </p>
+                                </div>
+                                <p>
+                                    <span style="color: #2d8cf0"
+                                        >话题:</span
+                                    >{{ item.title }}
+                                </p>
+                                <div>
+                                    <span style="color: #2d8cf0"
+                                        >内容:</span
+                                    >
+                                    <p v-html="item.comment"></p>
+                                </div>
+                                <div class="disAction">
+                                    <div>
+                                        <span
+                                            style="
+                                                margin-right: 20px;
+                                                color: #2d8cf0;
+                                            "
+                                            @click="openReply(item.id, 0)"
+                                            v-show="item.replyCount != 0"
+                                            >评论回复({{
+                                                item.replyCount
+                                            }})</span
+                                        >
+                                        <span
+                                            style="
+                                                margin-right: 20px;
+                                                color: #2d8cf0;
+                                            "
+                                            @click="replyOther(item, index)"
+                                            >回复</span
+                                        ><span
+                                            @click="delect(item.id, index)"
+                                            v-if="
+                                                item.tmdid ==
+                                                $store.state.userInfo
+                                                    .TEAMModelId
+                                            "
+                                            style="margin-right: 20px"
+                                            >删除</span
+                                        >
+                                        <span @click="report(item.tmdname)"
+                                            >举报</span
+                                        >
+                                    </div>
+                                    <p>{{ item.time | formatDate }}</p>
+                                </div>
+                            </div>
+                            <!-- 回复框 -->
+                            <div class="replyDisc" v-if="activeIn == index">
+                                <Input
+                                    v-model="replyDis"
+                                    type="textarea"
+                                    :autosize="{ minRows: 2, maxRows: 5 }"
+                                ></Input>
+                                <div>
+                                    <Button
+                                        type="primary"
+                                        size="small"
+                                        @click="
+                                            reply(
+                                                item.id,
+                                                item.tmdname,
+                                                item.tmdid
+                                            )
+                                        "
+                                        >确定</Button
+                                    >
+                                    <Button
+                                        style="margin-left: 8px"
+                                        @click="cancelReply"
+                                        size="small"
+                                        >取消</Button
+                                    >
+                                </div>
+                            </div>
+                            <!-- 别人的留言 -->
+                            <div
+                                v-if="replyStatus && item.id === replyId && replyData.length"
+                                class="disChild"
+                            >
+                                <div
+                                    v-for="(child, no) in replyData"
+                                    :key="no"
+                                >
+                                    <div class="disContent">
+                                        <p
+                                            class="disName"
+                                            :style="{
+                                                color:
+                                                    child.tmdid ==
+                                                    $store.state.userInfo
+                                                        .TEAMModelId
+                                                        ? '#2d8cf0'
+                                                        : '',
+                                            }"
+                                        >
+                                            {{ child.tmdname }}
+                                        </p>
+                                    </div>
+                                    <p v-html="child.comment"></p>
+                                    <div class="disAction">
+                                        <div>
+                                            <span
+                                                @click="
+                                                    delect(
+                                                        child.pid,
+                                                        index,
+                                                        child.id
+                                                    )
+                                                "
+                                                v-if="
+                                                    child.tmdid ==
+                                                        $store.state
+                                                            .userInfo
+                                                            .TEAMModelId ||
+                                                    item.tmdid ==
+                                                        $store.state
+                                                            .userInfo
+                                                            .TEAMModelId
+                                                "
+                                                style="margin-right: 20px"
+                                                >删除</span
+                                            >
+                                            <span
+                                                @click="
+                                                    report(child.tmdname)
+                                                "
+                                                >举报</span
+                                            >
+                                        </div>
+                                        <p>{{ child.time | formatDate }}</p>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </vuescroll>
+            </div>
+        </div>
+        <Modal v-model="isReport" title="举报" @on-ok="ok">
+            <p>请选择举报类型:</p>
+            <CheckboxGroup v-model="checkReport">
+                <Checkbox
+                    :label="index"
+                    v-for="(item, index) in reportList"
+                    :key="index"
+                >
+                    <span>{{ item }}</span>
+                </Checkbox>
+            </CheckboxGroup>
+        </Modal>
+    </div>
+</template>
+<script>
+import E from "wangeditor"
+import topicPublish from "./topicPublish.vue"
+import { formatDate } from "../../utils/time.js"
+export default {
+    components: { topicPublish },
+    props: {
+        pointInfo: {
+            type: Object,
+			default: () => {}
+        }
+    },
+    data() {
+        return {
+            activeIn: -1, //-1:没有点击回复
+            replyDis: "",
+            discuss: [],
+            replyDis: "",
+            replyStatus: false,
+            replyOpen: "展开回复",
+            replyData: [],
+            topicId: "",
+            replyId: "",
+            replyIndex: 0,
+            reportList: [
+                "违法信息",
+                "涉黄信息",
+                "泄露隐私",
+                "人身攻击",
+                "垃圾营销",
+            ],
+            isReport: false,
+            checkReport: [],
+            topstatus:false, //话题显示状态
+            topicname: '', //话题名称
+            nowtopicData: {}, //当前话题数据保留
+            stemContent:'',//文字内容
+        }
+    },
+    created() {
+    },
+    mounted() {
+        console.log(this.pointInfo);
+        let stemEditor = new E("#divtop")
+        stemEditor.config.onchange = (html) => {
+            this.stemContent = html
+        }
+        stemEditor.config.uploadImgShowBase64 = true
+        stemEditor.config.zIndex = 500
+        stemEditor.create()
+        /* this.totaltopic()*/
+        this.nowtopicData.comid = '792bacf7-5ced-4546-83ad-1bc055414683'
+        this.nowtopic('792bacf7-5ced-4546-83ad-1bc055414683')
+    },
+    methods: {
+        replyOther(data, i) {
+            if (this.activeIn == -1 || this.activeIn != i) {
+                this.activeIn = i
+                this.replyDis = ""
+            }
+        },
+        // 取消回复
+        cancelReply() {
+            this.activeIn = -1
+        },
+        //获取所有话题
+        totaltopic() {
+            let user = this.$store.state.userInfo
+            this.$api.jyzx
+                .totalTopic({
+                    code: this.$store.state.userInfo.schoolCode,
+                    source: "",
+                    comid: "",
+                    tmdid: "",
+                })
+                .then(
+                    (res) => {
+                        console.log(res)
+                        for (var i = 0; i < res.length; i++) {
+                            let comment = res[i].comment
+                            var commenfrt = comment.replace("<p>", "")
+                            var commenlast = commenfrt.replace("</p>", "")
+                            res[i].comment = commenlast
+                            var comidNmae = res[i].comid
+                            var traitData = JSON.parse(localStorage.getItem('trait'))
+                            if (traitData) {
+                                for (var e = 0; e < traitData.length; e++) {
+                                    if (comidNmae === traitData[e].comid) {
+                                        var name = traitData[e].no + traitData[e].name
+                                    }
+                                }
+                            }
+                            res[i].relevance = name
+                        }
+                        res.reverse()
+                        this.discuss = res
+                        localStorage.setItem("allTopic", JSON.stringify(res))
+                    },
+                    (error) => {
+                        this.$Message.error("API异常,数据获取失败!")
+                        console.log(error, "获取失败")
+                    }
+                )
+        },
+        // 回复话题
+        reply(topicId, replyName, replyId) {
+            console.log(topicId, replyName, replyId,'回复话题的信息')
+            let userme = this.$store.state.userInfo
+            console.log(userme, this.replyDis, "用户信息")
+            this.$api.jyzx
+                .replyTopic({
+                    opt: "add",
+                    debateId: topicId,
+                    debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                    reply: {
+                        id: "",
+                        pid: topicId,
+                        tmdid: this.$store.state.userInfo.TEAMModelId,
+                        tmdname: this.$store.state.userInfo.name,
+                        comment: this.replyDis,
+                        atTmdid: replyId,
+                        atTmdname: replyName,
+                    },
+                })
+                .then(
+                    (res) => {
+                        console.log(res)
+                        this.$Message.success("回复成功")
+                        this.discuss[this.activeIn].replyCount += 1
+                        this.activeIn = -1
+                        this.openReply(res.reply.pid, 1)
+                    },
+                    (error) => {
+                        console.log(error)
+                    }
+                )
+        },
+        //展开回复
+        openReply(topicId, status) {
+            this.topicId = topicId
+            if (!this.replyStatus) {
+                this.replyStatus = true
+                this.activeIn = -1
+            } else {
+                // status:1 回复
+                if (!status) {
+                    if (this.replyId == topicId) {
+                        this.replyStatus = false
+                    }
+                }
+            }
+            this.getReplyArr(topicId)
+        },
+        getReplyArr(topicId) {
+            this.$api.jyzx
+                .replyDetail({
+                    debateId: topicId,
+                    debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                })
+                .then(
+                    (res) => {
+                        this.replyData = res.debate.replies
+                        this.replyId = res.debate.id
+                    },
+                    (error) => {}
+                )
+        },
+        // 举报
+        report(name) {
+            this.isReport = true
+            this.checkReport = []
+        },
+        ok() {
+            if (this.checkReport.length) {
+                this.$Message.success("举报成功")
+            }
+        },
+        // 删除
+        delect(topicId, index, replyId) {
+            let cont = ""
+            if (replyId) {
+                cont = "回复"
+            } else {
+                cont = "话题"
+            }
+            this.$Modal.confirm({
+                title: "删除",
+                content: `您确定要删除该${cont}吗?`,
+                onOk: () => {
+                    // 回复的
+                    if (replyId) {
+                        let req = {
+                            opt: "del",
+                            debateId: topicId,
+                            debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                            reply: {
+                                id: replyId,
+                            },
+                        }
+                        this.$api.jyzx.replyTopic(req).then((res) => {
+                            if (res.status == 0) {
+                                console.log(topicId,'删除的')
+                                this.$Message.success("删除成功")
+                                this.getReplyArr(topicId)
+                                this.discuss[index].replyCount --
+                            } else {
+                                this.$Message.warning("删除失败")
+                            }
+                        })
+                    }
+                    // 自己的话题
+                    else {
+                        let req = {
+                            debateId: topicId,
+                            debateCode: "Debate-" + this.$store.state.userInfo.schoolCode,
+                        }
+                        // 调另一个api
+                        this.$api.jyzx.delReply(req).then((res) => {
+                            if (res.status == 200) {
+                                this.$Message.success("删除成功")
+                                this.nowtopic(this.nowtopicData.comid)
+                            } else {
+                                this.$Message.warning("删除失败")
+                            }
+                        })
+                    }
+                },
+            })
+        },
+        //当前能力点话题
+        nowtopic(id) {
+            console.log(id,'调用了接口')
+            this.$api.jyzx.totalTopic(
+                {
+                    code: this.$store.state.userInfo.schoolCode,
+                    source: "ability",
+                    comid: id,
+                    tmdid: "",
+                }
+            ).then(
+                res => {
+                    res.reverse()
+                    this.discuss=[]
+                    this.discuss = res
+                    console.log(this.discuss, '当前话题返回')
+                },
+                error => {
+                    this.$Message.error('当前话题获取数据异常')
+                }
+            )
+        },
+        //当前能力点发布话题
+        submitTopic() {
+            console.log(this.topicname, this.stemContent, '当前话题名')
+            var myDate = new Date().getTime()
+            var extime = new Date().getTime() + 1000 * 60 * 60 * 24 * 7
+            this.$api.jyzx.topicInitiate(
+                {
+                    comid: this.nowtopicData.comid,
+                    source: "ability",
+                    debate: {
+                        id: "",
+                        code: this.$store.state.userInfo.schoolCode,
+                        tmdid: this.$store.state.userInfo.TEAMModelId,
+                        tmdname: this.$store.state.userInfo.name,
+                        title: this.topicname,
+                        comment: this.stemContent,
+                        time: myDate,
+                        school: this.$store.state.userInfo.schoolCode,
+                        wordCount: 500,
+                        timeoutReply: false,
+                        comid: this.nowtopicData.comid,
+                        expire: extime,
+                    },
+                }
+            ).then(
+                res => {
+                    console.log(res, '发布成功的返回')
+                    this.nowtopic(res.debate.comid)
+                    this.$Message.success('话题发布成功')
+                    this.topstatus = false
+                    this.topicname = ''
+                    this.stemContent = ''
+
+                },
+                error => {
+                    console.log(res, '发布失败的返回')
+                }
+                )
+            },
+        },
+    watch: {
+        pointInfo: {
+            handler(value) {
+                console.log(value,'传递过来的值')
+                if (value) {
+                    this.nowtopic(value.comid)
+                    this.nowtopicData=value
+                    }
+                }
+            }
+        },
+    //时间处理
+    filters: {
+        formatDate(time) {
+            time = time
+            let date = new Date(time)
+            return formatDate(date, "yyyy-MM-dd hh:mm")
+        },
+    },
+}
+</script>
+
+<style lang="less" scoped>
+.discuss {
+    border-bottom: 1px solid #ccc;
+    padding: 10px;
+
+    .disContent {
+        .disName {
+            font-size: 20px;
+            // color: #2d8cf0;
+        }
+
+        & > p:nth-of-type(2) {
+            margin: 5px 0;
+        }
+    }
+    .disAction {
+        margin-top: 5px;
+        & > p:last-child {
+            color: #878787;
+        }
+        div {
+            float: right;
+            span {
+                cursor: pointer;
+            }
+        }
+    }
+    .inClass {
+        background: #2d8cf0;
+        color: white;
+        border-radius: 10px;
+        padding: 2px 10px;
+        margin: 2px 0;
+        float: right;
+    }
+    .disChild {
+        border: 1px solid #ccc;
+        border-radius: 10px;
+        margin: 15px 40px;
+        padding: 5px 10px;
+
+        & > div {
+            padding: 2px 10px 5px;
+            &:not(:last-child) {
+                border-bottom: 1px solid #ccc;
+            }
+        }
+
+        .disName {
+            font-size: 16px;
+            // color: #515a6e;
+        }
+    }
+
+    .replyDisc {
+        margin: 15px 40px;
+        margin-top: 10px;
+        text-align: right;
+
+        & > div:last-child {
+            margin-top: 10px;
+        }
+    }
+}
+</style>
+
+<style lang="less">
+.discuss1 {
+    .ivu-card-bordered,
+    .ivu-card-body {
+        height: 100%;
+    }
+    .ivu-card-body {
+        overflow: hidden;
+    }
+}
+    .btnicon {
+        margin-top: 2%;
+        .submit, .canle{
+        margin-right: 2%;
+        display: inline-block;
+            }
+    }
+</style>

+ 124 - 0
TEAMModelOS/ClientApp/src/view/jyzx/review.vue

@@ -0,0 +1,124 @@
+<template>
+    <div style="background: #eee; padding: 20px; height: 100%" class="review1">
+        <Card>
+            <div>
+                <div class="review-header">
+                    <Icon type="md-arrow-back" />
+                    <p>林黛玉 <span>[A1 技术支持的学情分析]</span></p>
+                </div>
+                <div>
+                    要求:
+                    <p>合理利用信息技术手段辅助分析学情,从而</p>
+                    <p>
+                        1.从多个方面分析学情,包括学生经验、知识储备、学习能力、学习风格以及学习条件等
+                    </p>
+                    <p>2.精准确定教学的适切目标</p>
+                    <p>
+                        3.为教学重难点的突破、教学策略的选择和教学活动的设计提供科学依据
+                    </p>
+                    <p>为教学中动态调整教学内容和方法提供参考</p>
+                </div>
+                <div class="plans">
+                    <div
+                        v-for="(item, index) in pointMall"
+                        :key="index"
+                        class="pointType"
+                    >
+                        <p class="pointName">
+                            <Icon type="md-paper-plane" />
+                            学情分析方案
+                        </p>
+                        <span
+                            >提交一份针对某一教学主题的学情分析方案,包括学情分析目的、内容(教学主题、教学对象、教学重点、学习难点等)、学情分析方法和工具。以PDF形式提交。</span
+                        >
+                        <Upload action="//jsonplaceholder.typicode.com/posts/">
+                            <Button
+                                icon="ios-cloud-upload-outline"
+                                type="primary"
+                                >上传</Button
+                            >
+                        </Upload>
+                        <p class="standard">评分标准:</p>
+                        <CheckboxGroup v-model="social">
+                            <Checkbox
+                                label="twitter"
+                                v-for="(item, index) in disPoint"
+                                :key="index"
+                                size="large"
+                            >
+                                <span>{{ item }}</span>
+                            </Checkbox>
+                        </CheckboxGroup>
+                    </div>
+                </div>
+                <div>
+                    <Button type="primary" style="margin-right: 20px"
+                        >提交</Button
+                    >
+                    <Button>取消</Button>
+                </div>
+            </div>
+        </Card>
+    </div>
+</template>
+
+<script>
+export default {
+    data() {
+        return {
+            pointMall: [{}, {}],
+            disPoint: [
+                "方案要素完整,表述清晰",
+                "方案能够有效支持学情分析目的达成",
+                "工具设计/选用科学合理,操作便捷,分析结果易于导出使用",
+                "技术支持方式富有创新性,有学习与借鉴价值",
+            ],
+        }
+    },
+}
+</script>
+
+<style lang="less">
+.review1 {
+    .ivu-checkbox-group {
+        display: flex;
+        flex-direction: column;
+    }
+}
+</style>
+<style lang="less" scoped>
+.review-header {
+    font-size: 25px;
+    display: flex;
+    align-items: center;
+    margin-bottom: 5px;
+    color: #2d8cf0;
+
+    p {
+        margin-left: 10px;
+    }
+}
+.plans {
+    margin-bottom: 10px;
+
+    .pointType {
+        border-bottom: 1px solid #ccc;
+        padding-bottom: 10px;
+        margin-top: 20px;
+
+        & > span {
+        }
+
+        .pointName {
+            font-size: 18px;
+            margin-bottom: 5px;
+            color: #408ada;
+        }
+
+        .standard {
+            font-size: 17px;
+            margin-bottom: 5px;
+        }
+    }
+}
+</style>

BIN
TEAMModelOS/ClientApp/src/view/jyzx/test.pdf


+ 386 - 0
TEAMModelOS/ClientApp/src/view/jyzx/topicPublish.vue

@@ -0,0 +1,386 @@
+<template>
+    <div class="topicbox">
+        <div class="topicheader">
+            <div class="headerleft">
+                <div class="totalbox">
+                    <Button type="primary" @click="topicShow = !topicShow">新增话题</Button>
+                </div>
+                <div class="taginfo">
+                    <span style="margin-left: 20px">话题类型:</span>
+                    <CheckboxGroup v-model="toType" @on-change="topicType">
+                        <Checkbox label="1" border>普通话题</Checkbox>
+                        <Checkbox label="2" border>能力点话题</Checkbox>
+                    </CheckboxGroup>
+                    <!-- <Tag checkable color="geekblue" size ="large" @on-change="topicType(1)">普通话题</Tag>
+                    <Tag checkable color="volcano" size ="large" @on-change="topicType(2)">能力点话题</Tag> -->
+                </div>
+            </div>
+            <div class="headerright">
+                <div class="searchbox">
+                    <Input
+                        search
+                        enter-button
+                        placeholder="请输入关键字"
+                        v-model="keyword"
+                        @on-search="searchKeyword"
+                    />
+                </div>
+                <div class="aboutme">
+                    <Dropdown @on-click="metopic">
+                        <a href="javascript:void(0)">
+                            {{ showType }}
+                            <Icon type="ios-arrow-down"></Icon>
+                        </a>
+                        <DropdownMenu slot="list">
+                            <DropdownItem name="allTopic">所有话题</DropdownItem>
+                            <DropdownItem name="metopic">我的话题</DropdownItem>
+                            <DropdownItem name="mereply">回复我的</DropdownItem>
+                        </DropdownMenu>
+                    </Dropdown>
+                </div>
+                <!-- <div class="topicbtn">
+                    <Button
+                        shape="circle"
+                        icon="md-chatboxes"
+                        size="large"
+                        @click="topicShow = !topicShow"
+                    ></Button>
+                </div> -->
+            </div>
+        </div>
+
+        <div id="statementBox" v-show="topicShow">
+            <Form :model="formItem">
+                <FormItem>
+                    <Input v-model="formItem.input"
+                           placeholder="新的话题"></Input>
+                </FormItem>
+                <Select v-model="topicnameId" style="width:100%;margin-bottom:1%;" @on-change="comidTopic">
+                    <Option v-for="item in topicData" :value="item.comid" :key="item.comid">{{item.no}}{{ item.name }}</Option>
+                </Select>
+                <div id="div1" style="height: 370px"></div>
+
+                <!--<FormItem>
+        <Input v-model="formItem.textarea" type="textarea" :autosize="{minRows: 5,maxRows: 9}"></Input>
+    </FormItem>
+    <FormItem>
+        <div class="addimage">
+            <Upload action="//jsonplaceholder.typicode.com/posts/" :show-upload-list="true" :max-size="50000">
+                <Button icon="ios-cloud-upload-outline">添加附件</Button>
+            </Upload>
+        </div>
+    </FormItem>-->
+                <!--<textarea id="text1" style="width:100%; height:200px;"></textarea>-->
+                <FormItem>
+                    <Button type="primary" @click="discussSubmit">确定</Button>
+                    <Button style="margin-left: 8px"
+                            @click="
+                            ()=>
+                        {
+                        topicShow = false
+                        }
+                        "
+                        >取消
+                    </Button>
+                </FormItem>
+            </Form>
+        </div>
+    </div>
+</template>
+<script>
+import E from "wangeditor"
+export default {
+    data() {
+        return {
+            toType: ["1", "2"], //话题类型
+            formItem: {
+                input: "",
+            },
+            topicShow: false,
+            stemContent: "",
+            keyword: "",
+            showType: "所有话题",
+            topicnameId: '',
+            topicData:[]
+        }
+        },
+        created() {
+            this.getAlltrait()
+        },
+    methods: {
+        //发表话题
+        discussSubmit() {
+            console.log(this.topicnameId,'comid123456')
+            console.log(this.formItem.input, "标题")
+            console.log(this.stemContent, "内容")
+            var soureceType=''
+            var myDate = new Date().getTime()
+            var extime = new Date().getTime() + 1000 * 60 * 60 * 24 * 7
+            let user = this.$store.state.userInfo
+            var comID = this.topicnameId
+            if (this.topicnameId != '') {
+                this.soureceType = "ability"
+            } else {
+                this.soureceType ="normal"
+            }
+            this.$api.jyzx
+                .topicInitiate({
+                    comid: comID,
+                    source: this.soureceType,
+                    debate: {
+                        id: "",
+                        code: this.$store.state.userInfo.schoolCode,
+                        tmdid: user.TEAMModelId,
+                        tmdname: user.name,
+                        title: this.formItem.input,
+                        comment: this.stemContent,
+                        time: myDate,
+                        school: this.$store.state.userInfo.schoolCode,
+                        wordCount: 500,
+                        timeoutReply: false,
+                        comid:comID,
+                        expire: extime,
+                    },
+                })
+                .then(
+                    (res) => {
+                        this.$Message.success("发表成功")
+                        console.log(res, "成功")
+                        this.topicShow = false
+                        this.$parent.$parent.totaltopic()
+                    },
+                    (error) => {
+                        this.$Message.error("发表失败")
+                        console.log(error, "失败")
+                    }
+                )
+        },
+        // 筛选话题类型,1:普通话题,2:能力点话题
+        topicType(check) {
+            let allTopic = JSON.parse(localStorage.getItem("allTopic"))
+            if(check.length == 1) {
+                let arr = []
+                if(check.includes("2")) {
+                    allTopic.forEach(item => {
+                        if(item.relevance) {
+                            arr.push(item)
+                        }
+                    })
+                } else {
+                    allTopic.forEach(item => {
+                        if(!item.relevance) {
+                            arr.push(item)
+                        }
+                    })
+                }
+                this.$parent.$parent.discuss = arr
+            }
+            else if(check.length == 2) {
+                this.$parent.$parent.discuss = allTopic
+            }
+            else if(!check.length) {
+                this.$parent.$parent.discuss = []
+            }
+           
+        },
+        //关于我的
+        metopic(name) {
+            let allTopic = JSON.parse(localStorage.getItem("allTopic"))
+            console.log(allTopic[0].tmdid, "数据")
+            let user = this.$store.state.userInfo
+            let metopicinfo = []
+            let mereplyinfo = []
+            function sortNumber(a, b) {
+                return a.time - b.time
+            }
+            // 我的话题
+            if (name === "metopic") {
+                this.showType = "我的话题"
+                for (var x = 0; x < allTopic.length; x++) {
+                    if (allTopic[x].tmdid === user.TEAMModelId) {
+                        metopicinfo.push(allTopic[x])
+                    }
+                }
+                metopicinfo.sort(sortNumber)
+                this.$parent.$parent.discuss = metopicinfo
+                this.$Message.success("切换至我的话题")
+                console.log(metopicinfo)
+            }
+            // 回复我的
+            else if (name === "mereply") {
+                this.showType = "回复我的"
+                for (var y = 0; y < allTopic.length; y++) {
+                    if (
+                        allTopic[y].tmdid === user.TEAMModelId &&
+                        allTopic[y].replyCount !== 0
+                    ) {
+                        mereplyinfo.push(allTopic[y])
+                    }
+                }
+                mereplyinfo.reverse()
+                this.$parent.$parent.discuss = mereplyinfo
+                this.$Message.success("切换至回复我的")
+            }
+            // 所有话题
+            else if (name === "allTopic") {
+                this.showType = "所有话题"
+                this.$parent.$parent.discuss = allTopic
+            }
+        },
+        //所有话题
+        alltopics() {
+            let all = JSON.parse(localStorage.getItem("allTopic"))
+            this.$parent.$parent.discuss = all
+        },
+        //搜索关键字
+        searchKeyword() {
+            let all = JSON.parse(localStorage.getItem("allTopic"))
+            let key = this.keyword
+            console.log(key)
+            let keywordData = []
+            for (var k = 0; k < all.length; k++) {
+                if (
+                    all[k].comment.indexOf(key) > -1 ||
+                    all[k].title.indexOf(key) > -1
+                ) {
+                    keywordData.push(all[k])
+                }
+            }
+            console.log(keywordData)
+            this.$parent.$parent.discuss = keywordData
+        },
+        //获取所有能力点
+        getAlltrait() {
+            this.$api.jyzx.getAlltrait(
+                {
+                    "code": this.$store.state.userInfo.schoolCode,
+                    "scope": "school",
+                    "status":1
+                }
+            ).then(
+                res => {
+                    this.topicData = res.abilities
+                    console.log(this.topicData)
+                    localStorage.setItem('trait', JSON.stringify(this.topicData))
+                },
+                error => {
+
+                }
+            )
+        },
+        comidTopic(res) {
+            this.topicnameId=res
+        },
+    },
+
+    mounted() {
+        let stemEditor = new E("#div1")
+        stemEditor.config.onchange = (html) => {
+            this.stemContent = html
+        }
+        stemEditor.config.uploadImgShowBase64 = true
+        stemEditor.config.zIndex = 500
+        stemEditor.create()
+    },
+}
+</script>
+<style lang="less" scoped>
+.topicbox {
+    width: 100%;
+    /* height:100%; */
+}
+.statementBox .w-e-text-container {
+    z-index: 1 !important;
+}
+.statementBox .w-e-toolbar {
+    z-index: 1 !important;
+}
+.topicheader {
+    /* width: 90%; */
+    height: 70px;
+    /* margin: 0% 5%; */
+    border-bottom: 1px solid #ccc;
+}
+
+.headerleft {
+    width: 48%;
+    float: left;
+    padding: 1% 0% 1% 2%;
+}
+
+.totalbox {
+    /* width:15%; */
+    font-size: 20px;
+    float: left;
+}
+.taginfo {
+    display: flex;
+    align-items: center;
+    margin-left: 4%;
+
+    .ivu-checkbox{
+        display: none;
+    }
+
+    .ivu-checkbox-wrapper-checked.ivu-checkbox-border{
+        background-color: #16C18E;
+        border-color: #16C18E;
+        color: #fff;
+    }
+}
+#statementBox {
+    margin: 1%;
+}
+
+.searchbox {
+    width: 50%;
+    /* float: left; */
+    padding: 0% 2%;
+    display: inline-block;
+    margin-top: 10px;
+}
+
+.headerright {
+    width: 45%;
+    float: right;
+    padding: 0.5% 2% 1% 2%;
+    text-align: right;
+}
+
+.aboutme {
+    /* width: 60%; */
+    /* display: inline-block;
+    line-height: 40px; */
+    line-height: 48px;
+    float: right;
+}
+
+.topicbtn {
+    width: 15%;
+    float: right;
+}
+
+.ivu-btn-circle {
+    font-size: 27px !important;
+}
+
+.statementBox {
+    width: 90%;
+    padding: 1%;
+    margin: 0% 3%;
+}
+
+.addfile,
+.addimage {
+    float: left;
+    margin: 0% 1% 0% 0%;
+}
+</style>
+<style lang="less">
+.taginfo {
+
+    .ivu-checkbox{
+        display: none;
+    }
+}
+</style>

+ 1 - 1
TEAMModelOS/ClientApp/src/view/knowledge-point/index/Index.vue

@@ -49,7 +49,7 @@
 							@click="hasModify ? handleConfirmSave({index},'2') : handleSubjectTap(index)">
 							<p class="gl-item-name">{{item.name}}</p>
 							<p class="gl-item-info">
-								<span></span>{{$t("knowledge.blockCount")}}:{{ countArr[index].countBlock }}
+								<span></span>{{$t("knowledge.blockCount")}}:{{ countArr.length ? countArr[index].countBlock : 0 }}
 								<!-- {{$t("knowledge.point")}}:{{ countArr[index].countPoint }} -->
 							</p>
 						</div>

+ 146 - 0
TEAMModelOS/ClientApp/src/view/policy/DiagDetail.vue

@@ -0,0 +1,146 @@
+<template>
+    <div class="literacy-detail-container">
+        <div class="literacy-mgt-top" @click="backList">
+            <Icon type="md-arrow-round-back" size="20" />
+            <span style="margin-left:10px">返回规划诊断课程</span>
+        </div>
+        <vuescroll>
+            <div class="literacy-block-box">
+                <div class="video-info-wrap">
+                    <h1 class="video-title">{{videoInfo.title}}</h1>
+                    <video :src="videoInfo.link" class="video-box" controls="controls" autoplay="autoplay"></video>
+                </div>
+                <div class="more-link-wrap">
+                    <p class="more-title">
+                        更多课程
+                        <Icon custom="iconfont icon-handle-down" size="20" />
+                    </p>
+                    <div class="video-list-box">
+                        <vuescroll>
+                            <div class="video-item" v-for="(item,index) in videoList" :key="index" @click="chooseCus(index)">
+                                <img :src="item.poster" class="video-poster">
+                                <div style="margin-left:15px">
+                                    <p class="video-attr-item">
+                                        <span>{{item.title}}</span>
+                                    </p>
+                                </div>
+                            </div>
+                        </vuescroll>
+                    </div>
+                </div>
+            </div>
+        </vuescroll>
+    </div>
+</template>
+<script>
+const courseData = require('@/static/course.json')
+export default {
+    data() {
+        return {
+            keyword: '',
+            videoInfo: {},
+            videoList: []
+        }
+    },
+    methods: {
+        search() {
+
+        },
+        backList() {
+            this.$router.push({
+                name: 'diagnosis'
+            })
+        },
+        chooseCus(index){
+            this.videoInfo = this.videoList[index]
+        }
+    },
+    created() {
+        this.videoList = require('@/static/diagnosis.json')
+        this.videoInfo = this.$route.params.videoInfo || this.videoList[0]
+        this.course = []
+        this.course = this._.cloneDeep(courseData)
+        let sas = this.$store.state.user.schoolProfile.blob_sas
+        let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+        this.course.forEach(item => {
+            let url = `${blobUrl}${item.link}?${sas}`
+            item.link = url
+        })
+        this.videoList.push(...this.course)
+    }
+}
+</script>
+<style lang="less" scoped>
+.info-label{
+    color: #808695;
+    display: block;
+}
+.video-list-box {
+    height: 620px;
+}
+.video-attr-item {
+    font-size: 12px;
+}
+.video-item {
+    padding: 10px;
+    border-bottom: 1px solid #eee;
+    display: flex;
+    cursor: pointer;
+    &:hover{
+        background: #f0f0f0;
+    }
+}
+.video-poster {
+    width: 120px;
+}
+.more-title {
+    height: 35px;
+    line-height: 35px;
+    font-size: 12px;
+    border-bottom: 1px solid #eee;
+    padding-left: 10px;
+}
+.video-title {
+    margin-bottom: 15px;
+}
+.info-item {
+    margin: 10px 0px;
+    font-size: 16px;
+}
+.video-box {
+    // height: 600px;
+    width: 66%;
+}
+.literacy-detail-container {
+    width: 100%;
+    height: 100%;
+    padding: 15px;
+}
+.literacy-block-box {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+}
+.video-info-wrap {
+    width: 78%;
+    min-height: 600px;
+    background: white;
+    padding: 15px;
+}
+.more-link-wrap {
+    width: 20%;
+    min-height: 600px;
+    background: white;
+}
+.literacy-mgt-top {
+    width: 100%;
+    height: 40px;
+    background: white;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    padding-left: 10px;
+    padding-right: 20px;
+    margin-bottom: 10px;
+    line-height: 40px;
+    cursor: pointer;
+}
+</style>

+ 135 - 0
TEAMModelOS/ClientApp/src/view/policy/Diagnosis.vue

@@ -0,0 +1,135 @@
+<template>
+    <div class="literacy-container">
+        <div class="literacy-mgt-top">
+            <Input v-model="keyword" suffix="ios-search" placeholder="搜索" style="width: 200px;margin-top:0px" @on-change="search" />
+        </div>
+        <vuescroll>
+            <div class="literacy-block-box">
+                <div class="literacy-item" v-for="(item,index) in videoListShow" :key="index" @click="toDetailPage(index)">
+                    <div class="literacy-img" :style="{backgroundImage: `url(${item.poster})`}"></div>
+                    <p class="img-text">{{item.title}}<p/>
+                </div>
+                <div class="literacy-item" v-for="(item,index) in course" :key="index" @click="toDetailPage(index)">
+                    <div class="literacy-img" :style="{backgroundImage: `url(${item.poster})`}"></div>
+                    <p class="img-text">{{item.title}}<p/>
+                </div>
+            </div>
+            <EmptyData textContent="未搜到相关课程" v-show="!videoListShow.length" :top="100"></EmptyData>
+        </vuescroll>
+    </div>
+</template>
+<script>
+const courseData = require('@/static/course.json')
+export default {
+    data() {
+        return {
+            keyword: '',
+            videoList: [],
+            videoListShow: [],
+            course:[]
+        }
+    },
+    methods: {
+        search() {
+            if (this.keyword) {
+                this.videoListShow = this.videoList.filter(item => {
+                    return JSON.stringify(item).includes(this.keyword)
+                })
+            }else{
+                this.videoListShow = this.videoList
+            }
+
+        },
+        toDetailPage(index) {
+            this.$router.push({
+                name: 'diagDetail',
+                params: {
+                    videoInfo: this.videoList[index]
+                }
+            })
+        }
+    },
+    created() {
+        this.videoList = require('@/static/diagnosis.json')
+        this.videoListShow = this.videoList
+        this.course = []
+        this.course = this._.cloneDeep(courseData)
+        let sas = this.$store.state.user.schoolProfile.blob_sas
+        let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+        this.course.forEach(item => {
+            let url = `${blobUrl}${item.link}?${sas}`
+            item.link = url
+        })
+    }
+}
+</script>
+<style lang="less" scoped>
+.literacy-container {
+    width: 100%;
+    height: 100%;
+    background: white;
+    padding: 15px;
+}
+.literacy-block-box {
+    width: 100%;
+    padding: 10px;
+    display: flex;
+    flex-wrap: wrap;
+}
+.literacy-item {
+    width: 300px;
+    background: #fcfcfc;
+    border: 1px solid #f0f0f0;
+    border-radius: 5px;
+    overflow: hidden;
+    margin-bottom: 30px;
+    margin-right: 30px;
+    cursor: pointer;
+    height: 180px;
+    position: relative;
+    transition: all 0.2s ease 0s;
+    &:hover {
+        box-shadow: 0 26px 40px -24px #aaa;
+        transform: translateY(-4px);
+    }
+    &:hover .literacy-img {
+        transform: scale(1.1);
+    }
+    .literacy-img-box {
+        width: 300px;
+        height: 150px;
+        overflow: hidden;
+    }
+    .literacy-img {
+        transition: all 0.3s;
+        width: 300px;
+        height: 180px;
+        background-size: cover;
+        background-repeat: no-repeat;
+    }
+    .img-text{
+        width: 300px;
+        height: 180px;
+        position: absolute;
+        left: 0px;
+        top: 0px;
+        background: rgba(20, 20, 20, .1);
+        color: white;
+        font-size: 14px;
+        padding: 5px;
+        &:hover{
+            background: rgba(20, 20, 20, .2);
+        }
+    }
+}
+.literacy-mgt-top {
+    width: ~"calc(100% - 20px)";
+    height: 40px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-left: 10px;
+    padding-left: 10px;
+    padding-right: 20px;
+    margin-bottom: 10px;
+    line-height: 40px;
+}
+</style>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 248 - 0
TEAMModelOS/ClientApp/src/view/policy/Leadership.vue


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 228 - 0
TEAMModelOS/ClientApp/src/view/policy/LeadershipDetail.vue


+ 164 - 0
TEAMModelOS/ClientApp/src/view/policy/Literacy.vue

@@ -0,0 +1,164 @@
+<template>
+    <div class="literacy-container">
+        <div class="literacy-mgt-top">
+            <Input v-model="keyword" suffix="ios-search" placeholder="搜索" style="width: 200px;margin-top:0px" @on-change="search" />
+        </div>
+        <vuescroll>
+            <div class="literacy-block-box">
+                <div class="literacy-item" v-for="(item,index) in videoListShow" :key="item.title" @click="toDetailPage(index)">
+                    <div class="literacy-img-box">
+                        <div class="literacy-img" :style="{backgroundImage: `url(${item.poster})`}"></div>
+                    </div>
+                    <div class="info-item" v-show="!item.type">
+                        <span class="info-label">报告人:</span>
+                        <span>{{item.reporter}}</span>
+                    </div>
+                    <div class="info-item">
+                        <span class="info-label">主题:</span>
+                        <span>{{item.title}}</span>
+                    </div>
+                </div>
+                <!-- <div class="literacy-item" v-for="(item,index) in course" :key="index" @click="toDetailPage1(index)">
+                    <div class="literacy-img-box">
+                        <div class="literacy-img" :style="{backgroundImage: `url(${item.poster})`}"></div>
+                    </div>
+                    <div class="info-item">
+                        <span class="info-label">主题:</span>
+                        <span>{{item.title}}</span>
+                    </div>
+                </div> -->
+            </div>
+            <EmptyData textContent="未搜到相关课程" v-show="!videoListShow.length" :top="100"></EmptyData>
+        </vuescroll>
+    </div>
+</template>
+<script>
+const courseData = require('@/static/course.json')
+export default {
+    data() {
+        return {
+            keyword: '',
+            videoList: [],
+            videoListShow: [],
+            course: []
+        }
+    },
+    methods: {
+        search() {
+            if (this.keyword) {
+                this.videoListShow = this.videoList.filter(item => {
+                    return JSON.stringify(item).includes(this.keyword)
+                })
+            } else {
+                this.videoListShow = this.videoList
+            }
+
+        },
+        toDetailPage(index) {
+            this.$router.push({
+                name: 'literacyDetail',
+                params: {
+                    videoInfo: this.videoList[index]
+                }
+            })
+        },
+        toDetailPage1(index) {
+            this.$router.push({
+                name: 'literacyDetail',
+                params: {
+                    videoInfo: this.course[index]
+                }
+            })
+        }
+    },
+    created() {
+        this.videoList = require('@/static/video.json')
+        this.course = []
+        this.course = this._.cloneDeep(courseData)
+        let sas = this.$store.state.user.schoolProfile.blob_sas
+        let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+        this.course.forEach(item => {
+            let url = `${blobUrl}${item.link}?${sas}`
+            item.link = url
+            item.type = 'policy'
+        })
+        this.videoList.push(...this.course)
+        //如果后面数据量更多需要做分页或者懒加载
+        this.videoListShow = this.videoList
+
+    }
+}
+</script>
+<style lang="less" scoped>
+
+.literacy-container {
+    width: 100%;
+    height: 100%;
+    background: white;
+    padding: 15px;
+}
+.literacy-block-box {
+    // background: white;
+    width: 100%;
+    // min-height: 800px;
+    padding: 10px;
+    display: flex;
+    flex-wrap: wrap;
+}
+.literacy-item {
+    width: 300px;
+    background: #fcfcfc;
+    border: 1px solid #f0f0f0;
+    padding-bottom: 10px;
+    border-radius: 5px;
+    overflow: hidden;
+    margin-bottom: 30px;
+    margin-right: 30px;
+    cursor: pointer;
+    height: fit-content;
+    transition: all 0.2s ease 0s;
+    .info-label {
+        color: #a5a5a5;
+    }
+    &:hover {
+        box-shadow: 0 26px 40px -24px #aaa;
+        transform: translateY(-4px);
+    }
+    &:hover .literacy-img {
+        transform: scale(1.1);
+    }
+    .literacy-img-box {
+        width: 300px;
+        height: 150px;
+        overflow: hidden;
+    }
+    .literacy-img {
+        transition: all 0.3s;
+        width: 300px;
+        height: 150px;
+        background-size: cover;
+        background-repeat: no-repeat;
+    }
+    .info-item {
+        margin-top: 10px;
+        width: 100%;
+        padding-left: 5px;
+        font-size: 16px;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+    }
+}
+.literacy-mgt-top {
+    width: ~"calc(100% - 20px)";
+    height: 40px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-left: 10px;
+    padding-left: 10px;
+    padding-right: 20px;
+    margin-bottom: 10px;
+    line-height: 40px;
+}
+</style>
+<style lang="less">
+</style>

+ 161 - 0
TEAMModelOS/ClientApp/src/view/policy/LiteracyDetail.vue

@@ -0,0 +1,161 @@
+<template>
+    <div class="literacy-detail-container">
+        <div class="literacy-mgt-top" @click="backList">
+            <Icon type="md-arrow-round-back" size="20" />
+            <span style="margin-left:10px">返回素养课程</span>
+        </div>
+        <vuescroll>
+            <div class="literacy-block-box">
+                <div class="video-info-wrap">
+                    <h1 class="video-title">{{videoInfo.title}}</h1>
+                    <video :src="videoInfo.link" class="video-box" controls="controls"  autoplay="autoplay"></video>
+                    <p class="info-item" v-show="videoInfo.reporter">
+                        <span class="info-label">报告人:</span>
+                        <span>{{videoInfo.reporter}}</span>
+                    </p>
+                    <p class="info-item" v-show="videoInfo.unit">
+                        <span class="info-label">单位:</span>
+                        <span>{{videoInfo.unit}}</span>
+                    </p>
+                    <p class="info-item"  v-show="videoInfo.content">
+                        <span class="info-label">简介:</span>
+                        <span>{{videoInfo.content}}</span>
+                    </p>
+                </div>
+                <div class="more-link-wrap">
+                    <p class="more-title">
+                        更多课程
+                        <Icon custom="iconfont icon-handle-down" size="20" />
+                    </p>
+                    <div class="video-list-box">
+                        <vuescroll>
+                            <div class="video-item" v-for="(item,index) in videoList" :key="index" @click="chooseCus(index)">
+                                <img :src="item.poster" class="video-poster">
+                                <div style="margin-left:15px">
+                                    <p class="video-attr-item">
+                                        <span class="info-label">报告人:</span>
+                                        <span>{{item.reporter}}</span>
+                                    </p>
+                                    <p class="video-attr-item">
+                                        <span class="info-label">单位:</span>
+                                        <span>{{item.unit}}</span>
+                                    </p>
+                                    <p class="video-attr-item">
+                                        <span class="info-label">主题:</span>
+                                        <span>{{item.title}}</span>
+                                    </p>
+                                </div>
+                            </div>
+                        </vuescroll>
+                    </div>
+
+                </div>
+            </div>
+        </vuescroll>
+    </div>
+</template>
+<script>
+export default {
+    data() {
+        return {
+            keyword: '',
+            videoInfo: {},
+            videoList: []
+        }
+    },
+    methods: {
+        search() {
+
+        },
+        backList() {
+            this.$router.push({
+                name: 'literacy'
+            })
+        },
+        chooseCus(index){
+            this.videoInfo = this.videoList[index]
+        }
+    },
+    created() {
+        this.videoList = require('@/static/video.json')
+        this.videoInfo = this.$route.params.videoInfo || this.videoList[0]
+    }
+}
+</script>
+<style lang="less" scoped>
+.info-label{
+    color: #808695;
+}
+.video-list-box {
+    height: 620px;
+}
+.video-attr-item {
+    font-size: 12px;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    width: 180px;
+    white-space: nowrap;
+}
+.video-item {
+    padding: 10px;
+    border-bottom: 1px solid #eee;
+    display: flex;
+    cursor: pointer;
+    &:hover{
+        background: #f0f0f0;
+    }
+}
+.video-poster {
+    width: 120px;
+}
+.more-title {
+    height: 35px;
+    line-height: 35px;
+    font-size: 12px;
+    border-bottom: 1px solid #eee;
+    padding-left: 10px;
+}
+.video-title {
+    margin-bottom: 15px;
+}
+.info-item {
+    margin: 10px 0px;
+    font-size: 16px;
+}
+.video-box {
+    // height: 600px;
+    width: 60%;
+}
+.literacy-detail-container {
+    width: 100%;
+    height: 100%;
+    padding: 15px;
+}
+.literacy-block-box {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+}
+.video-info-wrap {
+    width: 78%;
+    min-height: 600px;
+    background: white;
+    padding: 15px;
+}
+.more-link-wrap {
+    width: 20%;
+    min-height: 600px;
+    background: white;
+}
+.literacy-mgt-top {
+    width: 100%;
+    height: 40px;
+    background: white;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    padding-left: 10px;
+    padding-right: 20px;
+    margin-bottom: 10px;
+    line-height: 40px;
+    cursor: pointer;
+}
+</style>

+ 61 - 0
TEAMModelOS/ClientApp/src/view/policy/Policy.less

@@ -0,0 +1,61 @@
+.policy-container{
+    width: 100%;
+    height: 100%;
+    padding: 15px;
+}
+.policy-box{
+    display: flex;
+    justify-content: space-evenly;
+    flex-wrap: wrap;
+}
+.policy-card{
+    width: 48%;
+    height: 400px;
+    margin-bottom: 15px;
+}
+.policy-info{
+    list-style: none;
+    margin-bottom: 5px;
+}
+.policy-title{
+    width: 80%;
+    display: inline-block;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    vertical-align: bottom;
+    white-space: nowrap;
+    &:hover{
+        text-decoration: underline;
+    }
+}
+.policy-list-wrap{
+    height: 315px;
+}
+.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;
+    }
+}

+ 139 - 0
TEAMModelOS/ClientApp/src/view/policy/Policy.vue

@@ -0,0 +1,139 @@
+<template>
+
+    <div class="policy-container">
+        <vuescroll>
+            <div class="policy-box">
+                <Card :bordered="false" class="policy-card">
+                    <p slot="title" style="color:#17233d;font-weight:600;font-size:15px">
+                        <Icon custom="iconfont icon-policy" size="20" />
+                        国家文件
+                    </p>
+                    <div class="policy-list-wrap">
+                        <vuescroll>
+                            <EmptyData textContent="暂无相关文件" v-show="!country.length"></EmptyData>
+                            <ul>
+                                <li v-for="(item,index) in country" :key="index" class="policy-info">
+                                    <span>{{`【${item.type}】`}}</span>
+                                    <a :href="item.link" target="_blank" class="policy-title">{{item.title}}</a>
+                                    <span>{{item.time}}</span>
+                                </li>
+                            </ul>
+                        </vuescroll>
+                    </div>
+                </Card>
+                <Card :bordered="false" class="policy-card">
+                    <p slot="title" style="color:#17233d;font-weight:600;font-size:15px">
+                        <Icon custom="iconfont icon-policy" size="20" />省级文件
+                    </p>
+                    <div class="policy-list-wrap">
+                        <vuescroll>
+                            <EmptyData textContent="暂无相关文件" v-show="!province.length"></EmptyData>
+                            <ul>
+                                <li v-for="(item,index) in province" :key="index" class="policy-info">
+                                    <span>{{`【${item.type}】`}}</span>
+                                    <a :href="item.link" target="_blank" class="policy-title">{{item.title}}</a>
+                                    <span>{{item.time}}</span>
+                                </li>
+                            </ul>
+                        </vuescroll>
+                    </div>
+                </Card>
+                <Card :bordered="false" class="policy-card">
+                    <p slot="title" style="color:#17233d;font-weight:600;font-size:15px">
+                        <Icon custom="iconfont icon-policy" size="20" />市级文件
+                    </p>
+                    <div class="policy-list-wrap">
+                        <vuescroll>
+                            <EmptyData textContent="暂无相关文件" v-show="!city.length"></EmptyData>
+                            <ul>
+                                <li v-for="(item,index) in city" :key="index" class="policy-info">
+                                    <span>{{`【${item.type}】`}}</span>
+                                    <a :href="item.link" target="_blank" class="policy-title">{{item.title}}</a>
+                                    <span>{{item.time}}</span>
+                                </li>
+                            </ul>
+                        </vuescroll>
+                    </div>
+                </Card>
+                <Card :bordered="false" class="policy-card">
+                    <p slot="title" style="color:#17233d;font-weight:600;font-size:15px">
+                        <Icon custom="iconfont icon-policy" size="20" />解读课程
+                    </p>
+                    <div class="policy-list-wrap">
+                        <vuescroll>
+                            <EmptyData textContent="暂无相关课程" v-show="!course.length"></EmptyData>
+                            <ul>
+                                <li v-for="(item,index) in course" :key="index" class="policy-info">
+                                    <span>{{`【解读课程】`}}</span>
+                                    <a @click="previewVideo(index)" href="#" class="policy-title">{{item.title}}</a>
+                                    <span>{{item.time}}</span>
+                                </li>
+                            </ul>
+                        </vuescroll>
+                    </div>
+                </Card>
+            </div>
+        </vuescroll>
+        <div v-show="previewStatus" class="image-viewer">
+            <div style="width:fit-content;position:relative;margin:auto;">
+                <Icon type="md-close" class="close-icon" @click="closePreview" />
+                <video id="previewVideo" :src="videouUrl" width="870" controls="controls" style="max-height: 800px;" autoplay="autoplay">
+                    {{$t('teachContent.tips8')}}
+                </video>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+const courseData = require('@/static/course.json')
+export default {
+    data() {
+        return {
+            previewStatus: false,
+            country: [],
+            province: [],
+            city: [],
+            other: [],
+            course: [],
+            videouUrl: ''
+        }
+    },
+    methods: {
+        closePreview() {
+            let myVideo = document.getElementById('previewVideo') // 获取视频video
+            myVideo.pause()
+            this.previewStatus = false
+        },
+        previewVideo(index) {
+            this.videouUrl = this.course[index].link
+            this.previewStatus = true
+        }
+    },
+    created() {
+        let allPolicy = require('@/static/policy.json')
+        this.country = allPolicy.filter(item => {
+            return item.type == '国家文件'
+        })
+        this.province = allPolicy.filter(item => {
+            return item.type == '省级文件'
+        })
+        this.city = allPolicy.filter(item => {
+            return item.type == '市州文件'
+        })
+        this.other = allPolicy.filter(item => {
+            return item.type == '其他文件'
+        })
+        this.course = []
+        this.course = this._.cloneDeep(courseData)
+        let sas = this.$store.state.user.schoolProfile.blob_sas
+        let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+        this.course.forEach(item => {
+            let url = `${blobUrl}${item.link}?${sas}`
+            item.link = url
+        })
+    }
+}
+</script>
+<style lang="less" scoped>
+@import "./Policy.less";
+</style>

+ 157 - 0
TEAMModelOS/ClientApp/src/view/policy/Probation.vue

@@ -0,0 +1,157 @@
+<template>
+    <div class="literacy-container">
+        <div class="literacy-mgt-top">
+            <Input v-model="keyword" suffix="ios-search" placeholder="搜索" style="width: 200px;margin-top:0px" @on-change="search" />
+        </div>
+        <vuescroll>
+            <div class="literacy-block-box">
+                <div class="literacy-item" v-for="(item,index) in videoListShow" :key="index"  @click="toDetailPage(index)">
+                    <div :class="['literacy-img',index == 5 || index == 6 ? 'last-img' : '']" :style="{backgroundImage: `url(${item.img})`}"></div>
+                    <div class="info-item">
+                        <!-- <span class="info-label">报告人:</span> -->
+                        <span>{{item.name}}</span>
+                    </div>
+                </div>
+            </div>
+            <EmptyData textContent="未搜到相关课程" v-show="!videoListShow.length" :top="100"></EmptyData>
+        </vuescroll>
+    </div>
+</template>
+<script>
+export default {
+    data() {
+        return {
+            keyword: '',
+            videoList: [],
+            videoListShow: []
+        }
+    },
+    methods: {
+        search() {
+            if (this.keyword) {
+                this.videoListShow = this.videoList.filter(item => {
+                    return JSON.stringify(item).includes(this.keyword)
+                })
+            } else {
+                this.videoListShow = this.videoList
+            }
+
+        },
+        toDetailPage(index) {
+            window.open(this.videoListShow[index].link)
+        }
+    },
+    created() {
+        this.videoList = [
+            {
+                name: '《中国教育学刊》课堂变革于教学领导力提升专题培训',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E4%B8%AD%E5%9B%BD%E6%95%99%E8%82%B2%E5%AD%A6%E5%88%8A.PNG',
+                link: 'https://sokrates.teammodel.cn/exhibition/tbavideo#/myChannel/858'
+            },
+            {
+                name: '《中国教育学刊》基础教育国家级教学成果推广会',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E4%B8%AD%E5%9B%BD%E6%95%99%E8%82%B2%E5%AD%A6%E5%88%8A1.PNG',
+                link: 'https://sokrates.teammodel.cn/exhibition/tbavideo#/myChannel/837'
+            },
+            {
+                name: '智慧教学实验学校联盟(西南大学)',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E8%A5%BF%E5%8D%97%E5%A4%A7%E5%AD%A6.PNG',
+                link: 'https://sokrates.teammodel.cn/district/JXLMPT#/'
+            },
+            {
+                name: '全球醍摩豆AI智慧学校联盟',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E6%99%BA%E6%85%A7%E5%AD%A6%E6%A0%A1%E8%81%94%E7%9B%9F.PNG',
+                link: 'https://sokrates.teammodel.cn/exhibition/tbavideo#/myChannel/82'
+            },
+            {
+                name: '苏格拉底平台',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/team-model.jpg',
+                link: 'https://sokrates.teammodel.cn/exhibition/tbavideo#/'
+            },
+            {
+                name: '国家公共资源教育平台',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E5%85%AC%E5%85%B1%E6%9C%8D%E5%8A%A1%E5%B9%B3%E5%8F%B0.png',
+                link: 'https://www.eduyun.cn/'
+            },
+            {
+                name: '中国大学慕课MOOC',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/MOOC.png',
+                link: 'https://www.icourse163.org/'
+            },
+            {
+                name: '政策解读',
+                img: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/%E6%94%BF%E7%AD%96%E8%A7%A3%E8%AF%BB1.jpg',
+                link: '/home/policy'
+            }
+        ]
+        this.videoListShow = this.videoList
+    }
+}
+</script>
+<style lang="less" scoped>
+.last-img {
+    background-size: contain !important;
+    background-position: center;
+}
+.literacy-container {
+    width: 100%;
+    height: 100%;
+    background: white;
+    padding: 15px;
+}
+.literacy-block-box {
+    // background: white;
+    width: 100%;
+    // min-height: 800px;
+    padding: 10px;
+    display: flex;
+    flex-wrap: wrap;
+}
+.literacy-item {
+    width: 200px;
+    height: 200px;
+    background: #fcfcfc;
+    border: 1px solid #f0f0f0;
+    padding-bottom: 10px;
+    border-radius: 5px;
+    overflow: hidden;
+    margin-bottom: 30px;
+    margin-right: 30px;
+    cursor: pointer;
+    height: fit-content;
+    padding: 10px;
+    box-sizing: content-box;
+    transition: all 0.2s ease 0s;
+    .info-label {
+        color: #a5a5a5;
+    }
+    &:hover {
+        box-shadow: 0 26px 40px -24px #aaa;
+        transform: translateY(-4px);
+    }
+    .literacy-img {
+        width: 200px;
+        height: 200px;
+        background-size: cover;
+        background-repeat: no-repeat;
+        border-radius: 50%;
+    }
+    .info-item {
+        margin-top: 10px;
+        width: 100%;
+        padding-left: 5px;
+        font-size: 16px;
+        text-align: center;
+    }
+}
+.literacy-mgt-top {
+    width: ~"calc(100% - 20px)";
+    height: 40px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-left: 10px;
+    padding-left: 10px;
+    padding-right: 20px;
+    margin-bottom: 10px;
+    line-height: 40px;
+}
+</style>

+ 116 - 0
TEAMModelOS/ClientApp/src/view/statistics/AbilityPoint.vue

@@ -0,0 +1,116 @@
+<template>
+    <div id="ability-point"></div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        count: {
+            type: Array,
+            default: () => {
+                return [0, 0, 0]
+            }
+        }
+    },
+    data() {
+        return {
+            myData: [],
+            techScoreGau: undefined,
+            option: {
+                title: {
+                    text: '能力点选修占比',
+                    left: 'center',
+                    top: 0,
+                    z: 9999,
+                    textStyle: {
+                        color: '#516b91',
+                        fontSize: 15
+                    }
+                },
+                grid: {
+                    bottom: 18,
+                    top: 35,
+                    right: 10
+                },
+                // tooltip: {
+                //     trigger: 'item',
+                //     formatter: '{b} : {c} ({d}%)'
+                // },
+                xAxis: {
+                    type: 'category',
+                    data: ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'A10', 'A11', 'A12', 'A13', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'B10', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7']
+                },
+                yAxis: {
+                    type: 'value'
+                },
+                series: [
+                    {
+                        data: [],
+                        type: 'bar',
+                        itemStyle: {
+                            color: '#2db7f5'
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    mounted() {
+        this.techScoreGau = this.$echarts.init(document.getElementById('ability-point'))
+        this.techScoreGau.setOption(this.option)
+        this.erd = elementResizeDetectorMaker()
+        this.erd.listenTo(document.getElementById("ability-point"), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.techScoreGau.resize()
+            })
+        })
+    },
+    watch: {
+        count: {
+            handler(n, o) {
+                let data = this._.cloneDeep(n)
+                data.sort((s,t) => {
+                    var a = s.abilityNo.toLowerCase()
+                    var b = t.abilityNo.toLowerCase()
+                    if (a.length === 2) {
+                        a = a.slice(0, a.length - 1) + '0' + a.slice(-1)
+                    }
+                    if (b.length === 2) {
+                        b = b.slice(0, b.length - 1) + '0' + b.slice(-1)
+                    }
+                    if (a < b) return -1
+                    if (a > b) return 1
+                    return 0;
+                })
+                console.log('能力点数据', data)
+                if (this.count.length) {
+                    this.option.xAxis.data = data.map(item => {
+                        return item.abilityNo
+                    })
+                    this.option.series[0].data = data.map(item => {
+                        return item.count
+                    })
+                    if (this.techScoreGau) {
+                        this.techScoreGau.setOption(this.option)
+                    } else {
+                        this.techScoreGau = this.$echarts.init(document.getElementById('ability-point'))
+                        this.techScoreGau.setOption(this.option)
+                    }
+                }
+
+            },
+            immediate: true,
+            deep: true
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+#ability-point {
+    width: 90%;
+    height: 240px;
+}
+</style>
+<style>
+</style>

+ 332 - 0
TEAMModelOS/ClientApp/src/view/statistics/GroupData.vue

@@ -0,0 +1,332 @@
+<template>
+    <div class="group-data-container">
+        <Loading v-show="isLoading"></Loading>
+        <vuescroll>
+            <div class="group-data-box">
+                <div class="group-item" v-for="(item,index) in groupData" :key="index">
+                    <div class="group-info-wrap">
+                        <div class="group-img">
+                            <img :src="item.img" width="80">
+                        </div>
+                        <div>
+                            <h6 class="group-name">{{item.groupName}}</h6>
+                            <p class="group-info">人数:{{item.teachers.length}}</p>
+                            <p class="group-info">成员:
+                                <span class="view-member" @click="viewMember(index)">查看成员</span>
+                            </p>
+                        </div>
+                    </div>
+                    <div class="group-data-wrap">
+                        <div class="data-item">
+                            <p class="data-num">{{item.onlineCount}}</p>
+                            <p class="data-label">线上研修</p>
+                        </div>
+                        <div class="data-item">
+                            <p class="data-num">{{item.areaCount}}</p>
+                            <p class="data-label">区级研修</p>
+                        </div>
+                        <div class="data-item">
+                            <p class="data-num">{{item.schoolCount}}</p>
+                            <p class="data-label">校级研修</p>
+                        </div>
+                        <div class="data-item">
+                            <p class="data-num">{{item.examCount}}</p>
+                            <p class="data-label">评测反馈</p>
+                        </div>
+                        <div class="data-item">
+                            <p class="data-num">{{item.surveyCount}}</p>
+                            <p class="data-label">问卷活动</p>
+                        </div>
+                        <div class="data-item">
+                            <p class="data-num">{{item.voteCount}}</p>
+                            <p class="data-label">投票活动</p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </vuescroll>
+        <Modal v-model="viewStatus" title="教研组成员">
+            <div v-if="groupData[viewIndex]">
+                <h6 class="group-name">{{groupData[viewIndex].groupName}}</h6>
+                <div v-for="(item,index) in groupData[viewIndex].teachers" :key="index" class="teacher-item">
+                    <PersonalPhoto :name="item.name" :picture="item.picture" style="display: inline-block;" />
+                    <span class="teacher-name">{{item.name}}</span>
+                </div>
+            </div>
+        </Modal>
+    </div>
+</template>
+<script>
+import PersonalPhoto from '@/components/public/personalPhoto/Index.vue'
+import { mapGetters } from 'vuex'
+export default {
+    props:[
+        'schoolId'
+    ],
+    components: {
+        PersonalPhoto
+    },
+    data() {
+        return {
+            isLoading:false,
+            viewStatus: false,
+            viewIndex: 0,
+            groupData: [],
+            imgs: [
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject1.png',
+                    keyword: '语文'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject2.png',
+                    keyword: '数学'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject3.png',
+                    keyword: '英语'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject4.png',
+                    keyword: '物理'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject5.png',
+                    keyword: '化学'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject6.png',
+                    keyword: '生物'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject7.png',
+                    keyword: '历史'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject8.png',
+                    keyword: '政治'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject9.png',
+                    keyword: '地理'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject10.png',
+                    keyword: '体育'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject11.png',
+                    keyword: '音乐'
+                },
+                {
+                    url: 'https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/image/subject12.png',
+                    keyword: '综合,其他'
+                },
+            ]
+        }
+    },
+    computed: {
+        ...mapGetters({
+            teachers: 'user/getSchoolUserJoined', // 取得已加入此學校的使用者
+        })
+    },
+    methods: {
+        viewMember(index) {
+            this.viewIndex = index
+            this.viewStatus = true
+        },
+        //转分组模式数据结构
+        handelGroup() {
+            this.teachers.forEach(item => {
+                item.groupName = item.groupName || '默认组别'
+            })
+            let groupRes = this.$jsFn.groupBy(this.teachers, 'groupName')
+            this.groupData.length = 0
+            for (let index in groupRes) {
+                let img = this.getGroupImg(groupRes[index][0].groupName || '默认组别')
+                this.groupData.push({
+                    groupName: groupRes[index][0].groupName || '默认组别',
+                    groupId: groupRes[index][0].groupId,
+                    img: img,
+                    teachers: groupRes[index],
+                    onlineCount: 0,
+                    areaCount: 0,
+                    schoolCount: 0,
+                    surveyCount: 0,
+                    voteCount: 0,
+                    examCount: 0
+                })
+            }
+            // this.groupData.sort((a, b) => {
+            //     return parseInt(a.groupId) - parseInt(b.groupId)
+            // })
+        },
+        getGroupImg(name) {
+            let has = false
+            let url
+            for (let item of this.imgs) {
+                if (name.includes(item.keyword)) {
+                    has = true
+                    url = item.url
+                    console.log(name, url)
+                    break
+                }
+            }
+            if (has) {
+                return url
+            } else {
+                console.log(name, this.imgs[11].url)
+                return this.imgs[11].url
+            }
+        },
+        findTotalData() {
+            this.isLoading = true
+            let params = {
+                school: this.schoolId || this.$store.state.userInfo.schoolCode
+            }
+            this.$api.ability.FindTotalData(params).then(
+                res => {
+                    if (!res.error) {
+                        this.groupData.forEach(gItem => {
+                            let d = res.groupDatas.find(dItem => {
+                                return dItem.groupName == gItem.groupName
+                            })
+                            if (d) {
+                                gItem.onlineCount = d.thcSubCount
+                                gItem.areaCount = d.offlineAreaGroupCount
+                                gItem.schoolCount = d.offlineSchoolGroupCount
+                                gItem.surveyCount = d.surveyGroupeCount
+                                gItem.voteCount = d.voteGroupeCount
+                                gItem.examCount = d.examGroupeCount
+                            }
+                        })
+                    }
+                },
+                err => {
+
+                }
+            ).finally(() => {
+                this.isLoading = false
+            })
+        }
+    },
+    created() {
+        //由DB取得該校所有使用者並放入state.schoolUserList
+        this.$store.dispatch('user/getSchoolTeacher').then(
+            res => {
+                this.handelGroup()
+                this.findTotalData()
+            },
+            err => {
+                this.$Message.error('user/setSchoolTeacher API error!')
+            }
+        )
+    }
+}
+</script>
+<style lang="less" scoped>
+.view-member {
+    color: #1cc0f3;
+    text-decoration: underline;
+    cursor: pointer;
+}
+.teacher-name {
+    font-size: 16px;
+    margin-left: 10px;
+}
+.group-data-container {
+    box-shadow: 0px 2px 5px #e9e9e9 inset;
+    width: 100%;
+    height: ~"calc(100% - 50px)";
+    padding: 10px 20px;
+    background: white;
+    user-select: none;
+}
+.group-data-box {
+    display: flex;
+    flex-wrap: wrap;
+}
+.teacher-item {
+    margin-top: 8px;
+    display: flex;
+    align-items: center;
+}
+.group-info-wrap {
+    margin-bottom: 10px;
+    padding: 20px;
+    display: flex;
+    align-items: center;
+    position: relative;
+    &::after {
+        content: "";
+        width: 86%;
+        height: 1px;
+        background: rgba(0, 0, 0, 0.08);
+        position: absolute;
+        bottom: 0px;
+        left: 7%;
+    }
+    .group-img {
+        width: 80px;
+        height: 100px;
+        margin-right: 25px;
+        margin-left: 10px;
+    }
+
+    .group-info {
+        display: block;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        max-width: 220px;
+        line-height: 22px;
+        font-size: 14px;
+        margin-top: 5px;
+        color: #818289;
+    }
+}
+.group-name {
+    display: inline-block;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    max-width: 220px;
+    line-height: 32px;
+    font-size: 22px;
+    font-weight: bold;
+    color: #1e1f24;
+}
+.group-item {
+    width: 355px;
+    border: 1px solid rgba(0, 0, 0, 0.08);
+    margin-right: 50px;
+    margin-bottom: 30px;
+    transition: all 0.2s ease 0s;
+    &:hover {
+        box-shadow: 0 26px 40px -24px rgb(0, 0, 0);
+        transform: translateY(-6px);
+    }
+}
+.group-data-wrap {
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-evenly;
+    .data-item {
+        width: 33%;
+        text-align: center;
+        margin: 10px 0px;
+    }
+    .data-num {
+        line-height: 25px;
+        font-size: 18px;
+        font-weight: bold;
+        color: #1e1f24;
+    }
+    .data-label {
+        line-height: 22px;
+        color: #818289;
+        font-weight: bold;
+        font-size: 12px;
+    }
+}
+</style>

+ 47 - 0
TEAMModelOS/ClientApp/src/view/statistics/Hour.less

@@ -0,0 +1,47 @@
+.xueqing-container{
+    width: 100%;
+    height: ~"calc(100% - 50px)";
+    padding: 10px 10px 15px 15px;
+}
+.top-block-wrap{
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    margin-top: 10px;
+    padding-right: 10px;
+}
+.content-con-item{
+    width: 18%;
+    height: 110px;
+    position: relative;
+    border-radius: 2px;
+    overflow: hidden;
+    .left-area{
+        float: left;
+        height: 100%;
+        display: table;
+        text-align: center;
+        .icon{
+            display: table-cell;
+            vertical-align: middle;
+        }
+    }
+    .right-area{
+        background: white;
+        float: left;
+        height: 100%;
+        display: table;
+        text-align: center;
+        .count-style {
+            font-size: 50px;
+        }
+    }
+}
+.class-hours-table{
+    margin-top: 20px;
+    padding: 5px 10px;
+    background: white;
+}
+.table-tools-wrap{
+    margin: 10px 0px;
+}

+ 214 - 0
TEAMModelOS/ClientApp/src/view/statistics/Hour.vue

@@ -0,0 +1,214 @@
+<template>
+    <div class="xueqing-container">
+        <vuescroll>
+            <div class="top-block-wrap">
+                <div class="content-con-item" v-for="(item,index) in topData" :key="index">
+                    <div class="left-area" :style="{background: item.color, width: '36%'}">
+                        <Icon :type="item.icon" class="icon" style="font-size: 40px; color: rgb(255, 255, 255);"></Icon>
+                    </div>
+                    <div class="right-area" style="width: 64%;">
+                        <div>
+                            <div class="count-to-wrapper">
+                                <p class="content-outer">
+                                    <CountTo class="count-to-count-text count-style" :endVal="item.number" :duration="600"></CountTo>
+                                    <span style="font-size: 28px;" v-if="item.type === 'rate'">%</span>
+                                </p>
+                            </div>
+                            <p>{{item.text}}</p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="class-hours-table">
+                <div class="table-tools-wrap">
+                    <Input v-model="keyword" search placeholder="搜索" style="width:200px" @on-change="serchData" />
+                    <Button type="text" style="float:right" @click="exportData">
+                        <Icon type="md-download" />
+                        数据导出
+                    </Button>
+                </div>
+                <Table :columns="columns1" :data="hourDataShow" :loading="loading" :height="600">
+                    <template slot-scope="{ row }" slot="online">
+                        <span>{{ row.onlineTime + '/20' }}</span>
+                    </template>
+                    <template slot-scope="{ row }" slot="offline">
+                        <span>{{ row.offlinelTime + '/10' }}</span>
+                    </template>
+                    <template slot-scope="{ row }" slot="assessment">
+                        <span>{{ row.schoolScoreTime+ '/15' }}</span>
+                    </template>
+                    <template slot-scope="{ row }" slot="classRecord">
+                        <span>{{ row.classVideoTime + '/5'}}</span>
+                    </template>
+                    <template slot-scope="{ row }" slot="allHour">
+                        <span>{{ row.alltime + '/50' }}</span>
+                    </template>
+                    <template slot-scope="{ row }" slot="status">
+                        <span :style="{color:row.alltime < 50 ? '#ed4014':'#19be6b'}">
+                            {{ row.alltime < 50 ? '未完成' : '已完成' }}
+                        </span>
+                    </template>
+                </Table>
+            </div>
+        </vuescroll>
+    </div>
+</template>
+<script>
+import excel from '@/utils/excel.js'
+import CountTo from 'vue-count-to'
+export default {
+    components: {
+        CountTo
+    },
+    data() {
+        return {
+            keyword: '',
+            loading: false,
+            curTab: 0,
+            topData: [
+                {
+                    icon: 'ios-people',
+                    color: '#2d8cf0',
+                    number: 0,
+                    text: '参训人数',
+                    type: 'num'
+                },
+                {
+                    icon: 'md-cube',
+                    color: '#2db7f5',
+                    number: 0,
+                    text: '参训率',
+                    type: 'rate'
+                },
+                {
+                    icon: 'md-bookmark',
+                    color: '#5cadff',
+                    number: 0,
+                    text: '合格人数',
+                    type: 'num'
+                },
+                {
+                    icon: 'md-thumbs-up',
+                    color: '#ff9900',
+                    number: 0,
+                    text: '合格率',
+                    type: 'rate'
+                },
+                {
+                    icon: 'md-checkmark-circle',
+                    color: '#19be6b',
+                    number: 0,
+                    text: '完成率',
+                    type: 'rate'
+                }
+
+            ],
+            columns1: [
+                {
+                    title: '教师姓名',
+                    key: 'tmdname'
+                },
+                {
+                    title: '线上研修',
+                    slot: 'online',
+                    align: 'center'
+                },
+                {
+                    title: '校本研修',
+                    slot: 'offline',
+                    align: 'center'
+                },
+                {
+                    title: '应用考核',
+                    slot: 'assessment',
+                    align: 'center'
+                },
+                {
+                    title: '课堂实录',
+                    slot: 'classRecord',
+                    align: 'center'
+                },
+                {
+                    title: '总学时',
+                    slot: 'allHour',
+                    align: 'center'
+                },
+                {
+                    title: '完成状态',
+                    slot: 'status',
+                    align: 'center'
+                }
+            ],
+            hourData: [],
+            hourDataShow: [],
+        }
+    },
+    methods: {
+        exportData() {
+            let downloadData = this.hourData.map(item => {
+                return {
+                    tmdname: item.tmdname,
+                    onlineTime: item.onlineTime,
+                    offlinelTime: item.offlinelTime,
+                    schoolScoreTime: item.schoolScoreTime,
+                    classVideoTime: item.classVideoTime,
+                    alltime: item.alltime,
+                    status: item.alltime < 50 ? '未完成' : '已完成'
+                }
+            })
+            const params = {
+                title: this.columns1.map(i => i.title),
+                key: ['tmdname', 'onlineTime', 'offlinelTime', 'schoolScoreTime', 'classVideoTime', 'alltime', 'status'],
+                data: downloadData,
+                autoWidth: true,
+                filename: '学时统计'
+            }
+            excel.export_array_to_excel(params)
+        },
+        serchData() {
+            this.hourDataShow = this.hourData.filter(item => {
+                return item.tmdname.includes(this.keyword) || item.groupName.includes(this.keyword)
+            })
+        },
+        findTotalData() {
+            this.loading = true
+            let params = {
+                school: this.$store.state.userInfo.schoolCode
+            }
+            this.$api.ability.FindTotalData(params).then(
+                res => {
+                    if (!res.error) {
+                        let data = []
+                        //参训人数
+                        data.push(res.joinCount)
+                        // 参训率
+                        data.push(parseFloat((res.joinCount * 100 / res.teacherCount).toFixed(2)))
+                        // 合格人数
+                        data.push(res.ok50TimeCount)
+                        // 合格率
+                        data.push(parseFloat((res.ok50TimeCount * 100 / res.joinCount).toFixed(2)))
+                        // 完成率
+                        data.push(0)
+                        this.topData.forEach((item, index) => {
+                            item.number = data[index]
+                        })
+                        this.hourData = res.groupMembers
+                        this.hourDataShow = this.hourData
+                    }
+                },
+                err => {
+
+                }
+            ).finally(() => {
+                this.loading = false
+            })
+        }
+    },
+    created() {
+        this.findTotalData()
+    }
+}
+</script>
+<style lang="less" scoped>
+@import "./Hour.less";
+</style>

+ 153 - 0
TEAMModelOS/ClientApp/src/view/statistics/Percent.vue

@@ -0,0 +1,153 @@
+<template>
+    <div id="mark-prog-pie"></div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        count: {
+            type: Array,
+            default: () => {
+                return [0, 0, 0]
+            }
+        }
+    },
+    data() {
+        return {
+            myData: [],
+            techScoreGau: undefined,
+            option: {
+                title: {
+                    text: '能力维度选修占比',
+                    left: 'center',
+                    textStyle: {
+                        color: '#516b91',
+                        fontSize: 15
+                    }
+                },
+                tooltip: {
+                    trigger: 'item',
+                    formatter: '{b} : {c} ({d}%)'
+                },
+                grid: {
+                    bottom: 18,
+                    top: 35,
+                    right: 10
+                },
+                legend: {
+                    top: 0,
+                    left: 10,
+                    itemWidth: 10,
+                    itemHeight: 10,
+                    orient: 'vertical',
+                    data: [],
+                    textStyle: {
+                        color: '#606060',
+                        fontSize: 12,
+                    },
+                },
+                series: [
+                    {
+                        type: 'pie',
+                        radius: '70%',
+                        center: ['50%', '60%'],
+                        selectedMode: 'single',
+                        data: [
+                            {
+                                name: '学情分析',
+                                value: 0,
+                                itemStyle: {
+                                    color: '#319aff'
+                                }
+                            },
+                            {
+                                name: '教学设计',
+                                value: 0,
+                                itemStyle: {
+                                    color: '#1bd175'
+                                }
+                            },
+                            {
+                                name: '学法指导',
+                                value: 0,
+                                itemStyle: {
+                                    color: '#ffa800'
+                                }
+                            },
+                            {
+                                name: '学业评价',
+                                value: 0,
+                                itemStyle: {
+                                    color: '#fa76cd'
+                                }
+                            }
+
+                        ],
+                        label: {
+                            show: false
+                        },
+                        emphasis: {
+                            itemStyle: {
+                                shadowBlur: 10,
+                                shadowOffsetX: 0,
+                                shadowColor: 'rgba(0, 0, 0, 0.5)'
+                            }
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    mounted() {
+        this.techScoreGau = this.$echarts.init(document.getElementById('mark-prog-pie'))
+        this.techScoreGau.setOption(this.option)
+        this.erd = elementResizeDetectorMaker()
+        this.erd.listenTo(document.getElementById("mark-prog-pie"), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.techScoreGau.resize()
+            })
+        })
+    },
+    methods: {
+        randomColor() {
+            let r = Math.floor(Math.random() * 255);
+            let g = Math.floor(Math.random() * 255);
+            let b = Math.floor(Math.random() * 255);
+            return 'rgba(' + r + ',' + g + ',' + b + ',0.8)'
+        }
+    },
+    watch: {
+        count: {
+            handler(n, o) {
+                if (this.count.length) {
+                    console.log('统计数据', this.count)
+                    this.option.legend.data = this.count.map(item => {
+                        return item.name
+                    })
+                    this.option.series[0].data.forEach((item, index) => {
+                        item.name = this.count[index].name
+                        item.value = this.count[index].count
+                    })
+                    if (this.techScoreGau) {
+                        this.techScoreGau.setOption(this.option)
+                    } else {
+                        this.techScoreGau = this.$echarts.init(document.getElementById('mark-prog-pie'))
+                        this.techScoreGau.setOption(this.option)
+                    }
+                }
+            },
+            immediate: true,
+            deep: true
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+#mark-prog-pie {
+    width: 400px;
+    height: 240px;
+}
+</style>
+<style>
+</style>

+ 51 - 0
TEAMModelOS/ClientApp/src/view/statistics/Result.less

@@ -0,0 +1,51 @@
+.result-container{
+    width: 100%;
+    height: ~"calc(100% - 50px)";
+    padding: 10px 10px 15px 15px;
+}
+.result-table-wrap{
+    background: white;
+    width: 100%;
+}
+.result-table-wrap{
+    margin-top: 20px;
+    padding: 5px 10px;
+    background: white;
+}
+.table-tools-wrap{
+    margin: 10px 0px;
+}
+.top-block-wrap{
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    margin-top: 10px;
+    padding-right: 10px;
+}
+.content-con-item{
+    width: 18%;
+    height: 110px;
+    position: relative;
+    border-radius: 2px;
+    overflow: hidden;
+    .left-area{
+        float: left;
+        height: 100%;
+        display: table;
+        text-align: center;
+        .icon{
+            display: table-cell;
+            vertical-align: middle;
+        }
+    }
+    .right-area{
+        background: white;
+        float: left;
+        height: 100%;
+        display: table;
+        text-align: center;
+        .count-style {
+            font-size: 50px;
+        }
+    }
+}

+ 188 - 0
TEAMModelOS/ClientApp/src/view/statistics/Result.vue

@@ -0,0 +1,188 @@
+<template>
+    <div class="result-container">
+        <vuescroll>
+            <div class="top-block-wrap">
+                <div class="content-con-item" v-for="(item,index) in topData" :key="index">
+                    <div class="left-area" :style="{background: item.color, width: '36%'}">
+                        <Icon :type="item.icon" class="icon" style="font-size: 40px; color: rgb(255, 255, 255);"></Icon>
+                    </div>
+                    <div class="right-area" style="width: 64%;">
+                        <div>
+                            <div class="count-to-wrapper">
+                                <p class="content-outer">
+                                    <!-- <span class="count-to-count-text count-style">{{item.number}}</span> -->
+                                    <CountTo class="count-to-count-text count-style" :endVal="item.number" :duration="600"></CountTo>
+                                    <span style="font-size: 28px;" v-if="item.text === '合格率'">%</span>
+                                </p>
+                            </div>
+                            <p>{{item.text}}</p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="result-table-wrap">
+                <div class="table-tools-wrap">
+                    <Input v-model="keyword" search placeholder="搜索" style="width:200px" @on-change="serchData" />
+                    <Button type="text" style="float:right" @click="exportData">
+                        <Icon type="md-download" />
+                        数据导出
+                    </Button>
+                </div>
+                <Table :columns="columns1" :data="tableDataShow" :loading="loading">
+                    <template slot-scope="{ row }" slot="status">
+                        <span :style="{color:row.thcSubCount == row.tchSubmitCount ? '#19be6b':'#ed4014'}">
+                            {{row.thcSubCount == row.tchSubmitCount ? '已完成' : '未完成'}}
+                        </span>
+                    </template>
+                </Table>
+            </div>
+        </vuescroll>
+
+    </div>
+</template>
+<script>
+import excel from '@/utils/excel.js'
+import CountTo from 'vue-count-to'
+export default {
+    components: {
+        CountTo
+    },
+    data() {
+        return {
+            loading: false,
+            topData: [
+                {
+                    icon: 'ios-people',
+                    color: '#2d8cf0',
+                    number: 0,
+                    text: '已评审'
+                },
+                {
+                    icon: 'md-cube',
+                    color: '#2db7f5',
+                    number: 0,
+                    text: '已提交'
+                },
+                {
+                    icon: 'md-bookmark',
+                    color: '#5cadff',
+                    number: 0,
+                    text: '未完成'
+                },
+                {
+                    icon: 'md-thumbs-up',
+                    color: '#ff9900',
+                    number: 0,
+                    text: '未提交'
+                },
+                {
+                    icon: 'md-thumbs-up',
+                    color: '#19be6b',
+                    number: 0,
+                    text: '合格率'
+                }
+            ],
+            tableData: [],
+            tableDataShow: [],
+            columns1: [
+                {
+                    title: '教师姓名',
+                    key: 'tmdname'
+                },
+                {
+                    title: '教研组',
+                    key: 'groupName',
+                    align: 'center'
+                },
+                {
+                    title: '能力点个数',
+                    key: 'thcSubCount',
+                    align: 'center'
+                },
+                {
+                    title: '已提交',
+                    key: 'tchSubmitCount',
+                    align: 'center'
+                },
+                {
+                    title: '未提交',
+                    key: 'tchUnSubmitCount',
+                    align: 'center'
+                },
+                {
+                    title: '状态',
+                    slot: 'status',
+                    align: 'center'
+                }
+            ],
+            keyword: ''
+        }
+    },
+    methods: {
+        exportData() {
+            let downloadData = this.tableData.map(item => {
+                return {
+                    tmdname: item.tmdname,
+                    groupName: item.groupName,
+                    thcSubCount: item.thcSubCount,
+                    tchSubmitCount: item.tchSubmitCount,
+                    tchUnSubmitCount: item.tchUnSubmitCount,
+                    status: item.tchSubmitCount == item.thcSubCount ? '未完成' : '已完成'
+                }
+            })
+            const params = {
+                title: this.columns1.map(i => i.title),
+                key: ['tmdname', 'groupName', 'thcSubCount', 'tchSubmitCount', 'tchUnSubmitCount', 'status'],
+                data: downloadData,
+                autoWidth: true,
+                filename: '能力点统计'
+            }
+            excel.export_array_to_excel(params)
+        },
+        serchData() {
+            this.tableDataShow = this.tableData.filter(item => {
+                return item.tmdname.includes(this.keyword) || item.groupName.includes(this.keyword)
+            })
+        },
+        findTotalData() {
+            this.loading = true
+            let params = {
+                school: this.$store.state.userInfo.schoolCode
+            }
+            this.$api.ability.FindTotalData(params).then(
+                res => {
+                    if (!res.error) {
+                        let data = []
+                        //已评审
+                        data.push(res.hasScoreCount)
+                        // 已提交
+                        data.push(res.hasSubmitCount)
+                        // 未完成
+                        data.push(res.unDoneCount)
+                        // 未提交
+                        data.push(res.unSubmitCount)
+                        // 合格率
+                        data.push(parseFloat((res.hasScoreCount * 100 / res.teacherCount).toFixed(2)))
+                        this.topData.forEach((item, index) => {
+                            item.number = data[index]
+                        })
+                        this.tableData = res.groupMembers
+                        this.tableDataShow = this.tableData
+                    }
+                },
+                err => {
+
+                }
+            ).finally(() => {
+                this.loading = false
+            })
+        }
+    },
+    created() {
+        this.findTotalData()
+    }
+}
+</script>
+<style lang="less" scoped>
+@import "./Result.less";
+</style>

+ 26 - 0
TEAMModelOS/ClientApp/src/view/statistics/Statistics.less

@@ -0,0 +1,26 @@
+.statistics-container{
+    width: 100%;
+    height: 100%;
+}
+.statistics-header{
+    height: 45px;
+    line-height: 45px;
+    padding-left: 10px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    background-color: white;
+}
+.statistics-tab{
+    padding: 0px 10px;
+    display: inline-block;
+    line-height: 42px;
+    cursor: pointer;
+    color: #000;
+    margin-right: 20px;
+    user-select: none;
+    font-size: 15px;
+}
+.statistics-tab-active{
+    border-bottom: 2px solid #2b85e4;
+    color: #2b85e4;
+    font-weight: 600;
+}

+ 34 - 0
TEAMModelOS/ClientApp/src/view/statistics/Statistics.vue

@@ -0,0 +1,34 @@
+<template>
+    <div class="statistics-container">
+        <div class="statistics-header">
+            <span @click="tabClick('training')" :class="['statistics-tab',routerName == 'training' ? 'statistics-tab-active':'']">研修统计</span>
+            <span @click="tabClick('hour')" :class="['statistics-tab',routerName == 'hour' ? 'statistics-tab-active':'']">学时统计</span>
+            <span @click="tabClick('result')" :class="['statistics-tab',routerName == 'result' ? 'statistics-tab-active':'']">能力点统计</span>
+            <span @click="tabClick('groupData')" :class="['statistics-tab',routerName == 'groupData' ? 'statistics-tab-active':'']">教研组数据</span>
+        </div>
+        <router-view></router-view>
+    </div>
+</template>
+<script>
+export default {
+    data() {
+        return {
+            routerName: 'training'
+        }
+    },
+    methods: {
+        tabClick(router) {
+            this.$router.push({
+                name: router
+            })
+        },
+        
+    },
+    mounted(){
+        this.routerName = this.$route.name
+    }
+}
+</script>
+<style lang="less" scoped>
+@import "./Statistics.less";
+</style>

+ 0 - 0
TEAMModelOS/ClientApp/src/view/statistics/Training.less


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác