瀏覽代碼

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

CrazyIter_Bin 2 年之前
父節點
當前提交
87324de269

+ 14 - 1
TEAMModelOS.FunctionV4/TimeTrigger/IESTimerTrigger.cs

@@ -24,6 +24,7 @@ using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Models.Cosmos.Teacher;
+using TEAMModelOS.SDK.Models.Service;
 using TEAMModelOS.SDK.Models.Service.BI;
 using TEAMModelOS.SDK.Models.Table;
 using static TEAMModelOS.SDK.Models.Teacher;
@@ -416,7 +417,7 @@ namespace TEAMModelOS.FunctionV4.TimeTrigger
         }
 
         /// <summary>
-        /// 每天執行 取得IOT TeachingData 並統計每日每校Redis資料
+        /// 每天執行 取得IOT TeachingData 並統計昨日每校Redis資料 執行時間:每日凌晨1時1分
         /// </summary>
         [Function("BICrtDailyAnal")]
         //0 1 0 * * * 一天中00的第 1 分钟
@@ -430,7 +431,19 @@ namespace TEAMModelOS.FunctionV4.TimeTrigger
             var y = $"{datetime.Year}";
             var m = datetime.Month >= 10 ? $"{datetime.Month}" : $"0{datetime.Month}";
             var d = datetime.Day >= 10 ? $"{datetime.Day}" : $"0{datetime.Day}";
+            //生成數據
             await BIProdAnalysis.BICreatDailyAnalData(_azureRedis, _azureCosmosClient, _azureCosmosClientCsv1, _dingDing, y, m, d);
+            //刪除三個月以前的Redis數據 [待做]
+        }
+
+        /// <summary>
+        /// 每天執行 計算各學生各科錯題庫的數量,記入Redis 執行時間:每日2時1分
+        /// </summary>
+        [Function("CntStuErrorItems")]
+        public async Task CntStuErrorItems([TimerTrigger("0 1 2 * * *")] TimerInfo myTimer, ILogger log)
+        {
+            var _azureCosmosClient = _azureCosmos.GetCosmosClient();
+            await ErrorItemsService.cntStuErrorItemsAsync(_azureRedis, _azureCosmosClient, _dingDing);
         }
 
         public class UnusedLock { 

+ 127 - 0
TEAMModelOS.SDK/Models/Service/ErrorItemsService.cs

@@ -0,0 +1,127 @@
+using Azure.Cosmos;
+using StackExchange.Redis;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelOS.SDK.Models.Service
+{
+    public static class ErrorItemsService
+    {
+        public static async Task cntStuErrorItemsAsync(AzureRedisFactory _azureRedis, CosmosClient _azureCosmosClient, DingDing _dingDing)
+        {
+            try
+            {
+                var redisClinet8 = _azureRedis.GetRedisClient(8);
+                //各校在學學生名單製作
+                Dictionary<string, List<string>> schStuDic = new Dictionary<string, List<string>>();
+                await foreach (var item in _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: "SELECT c.id, c.schoolId FROM c WHERE (NOT IS_DEFINED(c.graduate) OR c.graduate = 0) AND IS_DEFINED(c.schoolId) AND CONTAINS(c.code, 'Base-')", null))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                        {
+                            string stuId = obj.GetProperty("id").GetString();
+                            string stuschId = obj.GetProperty("schoolId").GetString();
+                            if(schStuDic.ContainsKey(stuschId))
+                            {
+                                if (!schStuDic[stuschId].Contains(stuId))
+                                {
+                                    schStuDic[stuschId].Add(stuId);
+                                }
+                            }
+                            else
+                            {
+                                schStuDic.Add(stuschId, new List<string>() { stuId });
+                            }
+                        }
+                    }
+                }
+
+                //取得ErrorItems錯題
+                Dictionary<string, Dictionary<string, List<ErrorItemsStuRow>>> ErrorItemsDic = new Dictionary<string, Dictionary<string, List<ErrorItemsStuRow>>>();
+                string qry = "SELECT SUM(ARRAY_LENGTH(c.its)) AS number, c.stuId, c.school, c.subjectId, c.code FROM c WHERE " +
+                    "(c.code = 'ErrorItems' AND ( IS_NULL(c.school) OR c.school = '')) " +
+                    "OR " +
+                    "(CONTAINS(c.code, 'ErrorItems-') AND NOT IS_NULL(c.school) AND c.school != '') " +
+                    "GROUP BY c.stuId, c.school, c.subjectId, c.code";
+                await foreach (var item in _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: qry, requestOptions: null))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                        {
+                            string code = Convert.ToString(obj.GetProperty("code"));
+                            string stuId = Convert.ToString(obj.GetProperty("stuId"));
+                            string schoolId = Convert.ToString(obj.GetProperty("school"));
+                            schoolId = (!string.IsNullOrWhiteSpace(schoolId)) ? schoolId : "noschoolid";
+                            string subjectId = Convert.ToString(obj.GetProperty("subjectId"));
+                            int number = obj.GetProperty("number").GetInt32();
+                            bool goFlg = ((schStuDic.ContainsKey(schoolId) && schStuDic[schoolId].Contains(stuId)) || schoolId.Equals("noschoolid")) ? true : false;
+                            if (goFlg)
+                            {
+                                if (ErrorItemsDic.ContainsKey(schoolId))
+                                {
+                                    if (ErrorItemsDic[schoolId].ContainsKey(stuId))
+                                    {
+                                        List<ErrorItemsStuRow> ErrorItemsStuRowList = ErrorItemsDic[schoolId][stuId];
+                                        ErrorItemsStuRow ErrorItemsStuRow = ErrorItemsStuRowList.Where(s => s.subjectId.Equals(subjectId)).FirstOrDefault();
+                                        if (ErrorItemsStuRow == null)
+                                        {
+                                            ErrorItemsDic[schoolId][stuId].Add(new ErrorItemsStuRow() { subjectId = subjectId, number = number });
+                                        }
+                                    }
+                                    else
+                                    {
+                                        List<ErrorItemsStuRow> ErrorItemsStuRowList = new List<ErrorItemsStuRow>();
+                                        ErrorItemsStuRow ErrorItemsStuRow = new ErrorItemsStuRow() { subjectId = subjectId, number = number };
+                                        ErrorItemsStuRowList.Add(ErrorItemsStuRow);
+                                        ErrorItemsDic[schoolId].Add(stuId, ErrorItemsStuRowList);
+                                    }
+                                }
+                                else
+                                {
+                                    List<ErrorItemsStuRow> ErrorItemsStuRowList = new List<ErrorItemsStuRow>();
+                                    ErrorItemsStuRow ErrorItemsStuRow = new ErrorItemsStuRow() { subjectId = subjectId, number = number };
+                                    ErrorItemsStuRowList.Add(ErrorItemsStuRow);
+                                    Dictionary<string, List<ErrorItemsStuRow>> ErrorItemsSchRow = new Dictionary<string, List<ErrorItemsStuRow>> { { stuId, ErrorItemsStuRowList } };
+                                    ErrorItemsDic.Add(schoolId, ErrorItemsSchRow);
+                                }
+                            }
+                        }
+                    }
+                }
+                //寫入Redis
+                foreach(var SchItem in ErrorItemsDic)
+                {
+                    string schoolId = SchItem.Key;
+                    string hkey = $"ErrorItems:{schoolId}";
+                    List<HashEntry> hvalList = new List<HashEntry>();
+                    Dictionary<string, List<ErrorItemsStuRow>> itemSch = SchItem.Value;
+                    foreach(var itemStu in itemSch)
+                    {
+                        string stuId = itemStu.Key;
+                        string stuVal = JsonSerializer.Serialize(itemStu.Value);
+                        hvalList.Add(new HashEntry($"{stuId}", stuVal));
+                    }
+                    await redisClinet8.HashSetAsync(hkey, hvalList.ToArray());
+                }
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")},ErrorItemsService/cntStuErrorItemsAsync()\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+            }
+        }
+    }
+    public class ErrorItemsStuRow
+    {
+        public string subjectId { get; set; }
+        public int number { get; set; }
+    }
+}

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

@@ -6383,7 +6383,7 @@ const LANG_ZH_TW = {
         ach_title1: '得分率統計',
         ach_title2: '平均分數統計',
         ach_title3: '達標人數統計',
-        ach_title4: '達標情况統計',
+        ach_title4: '學生表現統計',
         ach_title5: '各班平均與達標率統計',
         ach_title5_2: '各班平均統計',
         ach_title6: '預警統計',

+ 2 - 1
TEAMModelOS/ClientApp/src/components/student-web/WrongQusetion/QuesList copy.vue

@@ -277,6 +277,7 @@ export default {
                 if(res.status === 200) {
                     if(res.data.length) {
                         this.exerciseTime = res.data
+                        this.topicTotal = res.data.length
                         this.getTopicList()
                     }
                 }
@@ -300,7 +301,7 @@ export default {
                 queryData.token = this.continuationToken
             }
             this.$api.studentWeb.getErrList(queryData).then(res => {
-                this.topicTotal = res.count
+                // this.topicTotal = res.count
                 this.continuationToken = res.token
                 if(res.errorItems) {
                     this.isLoading = false

+ 244 - 243
TEAMModelOS/ClientApp/src/view/artexam/WorkData.vue

@@ -1,281 +1,282 @@
 <template>
-    <div class="work-data">
-        <Table :height="520" :columns="tableColumn" :data="tableData" border :loading="tableLoading" style="margin-top:10px">
-            <template slot-scope="{ row }" slot="name">
-                <span style="color:#2db7f5;cursor: pointer;" :title="row.id">{{ row.name}}</span>
-            </template>
-            <template slot-scope="{ row }" slot="status">
-                <span :style="{color:row.createTime ? '#19be6b' : 'red' }">{{row.createTime ? $t('td.td43') : $t('ae.ae29')}}</span>
-            </template>
-            <template slot-scope="{ row }" slot="files">
-                <div v-for="(item,index) in row.files" :key="index">
-                    <div class="file-icon">
-                        <img v-if="item.extension == 'PPT' || item.extension == 'PPTX'" src="../../assets/source/ppt.png" />
-                        <img v-else-if="item.extension == 'DOC' || item.extension == 'DOCX'" src="../../assets/source/word.png" />
-                        <img v-else-if="item.extension == 'XLS' || item.extension == 'XLSX' || item.extension == 'CSV'" src="../../assets/source/excel.png" />
-                        <img v-else-if="item.extension == 'PDF'" src="../../assets/source/pdf.png" />
-                        <img v-else-if="item.extension == 'ZIP' || item.extension == 'RAR'" src="../../assets/source/zip.png" />
-                        <img v-else-if="item.type == 'image'" src="../../assets/source/image.png" />
-                        <img v-else-if="item.type == 'video'" src="../../assets/source/video.png" />
-                        <img v-else-if="item.type == 'audio'" src="../../assets/source/audio.png" />
-                        <img v-else-if="item.type == 'res'" src="../../assets/icon/htex.png" />
-                        <img v-else src="../../assets/source/unknow.png" />
-                    </div>
-                    <p v-if="item.type != 'zy'" class="work-file-name" @click="handlePreviewHw(item)">
-                        {{item.name}}
-                    </p>
-                    <a v-else :href="item.url" target="_blank">
-                        {{item.name}}
-                    </a>
-                </div>
-            </template>
-            <template slot-scope="{ row }" slot="createTime">
-                <span>{{row.createTime ? $jsFn.dateFormat(row.createTime) : '-'}}</span>
-            </template>
-        </Table>
-        <!-- 分页 -->
-        <div class="page-wrap">
-            <Page show-total size="small" :current="currentPage" :total="workData.length" :page-size="pageSize" @on-change="pageChange" />
+  <div class="work-data">
+    <Table :height="520" :columns="tableColumn" :data="tableData" border :loading="tableLoading" style="margin-top:10px">
+      <template slot-scope="{ row }" slot="name">
+        <span style="color:#2db7f5;cursor: pointer;" :title="row.id">{{ row.name}}</span>
+      </template>
+      <template slot-scope="{ row }" slot="status">
+        <span :style="{color:row.createTime ? '#19be6b' : 'red' }">{{row.createTime ? $t('td.td43') : $t('ae.ae29')}}</span>
+      </template>
+      <template slot-scope="{ row }" slot="files">
+        <div v-for="(item,index) in row.files" :key="index">
+          <div class="file-icon">
+            <img v-if="item.extension == 'PPT' || item.extension == 'PPTX'" src="../../assets/source/ppt.png" />
+            <img v-else-if="item.extension == 'DOC' || item.extension == 'DOCX'" src="../../assets/source/word.png" />
+            <img v-else-if="item.extension == 'XLS' || item.extension == 'XLSX' || item.extension == 'CSV'" src="../../assets/source/excel.png" />
+            <img v-else-if="item.extension == 'PDF'" src="../../assets/source/pdf.png" />
+            <img v-else-if="item.extension == 'ZIP' || item.extension == 'RAR'" src="../../assets/source/zip.png" />
+            <img v-else-if="item.type == 'image'" src="../../assets/source/image.png" />
+            <img v-else-if="item.type == 'video'" src="../../assets/source/video.png" />
+            <img v-else-if="item.type == 'audio'" src="../../assets/source/audio.png" />
+            <img v-else-if="item.type == 'res'" src="../../assets/icon/htex.png" />
+            <img v-else src="../../assets/source/unknow.png" />
+          </div>
+          <p v-if="item.type != 'zy'" class="work-file-name" @click="handlePreviewHw(item)">
+            {{item.name}}
+          </p>
+          <a v-else :href="item.url" target="_blank">
+            {{item.name}}
+          </a>
         </div>
-        <!-- 作业附件预览 -->
-        <Modal footer-hide v-model="hwViewStatus" width="900" @on-visible-change="closeViewModal">
-            <div slot="header">
-                <span>{{$t('train.detail.hwFile')}}</span>
-            </div>
-            <div v-if="hwPreviewFile.type === 'image' || hwPreviewFile.type === 'video' || hwPreviewFile.type === 'audio'" style="margin-top:20px">
-                <video v-if="hwPreviewFile.type == 'video'" id="previewVideo" autoplay :src="hwPreviewFile.url+schoolSas.sas" width="870" controls="controls" style="max-height: 800px;">
-                    {{$t('teachContent.tips8')}}
-                </video>
-                <audio v-else-if="hwPreviewFile.type == 'audio'" controls>
-                    <source :src="hwPreviewFile.url+schoolSas.sas">
-                    {{$t('teachContent.notAudio')}}
-                </audio>
-                <img v-else-if="hwPreviewFile.type == 'image'" :src="hwPreviewFile.url+schoolSas.sas" style="border-radius: 5px;max-height: 800px;max-width:870px;" />
-            </div>
-        </Modal>
+      </template>
+      <template slot-scope="{ row }" slot="createTime">
+        <span>{{row.createTime ? $jsFn.dateFormat(row.createTime) : '-'}}</span>
+      </template>
+    </Table>
+    <!-- 分页 -->
+    <div class="page-wrap">
+      <Page show-total size="small" :current="currentPage" :total="workData.length" :page-size="pageSize" @on-change="pageChange" />
     </div>
+    <!-- 作业附件预览 -->
+    <Modal footer-hide v-model="hwViewStatus" width="900" @on-visible-change="closeViewModal">
+      <div slot="header">
+        <span>{{$t('train.detail.hwFile')}}</span>
+      </div>
+      <div v-if="hwPreviewFile.type === 'image' || hwPreviewFile.type === 'video' || hwPreviewFile.type === 'audio'" style="margin-top:20px">
+        <video v-if="hwPreviewFile.type == 'video'" id="previewVideo" autoplay :src="hwPreviewFile.url+schoolSas.sas" width="870" controls="controls" style="max-height: 800px;">
+          {{$t('teachContent.tips8')}}
+        </video>
+        <audio v-else-if="hwPreviewFile.type == 'audio'" controls>
+          <source :src="hwPreviewFile.url+schoolSas.sas">
+          {{$t('teachContent.notAudio')}}
+        </audio>
+        <img v-else-if="hwPreviewFile.type == 'image'" :src="hwPreviewFile.url+schoolSas.sas" style="border-radius: 5px;max-height: 800px;max-width:870px;" />
+      </div>
+    </Modal>
+  </div>
 </template>
 
 <script>
 export default {
-    props: {
-        taskInfo: {
-            type: Object,
-            default: () => {
-                return {}
-            }
+  props: {
+    taskInfo: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    curClass: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    subjectId: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    let _this = this
+    return {
+      isRequesting: false,
+      schoolSas: {},
+      hwPreviewFile: {},
+      hwViewStatus: false,
+      pageSize: 10,
+      currentPage: 1,
+      tableColumn: [
+        {
+          title: this.$t('learnActivity.score.column1'),
+          key: "name",
+          align: "center",
+          width: 150,
         },
-        curClass: {
-            type: Object,
-            default: () => {
-                return {}
-            }
+        {
+          title: this.$t('td.td182'),
+          slot: "status",
+          align: "center",
         },
-        subjectId: {
-            type: String,
-            default: ''
+        {
+          title: this.$t('cusMgt.rcd.file'),
+          slot: "files",
+          align: "center",
+        },
+        {
+          title: _this.$t('ae.ae36'),
+          slot: "createTime",
+          align: "center",
         }
+      ],
+      tableData: [],
+      workData: [],
+      tableLoading: false
+    }
+  },
+  methods: {
+    closeViewModal(status) {
+      if (!status) {
+        this.hwPreviewFile = {}
+      }
     },
-    data() {
-        let _this = this
-        return {
-            isRequesting: false,
-            schoolSas: {},
-            hwPreviewFile: {},
-            hwViewStatus: false,
-            pageSize: 10,
-            currentPage: 1,
-            tableColumn: [
-                {
-                    title: this.$t('learnActivity.score.column1'),
-                    key: "name",
-                    align: "center",
-                    width: 150,
-                },
-                {
-                    title: this.$t('td.td182'),
-                    slot: "status",
-                    align: "center",
-                },
-                {
-                    title: this.$t('cusMgt.rcd.file'),
-                    slot: "files",
-                    align: "center",
-                },
-                {
-                    title: _this.$t('ae.ae36'),
-                    slot: "createTime",
-                    align: "center",
-                }
-            ],
-            tableData: [],
-            workData: [],
-            tableLoading: false
+    /**
+     * 预览/下载作业文件
+     * @param {string} type video 删除研修视频 file 删除资料
+     */
+    handlePreviewHw(info) {
+      if (info.type === 'image' || info.type === 'video' || info.type === 'audio') {
+        this.hwPreviewFile = info
+        this.hwViewStatus = true
+      } else if (info.type === 'doc') {
+        this.hwPreviewFile = info
+        if (info.extension === 'PDF') {
+          window.open('/web/viewer.html?file=' + encodeURIComponent(info.url + this.schoolSas.sas))
+        } else {
+          window.open('https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(info.url + this.schoolSas.sas))
         }
+      } else {
+        this.hwPreviewFile = info
+        this.downloadTrainFile(info.url + this.schoolSas.sas, info.name)
+      }
     },
-    methods: {
-        closeViewModal(status) {
-            if (!status) {
-                this.hwPreviewFile = {}
-            }
-        },
-        /**
-         * 预览/下载作业文件
-         * @param {string} type video 删除研修视频 file 删除资料
-         */
-        handlePreviewHw(info) {
-            if (info.type === 'image' || info.type === 'video' || info.type === 'audio') {
-                this.hwPreviewFile = info
-                this.hwViewStatus = true
-            } else if (info.type === 'doc') {
-                this.hwPreviewFile = info
-                if (info.extension === 'PDF') {
-                    window.open('/web/viewer.html?file=' + encodeURIComponent(info.url + this.schoolSas.sas))
-                } else {
-                    window.open('https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(info.url + this.schoolSas.sas))
-                }
-            } else {
-                this.hwPreviewFile = info
-                this.downloadTrainFile(info.url + this.schoolSas.sas, info.name)
-            }
-        },
-        downloadTrainFile(url, name) {
-            const downloadRes = async () => {
-                let response = await fetch(url); // 内容转变成blob地址
-                let blob = await response.blob();  // 创建隐藏的可下载链接
-                let objectUrl = window.URL.createObjectURL(blob);
-                let a = document.createElement('a');
-                a.href = objectUrl;
-                a.download = name;
-                a.click()
-                a.remove()
-            }
-            downloadRes()
-        },
-        // 分页页面变化
-        pageChange(page) {
-            let start = this.pageSize * (page - 1)
-            let end = this.pageSize * page
-            this.currentPage = page
-            this.tableData = this.workData.slice(start, end)
-        },
-        getWorkId() {
-            let task = this.taskInfo?.task?.find(item => item.subject == this.subjectId)
-            if (task) {
-                return task.acId
-            } else {
-                return undefined
-            }
+    downloadTrainFile(url, name) {
+      const downloadRes = async () => {
+        let response = await fetch(url); // 内容转变成blob地址
+        let blob = await response.blob();  // 创建隐藏的可下载链接
+        let objectUrl = window.URL.createObjectURL(blob);
+        let a = document.createElement('a');
+        a.href = objectUrl;
+        a.download = name;
+        a.click()
+        a.remove()
+      }
+      downloadRes()
+    },
+    // 分页页面变化
+    pageChange(page) {
+      let start = this.pageSize * (page - 1)
+      let end = this.pageSize * page
+      this.currentPage = page
+      this.tableData = this.workData.slice(start, end)
+    },
+    getWorkId() {
+      let task = this.taskInfo?.task?.find(item => item.subject == this.subjectId)
+      if (task) {
+        return task.acId
+      } else {
+        return undefined
+      }
+    },
+    findArtWorkData() {
+      // if(this.isRequesting) return
+      this.isRequesting = true
+      let workId = this.getWorkId()
+      if (!workId || !this.subjectId || !this.curClass.id) return
+      let req = {
+        id: workId,
+        subject: this.subjectId,
+        classId: this.curClass.id
+      }
+      this.$api.areaArt.findArtWork(req).then(
+        res => {
+          if (res?.works) this.setTableData(res.works)
         },
-        findArtWorkData() {
-            // if(this.isRequesting) return
-            this.isRequesting = true
-            let workId = this.getWorkId()
-            if (!workId || !this.subjectId || !this.curClass.id) return
-            let req = {
-                id: workId,
-                subject: this.subjectId,
-                classId: this.curClass.id
-            }
-            this.$api.areaArt.findArtWork(req).then(
-                res => {
-                    if (res?.works) this.setTableData(res.works)
-                },
-                err => {
+        err => {
 
-                }
-            ).finally(() => {
-                this.isRequesting = false
-            })
-        },
-        setTableData(data) {
-            this.workData = []
-            if (this.curClass && this.curClass.members) {
-                let students = this._.cloneDeep(this.curClass.members)
-                students.forEach(s => {
-                    let work = data.find(w => w.stuId === s.id)
-                    let hasWork = work && work.attachments?.length
-                    let hasZY = work && work.url
-                    let info = {
-                        id: s.id,
-                        name: s.name,
-                    }
-                    info.status = hasWork || hasZY ? 1 : 0
-                    info.createTime = hasWork ? work.attachments[0].createTime : hasZY ? work.createTime : 0
-                    info.count = hasWork ? work.attachments.length : hasZY ? 1 : 0
-                    info.files = hasWork ? work.attachments : hasZY ? [{ type: 'zy', name: '评唱结果', url: work.url }] : []
-                    this.workData.push(info)
-                })
-            }
-            this.pageChange(1)
-        },
-        // 分页页面变化
-        pageChange(page) {
-            let start = this.pageSize * (page - 1)
-            let end = this.pageSize * page
-            this.currentPage = page
-            this.tableData = this.workData.slice(start, end)
-        },
+        }
+      ).finally(() => {
+        this.isRequesting = false
+      })
     },
-    watch: {
-        subjectId: {
-            immediate: true,
-            handler(n, o) {
-                if (n) {
-                    this.findArtWorkData()
-                }
-            }
-        },
-        curClass: {
-            immediate: true,
-            deep: true,
-            handler(n, o) {
-                if (n && n.id) {
-                    this.findArtWorkData()
-                }
-            }
+    setTableData(data) {
+      console.error(data)
+      this.workData = []
+      if (this.curClass && this.curClass.members) {
+        let students = this._.cloneDeep(this.curClass.members)
+        students.forEach(s => {
+          let work = data.find(w => w.stuId === s.id)
+          let hasWork = work && work.attachments?.length
+          let hasZY = work && work.url
+          let info = {
+            id: s.id,
+            name: s.name,
+          }
+          info.status = hasWork || hasZY ? 1 : 0
+          info.createTime = hasWork ? work.attachments[0].createTime : hasZY ? work.createTime : 0
+          info.count = hasWork ? work.attachments.length : hasZY ? 1 : 0
+          info.files = hasWork ? work.attachments : hasZY ? [{ type: 'zy', name: '评唱结果', url: work.url }] : []
+          this.workData.push(info)
+        })
+      }
+      this.pageChange(1)
+    },
+    // 分页页面变化
+    pageChange(page) {
+      let start = this.pageSize * (page - 1)
+      let end = this.pageSize * page
+      this.currentPage = page
+      this.tableData = this.workData.slice(start, end)
+    },
+  },
+  watch: {
+    subjectId: {
+      immediate: true,
+      handler(n, o) {
+        if (n) {
+          this.findArtWorkData()
         }
+      }
     },
-    created() {
-        this.$tools.getSchoolSas().then(
-            res => {
-                this.schoolSas = res
-            },
-            err => {
-
-            }
-        )
+    curClass: {
+      //   immediate: true,
+      deep: true,
+      handler(n, o) {
+        if (n && n.id) {
+          this.findArtWorkData()
+        }
+      }
     }
+  },
+  created() {
+    this.$tools.getSchoolSas().then(
+      res => {
+        this.schoolSas = res
+      },
+      err => {
+
+      }
+    )
+  }
 }
 </script>
 
 <style lang="less" scoped>
 .page-wrap {
-    margin-top: 15px;
+  margin-top: 15px;
 }
 
 .file-icon {
+  display: inline-block;
+  img {
     display: inline-block;
-    img {
-        display: inline-block;
-        margin-right: 5px;
-        vertical-align: inherit;
-        border-radius: 2px;
-        width: 15px;
-    }
+    margin-right: 5px;
+    vertical-align: inherit;
+    border-radius: 2px;
+    width: 15px;
+  }
 }
 .work-data {
-    padding: 10px 10px;
-    border-radius: 6px;
-    background: #f5f7fa;
+  padding: 10px 10px;
+  border-radius: 6px;
+  background: #f5f7fa;
 }
 </style>
 <style lang="less">
 .work-file-name {
-    display: inline-block;
+  display: inline-block;
 }
 .work-file-name:hover {
-    color: #2d8cf0;
-    text-decoration: underline;
+  color: #2d8cf0;
+  text-decoration: underline;
 }
 </style>

+ 277 - 277
TEAMModelOS/ClientApp/src/view/shareSyllabus/ShareSyllabus.vue

@@ -1,292 +1,292 @@
 <template>
-	<div class="join-wrap">
-		<div class="join-main-box">
-			<p class="join-title">
-				<span>
-					{{ $t('syllabus.saveSyllabus') }}
-				</span>
-			</p>
-			<div v-show="isJoin || isRep" style="margin-bottom:50px">
-				<Icon type="md-checkmark-circle-outline" color="#19be6b" size="120" />
-				<p v-if="isJoin" class="success-tips">{{ $t('syllabus.receivedSuc1') }} <span @click="goSyllabus" style="text-decoration: underline;color: #19BF6C;cursor: pointer;">{{ $t('syllabus.receivedSuc2') }}</span> {{ $t('syllabus.receivedSuc3') }}</p>
-				<!-- <p v-else-if="isRep" class="success-tips">您已收藏成功,请勿重复操作!</p> -->
-			</div>
-			<p v-show="!isJoin && !isRep" class="course-name">{{ params.name }}</p>
-			<div style="width:fit-content;margin: auto;" v-if="!isRep && !isJoin">
-				<p class="info-item">
-					<span class="info-value">
-						{{ params.creatorName }}
-					</span>
-				</p>
-				<p class="info-item" v-show="isJoin || isRep">
-					<span class="info-lable">
-						{{$t('cusMgt.join.cusLabel')}}
-					</span>
-					<span class="info-value">
-						{{cusName}}
-					</span>
-				</p>
-			</div>
-			<div class="join-btn" @click="toLogin()" v-show="!isJoin && !isRep">
-				{{ $t('syllabus.receiveBtn') }}
-			</div>
-		</div>
-	</div>
+  <div class="join-wrap">
+    <div class="join-main-box">
+      <p class="join-title">
+        <span>
+          {{ $t('syllabus.saveSyllabus') }}
+        </span>
+      </p>
+      <div v-show="isJoin || isRep" style="margin-bottom:50px">
+        <Icon type="md-checkmark-circle-outline" color="#19be6b" size="120" />
+        <p v-if="isJoin" class="success-tips">{{ $t('syllabus.receivedSuc1') }} <span @click="goSyllabus" style="text-decoration: underline;color: #19BF6C;cursor: pointer;">{{ $t('syllabus.receivedSuc2') }}</span> {{ $t('syllabus.receivedSuc3') }}</p>
+        <!-- <p v-else-if="isRep" class="success-tips">您已收藏成功,请勿重复操作!</p> -->
+      </div>
+      <p v-show="!isJoin && !isRep" class="course-name">{{ params.name }}</p>
+      <div style="width:fit-content;margin: auto;" v-if="!isRep && !isJoin">
+        <p class="info-item">
+          <span class="info-value">
+            {{ params.creatorName }}
+          </span>
+        </p>
+        <p class="info-item" v-show="isJoin || isRep">
+          <span class="info-lable">
+            {{$t('cusMgt.join.cusLabel')}}
+          </span>
+          <span class="info-value">
+            {{cusName}}
+          </span>
+        </p>
+      </div>
+      <div class="join-btn" @click="toLogin()" v-show="!isJoin && !isRep">
+        {{ $t('syllabus.receiveBtn') }}
+      </div>
+    </div>
+  </div>
 </template>
 <script>
-	import jwtDecode from 'jwt-decode'
-	export default {
-		data() {
-			return {
-				params:{},
-				isRep: false,
-				isPC: false,
-				isLogin: false,
-				isJoin: false,
-				tId: '',
-				tName: '',
-				listNo: '',
-				listName: '',
-				cusName: '',
-				code: '',
-				userId: '',
-				userName: '',
-				id_token: '',
-				China: {
-					srvAdr: 'China',
-					clientID: 'c7317f88-7cea-4e48-ac57-a16071f7b884',
-					accAPIUrl: 'https://account-rc.teammodel.cn',
-					coreAPIUrl: 'https://api2.teammodel.cn',
-					domainUrl: [{
-							station: 'product',
-							url: 'https://www.teammodel.cn'
-						},
-						{
-							station: 'test',
-							url: 'https://test.teammodel.cn'
-						},
-					]
-				},
-				Global: {
-					srvAdr: 'Global',
-					clientID: '531fecd1-b1a5-469a-93ca-7984e1d392f2',
-					accAPIUrl: 'https://account.teammodel.net',
-					coreAPIUrl: 'https://api2.teammodel.net',
-					domainUrl: [{
-							station: 'product',
-							url: 'https://www.teammodel.net'
-						},
-						{
-							station: 'test',
-							url: 'https://test.teammodel.net'
-						},
-					]
-				}
-			}
-		},
-		created() {
-			this.params = this.$route.query
-			this.code = this.$route.query.code //登录成功返回的code
-			if (!this.params.id) {
-				this.$Message.error('Link Error')
-			} else if (this.code) {
-				//获取登录信息
-				let addr = this.getServeAddr()
-				let host = addr == 'Global' ? this.Global.coreAPIUrl : this.China.coreAPIUrl
-				let clientId = addr == 'Global' ? this.Global.clientID : this.China.clientID
-				this.$api.service.getToken(host, {
-					grant_type: "authorization_code",
-					client_id: clientId,
-					code: this.code
-				}).then(
-					res => {
-						if (!res.error) {
-							this.id_token = res.id_token
-							let tokenData = jwtDecode(res.id_token)
-							if (tokenData) {
-								this.userId = tokenData.sub
-								this.userName = tokenData.name
-								this.$loginTools.teacherLogin(res).then(info => {
-									this.doShare()
-								})
-							} 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'))
-					}
-				)
+import jwtDecode from 'jwt-decode'
+export default {
+  data() {
+    return {
+      params: {},
+      isRep: false,
+      isPC: false,
+      isLogin: false,
+      isJoin: false,
+      tId: '',
+      tName: '',
+      listNo: '',
+      listName: '',
+      cusName: '',
+      code: '',
+      userId: '',
+      userName: '',
+      id_token: '',
+      China: {
+        srvAdr: 'China',
+        clientID: 'c7317f88-7cea-4e48-ac57-a16071f7b884',
+        accAPIUrl: 'https://account.teammodel.cn',
+        coreAPIUrl: 'https://api2.teammodel.cn',
+        domainUrl: [{
+          station: 'product',
+          url: 'https://www.teammodel.cn'
+        },
+        {
+          station: 'test',
+          url: 'https://test.teammodel.cn'
+        },
+        ]
+      },
+      Global: {
+        srvAdr: 'Global',
+        clientID: '531fecd1-b1a5-469a-93ca-7984e1d392f2',
+        accAPIUrl: 'https://account.teammodel.net',
+        coreAPIUrl: 'https://api2.teammodel.net',
+        domainUrl: [{
+          station: 'product',
+          url: 'https://www.teammodel.net'
+        },
+        {
+          station: 'test',
+          url: 'https://test.teammodel.net'
+        },
+        ]
+      }
+    }
+  },
+  created() {
+    this.params = this.$route.query
+    this.code = this.$route.query.code //登录成功返回的code
+    if (!this.params.id) {
+      this.$Message.error('Link Error')
+    } else if (this.code) {
+      //获取登录信息
+      let addr = this.getServeAddr()
+      let host = addr == 'Global' ? this.Global.coreAPIUrl : this.China.coreAPIUrl
+      let clientId = addr == 'Global' ? this.Global.clientID : this.China.clientID
+      this.$api.service.getToken(host, {
+        grant_type: "authorization_code",
+        client_id: clientId,
+        code: this.code
+      }).then(
+        res => {
+          if (!res.error) {
+            this.id_token = res.id_token
+            let tokenData = jwtDecode(res.id_token)
+            if (tokenData) {
+              this.userId = tokenData.sub
+              this.userName = tokenData.name
+              this.$loginTools.teacherLogin(res).then(info => {
+                this.doShare()
+              })
+            } 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'))
+        }
+      )
 
-			}
-			// 判断移动端还是PC端
-			this.isPC = !(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent))
-		},
-		methods: {
-			goSyllabus(){
-				this.$router.push('/home/personalSyllabus')
-			},
-			/* 根据册别查询对应课纲树形结构 */
-			getTreeByVolumeId(volume) {
-				return new Promise((r, j) => {
-					this.$api.syllabus.GetTreeByVolume({
-						volumeId: volume.id,
-						volumeCode: volume.code,
-						scope: volume.scope
-					}).then(res => {
-						if (!res.error) {
-							let result = []
-							if(res.tree.length){
-								result = res.tree.map(i => {
-									return {
-										syllabusId:i.id,
-										syllabusName:this.params.name
-									}
-								})
-							}
-							r(result)
-						}
-					})
-				})
-			},
-			async doShare() {
-				let syllabus = await this.getTreeByVolumeId({
-					id: this.params.id,
-					code: 'Volume-' + this.params.creatorId,
-					scope: 'private'
-				})
-				let params = {
-					"school": "",
-					"scope": "private",
-					"tmdInfo": [{
-						"tmdid": this.userId,
-						"tmdname": this.userName
-					}],
-					"type": "share",
-					"issuer": this.params.creatorId,
-					"issuerName": this.params.creatorName,
-					"opt": "add",
-					"syllabus": syllabus,
-					"volumeId": this.params.id,
-					"volumeName": this.params.name
-				}
-				this.$api.syllabus.ShareTree(params).then(res => {
-					if(!res.error && res.code === 200){
-						this.$Message.success(this.$t('syllabus.doSuc'))
-						this.isJoin = true
-					}else{
-						this.$Message.success(this.$t('syllabus.saveFailTip'))
-					}
-				})
-			},
-			getServeAddr() {
-				let hostname = window.location.hostname
-				let addr
-				let hostarray = hostname.split('.')
-				if (hostarray[hostarray.length - 1] == 'net') {
-					addr = 'Global'
-				} else {
-					addr = 'China'
-				}
-				return addr
-			},
-			toLogin() {
-				let addr = this.getServeAddr()
-				// let type = process.env.NODE_ENV //product | development
-				let clientId, accUrl
-				//国际站
-				if (addr == 'Global') {
-					clientId = this.Global.clientID
-					accUrl = this.Global.accAPIUrl
-				}
-				// 大陆站
-				else {
-					clientId = this.China.clientID
-					accUrl = this.China.accAPIUrl
-				}
-				let callback = decodeURIComponent(window.location.href)
-				let state = this.$jsFn.getBtwRandom(1000, 9999)
-				let nonce = this.$jsFn.uuid()
-				let loginUrl =
-					`${accUrl}/oauth2/authorize?response_type=code&client_id=${clientId}&state=${state}&nonce=${nonce}&redirect_uri=${encodeURIComponent(callback)}`
-				window.location.href = loginUrl
-			}
-		}
-	}
+    }
+    // 判断移动端还是PC端
+    this.isPC = !(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent))
+  },
+  methods: {
+    goSyllabus() {
+      this.$router.push('/home/personalSyllabus')
+    },
+    /* 根据册别查询对应课纲树形结构 */
+    getTreeByVolumeId(volume) {
+      return new Promise((r, j) => {
+        this.$api.syllabus.GetTreeByVolume({
+          volumeId: volume.id,
+          volumeCode: volume.code,
+          scope: volume.scope
+        }).then(res => {
+          if (!res.error) {
+            let result = []
+            if (res.tree.length) {
+              result = res.tree.map(i => {
+                return {
+                  syllabusId: i.id,
+                  syllabusName: this.params.name
+                }
+              })
+            }
+            r(result)
+          }
+        })
+      })
+    },
+    async doShare() {
+      let syllabus = await this.getTreeByVolumeId({
+        id: this.params.id,
+        code: 'Volume-' + this.params.creatorId,
+        scope: 'private'
+      })
+      let params = {
+        "school": "",
+        "scope": "private",
+        "tmdInfo": [{
+          "tmdid": this.userId,
+          "tmdname": this.userName
+        }],
+        "type": "share",
+        "issuer": this.params.creatorId,
+        "issuerName": this.params.creatorName,
+        "opt": "add",
+        "syllabus": syllabus,
+        "volumeId": this.params.id,
+        "volumeName": this.params.name
+      }
+      this.$api.syllabus.ShareTree(params).then(res => {
+        if (!res.error && res.code === 200) {
+          this.$Message.success(this.$t('syllabus.doSuc'))
+          this.isJoin = true
+        } else {
+          this.$Message.success(this.$t('syllabus.saveFailTip'))
+        }
+      })
+    },
+    getServeAddr() {
+      let hostname = window.location.hostname
+      let addr
+      let hostarray = hostname.split('.')
+      if (hostarray[hostarray.length - 1] == 'net') {
+        addr = 'Global'
+      } else {
+        addr = 'China'
+      }
+      return addr
+    },
+    toLogin() {
+      let addr = this.getServeAddr()
+      // let type = process.env.NODE_ENV //product | development
+      let clientId, accUrl
+      //国际站
+      if (addr == 'Global') {
+        clientId = this.Global.clientID
+        accUrl = this.Global.accAPIUrl
+      }
+      // 大陆站
+      else {
+        clientId = this.China.clientID
+        accUrl = this.China.accAPIUrl
+      }
+      let callback = decodeURIComponent(window.location.href)
+      let state = this.$jsFn.getBtwRandom(1000, 9999)
+      let nonce = this.$jsFn.uuid()
+      let loginUrl =
+        `${accUrl}/oauth2/authorize?response_type=code&client_id=${clientId}&state=${state}&nonce=${nonce}&redirect_uri=${encodeURIComponent(callback)}`
+      window.location.href = loginUrl
+    }
+  }
+}
 </script>
 <style scoped lang="less">
-	.success-tips {
-		color: white;
-		font-size: 16px;
-		margin-top: 20px;
-	}
+.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-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);
-	}
+.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;
-	}
+.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;
+.join-main-box {
+  max-width: 90%;
+  width: fit-content;
+  text-align: center;
 
-		.info-item {
-			margin-top: 20px;
-			font-size: 15px;
-			width: fit-content;
-		}
+  .info-item {
+    margin-top: 20px;
+    font-size: 15px;
+    width: fit-content;
+  }
 
-		.info-lable {
-			color: #a5a5a5;
-		}
+  .info-lable {
+    color: #a5a5a5;
+  }
 
-		.info-value {
-			color: #eeeeee;
-		}
-	}
+  .info-value {
+    color: #eeeeee;
+  }
+}
 
-	.join-title {
-		position: absolute;
-		top: 15px;
-		text-align: center;
-		width: 100%;
-		left: 0px;
-		border-bottom: 1px dashed #5f5f5f;
-		padding-bottom: 8px;
-		color: #fff;
-	}
+.join-title {
+  position: absolute;
+  top: 15px;
+  text-align: center;
+  width: 100%;
+  left: 0px;
+  border-bottom: 1px dashed #5f5f5f;
+  padding-bottom: 8px;
+  color: #fff;
+}
 </style>

+ 2 - 1
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/AchievementAnalysis/AchievementAnalysis.vue

@@ -142,7 +142,8 @@ export default {
     /* 获取成绩分析模块 -- 及格率统计数据 */
     getPassRate(analysisJson) {
       console.log(analysisJson)
-      let result = [{
+      let isClouDAS = this.$route.name === 'privExam'
+      let result = isClouDAS ? [] : [{
         name: this.$t('totalAnalysis.allSubjects'),
         average: analysisJson.all.average,
         sRate: analysisJson.all.sRate,

+ 5 - 1
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/AchievementAnalysis/EntryTables.vue

@@ -2,7 +2,7 @@
   <div class="achievement-container">
     <!-- 进线率统计 -->
     <Row class-name="base-table-row">
-      <BaseTable :columns="entryRateColumns" :tableName="isAllSubject ? $t('totalAnalysis.ach_title5') : $t('totalAnalysis.ach_title5_2')" :tableDatas="entryBarData" tableRef="entryRateTable" :isScroll="false" :tableTitle="isAllSubject ? $t('totalAnalysis.ach_title5') : $t('totalAnalysis.ach_title5_2')" ref="rateTable"></BaseTable>
+      <BaseTable :columns="entryRateColumns" :tableName="(isAllSubject && !isClouDAS) ? $t('totalAnalysis.ach_title5') : $t('totalAnalysis.ach_title5_2')" :tableDatas="entryBarData" tableRef="entryRateTable" :isScroll="false" :tableTitle="isAllSubject ? $t('totalAnalysis.ach_title5') : $t('totalAnalysis.ach_title5_2')" ref="rateTable"></BaseTable>
     </Row>
     <!-- 进线情况统计 -->
     <Row class-name="base-table-row">
@@ -175,6 +175,7 @@ export default {
       let isClouDAS = this.$route.name === 'privExam'
       if (isClouDAS) {
         this.entryRateColumns = this.entryRateColumns.filter(i => i.key !== 'entryNum' && i.key !== 'overAverageRate' && i.key !== 'standardDeviation')
+        this.entryNumberColumns = this.entryNumberColumns.filter(i => i.key !== 'score')
       }
 
     },
@@ -260,6 +261,9 @@ export default {
   computed: {
     getAnalysisJson() {
       return this.$store.state.totalAnalysis.analysisJson
+    },
+    isClouDAS() {
+      return this.$route.name === 'privExam'
     }
   },
   watch: {

+ 228 - 237
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/KnowledgeAnalysis/ScoreDetails.vue

@@ -1,259 +1,250 @@
 <template>
-    <div class="scatter-container">
-        <Row class-name="base-table-row">
-            <Col span="12">
-                <span class="component-title" style="margin-right: 55px">{{$t('totalAnalysis.ka_title4')}}</span>
-                <!-- <span class="pie-to-bar" @click="changeRadarOrBar"><Icon :type="isShowRadar ? 'ios-podium' : 'ios-pie'" />{{isShowRadar?$t('totalAnalysis.ka_chart_text1'):$t('totalAnalysis.ka_chart_text2')}}</span> -->
-                <div>
-                    <BaseDetailBar echartsId="knowDetailBar" :classIndex="classIndex"  @handleItemClick="handleItemClick" ref="detailsBar"></BaseDetailBar>
-                </div>
-               <!-- <div v-if="isShowRadar">
+  <div class="scatter-container">
+    <Row class-name="base-table-row">
+      <Col span="12">
+      <span class="component-title" style="margin-right: 55px">{{$t('totalAnalysis.ka_title4')}}</span>
+      <!-- <span class="pie-to-bar" @click="changeRadarOrBar"><Icon :type="isShowRadar ? 'ios-podium' : 'ios-pie'" />{{isShowRadar?$t('totalAnalysis.ka_chart_text1'):$t('totalAnalysis.ka_chart_text2')}}</span> -->
+      <div>
+        <BaseDetailBar echartsId="knowDetailBar" :classIndex="classIndex" @handleItemClick="handleItemClick" ref="detailsBar"></BaseDetailBar>
+      </div>
+      <!-- <div v-if="isShowRadar">
                     <BaseRadar echartsId="knowRadar" :classIndex="classIndex"></BaseRadar>
                 </div> -->
-            </Col>
-            <Col span="12">
-            <!-- 知识点得分详情 -->
-                <span class="component-title-point"><span>{{$t('totalAnalysis.ka_text2')}}:{{currentPoint}}</span></span>
-                <div>
-                    <BaseMyTable :columns="detailsColumns"
-                                 :tableName="$t('totalAnalysis.ka_title5')"
-                                  tableRef="pointScoreTable"
-                                 :tableDatas="tableData"
-                                 ref="detailsTable"></BaseMyTable>
-                </div>
-            </Col>
-        </Row>
-        <Divider />
-        <Row class-name="base-table-row">
-            <div>
-                <BaseMyTable :columns="tableColumns"
-                             :tableName="$t('totalAnalysis.ka_title6')"
-                              tableRef="pointWrongTable"
-                             :tableDatas="numData"
-                             ref="numTable"
-                             :tips="$t('totalAnalysis.ka_tip1')"></BaseMyTable>
-            </div>
-            <!--<span style="/*font-size:14px;font-weight:bold;margin-left:60px;color:#66cccc*/"></span>-->
-        </Row>
-    </div>
+      </Col>
+      <Col span="12">
+      <!-- 知识点得分详情 -->
+      <span class="component-title-point"><span>{{$t('totalAnalysis.ka_text2')}}:{{currentPoint}}</span></span>
+      <div>
+        <BaseMyTable :columns="detailsColumns" :tableName="$t('totalAnalysis.ka_title5') + '(' + currentPoint + ')'" tableRef="pointScoreTable" :tableDatas="tableData" ref="detailsTable"></BaseMyTable>
+      </div>
+      </Col>
+    </Row>
+    <Divider />
+    <Row class-name="base-table-row">
+      <div>
+        <BaseMyTable :columns="tableColumns" :tableName="$t('totalAnalysis.ka_title6')" tableRef="pointWrongTable" :tableDatas="numData" ref="numTable" :tips="$t('totalAnalysis.ka_tip1')"></BaseMyTable>
+      </div>
+      <!--<span style="/*font-size:14px;font-weight:bold;margin-left:60px;color:#66cccc*/"></span>-->
+    </Row>
+  </div>
 </template>
 
 <script>
-    import BaseDetailBar from '@/components/student-analysis/total/BaseKnowledgeDetail.vue'
-    import BaseMyTable from '@/components/student-analysis/total/BaseMyTable.vue'
-    import BaseRadar from '@/components/student-analysis/total/BaseRadar.vue'
-    export default {
-		props:{
-			classIndex:{
-				type:Number,
-				default:-1
-			}
-		},
-        components: {
-            BaseDetailBar, BaseMyTable, BaseRadar
+import BaseDetailBar from '@/components/student-analysis/total/BaseKnowledgeDetail.vue'
+import BaseMyTable from '@/components/student-analysis/total/BaseMyTable.vue'
+import BaseRadar from '@/components/student-analysis/total/BaseRadar.vue'
+export default {
+  props: {
+    classIndex: {
+      type: Number,
+      default: -1
+    }
+  },
+  components: {
+    BaseDetailBar, BaseMyTable, BaseRadar
+  },
+  data() {
+    return {
+      isShowRadar: false,
+      curClassIndex: -1,
+      classList: [],
+      tableData: [],
+      classDatas: [],
+      currentPoint: '',
+      tipContent: '* RH:高分区段  /  RL:低分区段 (模拟数据,仅供参考)',
+      knowledgeData: [],
+      numData: [],
+      tableColumns: [
+        {
+          title: this.$t('totalAnalysis.ka_table_text1'),
+          key: 'name',
+          minWidth: 150
         },
-        data() {
-            return {
-                isShowRadar: false,
-				curClassIndex: -1,
-				classList:[],
-                tableData: [],
-                classDatas: [],
-                currentPoint: '',
-                tipContent: '* RH:高分区段  /  RL:低分区段 (模拟数据,仅供参考)',
-                knowledgeData: [],
-                numData: [],
-                tableColumns: [
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text1'),
-                        key: 'name',
-                        minWidth: 150
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text4'),
-                        key: 'point',
-                        minWidth: 100,
-                        sortable: 'custom',
-                        renderType: function(h, params) {
-                            return h('span', Number(params.row.point).toFixed(2))
-                        }
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text7'),
-                        key: 'itemNO',
-                        renderType: 'renderHard',
-                        width: 250
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text8'),
-                        key: 'persent',
-                        renderType: function(h, params) {
-                            return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
-                        },
-                        sortable: 'custom',
-                        minWidth: 100
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text9'),
-                        key: 'wrong',
-                        sortable: 'custom',
-                        minWidth: 100
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text10'),
-                        key: 'rhw',
-                        sortable: 'custom',
-                        minWidth: 100
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text11'),
-                        key: 'rlw',
-                        sortable: 'custom',
-                        minWidth: 100
-                    }
-                ],
-                detailsColumns: [
-                    {
-                        title: this.$t('totalAnalysis.base_name'),
-                        key: 'id',
-                        minWidth: 100
-                    },
-                    {
-                        title: this.$t('totalAnalysis.base_class'),
-                        key: 'className',
-                        width: 120
-                    },
-                    {
-                        title: this.$t('totalAnalysis.base_id'),
-                        key: 'seatNO',
-                        width: 100
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text4'),
-                        key: 'point',
-                        sortable: 'custom',
-                        renderType: function(h, params) {
-                            return h('span', Number(params.row.point).toFixed(1))
-                        },
-                        minWidth: 100
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text5'),
-                        key: 'anwPoint',
-                        minWidth: 100,
-                        sortable: 'custom',
-                        renderType: function(h, params) {
-                            return h('span', Number(params.row.anwPoint).toFixed(1))
-                        }
-                    },
-                    {
-                        title: this.$t('totalAnalysis.ka_table_text6'),
-                        key: 'persent',
-                        minWidth: 100,
-                        sortable: 'custom',
-                        renderType: function(h, params) {
-                            return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
-                        }
-                    }
-                ]
-            }
+        {
+          title: this.$t('totalAnalysis.ka_table_text4'),
+          key: 'point',
+          minWidth: 100,
+          sortable: 'custom',
+          renderType: function (h, params) {
+            return h('span', Number(params.row.point).toFixed(2))
+          }
         },
-        created() {
-
+        {
+          title: this.$t('totalAnalysis.ka_table_text7'),
+          key: 'itemNO',
+          renderType: 'renderHard',
+          width: 250
+        },
+        {
+          title: this.$t('totalAnalysis.ka_table_text8'),
+          key: 'persent',
+          renderType: function (h, params) {
+            return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
+          },
+          sortable: 'custom',
+          minWidth: 100
+        },
+        {
+          title: this.$t('totalAnalysis.ka_table_text9'),
+          key: 'wrong',
+          sortable: 'custom',
+          minWidth: 100
+        },
+        {
+          title: this.$t('totalAnalysis.ka_table_text10'),
+          key: 'rhw',
+          sortable: 'custom',
+          minWidth: 100
+        },
+        {
+          title: this.$t('totalAnalysis.ka_table_text11'),
+          key: 'rlw',
+          sortable: 'custom',
+          minWidth: 100
+        }
+      ],
+      detailsColumns: [
+        {
+          title: this.$t('totalAnalysis.base_name'),
+          key: 'id',
+          minWidth: 100
+        },
+        {
+          title: this.$t('totalAnalysis.base_class'),
+          key: 'className',
+          width: 120
+        },
+        {
+          title: this.$t('totalAnalysis.base_id'),
+          key: 'seatNO',
+          width: 100
         },
+        {
+          title: this.$t('totalAnalysis.ka_table_text4'),
+          key: 'point',
+          sortable: 'custom',
+          renderType: function (h, params) {
+            return h('span', Number(params.row.point).toFixed(1))
+          },
+          minWidth: 100
+        },
+        {
+          title: this.$t('totalAnalysis.ka_table_text5'),
+          key: 'anwPoint',
+          minWidth: 100,
+          sortable: 'custom',
+          renderType: function (h, params) {
+            return h('span', Number(params.row.anwPoint).toFixed(1))
+          }
+        },
+        {
+          title: this.$t('totalAnalysis.ka_table_text6'),
+          key: 'persent',
+          minWidth: 100,
+          sortable: 'custom',
+          renderType: function (h, params) {
+            return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
+          }
+        }
+      ]
+    }
+  },
+  created() {
 
-        methods: {
+  },
 
-            changeRadarOrBar() {
-                this.isShowRadar = !this.isShowRadar
-            },
+  methods: {
 
-            // 点击柱状图某个点事件
-            handleItemClick(item) {
-                console.log(item)
-                this.currentPoint = item.name
-                this.doRender(this.getKnowledgeData, this.currentPoint,this.curClassIndex)
-            },
+    changeRadarOrBar() {
+      this.isShowRadar = !this.isShowRadar
+    },
 
-            doRender(data, point) {
-				let classIndex = this.$store.state.totalAnalysis.curClassIndex
-                let origin = data.stupercent
-                let keys = origin.keys
-                let datas = classIndex === -1 ? (origin[point] || []) : origin[point].filter(i => i[1] === this.classList[classIndex + 1])
-				this.currentPoint = point
-                this.tableData = this.$tools.jsonTransform({ datas: datas, keys: keys })
-            },
+    // 点击柱状图某个点事件
+    handleItemClick(item) {
+      console.log(item)
+      this.currentPoint = item.name
+      this.doRender(this.getKnowledgeData, this.currentPoint, this.curClassIndex)
+    },
 
-            doRenderWrong(data) {
-				let classIndex = this.$store.state.totalAnalysis.curClassIndex
-                let origin = data.wrong
-				let allWrongData = this.$tools.jsonTransform({ datas: origin.datas, keys: origin.keys })
-                this.numData = classIndex === -1 ?  allWrongData : this.getClassWrongData(allWrongData,classIndex)
-            },
-			
-			getClassWrongData(allWrongData){
-				let classIndex = this.$store.state.totalAnalysis.curClassIndex
-				let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject)
-				let curItem = this.getAnalysisJson.classes[classIndex].subjects[curSubjectIndex]
-				allWrongData.forEach((item,index) => {
-					item.persent = curItem.krate[index]
-					item.rhw =  curItem.phc[index]
-					item.rlw = curItem.plc[index]
-					item.wrong = curItem.pc[index]
-				})
-				return allWrongData
-			}
-			
+    doRender(data, point) {
+      let classIndex = this.$store.state.totalAnalysis.curClassIndex
+      let origin = data.stupercent
+      let keys = origin.keys
+      let datas = classIndex === -1 ? (origin[point] || []) : origin[point].filter(i => i[1] === this.classList[classIndex + 1])
+      this.currentPoint = point
+      this.tableData = this.$tools.jsonTransform({ datas: datas, keys: keys })
+    },
 
-        },
-        mounted() {
-            this.$refs.detailsTable.$el.childNodes[1].style.borderRight = '0'
-            this.$refs.numTable.$el.childNodes[1].style.borderRight = '0'
-            if (this.getKnowledgeData) {
-                this.doRender(this.getKnowledgeData, this.getKnowledgeData.pointList[0],this.curClassIndex)
-                this.doRenderWrong(this.getKnowledgeData,this.curClassIndex)
-				this.classList = [this.$t('totalAnalysis.allClasses')].concat([...new Set(this.getAnalysisJson.classes.map(item => item.className))]) // 获取班级列表
-            }
-			
-			
-        },
-        computed: {
-            // 获取最新散点图数据
-            getKnowledgeData() {
-                let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject)
-                return this.$store.state.totalAnalysis.analysisJson.pointLevelKey[curSubjectIndex].pointKey
-            },
-			getAnalysisJson() {
-			    return this.$store.state.totalAnalysis.analysisJson
-			},
-        },
-        watch: {
-            getKnowledgeData: {
-                deep: true,
-                handler(val) {
-                    if (val) {
-                        this.doRender(val,val.pointList[0],this.curClassIndex)
-						this.doRenderWrong(val,this.curClassIndex)
-                    }
-                }
-            },
-			classIndex(n,o){
-				this.curClassIndex = n - 1
-				this.doRender(this.getKnowledgeData,this.getKnowledgeData.pointList[0],n - 1)
-				this.doRenderWrong(this.getKnowledgeData,n - 1)
-			}
+    doRenderWrong(data) {
+      let classIndex = this.$store.state.totalAnalysis.curClassIndex
+      let origin = data.wrong
+      let allWrongData = this.$tools.jsonTransform({ datas: origin.datas, keys: origin.keys })
+      this.numData = classIndex === -1 ? allWrongData : this.getClassWrongData(allWrongData, classIndex)
+    },
+
+    getClassWrongData(allWrongData) {
+      let classIndex = this.$store.state.totalAnalysis.curClassIndex
+      let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject)
+      let curItem = this.getAnalysisJson.classes[classIndex].subjects[curSubjectIndex]
+      allWrongData.forEach((item, index) => {
+        item.persent = curItem.krate[index]
+        item.rhw = curItem.phc[index]
+        item.rlw = curItem.plc[index]
+        item.wrong = curItem.pc[index]
+      })
+      return allWrongData
+    }
+
+
+  },
+  mounted() {
+    this.$refs.detailsTable.$el.childNodes[1].style.borderRight = '0'
+    this.$refs.numTable.$el.childNodes[1].style.borderRight = '0'
+    if (this.getKnowledgeData) {
+      this.doRender(this.getKnowledgeData, this.getKnowledgeData.pointList[0], this.curClassIndex)
+      this.doRenderWrong(this.getKnowledgeData, this.curClassIndex)
+      this.classList = [this.$t('totalAnalysis.allClasses')].concat([...new Set(this.getAnalysisJson.classes.map(item => item.className))]) // 获取班级列表
+    }
+
+
+  },
+  computed: {
+    // 获取最新散点图数据
+    getKnowledgeData() {
+      let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject)
+      return this.$store.state.totalAnalysis.analysisJson.pointLevelKey[curSubjectIndex].pointKey
+    },
+    getAnalysisJson() {
+      return this.$store.state.totalAnalysis.analysisJson
+    },
+  },
+  watch: {
+    getKnowledgeData: {
+      deep: true,
+      handler(val) {
+        if (val) {
+          this.doRender(val, val.pointList[0], this.curClassIndex)
+          this.doRenderWrong(val, this.curClassIndex)
         }
+      }
+    },
+    classIndex(n, o) {
+      this.curClassIndex = n - 1
+      this.doRender(this.getKnowledgeData, this.getKnowledgeData.pointList[0], n - 1)
+      this.doRenderWrong(this.getKnowledgeData, n - 1)
     }
+  }
+}
 </script>
 
 <style src="./KnowledgeAnalysis.css" scoped></style>
 <style>
-	.component-title-point{
-		position: absolute;
-		font-size: 14px;
-		font-weight: 600;
-		right: 220px;
-		top: 60px;
-		color: #70B1E7;
-	}
+.component-title-point {
+  position: absolute;
+  font-size: 14px;
+  font-weight: 600;
+  right: 220px;
+  top: 60px;
+  color: #70b1e7;
+}
 </style>

+ 255 - 256
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/LevelAnalysis/ScoreDetails.vue

@@ -1,277 +1,276 @@
 <template>
-	<div class="scatter-container">
-		<Row class-name="base-table-row">
-			<Col span="12">
-			<span class="component-title" style="margin-right: 55px">{{$t('totalAnalysis.le_title4')}}</span>
-			<!-- <span class="pie-to-bar" @click="changeRadarOrBar">
+  <div class="scatter-container">
+    <Row class-name="base-table-row">
+      <Col span="12">
+      <span class="component-title" style="margin-right: 55px">{{$t('totalAnalysis.le_title4')}}</span>
+      <!-- <span class="pie-to-bar" @click="changeRadarOrBar">
 				<Icon :type="isShowRadar ? 'ios-podium' : 'ios-pie'" />
 				{{isShowRadar?$t('totalAnalysis.ka_chart_text1'):$t('totalAnalysis.ka_chart_text2')}}
 			</span> -->
-			<div>
-				<BaseDetailBar echartsId="levelDetailBar" :classIndex="classIndex" @handleItemClick="handleItemClick"
-					ref="levelDetailBar"></BaseDetailBar>
-			</div>
-<!-- 			<div v-if="isShowRadar">
+      <div>
+        <BaseDetailBar echartsId="levelDetailBar" :classIndex="classIndex" @handleItemClick="handleItemClick" ref="levelDetailBar"></BaseDetailBar>
+      </div>
+      <!-- 			<div v-if="isShowRadar">
 				<BaseRadar echartsId="levelRadar" :classIndex="classIndex"></BaseRadar>
 			</div> -->
-			</Col>
-			<Col span="12">
-			<span class="component-title-point"><span>{{$t('totalAnalysis.ka_text3')}}:{{transArr[+currentPoint-1]}}</span></span>
-			<div>
-				<BaseMyTable :columns="detailsColumns" :tableName="$t('totalAnalysis.le_title5')"
-					tableRef="levelScoreTable" :tableDatas="tableData" ref="detailsTable"></BaseMyTable>
-			</div>
-			</Col>
-		</Row>
-		<Divider />
-		<Row class-name="base-table-row">
-			<BaseMyTable :columns="tableColumns" :tableName="$t('totalAnalysis.le_title6')" tableRef="levelWrongTable"
-				:tableDatas="numData" ref="numTable" :tips="$t('totalAnalysis.ka_tip1')"></BaseMyTable>
-		</Row>
-	</div>
+      </Col>
+      <Col span="12">
+      <span class="component-title-point"><span>{{$t('totalAnalysis.ka_text3')}}:{{transArr[+currentPoint-1]}}</span></span>
+      <div>
+        <BaseMyTable :columns="detailsColumns" :tableName="$t('totalAnalysis.le_title5') + '(' + pointName + ')'" tableRef="levelScoreTable" :tableDatas="tableData" ref="detailsTable"></BaseMyTable>
+      </div>
+      </Col>
+    </Row>
+    <Divider />
+    <Row class-name="base-table-row">
+      <BaseMyTable :columns="tableColumns" :tableName="$t('totalAnalysis.le_title6')" tableRef="levelWrongTable" :tableDatas="numData" ref="numTable" :tips="$t('totalAnalysis.ka_tip1')"></BaseMyTable>
+    </Row>
+  </div>
 </template>
 
 <script>
-	import BaseDetailBar from '@/components/student-analysis/total/BaseLevelDetail.vue'
-	import BaseMyTable from '@/components/student-analysis/total/BaseMyTable.vue'
-	import BaseRadar from '@/components/student-analysis/total/BaseRadar.vue'
-	export default {
-		props:{
-			classIndex:{
-				type:Number,
-				default:-1
-			}
-		},
-		components: {
-			BaseDetailBar,
-			BaseMyTable,
-			BaseRadar
-		},
-		data() {
-			return {
-				isShowRadar: false,
-				curClassIndex: -1,
-				tableData: [],
-				classDatas: [],
-				classList:[],
-				currentPoint: 1,
-				transArr: this.$GLOBAL.EXERCISE_LEVELS(),
-				tipContent: '* RH:高分区段  /  RL:低分区段 (模拟数据,仅供参考)',
-				levelData: [],
-				numData: [],
-				tableColumns: [{
-						title: this.$t('totalAnalysis.ka_table_text2'),
-						key: 'name',
-						minWidth: 150
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text4'),
-						key: 'point',
-						minWidth: 100,
-						sortable: 'custom',
-						renderType: function(h, params) {
-							return h('span', Number(params.row.point).toFixed(2))
-						}
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text7'),
-						key: 'itemNO',
-						renderType: 'renderHard',
-						width: 250
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text8'),
-						key: 'persent',
-						renderType: function(h, params) {
-							return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
-						},
-						sortable: 'custom',
-						minWidth: 100
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text9'),
-						key: 'wrong',
-						sortable: 'custom',
-						minWidth: 100
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text10'),
-						key: 'rhw',
-						sortable: 'custom',
-						minWidth: 100
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text11'),
-						key: 'rlw',
-						sortable: 'custom',
-						minWidth: 100
-					}
-				],
-				detailsColumns: [{
-						title: this.$t('totalAnalysis.base_name'),
-						key: 'id',
-						minWidth: 100
-					},
-					{
-						title: this.$t('totalAnalysis.base_class'),
-						key: 'className',
-						width: 120
-					},
-					{
-						title: this.$t('totalAnalysis.base_id'),
-						key: 'seatNO',
-						width: 100
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text4'),
-						key: 'point',
-						sortable: 'custom',
-						renderType: function(h, params) {
-							return h('span', Number(params.row.point).toFixed(1))
-						},
-						minWidth: 100
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text5'),
-						key: 'anwPoint',
-						minWidth: 100,
-						sortable: 'custom',
-						renderType: function(h, params) {
-							return h('span', Number(params.row.anwPoint).toFixed(1))
-						}
-					},
-					{
-						title: this.$t('totalAnalysis.ka_table_text6'),
-						key: 'persent',
-						minWidth: 100,
-						sortable: 'custom',
-						renderType: function(h, params) {
-							return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
-						}
-					}
-				]
-			}
-		},
-		created() {
+import BaseDetailBar from '@/components/student-analysis/total/BaseLevelDetail.vue'
+import BaseMyTable from '@/components/student-analysis/total/BaseMyTable.vue'
+import BaseRadar from '@/components/student-analysis/total/BaseRadar.vue'
+export default {
+  props: {
+    classIndex: {
+      type: Number,
+      default: -1
+    }
+  },
+  components: {
+    BaseDetailBar,
+    BaseMyTable,
+    BaseRadar
+  },
+  data() {
+    return {
+      pointName: this.$GLOBAL.EXERCISE_LEVELS()[0],
+      isShowRadar: false,
+      curClassIndex: -1,
+      tableData: [],
+      classDatas: [],
+      classList: [],
+      currentPoint: 1,
+      transArr: this.$GLOBAL.EXERCISE_LEVELS(),
+      tipContent: '* RH:高分区段  /  RL:低分区段 (模拟数据,仅供参考)',
+      levelData: [],
+      numData: [],
+      tableColumns: [{
+        title: this.$t('totalAnalysis.ka_table_text2'),
+        key: 'name',
+        minWidth: 150
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text4'),
+        key: 'point',
+        minWidth: 100,
+        sortable: 'custom',
+        renderType: function (h, params) {
+          return h('span', Number(params.row.point).toFixed(2))
+        }
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text7'),
+        key: 'itemNO',
+        renderType: 'renderHard',
+        width: 250
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text8'),
+        key: 'persent',
+        renderType: function (h, params) {
+          return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
+        },
+        sortable: 'custom',
+        minWidth: 100
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text9'),
+        key: 'wrong',
+        sortable: 'custom',
+        minWidth: 100
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text10'),
+        key: 'rhw',
+        sortable: 'custom',
+        minWidth: 100
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text11'),
+        key: 'rlw',
+        sortable: 'custom',
+        minWidth: 100
+      }
+      ],
+      detailsColumns: [{
+        title: this.$t('totalAnalysis.base_name'),
+        key: 'id',
+        minWidth: 100
+      },
+      {
+        title: this.$t('totalAnalysis.base_class'),
+        key: 'className',
+        width: 120
+      },
+      {
+        title: this.$t('totalAnalysis.base_id'),
+        key: 'seatNO',
+        width: 100
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text4'),
+        key: 'point',
+        sortable: 'custom',
+        renderType: function (h, params) {
+          return h('span', Number(params.row.point).toFixed(1))
+        },
+        minWidth: 100
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text5'),
+        key: 'anwPoint',
+        minWidth: 100,
+        sortable: 'custom',
+        renderType: function (h, params) {
+          return h('span', Number(params.row.anwPoint).toFixed(1))
+        }
+      },
+      {
+        title: this.$t('totalAnalysis.ka_table_text6'),
+        key: 'persent',
+        minWidth: 100,
+        sortable: 'custom',
+        renderType: function (h, params) {
+          return h('span', (Number(params.row.persent) * 100).toFixed(2) + '%')
+        }
+      }
+      ]
+    }
+  },
+  created() {
 
-		},
+  },
 
-		methods: {
+  methods: {
 
-			changeRadarOrBar() {
-				this.isShowRadar = !this.isShowRadar
-			},
+    changeRadarOrBar() {
+      this.isShowRadar = !this.isShowRadar
+    },
 
-			// 点击柱状图某个点事件
-			handleItemClick(item) {
-				this.currentPoint = this.transArr.indexOf(item.name) + 1
-				this.doRender(this.getLevelData, this.currentPoint,this.curClassIndex)
-			},
+    // 点击柱状图某个点事件
+    handleItemClick(item) {
+      this.pointName = item.name
+      this.currentPoint = this.transArr.indexOf(item.name) + 1
+      this.doRender(this.getLevelData, this.currentPoint, this.curClassIndex)
+    },
 
-			doRender(data, point) {
-				let classIndex = this.$store.state.totalAnalysis.curClassIndex
-				let origin = data.stupercent
-				let keys = origin.keys
-				let datas = classIndex === -1 ? (origin[point] || []) : origin[point].filter(i => i[1] === this.classList[classIndex + 1])
-				this.tableData = this.$tools.jsonTransform({
-					datas: datas,
-					keys: keys
-				})
-			},
+    doRender(data, point) {
+      let classIndex = this.$store.state.totalAnalysis.curClassIndex
+      let origin = data.stupercent
+      let keys = origin.keys
+      let datas = classIndex === -1 ? (origin[point] || []) : origin[point].filter(i => i[1] === this.classList[classIndex + 1])
+      this.tableData = this.$tools.jsonTransform({
+        datas: datas,
+        keys: keys
+      })
+    },
 
-			doRenderWrong(data) {
-				let classIndex = this.$store.state.totalAnalysis.curClassIndex
-				let origin = data.wrong
-				let keys = origin.keys
-				let datas = origin.datas
-				console.log(datas)
-				datas.forEach((i, index) => {
-					i[0] = this.transArr[index]
-				})
-				let allWrongData = this.$tools.jsonTransform({ datas: datas, keys: keys })
-				this.numData = classIndex === -1 ?  allWrongData : this.getClassWrongData(allWrongData,classIndex)
-				
-			},
-			
-			getClassWrongData(allWrongData){
-				let classIndex = this.$store.state.totalAnalysis.curClassIndex
-				let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject)
-				let curItem = this.getAnalysisJson.classes[classIndex].subjects[curSubjectIndex]
-				allWrongData.forEach((item,index) => {
-					item.persent = curItem.frate[index]
-					item.rhw =  curItem.fphc[index]
-					item.rlw = curItem.fplc[index]
-					item.wrong = curItem.fpc[index]
-				})
-				return allWrongData
-			}
+    doRenderWrong(data) {
+      let classIndex = this.$store.state.totalAnalysis.curClassIndex
+      let origin = data.wrong
+      let keys = origin.keys
+      let datas = origin.datas
+      console.log(datas)
+      datas.forEach((i, index) => {
+        i[0] = this.transArr[index]
+      })
+      let allWrongData = this.$tools.jsonTransform({ datas: datas, keys: keys })
+      this.numData = classIndex === -1 ? allWrongData : this.getClassWrongData(allWrongData, classIndex)
 
-		},
-		mounted() {
-			this.$refs.detailsTable.$el.childNodes[1].style.borderRight = '0'
-			this.$refs.numTable.$el.childNodes[1].style.borderRight = '0'
-			if (this.getLevelData) {
-				this.doRender(this.getLevelData, this.currentPoint,this.curClassIndex)
-				this.doRenderWrong(this.getLevelData,this.curClassIndex)
-				this.classList = [this.$t('totalAnalysis.allClasses')].concat([...new Set(this.getAnalysisJson.classes.map(item => item.className))]) // 获取班级列表
-			}
-		},
-		computed: {
-			// 获取最新散点图数据
-			getLevelData() {
-				let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this
-					.$store.state
-					.totalAnalysis.currentSubject)
-				let levelJson = this.$store.state.totalAnalysis.analysisJson.pointLevelKey[curSubjectIndex].levelKey
-				let transArr = this.$GLOBAL.EXERCISE_LEVELS()
-				levelJson.pointList = levelJson.pointList.map((i, index) => transArr[index])
-				for (let key in levelJson.classpercent) {
-					if (!isNaN(key)) {
-						let newKey = transArr[+key - 1]
-						levelJson.classpercent[newKey] = levelJson.classpercent[key]
-						levelJson.stupercent[newKey] = levelJson.stupercent[key]
-					}
-				}
-				return levelJson
-			},
-			// 获取最新散点图数据
-			getKnowledgeData() {
-				let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this
-					.$store.state
-					.totalAnalysis.currentSubject)
-				return this.$store.state.totalAnalysis.analysisJson.pointLevelKey[curSubjectIndex].pointKey
-			},
-			getAnalysisJson() {
-			    return this.$store.state.totalAnalysis.analysisJson
-			},
-		},
-		watch: {
-			// getLevelData: {
-			// 	handler(val) {
-			// 		if (val) {
-			// 			this.doRender(this.getLevelData, this.getLevelData.pointList[0],this.curClassIndex)
-			// 			this.doRenderWrong(this.getLevelData,this.curClassIndex)
-			// 		}
-			// 	}
-			// },
-			classIndex(n,o){
-				this.curClassIndex = n - 1
-				this.doRender(this.getLevelData,this.getLevelData.pointList[0],n - 1)
-				this.doRenderWrong(this.getLevelData,n - 1)
-			},
-			immediate:true
-		}
-	}
+    },
+
+    getClassWrongData(allWrongData) {
+      let classIndex = this.$store.state.totalAnalysis.curClassIndex
+      let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this.$store.state.totalAnalysis.currentSubject)
+      let curItem = this.getAnalysisJson.classes[classIndex].subjects[curSubjectIndex]
+      allWrongData.forEach((item, index) => {
+        item.persent = curItem.frate[index]
+        item.rhw = curItem.fphc[index]
+        item.rlw = curItem.fplc[index]
+        item.wrong = curItem.fpc[index]
+      })
+      return allWrongData
+    }
+
+  },
+  mounted() {
+    this.$refs.detailsTable.$el.childNodes[1].style.borderRight = '0'
+    this.$refs.numTable.$el.childNodes[1].style.borderRight = '0'
+    if (this.getLevelData) {
+      this.doRender(this.getLevelData, this.currentPoint, this.curClassIndex)
+      this.doRenderWrong(this.getLevelData, this.curClassIndex)
+      this.classList = [this.$t('totalAnalysis.allClasses')].concat([...new Set(this.getAnalysisJson.classes.map(item => item.className))]) // 获取班级列表
+    }
+  },
+  computed: {
+    // 获取最新散点图数据
+    getLevelData() {
+      let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this
+        .$store.state
+        .totalAnalysis.currentSubject)
+      let levelJson = this.$store.state.totalAnalysis.analysisJson.pointLevelKey[curSubjectIndex].levelKey
+      let transArr = this.$GLOBAL.EXERCISE_LEVELS()
+      levelJson.pointList = levelJson.pointList.map((i, index) => transArr[index])
+      for (let key in levelJson.classpercent) {
+        if (!isNaN(key)) {
+          let newKey = transArr[+key - 1]
+          levelJson.classpercent[newKey] = levelJson.classpercent[key]
+          levelJson.stupercent[newKey] = levelJson.stupercent[key]
+        }
+      }
+      return levelJson
+    },
+    // 获取最新散点图数据
+    getKnowledgeData() {
+      let curSubjectIndex = this.$store.state.totalAnalysis.analysisJson.subjects.map(i => i.name).indexOf(this
+        .$store.state
+        .totalAnalysis.currentSubject)
+      return this.$store.state.totalAnalysis.analysisJson.pointLevelKey[curSubjectIndex].pointKey
+    },
+    getAnalysisJson() {
+      return this.$store.state.totalAnalysis.analysisJson
+    },
+  },
+  watch: {
+    // getLevelData: {
+    // 	handler(val) {
+    // 		if (val) {
+    // 			this.doRender(this.getLevelData, this.getLevelData.pointList[0],this.curClassIndex)
+    // 			this.doRenderWrong(this.getLevelData,this.curClassIndex)
+    // 		}
+    // 	}
+    // },
+    classIndex(n, o) {
+      this.curClassIndex = n - 1
+      this.doRender(this.getLevelData, this.getLevelData.pointList[0], n - 1)
+      this.doRenderWrong(this.getLevelData, n - 1)
+    },
+    immediate: true
+  }
+}
 </script>
 
 <style src="./LevelAnalysis.css" scoped></style>
 <style>
-	.component-title-point{
-		position: absolute;
-		font-size: 14px;
-		font-weight: 600;
-		right: 220px;
-		top: 60px;
-		color: #70B1E7;
-	}
+.component-title-point {
+  position: absolute;
+  font-size: 14px;
+  font-weight: 600;
+  right: 220px;
+  top: 60px;
+  color: #70b1e7;
+}
 </style>

+ 1 - 1
TEAMModelOS/Controllers/Client/HiTeachController.cs

@@ -705,7 +705,7 @@ namespace TEAMModelOS.Controllers.Client
                 (List<Class> school_classes, _) = await SchoolService.DoGraduateClasses(_httpTrigger, _azureCosmos, null, school_base, _option, _dingDing);
 
                 //取得學校安排老師課程
-                var query = $"SELECT DISTINCT c.id, c.name, c.scope, c.subject, c.period.id AS periodId, schedule.classId AS scheduleClassId, schedule.teacher AS scheduleTeacher, schedule.stulist AS scheduleStulist, schedule.notice AS scheduleNotice FROM c JOIN schedule IN c.schedule WHERE schedule.teacherId = '{id}'";
+                var query = $"SELECT DISTINCT c.id, c.name, c.scope, c.subject, c.period.id AS periodId, schedule.classId AS scheduleClassId, schedule.stulist AS scheduleStulist, schedule.notice AS scheduleNotice FROM c JOIN schedule IN c.schedule WHERE schedule.teacherId = '{id}'";
                 //var query = $"SELECT c.id, c.name, c.teacher, cc.course, c.scope FROM c JOIN cc IN c.courses JOIN cct IN cc.teachers WHERE cct.id = '{id}'";
                 await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Course-{school_code}") }))
                 {

+ 48 - 0
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -3657,6 +3657,49 @@ namespace TEAMModelOS.Controllers
                 return BadRequest(new { code = 500 });
             }
         }
+        //取得錯題庫前日錯題數及現在錯題數
+        [ProducesDefaultResponseType]
+        //[Authorize(Roles = "IES")]
+        //[AuthToken(Roles = "teacher,admin,student")]
+        [HttpPost("get-error-item-cnt")]
+        public async Task<IActionResult> getErrorItemsCount(JsonElement request)
+        {
+            //輸入值
+            if (!request.TryGetProperty("stuId", out JsonElement stuId)) return BadRequest();
+            if (!request.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
+            string code = (request.TryGetProperty("code", out JsonElement codeJobj)) ? codeJobj.GetString() : string.Empty;
+            string schCode = (!string.IsNullOrWhiteSpace(code)) ? code : "noschoolid";
+            //取得Redis該學生該科目的錯題數
+            int record = 0; //昨日取得的錯題數
+            var redisClient = _azureRedis.GetRedisClient(8);
+            string hkey = $"ErrorItems:{schCode}";
+            string hval = await redisClient.HashGetAsync(hkey, $"{stuId}");
+            List<stuErrorItemCnt> stuErrCntList = JsonSerializer.Deserialize<List<stuErrorItemCnt>>(hval);
+            stuErrorItemCnt stuErrCnt = stuErrCntList.Where(s => s.subjectId.Equals($"{subjectId}")).FirstOrDefault();
+            if(stuErrCnt != null)
+            {
+                record = stuErrCnt.number;
+            }
+            //取得CosmosDB該學生該科目現在錯題數
+            int avaliable = 0; //現在取得的錯題數
+            var client = _azureCosmos.GetCosmosClient();
+            string pk = (!string.IsNullOrWhiteSpace(code)) ? $"ErrorItems-{code}" : "ErrorItems";
+            string qryAdd = (!string.IsNullOrWhiteSpace(code)) ? $" AND c.school = '{code}' " : string.Empty;
+            string qry = $"SELECT SUM(ARRAY_LENGTH(c.its)) AS number, c.stuId, c.school, c.subjectId FROM c WHERE c.stuId = '{stuId}' AND c.subjectId = '{subjectId}'{qryAdd} GROUP BY c.stuId, c.school, c.subjectId";
+            await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryStreamIterator(queryText: qry, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{pk}") }))
+            {
+                using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                {
+                    foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                    {
+                        avaliable = obj.GetProperty("number").GetInt32();
+                    }
+                }
+            }
+
+            return Ok(new { record, avaliable });
+        }
         //阅卷信息统计
         [ProducesDefaultResponseType]
         [Authorize(Roles = "IES")]
@@ -3979,4 +4022,9 @@ namespace TEAMModelOS.Controllers
         public string name { get; set; }
         public int type { get; set; }
     }
+    public class stuErrorItemCnt
+    {
+        public string subjectId { get; set; }
+        public int number { get; set; }
+    }
 }

+ 13 - 0
TEAMModelOS/Controllers/XTest/FixDataController.cs

@@ -3505,6 +3505,19 @@ namespace TEAMModelOS.Controllers
             return Ok(new { state = 200, noFail });
         }
 
+        /// <summary>
+        /// 根據錯題庫(ErrorItems)生成各學校學生各科錯題數後記入Redis
+        /// </summary>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("set-erroritems-count")]
+        public async Task<IActionResult> SetErrorItemsCount(JsonElement json)
+        {
+            var azureCosmosClient = _azureCosmos.GetCosmosClient();
+            await ErrorItemsService.cntStuErrorItemsAsync(_azureRedis, azureCosmosClient, _dingDing);
+            return Ok(new { state = 200 });
+        }
+
         public record TrainingId 
         {
             public string oldId { get; set; }

+ 21 - 1
TEAMModelOS/appsettings.Development.json

@@ -21,6 +21,7 @@
     "Version": "5.2305.10.1"
   },
   "Azure": {
+    // 测试站数据库
     "Storage": {
       "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodeltest;AccountKey=O2W2vadCqexDxWO+px+QK7y1sHwsYj8f/WwKLdOdG5RwHgW/Dupz9dDUb4c1gi6ojzQaRpFUeAAmOu4N9E+37A==;EndpointSuffix=core.chinacloudapi.cn"
     },
@@ -38,7 +39,26 @@
     },
     "SignalR": {
       "ConnectionString": "Endpoint=https://channel.service.signalr.net;AccessKey=KrblW06tuA4a/GyqRPDU0ynFFmAWxbAvyJihHclSXbQ=;Version=1.0;"
-    }
+    },
+    // 正式站数据库
+    //"Storage": {
+    //  "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodelos;AccountKey=Dl04mfZ9hE9cdPVO1UtqTUQYN/kz/dD/p1nGvSq4tUu/4WhiKcNRVdY9tbe8620nPXo/RaXxs+1F9sVrWRo0bg==;EndpointSuffix=core.chinacloudapi.cn"
+    //},
+    //"Cosmos": {
+    //  "ConnectionString": "AccountEndpoint=https://teammodelos.documents.azure.cn:443/;AccountKey=clF73GwPECfP1lKZTCvs8gLMMyCZig1HODFbhDUsarsAURO7TcOjVz6ZFfPqr1HzYrfjCXpMuVD5TlEG5bFGGg==;"
+    //},
+    //"Redis": {
+    //  "ConnectionString": "CoreRedisCN.redis.cache.chinacloudapi.cn:6380,password=LyJWP1ORJdv+poXWofAF97lhCEQPg1wXWqvtzXGXQuE=,ssl=True,abortConnect=False"
+    //},
+    //"ServiceBus": {
+    //  "ConnectionString": "Endpoint=sb://coreiotservicebuscnpro.servicebus.chinacloudapi.cn/;SharedAccessKeyName=TEAMModelOS;SharedAccessKey=llRPBMDJG9w1Nnifj+pGhV0g4H2REcq0PjvX2qqpcOg=",
+    //  "ActiveTask": "active-task",
+    //  "ItemCondQueue": "itemcond",
+    //  "GenPdfQueue": "genpdf"
+    //},
+    //"SignalR": {
+    //  "ConnectionString": "Endpoint=https://channel.signalr.azure.cn;AccessKey=AtcB7JYFNUbUXb1rGxa3PVksQ2X5YSv3JOHZR9J88tw=;Version=1.0;"
+    //}
   },
   "HaBookAuth": {
     "CoreId": {