Pārlūkot izejas kodu

Merge branch 'develop' of http://52.130.252.100:10000/TEAMMODEL/TEAMModelOS into develop

zhouj1203@hotmail.com 1 gadu atpakaļ
vecāks
revīzija
58c7b16d5f
26 mainītis faili ar 1469 papildinājumiem un 93 dzēšanām
  1. 1 1
      TEAMModelBI/ClientApp/package.json
  2. 2 11
      TEAMModelBI/ClientApp/src/view/created/created.vue
  3. 3 46
      TEAMModelBI/ClientApp/src/view/login.vue
  4. 36 3
      TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs
  5. 29 0
      TEAMModelOS.SDK/Models/Cosmos/Common/LessonRecord.cs
  6. 83 1
      TEAMModelOS.SDK/Models/Cosmos/Common/StudentScoreRecord.cs
  7. 12 0
      TEAMModelOS.SDK/Models/Service/StudentLessonService.cs
  8. 1 0
      TEAMModelOS/ClientApp/public/lang/en-US.js
  9. 1 0
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  10. 1 0
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  11. 35 19
      TEAMModelOS/ClientApp/src/components/dashboard/student/BaseExamBar.vue
  12. 21 2
      TEAMModelOS/ClientApp/src/components/dashboard/studentAll/BaseLineBar.vue
  13. 1 1
      TEAMModelOS/ClientApp/src/components/public/frontEndMain/Index.vue
  14. 31 2
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/RecordView.vue
  15. 343 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/SmartRating.vue
  16. 143 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/SmartScore.vue
  17. 99 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/SmartVote.vue
  18. 1 1
      TEAMModelOS/ClientApp/src/view/artexam/Create.vue
  19. 34 2
      TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.vue
  20. 313 0
      TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SmartRating.vue
  21. 143 0
      TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SmartScore.vue
  22. 99 0
      TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SmartVote.vue
  23. 19 1
      TEAMModelOS/ClientApp/src/view/dashboard/study/BaseExamLineBar.vue
  24. 1 1
      TEAMModelOS/ClientApp/src/view/iot/schooliot.vue
  25. 16 1
      TEAMModelOS/Controllers/Student/StudentController.cs
  26. 1 1
      TEAMModelOS/TEAMModelOS.csproj

+ 1 - 1
TEAMModelBI/ClientApp/package.json

@@ -29,7 +29,7 @@
         "splitpanes": "^3.1.1",
         "vue": "^3.2.0",
         "vue-clipboard3": "^2.0.0",
-        "vue-i18n": "^9.2.0-beta.35",
+        "vue-i18n": "^9.2.2",
         "vue-loader-v16": "npm:vue-loader@^16.0.0-alpha.3",
         "vue-router": "^4.0.0-rc.5",
         "vue3-count-to": "^1.1.2",

+ 2 - 11
TEAMModelBI/ClientApp/src/view/created/created.vue

@@ -16,16 +16,8 @@
           <el-form :label-position="labelPosition" label-width="125px" :model="formArea" ref="formRef" :rules="areaRules">
             <el-form-item label="学区名称" class="special" prop="areaname.value">
               <div class="areanames">
-                <!-- <el-input v-model="formArea.areaname.value" disabled
-                            :placeholder="$t(`areaManages.createdArea.namehint`)"  
-                            v-show="formArea.areaname.state == false" size="medium" /> -->
                 <el-input v-model="formArea.areaname.value" placeholder="创建学区名称" />
               </div>
-              <!-- <div class="areanames-btn">
-                        <el-button type="primary" icon="el-icon-edit"
-                            v-if="formArea.areaname.state == false && formArea.areaname.value"
-                            @click="formArea.areaname.state = true"></el-button>
-                    </div> -->
             </el-form-item>
             <el-form-item :label="$t(`areaManages.createdArea.affiliation`)" class="belong" prop="institution">
               <div class="institutionbox">
@@ -579,8 +571,7 @@ import * as excel from '../../until/excel.js'
 import option from '@/static/region.json'
 import router from '@/router/index.js'
 import { useRoute } from 'vue-router'
-import { useI18n } from 'vue-i18n'
-import { FLIPPED_ALIAS_KEYS } from '@babel/types'
+ import { useI18n } from 'vue-i18n'
 import option_cn from '@/static/regions/region_cn.json'
 import option_gl from '@/static/regions/region_gl.json'
 const chinaData = new EluiChinaAreaDht.ChinaArea().chinaAreaflat
@@ -656,7 +647,7 @@ export default {
   components: { EluiChinaAreaDht, Search, StarFilled },
   setup () {
     const siteValue = window.location.host === 'localhost:5001' ? 'cn' : window.location.host === 'bi.teammodel.cn' ? 'cn' : window.location.host === 'bitest.teammodel.cn' ? 'cn' : 'international'
-    const { t } = useI18n()
+     const { t } = useI18n()
     const labelPosition = 'right'
     let { proxy } = getCurrentInstance()
     const store = useStore()

+ 3 - 46
TEAMModelBI/ClientApp/src/view/login.vue

@@ -2,60 +2,18 @@
   <div class="backgorundbox">
     <div class="loginbox">
       <div class="logintitle">{{ $t(`login.title`) }}</div>
-      <!-- <div class="usrpwd" v-if="loginModel">
-                <div class='userbox' style="margin-bottom:10%">
-                    <el-input v-model="user" placeholder="醍摩豆ID/手机号码" prefix-icon="el-icon-user" />
-                </div>
-                <div class='userbox'>
-                    <el-input v-model="pwd" placeholder="密码" type="password" prefix-icon="el-icon-key" />
-                    <div class="loginbtn" v-show="user !='' && pwd !='' " @click="userlogin(proxy)"><img src="@/assets/img/login.png"></div>
-                </div>
-                <div class="not_has_more">
-                    或下列方式登录
-                    <div class="ddlogin" @click="loginModel=false"><img src="../assets/img/ddlogin.png"></div>
-                </div>
-            </div> -->
       <div class="usrpwd">
-        <ddlogin></ddlogin>
+        <DDlogin></DDlogin>
       </div>
-      <!-- <div class="accessbox">
-        <div class="access" title="IES5应用接入说明文档" @click="openSkip('movement')">
-          <el-icon>
-            <Share color="#fff" style="width:20px;height:20px" />
-          </el-icon>
-        </div>
-      </div> -->
-      <!-- <div class="cut" @click="loginModel = !loginModel">
-                <svg width="52" height="52" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" style="border-top-right-radius:10px">
-                    <mask id="id-3757926926-a" width="52" height="52" x="0" y="0" maskUnits="userSpaceOnUse">
-                        <path fill="#fff" d="M0 0l52 52V0H0z"></path>
-                    </mask>
-                    <g mask="url(#id-3757926926-a)">
-                        <path fill="#ccF" d="M0 0h48a4 4 0 014 4v48L0 0z"></path>
-                        <path fill="url(#pattern0)" d="M0 0h52v52H0z"></path>
-                    </g>
-                    <defs>
-                        <pattern id="pattern0" width="1" height="1" patternContentUnits="objectBoundingBox">
-                            <use transform="scale(.00216)" xlink:href="#image0"></use>
-                        </pattern>
-                        <image id="image0" width="463" height="463" :xlink:href="loginImg"></image>
-                    </defs>
-                </svg>
-            </div> -->
     </div>
     <div class="footerbox">
       <Footer></Footer>
     </div>
-    <!-- <div id="login_container" style="transform: scale(.8);">123456</div> -->
-    <!--绑定弹窗-->
-    <!-- <bind :callbackStatus=callbackStatus v-if="callbackStatus.state ===201" ref="comRef"></bind> -->
-    <!--绑定弹窗end-->
   </div>
 </template>
 <script>
 import { ref, watch, onMounted, getCurrentInstance, toRef, reactive } from 'vue'
-import ddlogin from './ddlogin.vue'
-import bind from './bindPhone.vue'
+import DDlogin from './ddlogin.vue'
 import Footer from '@/view/common/footer.vue'
 import { ElMessage, ElLoading } from 'element-plus'
 import { useRouter } from 'vue-router'
@@ -64,8 +22,7 @@ import jwt_decode from 'jwt-decode'
 import { Share } from '@element-plus/icons-vue'
 export default {
   components: {
-    ddlogin,
-    bind,
+    DDlogin,
     Share,
     Footer
   },

+ 36 - 3
TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs

@@ -1337,13 +1337,17 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                             //更新 基础统计信息
                             case "up-base":
 
-                                //如果有更新 则去读取/{_lessonId}/IES/base.json
+                                //读取TimeLine.json
                                 try
                                 {
-                                    BlobDownloadResult timelineblobDownload = await _azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{_lessonId}/IES/TimeLine.json").DownloadContentAsync();
-                                    TimeLineData timeLineData = timelineblobDownload.Content.ToObjectFromJson<TimeLineData>();
+                                    BlobDownloadResult timeLineBlobDownload = await _azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{_lessonId}/IES/TimeLine.json").DownloadContentAsync();
+                                    TimeLineData timeLineData = timeLineBlobDownload.Content.ToObjectFromJson<TimeLineData>();
                                     lessonRecord.hitaClientCmpCount = timeLineData.events.Where(z => !string.IsNullOrWhiteSpace(z.WrkCmpSrcType) && z.WrkCmpSrcType.Equals("HitaClientCmp")).Count();
                                     //lessonRecord.collateTaskCount = lessonRecord.hitaClientCmpCount;
+                                    List<TimeLineEvent> timeLineEvents=  timeLineData.events.FindAll(z => !string.IsNullOrWhiteSpace(z.Event) && z.Event.Equals("PickupResult"));
+                                    if (timeLineEvents.IsNotEmpty()) { 
+                                        
+                                    }
                                 }
                                 catch (Exception ex)
                                 {
@@ -1352,7 +1356,36 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                         await _dingDing.SendBotMsg($"TimeLine.json转换异常,{ex.Message}{ex.StackTrace},{lessonRecord.id}", GroupNames.成都开发測試群組);
                                     }
                                 }
+                                //读取Task.json
+                                try
+                                {
+                                    BlobDownloadResult taskBlobDownload = await _azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{_lessonId}/IES/Task.json").DownloadContentAsync();
+                                    List<TaskData> timeLineData = taskBlobDownload.Content.ToObjectFromJson<List<TaskData>>();
+                                   
+                                }
+                                catch (Exception ex)
+                                {
+                                    if (!ex.Message.Contains("The specified blob does not exist"))
+                                    {
+                                        await _dingDing.SendBotMsg($"Task.json转换异常,{ex.Message}{ex.StackTrace},{lessonRecord.id}", GroupNames.成都开发測試群組);
+                                    }
+                                }
+                                //读取IRS.json
+                                try
+                                {
+                                    BlobDownloadResult irsBlobDownload = await _azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{_lessonId}/IES/IRS.json").DownloadContentAsync();
+                                    List<IRSData> iRSDatas = irsBlobDownload.Content.ToObjectFromJson<List<IRSData>>();
 
+                                }
+                                catch (Exception ex)
+                                {
+                                    if (!ex.Message.Contains("The specified blob does not exist"))
+                                    {
+                                        await _dingDing.SendBotMsg($"IRS.json转换异常,{ex.Message}{ex.StackTrace},{lessonRecord.id}", GroupNames.成都开发測試群組);
+                                    }
+                                }
+
+                                //如果有更新 则去读取/{_lessonId}/IES/base.json
                                 try
                                 {
                                     // await _dingDing.SendBotMsg($"{_option.Location},课堂id:{_lessonId} 收到更新", GroupNames.醍摩豆服務運維群組);

+ 29 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/LessonRecord.cs

@@ -229,6 +229,33 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public int source { get; set; } = 0;
     }
+
+    public class TaskData { 
+        public string jobName { get; set; }
+        public string pageID { get; set; }
+        public int reciveCount { get; set; }
+        public string duration { get; set; }
+        public string collateType { get; set; }
+        public List<ClientWork> clientWorks { get; set; } = new List<ClientWork>();
+    }
+    public class ClientWork { 
+        public List<string> blobFiles { get; set; }= new List<string>();
+        public int seatID  { get; set; }
+        public string groupID { get; set; }
+        public bool isGroupItem { get; set; }
+        public string reciveTime { get; set; }
+        public int displayIndex {  get; set; }
+    }
+    public class IRSData
+    {
+        public string pageID { get; set; }
+        public bool isBuzz { get; set; }
+        public List<string> buzzClients { get; set; } = new List<string>();
+
+    }
+    public class IRSClientAnswer {
+        public Dictionary<string, List<List<string>>> clientAnswers { get; set; } = new Dictionary<string, List<List<string>>>();
+    }
     public class TimeLineData {
       public   List<TimeLineEvent> events { get; set; } = new List<TimeLineEvent>();
     }
@@ -236,9 +263,11 @@ namespace TEAMModelOS.SDK.Models
     {
         public double Time{ get; set; }
         public int EventId { get; set; }
+        public string Event { get; set; }
         public string WrkCmpSrcType { get; set; }
         public int  WrkCmpCount { get; set; }
         public int WrkType { get; set; }
+        public string PickupMemberId { get; set; }
     }
     public class LearningCategory {
         /// <summary>

+ 83 - 1
TEAMModelOS.SDK/Models/Cosmos/Common/StudentScoreRecord.cs

@@ -1,4 +1,5 @@
-using System;
+using NUnit.Framework.Internal.Execution;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -6,6 +7,70 @@ using System.Threading.Tasks;
 
 namespace TEAMModelOS.SDK.Models.Cosmos.Common
 {
+    public class TeacherSemesterRecord : CosmosEntity { 
+    
+    }
+
+    /// <summary>
+    /// 
+    /// 互动:
+//    答题:按反馈器。
+//isBuzz 抢权
+//互动 => TimeLine.json => PickupOption=>   挑人
+//差异化:推送给学生。???
+//绿灯数: 拉取所有的分数,然后 计算中位数,
+//教师喜欢的功能 :Event 事件,同页过滤。
+    /// 学生计分记录
+    /// </summary>
+    public class StudentSemesterRecord : CosmosEntity
+    {
+        // id 校内学生: 学年-学期id-学生id, 醍摩豆id: year-tmdid
+        //code  StudentSemesterRecord[-学校编码],  StudentSemesterRecord
+        //其他基础信息
+        public StudentSemesterRecord()
+        {
+            pk = "StudentSemesterRecord";
+        }
+        public string stuid { get; set; }
+        public string tmdid { get; set; }
+        public string userType { get; set; }
+        public string school { get; set; }
+        public int studyYear { get; set; }
+        public string semesterId { get; set; }
+
+        public List<StudentLessonRecord> lessonRecords { get; set; } = new List<StudentLessonRecord>();
+        //单独记录 组计分,个人积分,互动分,
+        /// <summary>
+        /// //组计分
+        /// </summary>
+        public double gscore { get; set; }
+        /// <summary>
+        ///  //个人计分
+        /// </summary>
+        public double pscore { get; set; }
+        /// <summary>
+        /// //互动计分
+        /// </summary>
+        public double tscore { get; set; } 
+        /// <summary>
+        /// 互动参与率
+        /// </summary>
+        public double interactRate { get; set; }
+        /// <summary>
+        /// 任务参与率
+        /// </summary>
+        public double taskRate { get; set; }
+        /// <summary>
+        /// 出席率
+        /// </summary>
+        public double attendRate { get; set; }
+        /// <summary>
+        /// 组任务参与率
+        /// </summary>
+        public double groupTaskRate { get; set; }
+    }
+
+
     /// <summary>
     /// 学生计分记录
     /// </summary>
@@ -75,6 +140,23 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
         /// 课例时间
         /// </summary>
         public long time { get; set; }
+        /// <summary>
+        /// 互动参与率
+        /// </summary>
+        public double interactRate { get; set; }
+        /// <summary>
+        /// 任务参与率
+        /// </summary>
+        public double taskRate { get; set; }
+        /// <summary>
+        /// 出席状态
+        /// </summary>
+        public int attend { get; set; }
+        /// <summary>
+        /// 组任务参与率
+        /// </summary>
+        public double groupTaskRate { get; set; }
+
         /// <summary>
         /// 名单信息
         /// </summary>

+ 12 - 0
TEAMModelOS.SDK/Models/Service/StudentLessonService.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TEAMModelOS.SDK.Models.Service
+{
+    public class StudentLessonService
+    {
+    }
+}

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

@@ -7555,6 +7555,7 @@ const LANG_EN_US = {
             studentTime: 'Total Accumulated Time of Student Participation',
             classroomTotal: 'Total Lesson Periods',
             classroomTime: 'Total Time of All Lessons',
+            totals:'totals'
         },
         classrooming: {
             title: 'Lesson Statistics',

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

@@ -7559,6 +7559,7 @@ const LANG_ZH_CN = {
             studentTime: '学生参与总时数',
             classroomTotal: '课堂总数',
             classroomTime: '课堂总时数',
+            totals:'总计',
         },
         classrooming: {
             title: '课中统计',

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

@@ -7555,6 +7555,7 @@ const LANG_ZH_TW = {
             studentTime: '學生參與總時數',
             classroomTotal: '課堂總數',
             classroomTime: '課堂總時數',
+            totals: '總計',
         },
         classrooming: {
             title: '應用統計',

+ 35 - 19
TEAMModelOS/ClientApp/src/components/dashboard/student/BaseExamBar.vue

@@ -9,7 +9,7 @@ export default {
     };
   },
   methods: {
-    doRender() {
+    doRender(examData) {
       let myChart = this.$echarts.init(document.getElementById("stuExamBar"));
       let option = {
         tooltip: {
@@ -39,11 +39,7 @@ export default {
         },
         xAxis: {
           type: "category",
-          data: [
-            "评测一",
-            "评测二",
-            "评测三"
-          ],
+          data: examData.nameArr,
           axisLabel: {
             textStyle: {
               fontFamily: "Hm",
@@ -122,7 +118,7 @@ export default {
                 ]),
               },
             },
-            data: [68, 45, 88, 77, 65, 67, 72, 85, 46],
+            data: examData.gradeScores,
           },
           {
             name: "班级平均",
@@ -142,7 +138,7 @@ export default {
                 ]),
               },
             },
-            data: [46, 75, 55, 75, 78, 66, 45, 78, 87],
+            data: examData.classScores,
           },
           {
             name: "学生得分率",
@@ -162,7 +158,7 @@ export default {
                 ]),
               },
             },
-            data: [77, 88, 66, 78, 45, 86, 78, 45, 68],
+            data: examData.stuScores,
           },
         ],
       };
@@ -174,17 +170,37 @@ export default {
     },
   },
   mounted() {
-    this.doRender();
+    // this.doRender();
+  },
+  watch: {
+    "$store.state.fiveEdu.dashEnv": {
+      immediate:true,
+      deep:true,
+      handler(n, o) {
+        console.error('env变化',n.envName)
+        console.error('env变化',n.envChildren)
+        let overbase = this.$store.state.fiveEdu.overbase
+        let examScores = overbase.newExam3Scores
+        let renderJson = {
+          nameArr:[],
+          countArr1:[],
+          countArr2:[],
+          countArr3:[],
+        }
+        if(examScores.length){
+          renderJson = {
+            nameArr: examScores.map(i => i.name),
+            gradeScores: examScores.map(i => parseInt(i.gradeScore * 100)),
+            classScores: examScores.map(i => parseInt(i.classScore * 100)),
+            stuScores: examScores.map(i => parseInt(i.data[0].value * 100)),
+          }
+        }
+        this.$nextTick(() => {
+          this.doRender(renderJson)
+        })
+      },
+    },
   },
-  //   watch: {
-  //     '$store.state.dashboard.artDashboard': {
-  //       deep: true,
-  //       immediate: true,
-  //       handler(n, o) {
-
-  //       }
-  //     }
-  //   }
 };
 </script>
 

+ 21 - 2
TEAMModelOS/ClientApp/src/components/dashboard/studentAll/BaseLineBar.vue

@@ -6,7 +6,8 @@
 		data() {
 			return {
 				option: null,
-				averageArr: []
+				averageArr: [],
+				examNameArr:[]
 			};
 		},
 		methods: {
@@ -19,7 +20,8 @@
 						axisPointer: {
 							// 坐标轴指示器,坐标轴触发有效
 							type: "shadow" // 默认为直线,可选为:'line' | 'shadow'
-						}
+						},
+						formatter: '{a} <br> {b} 得分率: {c} %'
 					},
 					grid: {
 						left: "2%",
@@ -247,6 +249,22 @@
 						}
 					]
 				};
+				console.log(that.examNameArr);
+				option.legend.data = that.examNameArr
+				if(that.examNameArr[0]){
+					option.series[0].name = that.examNameArr[0]
+				}
+				if(that.examNameArr[1]){
+					option.series[1].name = that.examNameArr[1]
+				}else{
+					option.series = option.series.slice(0,1)
+				}
+				if(that.examNameArr[2]){
+					option.series[2].name = that.examNameArr[2]
+				}else{
+					option.series = option.series.slice(0,2)
+				}
+				console.log(option);
 				myChart.clear();
 				myChart.setOption(option);
 				window.addEventListener("resize", function () {
@@ -271,6 +289,7 @@
 						countArr3: []
 					};
 					if (examScores.length) {
+						this.examNameArr = examScores.map(i => i.name)
 						renderJson = {
 							nameArr: examScores[0].data.map((i) => i.name),
 							countArr1: examScores[0].data.map((i) => parseInt(i.value * 100)),

+ 1 - 1
TEAMModelOS/ClientApp/src/components/public/frontEndMain/Index.vue

@@ -180,7 +180,7 @@ export default {
             return host.includes(item.host)
         })
         console.log(cur,this.$route.name, this.$route.from,'mingzimingzi')
-        if (cur && this.$route.name == 'login') {
+        if (cur && (this.$route.name == 'login' || this.$route.name == 'zylogin')) {
             this.isShowTMD = cur.isShowTMD
             this.$router.push({
                 name: cur.router,

+ 31 - 2
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/RecordView.vue

@@ -133,6 +133,7 @@
                                     <Button type="warning" :disabled="!filtertype.task" @click="filterFn('task')">{{ $t("cusMgt.rcd.filter3") }}<!-- ({{ filtertype.task }}) --></Button>
                                     <Button type="warning" :disabled="!filtertype.irs" @click="filterFn('irs')">{{ $t("cusMgt.rcd.filter4") }}<!-- ({{ filtertype.irs }}) --></Button>
                                     <Button type="warning" :disabled="!filtertype.exam" @click="filterFn('exam')">{{ $t("cusMgt.rcd.filter5") }}<!-- ({{ filtertype.exam }}) --></Button>
+                                    <Button type="warning" :disabled="!filtertype.smart" @click="filterFn('smart')">智慧评分<!-- ({{ filtertype.exam }}) --></Button>
                                 </div>
                             </div>
                         </div>
@@ -172,6 +173,7 @@
                                                             <Pick class="event-item student-event" v-if="event.Event === 'PickupResult' && baseData" :pickData="event.data" :students="baseData.student"></Pick>
                                                             <!-- 课中评测 -->
                                                             <Exam class="student-event event-item" :examInfo="event.data" :recordInfo="recordInfo" v-if="event.Event === 'SPQStrt'"></Exam>
+                                                            <SmartRating class="event-item student-event" :recordInfo="recordInfo" :smartRate="event.data" :students="baseData.student" :vote="event.vote" :sas="sasRecd" v-else-if="event.Event === 'RatingStart'"></SmartRating>
                                                         </div>
                                                     </template>
                                                 </div>
@@ -217,12 +219,13 @@ import ReceiveBack from './ReceiveBack.vue';
 import Pick from './Pick.vue';
 import Exam from './Exam.vue';
 import myWorks from './myWorks.vue';
+import SmartRating from './SmartRating.vue';
 
 export default {
     components: {
         RcdPoster,
         Loading,
-        DataCount, ShowQues, PopQues, Buzr, Push,
+        DataCount, ShowQues, PopQues, Buzr, Push, SmartRating,
         StuReceive, ReceiveBack, Pick, Exam, myWorks,
     },
     data () {
@@ -285,6 +288,7 @@ export default {
             pushData: [], //push.json
             irsData: [], //irs.json
             taskData: [], //task.json
+            smartData: [],//smartRating.json
             fnEvents: [], //功能事件
             events: [], //事件ID
             hiTeachEvent: [], //需要解析的事件信息
@@ -300,6 +304,7 @@ export default {
                 task: 0, //任务
                 irs: 0, //互动
                 exam: 0, //测验
+                smart: 0, //智慧评分
             },
             myWorks: [],
             haveInteraction: true,
@@ -494,8 +499,9 @@ export default {
             this.pushData = []
             this.irsData = []
             this.taskData = []
+            this.smartData = []
             this.baseData = undefined
-            this.filtertype = {push: 0, task: 0, irs: 0, exam: 0}
+            this.filtertype = {push: 0, task: 0, irs: 0, exam: 0, smart: 0}
             let sas = await this.$tools.getBlobSas(this.recordInfo.scope === 'school' ? this.recordInfo.school : this.recordInfo.tmdid)
             this.sasRecd = sas
             this.recordInfo.eNote = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Note.pdf?${sas.sas}`
@@ -582,6 +588,12 @@ export default {
             } catch (e) {
                 this.baseData = undefined
             }
+            try {
+                let smartUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/SmartRating.json?${sas.sas}`
+                this.smartData = JSON.parse(await this.$tools.getFile(smartUrl) || '[]')
+            } catch (e) {
+                this.smartData = []
+            }
 
             //这里需要判断录制开始的pageid
             let startInfo = pageEvents.find(item => item.Event === 'EzsStartRecord')
@@ -629,6 +641,23 @@ export default {
                             this.filtertype.exam += 1
                             e.data = this._.cloneDeep(e)
                             break
+                        case 'smart':
+                            e.data = this.smartData.find(t => t.pageID == e.Pgid)
+                            e.data.smartRateSummary.rateInfo = {
+                                RatingSource: e.RatingSource,// 互评的作品类型 IRS:文字题 StudentWork:图片
+                                AnonyCandi: e.AnonyCandi,// 评论是否匿名
+                                votes: e.Votes,// 可投票数量
+                            }
+                            // 按照轮数返回的
+                            e.vote = undefined
+                            if(e.RatingType === 'Voting') {
+                                e.vote = {
+                                    round: e.Round,
+                                    votes: e.Votes
+                                }
+                            }
+                            this.filtertype.smart += 1
+                        break
                         default:
                             break
                     }

+ 343 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/SmartRating.vue

@@ -0,0 +1,343 @@
+<template>
+    <div>
+        <!-- 
+            scoreDetailResult:星光大评分
+            voteDetailResult:投票
+            mutualSummary:互评
+            mutualDetailSummary:互评数据
+         -->
+        <div class="smart-wrap">
+            <p class="clt-type">{{ smartType.name }}:</p>
+            <template v-if="smartType.value === 'vote'">
+                <!-- <div v-for="(item, index) in scoreListNew" :key="index" style="margin-bottom: 10px;"> -->
+                    第{{ vote.round }}轮({{ vote.votes }}票)
+                    <Icon title="查看评语" type="md-chatbubbles" size="17" color="#2EC7C9" @click="openComment('vote')" style="cursor: pointer; margin-top: 3px; margin-right: 5px;" />
+                    <SmartVote :smartData="scoreListNew"></SmartVote>
+                <!-- </div> -->
+            </template>
+            <template v-else-if="smartType.value === 'score'">
+                <SmartScore :smartData="scoreListNew"></SmartScore>
+            </template>
+            <template v-else>
+                <div v-for="(item, index) in scoreListNew" :key="index" class="smart-list">
+                    <p style="height: 21px;">
+                        <Icon type="md-trophy" v-if="item.king" color="#ff880d" />
+                    </p>
+                    <p>
+                        <span style="color: #2d8cf0;">{{ item.name }}</span>
+                        <Icon type="md-chatbubbles" color="#2EC7C9" style="cursor: pointer;" @click="openComment('mutal', index, item)"
+                                v-show="!smartRate.smartRateSummary.rateInfo.AnonyCandi" />
+                    </p>
+                    <p>平均得分:{{ item.result }}</p>
+                    <img v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork'" :src="item.material" @click="$hevueImgPreview(item.material)" />
+                    <p v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'IRS'" v-html="item.material" class="smart-material"></p>
+                </div>
+            </template>
+        </div>
+        <StudentClient></StudentClient>
+        <Modal v-model="isComment" title="评语" :footer-hide="true">
+            <div class="mutal-info" v-if="mutalInfo">
+                <img v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork'" :src="mutalInfo" />
+                <p v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'IRS'" v-html="mutalInfo"></p>
+            </div>
+            <div v-for="(item, index) in showResult" :key="index" style="margin-bottom: 5px;">
+                <span style="color: #2d8cf0;">
+                    {{ item.id === '-1' ? '别人给我的评语' : item.name }}
+                </span>:
+                <span>{{ item.details }}</span>
+            </div>
+            <div v-if="!showResult.length">暂无评语</div>
+        </Modal>
+    </div>
+</template>
+
+<script>
+import StudentClient from '@/view/classrecord/eventchart/StudentClient.vue'
+import SmartVote from './SmartVote.vue'
+import SmartScore from './SmartScore.vue'
+export default {
+    components: {
+        StudentClient,
+        SmartVote,
+        SmartScore
+    },
+    props: {
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        smartRate: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        vote: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        sas: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+    },
+    data () {
+        return {
+            scoreListNew: [],
+            showResult: [],
+            isComment: false,
+            allResult: [],
+            mutalInfo: '',
+        }
+    },
+    computed: {
+        smartType() {
+            let types = {
+                value: '',
+                name: ''
+            }
+            if(this.smartRate.smartRateSummary.scoreDetailResult && Object.keys(this.smartRate.smartRateSummary.scoreDetailResult).length) {
+                types = {
+                    value: 'score',
+                    name: "星光大评分"
+                }
+            } else if(this.smartRate.smartRateSummary.voteDetailResult && Object.keys(this.smartRate.smartRateSummary.voteDetailResult).length) {
+                types = {
+                    value: 'vote',
+                    name: "投票"
+                }
+            } else if(this.smartRate.smartRateSummary.mutualSummary && Object.keys(this.smartRate.smartRateSummary.mutualSummary).length) {
+                types.value = 'mutual'
+                if(this.smartRate.smartRateSummary.mutualSummary.mutualType === 'All') {
+                    types.name = "每人多件评分"
+                } else if(this.smartRate.smartRateSummary.mutualSummary.mutualType === 'Two') {
+                    types.name = "随机分配互评"
+                } else {
+                    types.name = "自评"
+                }
+            }
+            return types
+        },
+    },
+    created () {
+    },
+    mounted () {
+        this.getSmartInfo()
+    },
+    methods: {
+        async getSmartInfo() {
+            let lists = []
+            let stuInfo = this.students.find(stu => stu.id === this.$store.state.userInfo.sub)
+            let seatID = stuInfo.seatID.toString()
+            if(this.smartType.value === 'vote') {
+                let objectInd = (this.vote.round - 1).toString()
+                lists = this.smartRate.smartRateSummary.meteor_VoteSummary[objectInd].sort((a, b) => {
+                    return a.result - b.result
+                })
+                lists = lists.map(item => {
+                    if(item.id === seatID) {
+                        // id:'-1' 别人给自己的评语
+                        this.allResult.push({
+                            details: item.comment,
+                            id: '-1',
+                            name: ''
+                        })
+                    }
+                    if(item.result === lists[0].result) {
+                        item.king = true
+                    }
+                    return item
+                })
+                let voteSummary = [...this.smartRate.smartRateSummary.voteDetailResult[objectInd]]
+                this.allResult.push(voteSummary.find(item => {
+                    return item.id === seatID
+                }))
+            } else {
+                let allResultList = {}
+                if(this.smartType.value === 'score') {
+                    lists = this.smartRate.smartRateSummary.meteor_ScoreSummary.sort((a, b) => {
+                        return a.result - b.result
+                    })
+                    let giveMe = lists.find(item => {
+                        return item.id === seatID
+                    })
+                    if(giveMe) {
+                        this.allResult.push({
+                            details: giveMe.comment,
+                            id: '-1',
+                            name: ''
+                        })
+                    }
+                    allResultList = {...this.smartRate.smartRateSummary.scoreDetailResult}
+                } else if(this.smartType.value === 'mutual') {
+                    lists = this.smartRate.smartRateSummary.mutualSummary.mutualResults.sort((a, b) => {
+                        return b.result - a.result
+                    })
+                    allResultList = {...this.smartRate.smartRateSummary.mutualDetailSummary}
+                }
+                // 非通用作品&&互评,需要找出当前学生的内容
+                if(!lists[0].isGeneral && this.smartType.value === 'mutual') {
+                    let s = lists.find(item => {
+                        return item.id === seatID
+                    })
+                    if(this.smartRate.smartRateSummary.mutualSummary.materialInfos.length) {
+                        let materialInfos = this.smartRate.smartRateSummary.mutualSummary.materialInfos.find(info => {
+                            return info.id === s.id
+                        })
+                        // IRS:文字题 StudentWork:图片
+                        if(this.smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork') {
+                            s.material = `${this.sas.url}/${this.sas.name}/records/${this.recordInfo.id}${materialInfos.material}?${this.sas.sas}`
+                        } else {
+                            s.material = materialInfos.material
+                        }
+                    } else  {
+                        s.material = ''
+                    }
+                    lists = [s]
+                } else {
+                    lists = lists.map(item => {
+                        item.material = ''
+                        if(item.result === lists[0].result) {
+                            item.king = true
+                        }
+                        if(this.smartType.value === 'mutual' && this.smartRate.smartRateSummary.mutualSummary.materialInfos.length) {
+                            let materialInfos = this.smartRate.smartRateSummary.mutualSummary.materialInfos.find(info => {
+                                return info.id === item.id
+                            })
+                            // IRS:文字题 StudentWork:图片
+                            if(this.smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork') {
+                                item.material = `${this.sas.url}/${this.sas.name}/records/${this.recordInfo.id}${materialInfos.material}?${this.sas.sas}`
+                            } else {
+                                item.material = materialInfos.material
+                            }
+                        }
+                        return item
+                    })
+                }
+
+                let results = new Array(lists.length)
+                for (let i = 0; i < results.length; i++) {
+                    results[i] = new Array()
+                }
+                for (let k in allResultList) {
+                    let studentInfo = this.students.find(item => {
+                        let kk = Number(k)
+                        return item.seatID === kk
+                    })
+                    // 通用展示所有 && 自己写给别人的评语
+                    if(lists[0].isGeneral || seatID === k) {
+                        for (let j in allResultList[k]) {
+                            let index = lists.findIndex(item => {
+                                return item.id === j
+                            })
+                            if(index != -1) {
+                                results[index].push({
+                                    details: allResultList[k][j],
+                                    id: k,
+                                    name: studentInfo.name
+                                })
+                            }
+                        }
+                    } else if(!this.smartRate.smartRateSummary.rateInfo.AnonyCandi && allResultList[k][seatID]) {
+                        // 互评为匿名,则不能看别人给自己的评语
+                        let index = lists.findIndex(item => {
+                            return item.id === seatID
+                        })
+                        if(index != -1) {
+                            // id:'-1' 别人给自己的评语
+                            let other = results[index].findIndex(item => {
+                                return item.id === '-1'
+                            })
+                            if(other != -1) {
+                                results[index][other].details = results[index][other].details + allResultList[k][seatID]
+                            } else {
+                                results[index].unshift({
+                                    details: allResultList[k][seatID],
+                                    id: '-1',
+                                    name: ''
+                                })
+                            }
+                        }
+                    }
+                }
+                this.allResult = results
+            }
+            this.scoreListNew = lists
+        },
+        openComment(type, index, item) {
+            if(type === 'vote') {
+                this.showResult = [...this.allResult]
+            } else {
+                this.mutalInfo = ''
+                if(type === 'mutal') {
+                    this.mutalInfo = item.material
+                }
+                this.showResult = [...this.allResult[index]]
+            }
+            this.isComment = true
+        },
+    },
+}
+</script>
+
+<style lang="less" scoped>
+.smart-wrap {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: end;
+    position: relative;
+
+    .clt-type {
+        margin-right: 10px;
+        font-size: 15px;
+        font-weight: 600;
+    }
+
+    .smart-vote {
+        width: 200px;
+        height: 200px;
+    }
+}
+.smart-list {
+    width: 150px;
+    text-align: center;
+    margin-right: 20px;
+
+    img {
+        max-width: 150px;
+        max-height: 150px;
+        border: 1px solid #eeeeee;
+        cursor: pointer;
+    }
+
+    .smart-material {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        width: 150px;
+    }
+}
+</style>
+<style lang="less">
+.mutal-info {
+    border-bottom: 1px #ccc dashed;
+    margin-bottom: 10px;
+    padding-bottom: 5px;
+
+    img {
+        width: 300px;
+    }
+}
+</style>

+ 143 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/SmartScore.vue

@@ -0,0 +1,143 @@
+<template>
+    <div>
+        <Icon type="md-trophy" size="25" color="#ff880d" class="trophy" />
+        <div class="ev-score-matrix" :id="'vote'+id"></div>
+        <p>
+            查看评语:
+            <span v-for="(item, index) in smartData" :key="index" class="show-name" @click="openResult(index)">{{ item.name }}</span>
+        </p>
+    </div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        smartData: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        return {
+            id: '',
+            progressPie: undefined,
+            option: {},
+            dataName: [],
+        }
+    },
+    mounted() {
+        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+        this.progressPie.setOption(this.option)
+        let erd11 = elementResizeDetectorMaker()
+        erd11.listenTo(document.getElementById("vote" + this.id), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.progressPie.resize()
+            })
+        })
+    },
+    created() {
+        this.id = this.$jsFn.getBtwRandom(0, 100000000)
+    },
+    watch: {
+        smartData: {
+            handler(n, o) {
+                let funnelData = []
+                n.forEach(item => {
+                    this.dataName.push(item.name)
+                    funnelData.push({
+                        value: item.result,
+                        name: item.isGeneral ? item.name : `${item.name}(${item.id})`
+                    })
+                })
+                this.$nextTick(() => {
+                    console.log('成绩分布数据:', n)
+                    this.option = {
+                        /* title: {
+                            text: 'Funnel'
+                        }, */
+                        tooltip: {
+                            trigger: 'item',
+                            formatter: '{b} : {c}'
+                        },
+                        /* legend: {
+                            data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
+                        }, */
+                        series: [
+                            {
+                                name: 'Funnel',
+                                type: 'funnel',
+                                left: '5%',
+                                top: 10,
+                                bottom: 10,
+                                width: '75%',
+                                // min: 0,
+                                // max: 100,
+                                minSize: '0%',
+                                maxSize: '100%',
+                                sort: 'descending',
+                                // gap: 2,
+                                labelLine: {
+                                    length: 10,
+                                    lineStyle: {
+                                        width: 1,
+                                        type: 'solid'
+                                    }
+                                },
+                                itemStyle: {
+                                    borderColor: '#fff',
+                                    borderWidth: 1
+                                },
+                                emphasis: {
+                                    label: {
+                                        fontSize: 20
+                                    }
+                                },
+                                label: {
+                                    formatter: '{b}: {c}',
+                                },
+                                data: funnelData
+                            }
+                        ]
+                    }
+                    if (!this.progressPie) {
+                        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+                    }
+                    this.progressPie.setOption(this.option, true)
+                })
+            },
+            deep: true,
+            immediate: true
+        }
+    },
+    methods: {
+        openResult(index) {
+            this.$parent.openComment('score', index)
+        },
+    }
+}
+</script>
+<style scoped lang="less">
+.trophy {
+    position: absolute;
+    top: 10px;
+    left: 47%;
+    z-index: 5;
+}
+.ev-score-matrix {
+    width: 400px;
+    height: 200px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-right: 10px;
+    padding-right: 20px;
+}
+.show-name {
+    color: #2ec7c9;
+    cursor: pointer;
+    margin-right: 10px;
+}
+</style>
+<style>
+</style>

+ 99 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/SmartVote.vue

@@ -0,0 +1,99 @@
+<template>
+    <div class="ev-score-matrix" :id="'vote'+id"></div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        smartData: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        return {
+            id: '',
+            progressPie: undefined,
+            option: {}
+        }
+    },
+    mounted() {
+        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+        this.progressPie.setOption(this.option)
+        let erd11 = elementResizeDetectorMaker()
+        erd11.listenTo(document.getElementById("vote" + this.id), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.progressPie.resize()
+            })
+        })
+    },
+    created() {
+        this.id = this.$jsFn.getBtwRandom(0, 100000000)
+    },
+    watch: {
+        smartData: {
+            handler(n, o) {
+                let funnelName = []
+                let funnelData = []
+                n.forEach(item => {
+                    funnelName.push(item.isGeneral ? item.name : `${item.name}(${item.id})`)
+                    funnelData.push(item.result)
+                })
+                this.$nextTick(() => {
+                    this.option = {
+                        tooltip: {
+                            trigger: 'axis',
+                            axisPointer: {
+                                type: 'shadow'
+                            }
+                        },
+                        grid: {
+                            top: '10%',
+                            left: '5%',
+                            right: '4%',
+                            bottom: '3%',
+                            containLabel: true
+                        },
+                        xAxis: {
+                            type: 'value'
+                        },
+                        yAxis: {
+                            type: 'category',
+                            data: funnelName
+                        },
+                        series: [
+                            {
+                                data: funnelData,
+                                type: 'bar',
+                                label: {
+                                    show: true,
+                                    position: 'right',
+                                },
+                            }
+                        ]
+                    }
+                    if (!this.progressPie) {
+                        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+                    }
+                    this.progressPie.setOption(this.option, true)
+                })
+            },
+            deep: true,
+            immediate: true
+        }
+    }
+}
+</script>
+<style scoped lang="less">
+.ev-score-matrix {
+    width: 500px;
+    height: 200px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-right: 10px;
+}
+</style>
+<style>
+</style>

+ 1 - 1
TEAMModelOS/ClientApp/src/view/artexam/Create.vue

@@ -538,7 +538,7 @@ export default {
               {
                 id: setting.subject,
                 name:
-                  setting.subject == "music" ? this.$t('ae.ae10') : this.$t('ae.ae11')
+                  setting.subject === "subject_music" ? this.$t('ae.ae10') : this.$t('ae.ae11')
               }
             ],
             papers: [apiPapers],

+ 34 - 2
TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.vue

@@ -162,6 +162,8 @@
                       <Pick class="event-item student-event" :pickData="event.data" v-else-if="event.Event === 'PickupResult'" :students="baseData.student"></Pick>
                       <!-- 课中评测 -->
                       <Exam class="event-item" :examInfo="event.data" :recordInfo="recordInfo" v-else-if="event.Event === 'SPQStrt'"></Exam>
+                      <!-- 智慧评分 -->
+                      <SmartRating class="event-item student-event" :recordInfo="recordInfo" :smartRate="event.data" :students="baseData.student" :vote="event.vote" v-else-if="event.Event === 'RatingStart'"></SmartRating>
                     </div>
                   </div>
                 </div>
@@ -259,13 +261,14 @@ import Push from './eventchart/Push.vue'
 import Exam from './eventchart/Exam.vue'
 import Receive from './eventchart/Receive.vue'
 import DataCount from './eventchart/DataCount.vue'
+import SmartRating from './eventchart/SmartRating.vue';
 import CountTo from 'vue-count-to'
 import BlobTool from '@/utils/blobTool.js'
 import FileSaver from "file-saver";
 import JSZip from "jszip";
 export default {
   components: {
-    PopQues, Pick, Push, Receive, DataCount, CountTo, Buzr, Exam, WrkCmp, BaseReportPie, BaseReportRadar
+    PopQues, Pick, Push, Receive, DataCount, CountTo, Buzr, Exam, WrkCmp, BaseReportPie, BaseReportRadar, SmartRating
   },
   data() {
     return {
@@ -299,6 +302,12 @@ export default {
           text: this.$t('cusMgt.rcd.filter5'),
           events: ['SPQStrt'],
           count: 0
+        },
+        {
+          value: 'SmartRating',
+          text: "智慧评分",
+          events: ['RatingStart'],
+          count: 0
         }
       ],
       split1: 0.4,
@@ -307,6 +316,7 @@ export default {
       pushData: [],//push.json
       irsData: [],//irs.json
       taskData: [],//task.json
+      smartData: [],//smartRating.json
       fnEvents: [],//功能事件
       events: [],//事件ID
       hiTeachEvent: [],//需要解析的事件信息
@@ -514,7 +524,7 @@ export default {
         return
       }
       // 时间轴数据正常
-      //获取Push.json、IRS.json、Task.json、Base.json数据
+      //获取Push.json、IRS.json、Task.json、Base.json数据  新增SmartRating.json(智慧评分)
       try {
         let pushUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/Push.json?${blobInfo.blob_sas}`
         this.pushData = JSON.parse(await this.$tools.getFile(pushUrl) || '[]')
@@ -542,6 +552,12 @@ export default {
       } catch (e) {
         this.baseUrl = {}
       }
+      try {
+        let smartUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/SmartRating.json?${blobInfo.blob_sas}`
+        this.smartData = JSON.parse(await this.$tools.getFile(smartUrl) || '[]')
+      } catch (e) {
+        this.smartData = []
+      }
       let pgids = this.pageIds
       //这里需要判断录制开始的pageid
       // let startInfo = this.pageEvents?.findLast(item => item.Event === 'EzsStartRecord')
@@ -594,6 +610,22 @@ export default {
               e.data = this._.cloneDeep(e)
               this.filterInte[4].count++
               break
+            case 'smart':
+              e.data = this.smartData.find(t => t.pageID == e.Pgid)
+              e.data.smartRateSummary.rateInfo = {
+                  RatingSource: e.RatingSource,// 互评的作品类型 IRS:文字题 StudentWork:图片
+                  AnonyCandi: e.AnonyCandi,// 评论是否匿名
+              }
+              // 按照轮数返回的
+              e.vote = undefined
+              if(e.RatingType === 'Voting') {
+                  e.vote = {
+                      round: e.Round,
+                      votes: e.Votes
+                  }
+              }
+              this.filterInte[5].count++
+              break
             default:
               break
           }

+ 313 - 0
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SmartRating.vue

@@ -0,0 +1,313 @@
+<template>
+    <div>
+        <!-- 
+            scoreDetailResult:星光大评分
+            voteDetailResult:投票
+            mutualSummary:互评
+            mutualDetailSummary:互评数据
+         -->
+        <div class="smart-wrap">
+            <p class="clt-type">
+                {{ smartType.name }}
+                <Icon type="md-eye-off" title="匿名提交" v-if="smartRate.smartRateSummary.rateInfo.AnonyCandi" />
+                :
+            </p>
+            <template v-if="smartType.value === 'vote'">
+                <!-- <div v-for="(item, index) in scoreListNew" :key="index" style="margin-bottom: 10px;"> -->
+                    第{{ vote.round }}轮({{ vote.votes }}票)
+                    <Icon title="查看评语" type="md-chatbubbles" size="17" color="#2EC7C9" @click="openComment('vote')" style="cursor: pointer; margin-top: 3px; margin-right: 5px;" />
+                    <SmartVote :smartData="scoreListNew"></SmartVote>
+                <!-- </div> -->
+            </template>
+            <template v-else-if="smartType.value === 'score'">
+                <SmartScore :smartData="scoreListNew"></SmartScore>
+            </template>
+            <template v-else>
+                <div v-for="(item, index) in scoreListNew" :key="index" class="smart-list">
+                    <p style="height: 21px;">
+                        <Icon type="md-trophy" v-if="item.king" color="#ff880d" />
+                    </p>
+                    <p>
+                        <span style="color: #2d8cf0;">{{ item.name }}
+                            <span v-show="!item.isGeneral">({{ item.id }})</span>
+                        </span>
+                        <Icon type="md-chatbubbles" color="#2EC7C9" style="cursor: pointer;" @click="openComment('mutal', index, item)" />
+                    </p>
+                    <p>平均得分:{{ item.result }}</p>
+                    <img v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork'" :src="item.material" @click="$hevueImgPreview(item.material)" />
+                    <p v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'IRS'" v-html="item.material" class="smart-material"></p>
+                </div>
+            </template>
+        </div>
+        <StudentClient></StudentClient>
+        <Modal v-model="isComment" title="评语" :footer-hide="true">
+            <div class="mutal-info" v-if="mutalInfo">
+                <img v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork'" :src="mutalInfo.material" />
+                <p v-if="smartRate.smartRateSummary.rateInfo.RatingSource === 'IRS'" v-html="mutalInfo.material"></p>
+                
+                <Carousel v-model="mutalInfoCar" :radius-dot="true" loop class="svg-box">
+                    <CarouselItem v-for="(item, index) in mutalInfo.obtained" :key="index">
+                        <span v-html="item.strokeComment"></span>
+                    </CarouselItem>
+                </Carousel>
+                <!-- <span class="svg-box" v-for="(item, index) in mutalInfo.obtained" :key="index" v-html="item.strokeComment"></span> -->
+            </div>
+            <div v-for="(item, index) in showResult" :key="index" style="margin-bottom: 5px;">
+                <span style="color: #2d8cf0;">{{ item.name }}</span>:
+                <span>{{ item.details }}</span>
+            </div>
+            <div v-if="!showResult.length">暂无评语</div>
+        </Modal>
+    </div>
+</template>
+
+<script>
+import StudentClient from './StudentClient.vue'
+import SmartVote from './SmartVote.vue'
+import SmartScore from './SmartScore.vue'
+export default {
+    components: {
+        StudentClient,
+        SmartVote,
+        SmartScore
+    },
+    props: {
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        smartRate: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        vote: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+    },
+    data () {
+        return {
+            scoreListNew: [],
+            showResult: [],
+            isComment: false,
+            allResult: [],
+            mutalInfo: undefined,
+            mutalInfoCar: 0,
+        }
+    },
+    computed: {
+        smartType() {
+            let types = {
+                value: '',
+                name: ''
+            }
+            if(this.smartRate.smartRateSummary.scoreDetailResult && Object.keys(this.smartRate.smartRateSummary.scoreDetailResult).length) {
+                types = {
+                    value: 'score',
+                    name: "星光大评分"
+                }
+            } else if(this.smartRate.smartRateSummary.voteDetailResult && Object.keys(this.smartRate.smartRateSummary.voteDetailResult).length) {
+                types = {
+                    value: 'vote',
+                    name: "投票"
+                }
+            } else if(this.smartRate.smartRateSummary.mutualSummary && Object.keys(this.smartRate.smartRateSummary.mutualSummary).length) {
+                types.value = 'mutual'
+                if(this.smartRate.smartRateSummary.mutualSummary.mutualType === 'All') {
+                    types.name = "每人多件评分"
+                } else if(this.smartRate.smartRateSummary.mutualSummary.mutualType === 'Two') {
+                    types.name = "随机分配互评"
+                } else {
+                    types.name = "自评"
+                }
+            }
+            return types
+        },
+    },
+    created () {
+    },
+    mounted () {
+        this.getSmartInfo()
+    },
+    methods: {
+        getSmartInfo() {
+            let lists = []
+            if(this.smartType.value === 'vote') {
+                /* let voteLength = Object.keys(this.smartRate.smartRateSummary.meteor_VoteSummary).length
+                for (let i = 0; i < voteLength; i++) {
+                    let objectInd = i.toString()
+                    let info = {
+                        round: thi,
+                        data: this.smartRate.smartRateSummary.meteor_VoteSummary[objectInd].sort((a, b) => {
+                            return a.result - b.result
+                        })
+                    }
+                } */
+                let objectInd = (this.vote.round - 1).toString()
+                lists = this.smartRate.smartRateSummary.meteor_VoteSummary[objectInd].sort((a, b) => {
+                    return a.result - b.result
+                })
+                lists = lists.map(item => {
+                    if(item.result === lists[0].result) {
+                        item.king = true
+                    }
+                    return item
+                })
+                
+                this.allResult = [...this.smartRate.smartRateSummary.voteDetailResult[objectInd]]
+            } else {
+                let allResultList = {}
+                if(this.smartType.value === 'score') {
+                    lists = this.smartRate.smartRateSummary.meteor_ScoreSummary.sort((a, b) => {
+                        return a.result - b.result
+                    })
+                    allResultList = {...this.smartRate.smartRateSummary.scoreDetailResult}
+                } else if(this.smartType.value === 'mutual') {
+                    lists = this.smartRate.smartRateSummary.mutualSummary.mutualResults.sort((a, b) => {
+                        return b.result - a.result
+                    })
+                    allResultList = {...this.smartRate.smartRateSummary.mutualDetailSummary}
+                }
+                lists = lists.map(item => {
+                    item.material = ''
+                    if(item.result === lists[0].result) {
+                        item.king = true
+                    }
+                    if(this.smartType.value === 'mutual' && this.smartRate.smartRateSummary.mutualSummary.materialInfos.length) {
+                        let materialInfos = this.smartRate.smartRateSummary.mutualSummary.materialInfos.find(info => {
+                            return info.id === item.id
+                        })
+                        console.log(materialInfos);
+                        // IRS:文字题 StudentWork:图片
+                        if(this.smartRate.smartRateSummary.rateInfo.RatingSource === 'StudentWork') {
+                            let blobInfo = this.recordInfo.scope === 'school' ? this.$store.state.user.schoolProfile : this.$store.state.user.userProfile
+                            item.material = `${blobInfo.blob_uri}/records/${this.recordInfo.id}${materialInfos.material}?${blobInfo.blob_sas}`
+                            item.obtained = materialInfos.obtained.comments
+                            item.studentCom = materialInfos.obtained.comments
+                        } else {
+                            item.material = materialInfos.material
+                            item.obtained = []
+                        }
+                    }
+                    return item
+                })
+
+
+                let results = new Array(lists.length)
+                for (let i = 0; i < results.length; i++) {
+                    results[i] = new Array()
+                }
+                for (let k in allResultList) {
+                    let studentInfo = this.students.find(item => {
+                        let kk = Number(k)
+                        return item.seatID === kk
+                    })
+                    for (let j in allResultList[k]) {
+                        let index = lists.findIndex(item => {
+                            return item.id === j
+                        })
+                        if(index != -1) {
+                            results[index].push({
+                                details: allResultList[k][j],
+                                id: k,
+                                name: studentInfo.name
+                            })
+                        }
+                    }
+                }
+                this.allResult = results
+            }
+            this.scoreListNew = lists
+        },
+        openComment(type, index, item) {
+            if(type === 'vote') {
+                this.showResult = [...this.allResult]
+            } else {
+                this.mutalInfo = undefined
+                if(type === 'mutal') {
+                    this.mutalInfo = {
+                        material: item.material,
+                        obtained: item.obtained
+                    }
+                }
+                this.showResult = [...this.allResult[index]]
+            }
+            this.isComment = true
+        },
+    },
+}
+</script>
+
+<style lang="less" scoped>
+.smart-wrap {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: end;
+    position: relative;
+
+    .clt-type {
+        margin-right: 10px;
+        font-size: 15px;
+        font-weight: 600;
+    }
+
+    .smart-vote {
+        width: 200px;
+        height: 200px;
+    }
+}
+.smart-list {
+    width: 150px;
+    text-align: center;
+    margin-right: 20px;
+
+    img {
+        max-width: 150px;
+        max-height: 150px;
+        border: 1px solid #eeeeee;
+        cursor: pointer;
+    }
+
+    .smart-material {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        width: 150px;
+    }
+}
+</style>
+<style lang="less">
+.mutal-info {
+    border-bottom: 1px #ccc dashed;
+    margin-bottom: 10px;
+    padding-bottom: 5px;
+    position: relative;
+
+    img {
+        width: 300px;
+    }
+    .svg-box {
+        position: absolute;
+        top: 0;
+        left: 0;
+        height: 100%;
+        width: 300px;
+    }
+    svg {
+        width: 300px;
+        height: 100%;
+    }
+}
+</style>

+ 143 - 0
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SmartScore.vue

@@ -0,0 +1,143 @@
+<template>
+    <div style="width: 100%;">
+        <Icon type="md-trophy" size="25" color="#ff880d" class="trophy" />
+        <div class="ev-score-matrix" :id="'vote'+id"></div>
+        <p>
+            查看评语:
+            <span v-for="(item, index) in smartData" :key="index" class="show-name" @click="openResult(index)">{{ item.name }}</span>
+        </p>
+    </div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        smartData: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        return {
+            id: '',
+            progressPie: undefined,
+            option: {},
+            dataName: [],
+        }
+    },
+    mounted() {
+        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+        this.progressPie.setOption(this.option)
+        let erd11 = elementResizeDetectorMaker()
+        erd11.listenTo(document.getElementById("vote" + this.id), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.progressPie.resize()
+            })
+        })
+    },
+    created() {
+        this.id = this.$jsFn.getBtwRandom(0, 100000000)
+    },
+    watch: {
+        smartData: {
+            handler(n, o) {
+                let funnelData = []
+                n.forEach(item => {
+                    this.dataName.push(item.name)
+                    funnelData.push({
+                        value: item.result,
+                        name: item.isGeneral ? item.name : `${item.name}(${item.id})`
+                    })
+                })
+                this.$nextTick(() => {
+                    console.log('成绩分布数据:', n)
+                    this.option = {
+                        /* title: {
+                            text: 'Funnel'
+                        }, */
+                        tooltip: {
+                            trigger: 'item',
+                            formatter: '{b} : {c}'
+                        },
+                        /* legend: {
+                            data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
+                        }, */
+                        series: [
+                            {
+                                name: 'Funnel',
+                                type: 'funnel',
+                                left: '5%',
+                                top: 10,
+                                bottom: 10,
+                                width: '75%',
+                                // min: 0,
+                                // max: 100,
+                                minSize: '0%',
+                                maxSize: '100%',
+                                sort: 'descending',
+                                // gap: 2,
+                                labelLine: {
+                                    length: 10,
+                                    lineStyle: {
+                                        width: 1,
+                                        type: 'solid'
+                                    }
+                                },
+                                itemStyle: {
+                                    borderColor: '#fff',
+                                    borderWidth: 1
+                                },
+                                emphasis: {
+                                    label: {
+                                        fontSize: 20
+                                    }
+                                },
+                                label: {
+                                    formatter: '{b}: {c}',
+                                },
+                                data: funnelData
+                            }
+                        ]
+                    }
+                    if (!this.progressPie) {
+                        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+                    }
+                    this.progressPie.setOption(this.option, true)
+                })
+            },
+            deep: true,
+            immediate: true
+        }
+    },
+    methods: {
+        openResult(index) {
+            this.$parent.openComment(index)
+        },
+    }
+}
+</script>
+<style scoped lang="less">
+.trophy {
+    position: absolute;
+    top: 10px;
+    left: 30%;
+    z-index: 5;
+}
+.ev-score-matrix {
+    width: 400px;
+    height: 200px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-right: 10px;
+    padding-right: 20px;
+}
+.show-name {
+    color: #2ec7c9;
+    cursor: pointer;
+    margin-right: 10px;
+}
+</style>
+<style>
+</style>

+ 99 - 0
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/SmartVote.vue

@@ -0,0 +1,99 @@
+<template>
+    <div class="ev-score-matrix" :id="'vote'+id"></div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        smartData: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        return {
+            id: '',
+            progressPie: undefined,
+            option: {}
+        }
+    },
+    mounted() {
+        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+        this.progressPie.setOption(this.option)
+        let erd11 = elementResizeDetectorMaker()
+        erd11.listenTo(document.getElementById("vote" + this.id), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.progressPie.resize()
+            })
+        })
+    },
+    created() {
+        this.id = this.$jsFn.getBtwRandom(0, 100000000)
+    },
+    watch: {
+        smartData: {
+            handler(n, o) {
+                let funnelName = []
+                let funnelData = []
+                n.forEach(item => {
+                    funnelName.push(item.isGeneral ? item.name : `${item.name}(${item.id})`)
+                    funnelData.push(item.result)
+                })
+                this.$nextTick(() => {
+                    this.option = {
+                        tooltip: {
+                            trigger: 'axis',
+                            axisPointer: {
+                                type: 'shadow'
+                            }
+                        },
+                        grid: {
+                            top: '10%',
+                            left: '5%',
+                            right: '4%',
+                            bottom: '3%',
+                            containLabel: true
+                        },
+                        xAxis: {
+                            type: 'value'
+                        },
+                        yAxis: {
+                            type: 'category',
+                            data: funnelName
+                        },
+                        series: [
+                            {
+                                data: funnelData,
+                                type: 'bar',
+                                label: {
+                                    show: true,
+                                    position: 'right',
+                                },
+                            }
+                        ]
+                    }
+                    if (!this.progressPie) {
+                        this.progressPie = this.$echarts.init(document.getElementById('vote' + this.id), 'macarons')
+                    }
+                    this.progressPie.setOption(this.option, true)
+                })
+            },
+            deep: true,
+            immediate: true
+        }
+    }
+}
+</script>
+<style scoped lang="less">
+.ev-score-matrix {
+    width: 500px;
+    height: 200px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    margin-right: 10px;
+}
+</style>
+<style>
+</style>

+ 19 - 1
TEAMModelOS/ClientApp/src/view/dashboard/study/BaseExamLineBar.vue

@@ -6,7 +6,8 @@ export default {
   data() {
     return {
       option: null,
-      averageArr:[]
+      averageArr:[],
+      examNameArr:[]
     };
   },
   methods: {
@@ -20,6 +21,7 @@ export default {
             // 坐标轴指示器,坐标轴触发有效
             type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
           },
+          formatter: '{a} <br> {b} 得分率: {c} %'
         },
         grid: {
           left: "2%",
@@ -248,6 +250,21 @@ export default {
           },
         ],
       };
+      option.legend.data = that.examNameArr
+      if(that.examNameArr[0]){
+        option.series[0].name = that.examNameArr[0]
+      }
+      if(that.examNameArr[1]){
+        option.series[1].name = that.examNameArr[1]
+      }else{
+        option.series = option.series.slice(0,1)
+      }
+      if(that.examNameArr[2]){
+        option.series[2].name = that.examNameArr[2]
+      }else{
+        option.series = option.series.slice(0,2)
+      }
+      console.log(option);
       myChart.clear();
       myChart.setOption(option);
       window.addEventListener("resize", function () {
@@ -271,6 +288,7 @@ export default {
           countArr3:[],
         }
         if(examScores.length){
+          this.examNameArr = examScores.map(i => i.name)
           renderJson = {
             nameArr: examScores[0].data.map(i => i.name),
             countArr1: examScores[0].data.map(i => parseInt(i.value * 100)),

+ 1 - 1
TEAMModelOS/ClientApp/src/view/iot/schooliot.vue

@@ -46,7 +46,7 @@
                                         <span class="timetag">Min</span>
                                         <br/>
                                         <span>
-                                            <span class="total-text">总计</span>
+                                            <span class="total-text">{{$t('schoolIot.basics.totals')}}</span>
                                             {{Math.round(item.value/60)}}
                                             <span class="timetag">H</span>
                                         </span>

+ 16 - 1
TEAMModelOS/Controllers/Student/StudentController.cs

@@ -416,7 +416,22 @@ namespace TEAMModelOS.Controllers
                 {
                     countAuthorized = countStudent.Length;
                 }
-                return Ok(new { school.scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
+                int scale = school.scale;
+                if (scale<=0)
+                {
+                    string sql = $" SELECT value s  FROM c join s in  c.service  where c.id='{school_code}'  and s.prodCode='3CLYJ6NP' ";
+                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<SchoolProductSumDataService>(sql, "ProductSum");
+                    if (result.list.IsNotEmpty())
+                    {
+                        SchoolProductSumDataService service = result.list[0];
+                        if (service.avaliable>0)
+                        {
+                            scale= service.avaliable;
+                        }
+                    }
+                }
+               
+                return Ok(new { scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
             }
             else
             {

+ 1 - 1
TEAMModelOS/TEAMModelOS.csproj

@@ -109,7 +109,7 @@
     <!-- Build Target: Restore NPM packages using npm -->
     <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
 
-    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
+    <Exec WorkingDirectory="$(SpaRoot)" Command="cnpm install" />
   </Target>
 
   <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">