瀏覽代碼

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

jeff 9 月之前
父節點
當前提交
1350921131
共有 47 個文件被更改,包括 2588 次插入573 次删除
  1. 2 2
      TEAMModelBI/Controllers/BITest/TestController.cs
  2. 2 2
      TEAMModelBI/Filter/RequestAuditFilter.cs
  3. 3 3
      TEAMModelBI/TEAMModelBI.csproj
  4. 541 198
      TEAMModelOS.Extension/HTEX.Test/Controllers/LessonRecordController.cs
  5. 80 3
      TEAMModelOS.Extension/HTEX.Test/Program.cs
  6. 25 0
      TEAMModelOS.Extension/HTEX.Test/PythonCode/calculate_similarity.py
  7. 34 0
      TEAMModelOS.Extension/HTEX.Test/PythonCode/demo.py
  8. 3 3
      TEAMModelOS.Function/TEAMModelOS.Function.csproj
  9. 12 7
      TEAMModelOS.SDK/Models/Cosmos/Common/ExamClassResult.cs
  10. 2 2
      TEAMModelOS.SDK/Models/Cosmos/Common/LessonRecord.cs
  11. 3 3
      TEAMModelOS.SDK/Models/Service/ExamService.cs
  12. 3 3
      TEAMModelOS.SDK/TEAMModelOS.SDK.csproj
  13. 4 0
      TEAMModelOS/ClientApp/public/lang/en-US.js
  14. 4 0
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  15. 4 0
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  16. 4 0
      TEAMModelOS/ClientApp/src/api/htcommunity.js
  17. 8 0
      TEAMModelOS/ClientApp/src/api/knowledge.js
  18. 10 0
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  19. 167 60
      TEAMModelOS/ClientApp/src/common/BaseQuickPaper.vue
  20. 7 8
      TEAMModelOS/ClientApp/src/components/evaluation/SyllabusPicker.vue
  21. 9 0
      TEAMModelOS/ClientApp/src/router/routes.js
  22. 4 0
      TEAMModelOS/ClientApp/src/view/Home.vue
  23. 24 18
      TEAMModelOS/ClientApp/src/view/coursemgt/NewCusMgt.vue
  24. 1 1
      TEAMModelOS/ClientApp/src/view/coursemgt/components/AddTask.vue
  25. 6 0
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue
  26. 8 0
      TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.vue
  27. 83 4
      TEAMModelOS/ClientApp/src/view/htcommunity/htExamMark.vue
  28. 20 25
      TEAMModelOS/ClientApp/src/view/htcommunity/htMgtExam.vue
  29. 45 45
      TEAMModelOS/ClientApp/src/view/htcommunity/htMgtHome.vue
  30. 108 27
      TEAMModelOS/ClientApp/src/view/knowledge-point/index/page.vue
  31. 1 1
      TEAMModelOS/ClientApp/src/view/learnactivity/ManualCreateNew.vue
  32. 46 21
      TEAMModelOS/ClientApp/src/view/learnactivity/byStu/htByStuMark.vue
  33. 918 0
      TEAMModelOS/ClientApp/src/view/learnactivity/htByQuMark.vue
  34. 7 5
      TEAMModelOS/ClientApp/src/view/learnactivity/htCreateEva.vue
  35. 66 15
      TEAMModelOS/ClientApp/src/view/learnactivity/tabs/htAnswerTable.vue
  36. 1 0
      TEAMModelOS/ClientApp/src/view/student-account/class/ClassMgt.vue
  37. 59 39
      TEAMModelOS/Controllers/Analysis/ArtAnalysisController.cs
  38. 42 15
      TEAMModelOS/Controllers/Both/KnowledgeController.cs
  39. 1 5
      TEAMModelOS/Controllers/Client/HiTeachController.cs
  40. 30 19
      TEAMModelOS/Controllers/Common/ExamController.cs
  41. 32 7
      TEAMModelOS/Controllers/School/KnowledgesController.cs
  42. 37 23
      TEAMModelOS/Controllers/Student/OverallEducationController.cs
  43. 2 2
      TEAMModelOS/Filter/RequestAuditFilter.cs
  44. 113 0
      TEAMModelOS/Properties/ServiceDependencies/teammodelos-test - Web Deploy/profile.arm.json
  45. 4 4
      TEAMModelOS/TEAMModelOS.csproj
  46. 2 2
      TEAMModelOS/appsettings.Development.json
  47. 1 1
      TEAMModelOS/appsettings.json

+ 2 - 2
TEAMModelBI/Controllers/BITest/TestController.cs

@@ -1435,7 +1435,7 @@ namespace TEAMModelBI.Controllers.BITest
                 //https://teammodelos.blob.core.chinacloudapi.cn/0-public/pie-borderRadius.html
                 string publishUrl = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={HttpUtility.UrlEncode(retn.saveUrls.First(), Encoding.UTF8)}&time={HttpUtility.UrlEncode(datetime.AddHours(8).ToString("yyyy年MM月dd日 HH时"), Encoding.UTF8)}";
                 string ulrs = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={retn.saveUrls.First()}&time={datetime.AddHours(8).ToString("yyyy年MM月dd日 HH时")}";
-                string ulr = $"http://cdhabook.teammodel.cn:8805/screen/screenshot-png?width=1920&height=1450&url={HttpUtility.UrlEncode(ulrs, Encoding.UTF8)}&delay=6000";
+                string ulr = $"http://52.130.252.100:8805/screen/screenshot-png?width=1920&height=1450&url={HttpUtility.UrlEncode(ulrs, Encoding.UTF8)}&delay=6000";
                 string image = "";
 
                 try
@@ -1472,7 +1472,7 @@ namespace TEAMModelBI.Controllers.BITest
                     //一天的统计
                     string dayPublishUrl = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={HttpUtility.UrlEncode(dayRetn.saveUrls.First(), Encoding.UTF8)}&time={HttpUtility.UrlEncode(pastTime.ToString("yyyy年MM月dd日"), Encoding.UTF8)}";
                     string dayUrls = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={dayRetn.saveUrls.First()}&time={pastTime.ToString("yyyy年MM月dd日")}";
-                    string dayUrl = $"http://cdhabook.teammodel.cn:8805/screen/screenshot-png?width=1920&height=1450&url={HttpUtility.UrlEncode(dayUrls, Encoding.UTF8)}&delay=6000";
+                    string dayUrl = $"http://52.130.252.100:8805/screen/screenshot-png?width=1920&height=1450&url={HttpUtility.UrlEncode(dayUrls, Encoding.UTF8)}&delay=6000";
                     string dayImage = "";
 
                     string dayStr = await _httpClient.CreateClient().GetStringAsync(dayUrl);

+ 2 - 2
TEAMModelBI/Filter/RequestAuditFilter.cs

@@ -194,14 +194,14 @@ namespace TEAMModelOS.Filter
                 var httpclient = _httpClient.CreateClient();
                 httpclient.Timeout=  TimeSpan.FromSeconds(10);
 #if DEBUG
-                var response = await httpclient.PostAsJsonAsync("http://cdhabook.teammodel.cn:8806/api/http-log", data);
+                var response = await httpclient.PostAsJsonAsync("http://52.130.252.100:8806/api/http-log", data);
                 //if (response.StatusCode==HttpStatusCode.OK) 
                 //{
                 //    string result =   await response.Content.ReadAsStringAsync();
 
                 //}
 #else
-            _=  httpclient.PostAsJsonAsync("http://cdhabook.teammodel.cn:8806/api/http-log",data);
+            _=  httpclient.PostAsJsonAsync("http://52.130.252.100:8806/api/http-log",data);
 #endif
                 //   _ = _httpTrigger.RequestHttpTrigger(data, "China", "http-log");
             }

+ 3 - 3
TEAMModelBI/TEAMModelBI.csproj

@@ -65,9 +65,9 @@
 		<SpaRoot>ClientApp\</SpaRoot>
 		<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
 		<UserSecretsId>078b5d89-7d90-4f6a-88fc-7d96025990a8</UserSecretsId>
-		<Version>5.2408.28</Version>
-		<AssemblyVersion>5.2408.28.1</AssemblyVersion>
-		<FileVersion>5.2408.28.1</FileVersion>
+		<Version>5.2409.4</Version>
+		<AssemblyVersion>5.2409.4.1</AssemblyVersion>
+		<FileVersion>5.2409.4.1</FileVersion>
 		<Description>TEAMModelBI(BI)</Description>
 		<PackageReleaseNotes>BI版本说明版本切换标记2022000908</PackageReleaseNotes>
 		<PackageId>TEAMModelBI</PackageId>

+ 541 - 198
TEAMModelOS.Extension/HTEX.Test/Controllers/LessonRecordController.cs

@@ -5,6 +5,8 @@ using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Extension;
+using StackExchange.Redis;
+using System.Text.RegularExpressions;
 
 namespace HTEX.Test.Controllers
 {
@@ -16,48 +18,35 @@ namespace HTEX.Test.Controllers
         private readonly AzureCosmosFactory _azureCosmos;
         private readonly AzureStorageFactory _azureStorage;
 
-        // 创建一个字典来存储状态编号和对应的分数
-        private readonly Dictionary<int, double> states = new Dictionary<int, double>();
+      
         public LessonRecordController(ILogger<LessonRecordController> logger, AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage)
         {
             _logger = logger;
             _azureCosmos = azureCosmos;
             _azureStorage = azureStorage;
-            #region
-            // 无二次作答的互动,且未设置正确答案
-            states.Add(1, 0); // 未作答0
-            states.Add(2, 1); // 已作答1
-
-            // 无二次作答的互动,且设置了正确答案
-            states.Add(3, 0); // 未作答0
-            states.Add(4, 1); // 已作答1
-            states.Add(5, 1.5); // 作答正确1.5
-
-            // 有二次作答的互动,且未设置正确答案
-            states.Add(6, 0); // 第一次未作答0,第二次未作答0
-            states.Add(7, 1); // 第一次已作答1,第二次未作答0
-            states.Add(8, 1); // 第一次未作答0,第二次已作答1
-            states.Add(9, 2); // 第一次已作答1,第二次已作答1
-
-            // 有二次作答的互动,且设置了正确答案
-            states.Add(10, 0); // 第一次未作答0,第二次未作答0
-            states.Add(11, 1.5); // 第一次已作答,作答正确1.5,第二次未作答0
-            states.Add(12, 1); // 第一次已作答,作答错误1.5,第二次未作答0
-            states.Add(13, 1.5); // 第一次未作答0,第二次已作答,作答正确1.5
-            states.Add(14, 1); // 第一次未作答0,第二次已作答,作答错误1
-            states.Add(15, 2); // 第一次已作答,作答错误1,第二次已作答,作答错误1
-            states.Add(16, 2.5); // 第一次已作答,作答正确1.5,第二次已作答,作答错误1
-            states.Add(17, 2.5); // 第一次已作答,作答错误1,第二次已作答,作答正确1.5
-            states.Add(18, 3); // 第一次已作答,作答正确1.5,第二次已作答,作答正确1.5
-
-            // 抢权模式
-            states.Add(19, 0); // 未参与抢权0
-            states.Add(20, 1); // 参与抢权1
-            states.Add(21, 1.5); // 抢权成功1.5
-            // 挑人时被挑到
-            states.Add(22, 1.5); // 被挑到1.5
-
-            #endregion
+        }
+        [HttpPost("read")]
+        public async Task<IActionResult> Read(JsonElement json) 
+        {
+            string m = await System.IO.File.ReadAllTextAsync("C:\\Users\\CrazyIter\\Downloads\\m.json");
+            string p = await System.IO.File.ReadAllTextAsync("C:\\Users\\CrazyIter\\Downloads\\p.json");
+            List<ItemInfo> mlist=m.ToObject<List<ItemInfo>>();
+            List<ItemInfo> plist=p.ToObject<List<ItemInfo>>();
+            var km = mlist.Where(x=> x.knowledge.IsNotEmpty()).SelectMany(x => x.knowledge).Distinct();
+            var kp = plist.Where(x=> x.knowledge.IsNotEmpty()).SelectMany(x => x.knowledge).Distinct();
+            List<CodeLong> cp = new List<CodeLong>();
+            foreach (var item in kp)
+            {
+                var count = plist.Where(x => x.knowledge.Contains(item)).Count();
+                cp.Add(new CodeLong() { code = item, value = count });
+            }
+            List<CodeLong> cm = new List<CodeLong>();
+            foreach (var item in km)
+            {
+               var count =  mlist.Where(x => x.knowledge.Contains(item)).Count();
+               cm.Add(new CodeLong() { code = item, value = count });
+            }
+            return Ok(new { cm,cp });
         }
         [HttpPost("process-history")]
         public async Task<IActionResult> ProcessHistory(JsonElement json)
@@ -89,6 +78,7 @@ namespace HTEX.Test.Controllers
                     //base.json
                     LessonBase? lessonBase = null;
                     List<StudentLessonData> studentLessonDatas = new List<StudentLessonData>();
+                    //名单出席率低于30%的 不纳入计算。
                     try
                     {
                         BlobDownloadResult baseblobDownload = await _azureStorage.GetBlobContainerClient(item.school).GetBlobClient($"/records/{item.id}/IES/base.json").DownloadContentAsync();
@@ -185,7 +175,7 @@ namespace HTEX.Test.Controllers
         /// <param name="lessonRecord"></param>
         /// <param name="lessonBase"></param>
         /// <returns></returns>
-        public (LessonBase lessonBase, List<StudentLessonData> studentLessonDatas) GetBaseData(LessonRecord lessonRecord, LessonBase lessonBase)
+        private (LessonBase lessonBase, List<StudentLessonData> studentLessonDatas) GetBaseData(LessonRecord lessonRecord, LessonBase lessonBase)
         {
             //处理学生定位数据
             List<StudentLessonData> studentLessonDatas = new List<StudentLessonData>();
@@ -215,131 +205,39 @@ namespace HTEX.Test.Controllers
         /// <param name="lessonBase"></param>
         /// <param name="irsDatas"></param>
         /// <returns></returns>
-        public async Task<List<StudentLessonData>> GetIRSData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<IRSData> irsDatas, List<StudentLessonData> studentLessonDatas)
+        private async Task<List<StudentLessonData>> GetIRSData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<IRSData> irsDatas, List<StudentLessonData> studentLessonDatas)
         {
             List<string> interactTypes = new List<string>() { "PopQuesLoad", "ReAtmpAnsStrt", "BuzrAns", "BuzrLoad", "PickupResult" };
             //去重页面
-            var enventsInteract = timeLineData.events.Where(x => interactTypes.Contains(x.Event)).GroupBy(x => x.Pgid)
-                .Select(x => new { key = x.Key, list = x.ToList() });
-            foreach (var item in enventsInteract)
+            var enventsInteract = timeLineData.events.Where(x => interactTypes.Contains(x.Event)).GroupBy(x => x.Pgid) .Select(x => new { key = x.Key, list = x.ToList() });
+
+            if (enventsInteract!= null)
             {
-                var irsDataPages = irsDatas.Where(y => item.key.Equals(y.pageID));
-                foreach (var irsDataPage in irsDataPages)
+                var keys = enventsInteract.Select(x => x.key).ToList();
+
+                foreach (var item in enventsInteract)
                 {
-                    //检查是否设置正确答案。
-                    var answers = irsDataPage.question?["exercise"]?["answer"]?.ToJsonString().ToObject<List<string>>();
-                    var scoreNode = irsDataPage.question?["exercise"]?["score"];
-                    double score = 0;
-                    if (scoreNode!=null)
+                    ProcessIRSPageData(irsDatas, studentLessonDatas, item);
+                }
+                //处理其他,评测类型的互动,因为有可能不会记录在TimeLine.json中
+                var envents_other = timeLineData.events.Where(x => !keys.Contains(x.Pgid)).GroupBy(x => x.Pgid)  .Select(x => new { key = x.Key, list = x.ToList() });
+                if (envents_other!=null)
+                {
+                    foreach (var item in envents_other)
                     {
-                        double.TryParse(scoreNode.ToString(), out score);
+                        ProcessIRSPageData(irsDatas, studentLessonDatas, item);
                     }
-                    string interactType = string.Empty;
-
-                    if (irsDataPage.clientAnswers.IsNotEmpty())
+                }
+            }
+            else 
+            {
+                //处理其他,评测类型的互动,因为有可能不会记录在TimeLine.json中
+                var envents_other = timeLineData.events.GroupBy(x => x.Pgid) .Select(x => new { key = x.Key, list = x.ToList() });
+                if (envents_other!=null)
+                {
+                    foreach (var item in envents_other)
                     {
-
-                        //第一个list是几轮,一次作答,二次作答, 第二个list是学生的下标, 第三个list是 答案
-                        List<List<List<string>>> clientAnswers = new List<List<List<string>>>();
-                        foreach (var key in irsDataPage.clientAnswers.Keys)
-                        {
-                            clientAnswers.Add(irsDataPage.clientAnswers[key]);
-                        }
-                        // 获取第一个列表的长度作为比较基准
-                        int firstListLength = clientAnswers.First().Count;
-                        bool isSameLength = true;
-                        // 遍历剩余的列表并检查它们的长度是否与第一个列表相同
-                        foreach (var innerList in clientAnswers.Skip(1))
-                        {
-                            if (innerList.Count != firstListLength)
-                            {
-                                isSameLength = false;
-                                break;
-                            }
-                        }
-                        //并检查学生集合的长度是否与第一个列表相同
-                        if (!isSameLength  && studentLessonDatas.Count()==firstListLength)
-                        {
-                            //有设置答案
-                            if (answers.IsNotEmpty())
-                            {
-                                for (int index = 0; index< clientAnswers[0].Count; index++)
-                                {
-                                    //index  代表学生下标
-                                    List<InteractRecord> interactRecords = new List<InteractRecord>();
-                                    if (clientAnswers.Count==1)
-                                    {
-                                        //即问即答
-                                        interactType = "PopQuesLoad";
-                                        var ans0 = clientAnswers[0][index];
-                                        var IS0 = GetInteractResultHasAnswer(answers, ans0);
-                                        interactRecords.Add(new InteractRecord()
-                                        {
-                                            status = IS0,
-                                            interactType= interactType
-                                        });
-                                    }
-                                    if (clientAnswers.Count==2)
-                                    {
-                                        //二次作答
-                                        interactType="ReAtmpAnsStrt";
-                                        var ans1 = clientAnswers[1][index];
-                                        var IS1 = GetInteractResultHasAnswer(answers, ans1);
-                                        interactRecords.Add(new InteractRecord()
-                                        {
-                                            status = IS1,
-                                            interactType= interactType
-                                        });
-                                    }
-                                    if (clientAnswers.Count>2)
-                                    {
-                                        //三次作答
-                                        interactType="TeAtmpAnsStrt";
-                                        var ans2 = clientAnswers[2][index];
-                                        var IS2 = GetInteractResultHasAnswer(answers, ans2);
-                                        interactRecords.Add(new InteractRecord()
-                                        {
-                                            status = IS2,
-                                            interactType= interactType
-                                        });
-                                    }
-                                    studentLessonDatas[index].interactRecords.AddRange(interactRecords);
-                                }
-                            }
-                        }
-                    }
-                    //是否抢权作答的模式
-                    if (irsDataPage.isBuzz)
-                    {
-                        interactType = "BuzrAns";
-                        //处理参与抢权的
-                        Dictionary<string, InteractRecord> buzzParticipants = new Dictionary<string, InteractRecord>();
-                        foreach (var buzzParticipant in irsDataPage.buzzParticipants)
-                        {
-                            var studentData = studentLessonDatas.Find(x => x.seatID!.Equals(buzzParticipant));
-                            if (studentData != null)
-                            {
-                                buzzParticipants[buzzParticipant]=new InteractRecord() { status = InteractStatus.T1, interactType= interactType };
-                            }
-                        }
-                        //处理抢权成功的
-                        foreach (var buzzClient in irsDataPage.buzzClients)
-                        {
-                            buzzParticipants[buzzClient]=new InteractRecord() { status = InteractStatus.TT, interactType= interactType };
-                        }
-                        foreach (var studentLessonData in studentLessonDatas)
-                        {
-                            if (buzzParticipants.ContainsKey(studentLessonData.seatID!))
-                            {
-                                //处理已经有抢权结果的数据
-                                studentLessonData.interactRecords.Add(buzzParticipants[studentLessonData.seatID!]);
-                            }
-                            else
-                            {
-                                //处理未参与抢权的
-                                studentLessonData.interactRecords.Add(new InteractRecord() { status = InteractStatus.T0, interactType = interactType });
-                            }
-                        }
+                        ProcessIRSPageData(irsDatas, studentLessonDatas, item);
                     }
                 }
             }
@@ -359,7 +257,12 @@ namespace HTEX.Test.Controllers
                         var studentLessonData = studentLessonDatas.Find(x => x.seatID!.Equals($"{mbr}"));
                         if (studentLessonData!=null)
                         {
-                            studentLessonData.interactRecords.Add(new InteractRecord() { status = InteractStatus.TT, interactType = string.IsNullOrWhiteSpace(item.PickupType) ? "PickupResult" : item.PickupType });
+                            studentLessonData.interactRecords.Add(new InteractRecord() 
+                            {
+                                resultWeight = InteractWeight.TT,
+                                resultType="TT",
+                                interactType = string.IsNullOrWhiteSpace(item.PickupType) ? "PickupResult" : item.PickupType
+                            });
                         }
                     }
                 }
@@ -367,54 +270,372 @@ namespace HTEX.Test.Controllers
             return studentLessonDatas;
         }
 
-        private static InteractStatus GetInteractResultHasAnswer(List<string>? answers, List<string> ans0)
+        private static void ProcessIRSPageData(List<IRSData> irsDatas, List<StudentLessonData> studentLessonDatas, dynamic item)
         {
-            InteractStatus status = InteractStatus.T0;
-            if (ans0.IsNotEmpty())
+            var irsDataPages = irsDatas.Where(y => item.key.Equals(y.pageID));
+            foreach (var irsDataPage in irsDataPages)
             {
-                //标准答案等于作答的结果
-                if (answers!.Count == ans0.Count)
+                //检查是否设置正确答案。
+                var answers_q = irsDataPage.question?["exercise"]?["answer"]?.ToJsonString().ToObject<List<string>>();
+                List<string> answers = new List<string>();
+                answers_q?.ForEach(x => {
+                    if (!string.IsNullOrWhiteSpace(x))
+                    {
+                        answers.Add(x);
+                    }
+                });
+                var _objective = irsDataPage.question?["exercise"]?["objective"];
+                var scoreNode = irsDataPage.question?["exercise"]?["score"];
+                var _type = irsDataPage.question?["exercise"]?["type"];
+                var _answerType = irsDataPage.question?["exercise"]?["answerType"];//file,audio,text,image
+                double score = 0;
+                bool objective = false;
+              
+                if (_objective!=null) { 
+                    objective = _objective.GetValue<bool>();
+                }
+                //题型
+                string type= string.Empty;
+                if (_type!=null)
                 {
-                    if (answers.All(item => ans0.Contains(item)))
+                    //题型
+                     type  = _type.GetValue<string>();
+                    List<string> types = new List<string>() { "single", "multiple" , "judge" , "sortmultiple" };
+                    if (types.Contains(type))
                     {
-                        //完全正确
-                        status= InteractStatus.TT;
+                        objective = true;
                     }
-                    else
+                    else {
+                        objective = false;
+                    }
+                   
+                }
+
+                if (_answerType!=null)
+                {
+                    _answerType.GetValue<string>();
+
+                    //暂不处理,可能存在依然传文字的情况
+                    //不是文本作答的处理,题目不是客观题,答案不记录
+                    //if (!_answerType.Equals("text"))
+                    //{
+                    //    objective=false;
+                    //    answers=new List<string>();
+                    //}
+                }
+                if (scoreNode!=null)
+                {
+                    double.TryParse(scoreNode.ToString(), out score);
+                }
+                string interactType = string.Empty;
+
+                if (irsDataPage.clientAnswers.IsNotEmpty())
+                {
+
+                    //第一个list是几轮,一次作答,二次作答, 第二个list是学生的下标, 第三个list是 答案
+                    List<List<List<string>>> clientAnswers = new List<List<List<string>>>();
+                    foreach (var key in irsDataPage.clientAnswers.Keys)
+                    {
+                        clientAnswers.Add(irsDataPage.clientAnswers[key]);
+                    }
+                    // 获取第一个列表的长度作为比较基准
+                    int firstListLength = clientAnswers.First().Count;
+                    bool isSameLength = true;
+                    // 遍历剩余的列表并检查它们的长度是否与第一个列表相同
+                    foreach (var innerList in clientAnswers.Skip(1))
+                    {
+                        if (innerList.Count != firstListLength)
+                        {
+                            isSameLength = false;
+                            break;
+                        }
+                    }
+                    //并检查学生集合的长度是否与第一个列表相同
+                    if (!isSameLength  && studentLessonDatas.Count()==firstListLength)
+                    {
+                        for (int index = 0; index< clientAnswers[0].Count; index++)
+                        {
+                            //index  代表学生下标
+                            List<InteractRecord> interactRecords = new List<InteractRecord>();
+                            if (clientAnswers.Count==1)
+                            {
+                                //即问即答
+                                interactType = "PopQuesLoad";
+                                var ans0 = clientAnswers[0][index];
+                                var IS0 = GetInteractResultHasAnswer(answers, ans0, objective,type);
+                                interactRecords.Add(new InteractRecord()
+                                {
+                                    resultWeight = IS0.weight,
+                                    resultType=IS0.reultType,
+                                    interactType= interactType
+                                });
+                            }
+                            if (clientAnswers.Count==2)
+                            {
+                                //二次作答
+                                interactType="ReAtmpAnsStrt";
+                                var ans1 = clientAnswers[1][index];
+                                var IS1 = GetInteractResultHasAnswer(answers, ans1, objective,type);
+                                interactRecords.Add(new InteractRecord()
+                                {
+                                    resultWeight = IS1.weight,
+                                    resultType=IS1.reultType,
+                                    interactType= interactType
+                                });
+                            }
+                            if (clientAnswers.Count>2)
+                            {
+                                //三次作答
+                                interactType="TeAtmpAnsStrt";
+                                var ans2 = clientAnswers[2][index];
+                                var IS2 = GetInteractResultHasAnswer(answers, ans2, objective,type);
+                                interactRecords.Add(new InteractRecord()
+                                {
+                                    resultWeight = IS2.weight,
+                                    resultType=IS2.reultType,
+                                    interactType= interactType
+                                });
+                            }
+                            studentLessonDatas[index].interactRecords.AddRange(interactRecords);
+                        }
+
+                    }
+                }
+                //是否抢权作答的模式
+                if (irsDataPage.isBuzz)
+                {
+                    interactType = "BuzrAns";
+                    //处理参与抢权的
+                    Dictionary<string, InteractRecord> buzzParticipants = new Dictionary<string, InteractRecord>();
+                    foreach (var buzzParticipant in irsDataPage.buzzParticipants)
+                    {
+                        var studentData = studentLessonDatas.Find(x => x.seatID!.Equals(buzzParticipant));
+                        if (studentData != null)
+                        {
+                            buzzParticipants[buzzParticipant]=new InteractRecord() { resultWeight = InteractWeight.T1, interactType= interactType };
+                        }
+                    }
+                    //处理抢权成功的
+                    foreach (var buzzClient in irsDataPage.buzzClients)
+                    {
+                        buzzParticipants[buzzClient]=new InteractRecord() { resultWeight = InteractWeight.TT, interactType= interactType };
+                    }
+                    foreach (var studentLessonData in studentLessonDatas)
                     {
-                        //作答错误
-                        status= InteractStatus.T1;
+                        if (buzzParticipants.ContainsKey(studentLessonData.seatID!))
+                        {
+                            //处理已经有抢权结果的数据
+                            studentLessonData.interactRecords.Add(buzzParticipants[studentLessonData.seatID!]);
+                        }
+                        else
+                        {
+                            //处理未参与抢权的
+                            studentLessonData.interactRecords.Add(new InteractRecord() { resultWeight = InteractWeight.T0, interactType = interactType });
+                        }
                     }
                 }
-                //标准答案比作答的结果多
-                else if (answers!.Count > ans0.Count)
+            }
+        }
+
+        private static (double weight,string reultType) GetInteractResultHasAnswer(List<string>? answers, List<string> ans0 , bool objective,string type)
+        {
+            //List<string> ans0 = new List<string>();
+            //ans?.ForEach(x => {
+            //    if (!string.IsNullOrWhiteSpace(x))
+            //    {
+            //        ans0.Add(x);
+            //    }
+            //    else { ans.Add("");}
+            //});
+            double weight = InteractWeight.T0;
+            string reultType = InteractReultType.T0;
+            if (answers.IsNotEmpty())
+            {
+                if (ans0.IsNotEmpty())
                 {
-                    if (ans0.All(item => answers.Contains(item)))
+                    if (objective) //客观题
                     {
-                        //部分正确
-                        status= InteractStatus.TP;
+                        //标准答案等于作答的结果
+                        if (answers!.Count == ans0.Count)
+                        {
+                            if (answers.All(item => ans0.Contains(item)))
+                            {
+                                //完全正确
+                                weight= InteractWeight.TT;
+                                reultType= InteractReultType.TT;
+                            }
+                            else
+                            {
+                                //作答错误
+                                weight= InteractWeight.T1;
+                                reultType = InteractReultType.T1;
+                            }
+                        }
+                        //标准答案比作答的结果多
+                        else if (answers!.Count > ans0.Count)
+                        {
+                            if (ans0.All(item => answers.Contains(item)))
+                            {
+                                //部分正确
+                                weight= InteractWeight.TP;
+                                reultType = InteractReultType.TP;
+                            }
+                            else
+                            {
+                                //作答错误
+                                weight= InteractWeight.T1;
+                                reultType = InteractReultType.T1;
+                            }
+                        }
+                        //标准答案比作答结果少
+                        else
+                        {
+                            //作答错误
+                            weight= InteractWeight.T1;
+                            reultType = InteractReultType.T1;
+                        }
                     }
                     else
                     {
-                        //作答错误
-                        status= InteractStatus.T1;
+                        //填空题
+                        if ("complete".Equals(type) && answers!.Count==ans0.Count)
+                        {
+                            bool hasT = false;
+                            bool hasF = false;
+                            for (int i = 0; i < answers!.Count; i++) 
+                            {
+                                if (answers[i].Equals(ans0[i]))
+                                {
+                                    hasT=true;
+                                }
+                                else {
+                                    hasF=true;
+                                }
+                            }
+                            if (hasT && !hasF)
+                            {
+                                //完全正确
+                                weight= InteractWeight.TT;
+                                reultType = InteractReultType.TT;
+                            }
+                            else if (hasT && hasF)
+                            {
+                                //部分正确
+                                weight= InteractWeight.TP;
+                                reultType = InteractReultType.TP;
+                            }
+                            else if (!hasT && hasF)
+                            {
+                                //没有正确的,但有错误的,代表参与了
+                                weight= InteractWeight.T1;
+                                reultType = InteractReultType.T1;
+                            }
+                            else if (!hasT && !hasF)
+                            {
+                                //没有正确的,也没有错误的,代表没有作答
+                                weight= InteractWeight.T0;
+                                reultType = InteractReultType.T0;
+                            }
+                        }
+                        else
+                        {  
+                            //主观题,完全匹配的
+                            if (answers!.All(item => ans0.Contains(item)))
+                            {
+                                //完全正确
+                                weight= InteractWeight.TT;
+                                reultType = InteractReultType.TT;
+                            }
+                            else
+                            {   // 使用LINQ查询来判断是否有匹配的答案
+                                bool hasMatchingAnswer = answers!.Intersect(ans0).Any();
+                                if (hasMatchingAnswer)
+                                {
+                                    //主观题回答正确即为完全正确
+                                    weight= InteractWeight.TT;
+                                    reultType = InteractReultType.TT;
+                                }
+                                else
+                                {
+                                    //没有匹配上答案,则采用Levenshtein距离来评估两个字符串的相似度
+                                    var sc = CalculateSimilarity(answers![0], ans0[0]) * 1.0/100 *(InteractWeight.TT-InteractWeight.T1);
+                                    weight = sc;
+                                    reultType = InteractReultType.TP;
+                                }
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    //没有作答
+                    weight= InteractWeight.T0;
+                    reultType = InteractReultType.T0;
+                }
+            }
+            else 
+            {
+                //没有答案的情况
+                if (ans0.IsNotEmpty()) 
+                {
+                    bool hasAns = false;
+                    ans0.ForEach(x => {
+                        if (!string.IsNullOrWhiteSpace(x)) { 
+                         hasAns = true;    
+                        }
+                    });
+                    if (hasAns)
+                    {
+                        //作答了
+                        weight= InteractWeight.T1;
+                        reultType = InteractReultType.T1;
                     }
+                    else {
+                        //没有作答
+                        weight= InteractWeight.T0;
+                        reultType = InteractReultType.T0;
+                    }
+                   
                 }
-                //标准答案比作答结果少
                 else
                 {
-                    //作答错误
-                    status= InteractStatus.T1;
+                    //没有作答
+                    weight= InteractWeight.T0;
+                    reultType = InteractReultType.T0;
                 }
             }
-            else
+            return (weight,reultType);
+        }
+        #region C# 代码 如何判断两句话是否一个意思,非机器学习的算法。使用Levenshtein距离来评估两个字符串的相似度,但是不能判断它们是否表达了同一个意思,后续借助AI实现
+        public static double CalculateSimilarity(string s1, string s2)
+        {
+            int n = s1.Length;
+            int m = s2.Length;
+            int[,] d = new int[n + 1, m + 1];
+            for (int i = 0; i <= n; i++)
+            {
+                d[i, 0] = i;
+            }
+
+            for (int j = 0; j <= m; j++)
             {
-                //没有作答
-                status= InteractStatus.T0;
+                d[0, j] = j;
             }
-            return status;
+
+            for (int i = 1; i <= n; i++)
+            {
+                for (int j = 1; j <= m; j++)
+                {
+                    int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
+                    d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
+                }
+            }
+
+            return (1.0 - ((double)d[n, m] / Math.Max(s1.Length, s2.Length))) * 100;
         }
 
+         
+        #endregion
         /// <summary>
         /// 获取课中评测数据
         /// </summary>
@@ -424,20 +645,79 @@ namespace HTEX.Test.Controllers
         /// <param name="coworkDatas"></param>
         /// <param name="studentLessonDatas"></param>
         /// <returns></returns>
-        public async Task<dynamic> GetExamData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<ExamData> examDatas, List<StudentLessonData> studentLessonDatas) 
+        private async Task<dynamic> GetExamData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<ExamData> examDatas, List<StudentLessonData> studentLessonDatas) 
         {
+            foreach (var examData in examDatas) 
+            {
+                var allocation=  examData?.exam?.papers?.SelectMany(x => x.point).Sum();
+                var answersStd = examData?.exam?.papers?.FirstOrDefault()?.answers;
+                List<List<string>> answers= new List<List<string>>();
+                if (answersStd!=null) 
+                {
+                 answersStd.ForEach(x =>
+                 {
+                     List<string> ans = new List<string>();
+                    x.ForEach(y =>
+                    {
+                        if (string.IsNullOrWhiteSpace(y)) 
+                        {
+                        
+                        }
+                    });
+                     answers.Add(ans);
+                 });
+                }
+                examData?.examClassResult?.ForEach(item =>{
+                    int index = 0;
+                    item.studentAnswersArray.ForEach(x => {
+                        //是否要判断主观题或者客观题, 多套试卷,有主观题的
+                        //主观题有回答的:608942756458532864\Clients\18782481024\Ans\27-4341670635487887360-examExchangeAnswerlist   27 从1开始的学生序号-4341670635487887360评测编号,内容qNo 是从1开始的题号。
+                        if (x.IsNotEmpty()) 
+                        {
+                        }
+                    });
+                });
+            }
             return null;
         }
-
-        public async Task<dynamic> GetCoworkData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<CoworkData> coworkDatas, List<StudentLessonData> studentLessonDatas)
+        /// <summary>
+        /// 协作参与率 态度计算
+        /// </summary>
+        /// <param name="lessonRecord"></param>
+        /// <param name="lessonBase"></param>
+        /// <param name="timeLineData"></param>
+        /// <param name="coworkDatas"></param>
+        /// <param name="studentLessonDatas"></param>
+        /// <returns></returns>
+        private async Task<dynamic> GetCoworkData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<CoworkData> coworkDatas, List<StudentLessonData> studentLessonDatas)
         {
             return Ok(lessonRecord);
         }
-        public async Task<dynamic> GetTaskData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<TaskData> taskDatas, List<StudentLessonData> studentLessonDatas)
+
+        /// <summary>
+        /// 处理学生回推数据,并将回推纳入学习态度计算。
+        /// </summary>
+        /// <param name="lessonRecord"></param>
+        /// <param name="lessonBase"></param>
+        /// <param name="timeLineData"></param>
+        /// <param name="taskDatas"></param>
+        /// <param name="studentLessonDatas"></param>
+        /// <returns></returns>
+        private async Task<dynamic> GetTaskData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<TaskData> taskDatas, List<StudentLessonData> studentLessonDatas)
         {
             return Ok(lessonRecord);
         }
-        public async Task<dynamic> GetSmartRatingData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<SmartRatingData> smartRatingDatas, List<StudentLessonData> studentLessonDatas)
+
+        /// <summary>
+        /// 评分参与率 态度计算
+        /// </summary>
+        /// <param name="lessonRecord"></param>
+        /// <param name="lessonBase"></param>
+        /// <param name="timeLineData"></param>
+        /// <param name="smartRatingDatas"></param>
+        /// <param name="studentLessonDatas"></param>
+        /// <returns></returns>
+        private async Task<dynamic> GetSmartRatingData(LessonRecord lessonRecord, LessonBase lessonBase, TimeLineData timeLineData, List<SmartRatingData> smartRatingDatas, List<StudentLessonData> studentLessonDatas)
         {
             return Ok(lessonRecord);
         }
@@ -492,46 +772,109 @@ namespace HTEX.Test.Controllers
         /// 互动积分记录[-1,0,10]
         /// </summary>
         public List<InteractRecord> interactRecords { get; set; } = new List<InteractRecord>();
+        /// <summary>
+        /// 学生评测记录相关
+        /// </summary>
+        public List<StudentExamRecord> examRecords { get; set; } = new List<StudentExamRecord>();
     }
+    /// <summary>
+    /// 学生课中评测记录
+    /// </summary>
     public class StudentExamRecord
     {
-
+        /// <summary>
+        /// 作答率0 未作答,1,所有题目作答,小数点则表示部分作答。 可纳入学习态度计算,  主观题需要确认 是否会在此写入答案。
+        /// </summary>
+        public int  answerRate{ get; set; }
+        /// <summary>
+        /// 评测得分
+        /// </summary>
+        public double score { get; set;}
+        /// <summary>
+        /// 评测id
+        /// </summary>
+        public string? examId { get; set; }
+        /// <summary>
+        /// 得分率
+        /// </summary>
+        public double scoreRate { get; set; }
+        /// <summary>
+        /// 配分
+        /// </summary>
+      //  public double allocation { get; set; }
+        /// <summary> 
+        /// 暂不计算
+        /// 每个题的得分情况
+        /// </summary>
+        // public List<double> itemScores { get; set; } = new List<double>();
     }
 
     public class InteractRecord
     {
+       
+        /// <summary>
+        ///    { "PopQuesLoad", "ReAtmpAnsStrt", "BuzrAns", "BuzrLoad", "PickupResult" };
+        /// </summary>
+        public string? interactType { get; set; }
+        /// <summary>
+        /// 互动结果权重,纳入学习状态计算
+        /// </summary>
+        public double resultWeight { get; set; }
+        /// <summary>
+        /// 互动结果类型,如:T0没有作答, 没有参与,T1作答错误,有参加,有抢权,TP部分正确,TT作答正确,抢权成功,被抽到
+        /// </summary>
+        public string? resultType { get; set; }
+         /// <summary>
+         /// 互动结果分值
+         /// </summary>
+        //public double resultScore { get; set; }
+        /// <summary>
+        ///  互动积分,试题评分
+        /// </summary>
+        public double interactScore { get; set; }
         /// <summary>
         /// 基准分值
         /// </summary>
         public double criterion { get; set; }
+    }
+    public static class InteractReultType 
+    {
         /// <summary>
-        ///    { "PopQuesLoad", "ReAtmpAnsStrt", "BuzrAns", "BuzrLoad", "PickupResult" };
+        /// 没有作答, 没有参与0
         /// </summary>
-        public string? interactType { get; set; }
-
+        public static readonly string T0 = "T0";
         /// <summary>
-        /// 互动结果状态
+        /// 作答错误,有参加,有抢权1
+        /// </summary>
+        public static readonly string T1 = "T1";
+        /// <summary>
+        /// 部分正确1.3
         /// </summary>
-        public InteractStatus status { get; set; }
+        public static readonly string TP = "TP";
+        /// <summary>
+        /// 作答正确,抢权成功,被抽到1.5
+        /// </summary>
+        public static readonly string TT ="TT";
+
     }
-    public enum InteractStatus
+    public static class InteractWeight 
     {
         /// <summary>
         /// 没有作答, 没有参与0
         /// </summary>
-        T0,
+        public static readonly double T0 = 0;
         /// <summary>
         /// 作答错误,有参加,有抢权1
         /// </summary>
-        T1,
+        public static readonly double T1 = 1;
         /// <summary>
         /// 部分正确1.3
         /// </summary>
-        TP,
+        public static readonly double TP =1.3;
         /// <summary>
-        /// 作答正确,抢权成功,被抽到
+        /// 作答正确,抢权成功,被抽到1.5
         /// </summary>
-        TT
+        public static readonly double TT = 1.5;
 
         /// 互动参与指数(按倍数的权重设计)
         /// 无二次作答的互动,且未设置正确答案:1.未作答0

+ 80 - 3
TEAMModelOS.Extension/HTEX.Test/Program.cs

@@ -1,5 +1,7 @@
 using MathNet.Numerics;
 using System.Configuration;
+using System.Diagnostics;
+using System.Text.RegularExpressions;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 namespace HTEX.Test
@@ -37,14 +39,89 @@ namespace HTEX.Test
             app.Run();
         }
 
-        public  static string Test()
+        public static string Test()
         {
-            List<string > a = new List<string>() { "a","d"};
+            string originalString = "这 是 一      个 测 试。\n \r\t  ";
+            string stringWithoutSpaces = Regex.Replace(originalString, @"\s+", "");
+            // 标准答案集合
+            List<string> standardAnswers = new List<string> { "apple", "banana", "cherry" };
+            // 学生作答集合
+            List<string> studentAnswers = new List<string> { "bana1na", "orange", "grape", "111" };
+
+            // 使用LINQ查询来判断是否有匹配的答案
+            bool hasMatchingAnswer = standardAnswers.Intersect(studentAnswers).Any();
+
+            // 输出结果
+            Console.WriteLine("学生答案中是否有符合标准答案的? " + (hasMatchingAnswer ? "是" : "否"));
+
+
+            string sentence1 = "学生答案中是否有符合标准答案的";
+            string sentence2 = "学生答案中是否有标合标准答案的";
+
+            double areSimilar = AreSentencesSimilar(sentence1, sentence2);
+            Console.WriteLine($"Are the sentences similar? {areSimilar}");
+
+            List<string> a = new List<string>() { "a", "d" };
             List<string> b = new List<string>() { "b", "a", "c" };
-            var v = ( a.All(item => b.Contains(item)));
+            var v = (a.All(item => b.Contains(item)));
             string c = "[\r\n  1\r\n]";
             List<int> d = c.ToObject<List<int>>();
             return "Hello World!";
         }
+        public static void  calculate_text_similarity() 
+        {
+            string pythonExe = @"C:\Path\To\Python\python.exe"; // 修改为你的Python解释器路径  
+            string scriptPath = @"C:\Path\To\Your\Script\calculate_similarity.py"; // 修改为你的脚本路径  
+            string text1 = "今天天气真好";
+            string text2 = "今天天气不错";
+
+            ProcessStartInfo start = new ProcessStartInfo();
+            start.FileName = pythonExe;
+            start.Arguments = $"\"{scriptPath}\" \"{text1}\" \"{text2}\"";
+            start.UseShellExecute = false;
+            start.RedirectStandardOutput = true;
+
+            using (Process process = Process.Start(start))
+            {
+                using (StreamReader reader = process.StandardOutput)
+                {
+                    string result = reader.ReadToEnd();
+                    Console.Write(result);
+                }
+            }
+        }
+        public static double CalculateSimilarity(string s1, string s2)
+        {
+            int n = s1.Length;
+            int m = s2.Length;
+            int[,] d = new int[n + 1, m + 1];
+            for (int i = 0; i <= n; i++)
+            {
+                d[i, 0] = i;
+            }
+
+            for (int j = 0; j <= m; j++)
+            {
+                d[0, j] = j;
+            }
+
+            for (int i = 1; i <= n; i++)
+            {
+                for (int j = 1; j <= m; j++)
+                {
+                    int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
+                    d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
+                }
+            }
+
+            return (1.0 - ((double)d[n, m] / Math.Max(s1.Length, s2.Length))) * 100;
+        }
+
+        public static double AreSentencesSimilar(string sentence1, string sentence2)
+        {
+          //  double threshold = 70; // 设置一个阈值,超过这个百分比则认为是相同的句子
+            double similarity = CalculateSimilarity(sentence1, sentence2);
+            return similarity ;
+        }
     }
 }

+ 25 - 0
TEAMModelOS.Extension/HTEX.Test/PythonCode/calculate_similarity.py

@@ -0,0 +1,25 @@
+# calculate_similarity.py  
+import sys  
+import torch  
+from transformers import BertTokenizer, BertModel  
+from torch.nn.functional import cosine_similarity, normalize  
+  
+tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')  
+model = BertModel.from_pretrained('bert-base-chinese')  
+  
+def calculate_text_similarity(text1, text2):  
+    encoded_inputs = tokenizer([text1, text2], padding=True, truncation=True, return_tensors="pt")  
+    with torch.no_grad():  
+        outputs = model(**encoded_inputs)  
+        last_hidden_states = outputs.last_hidden_state  
+    cls_vectors = last_hidden_states[:, 0, :]  
+    cls_vectors_normalized = normalize(cls_vectors, p=2, dim=1)  
+    similarity = cosine_similarity(cls_vectors_normalized[0:1], cls_vectors_normalized[1:2]).item()  
+    return similarity  
+  
+if __name__ == "__main__":  
+    text1 = sys.argv[1]  
+    text2 = sys.argv[2]  
+    print(calculate_text_similarity(text1, text2))
+
+ 

+ 34 - 0
TEAMModelOS.Extension/HTEX.Test/PythonCode/demo.py

@@ -0,0 +1,34 @@
+import torch
+from transformers import BertTokenizer, BertModel
+from torch.nn.functional import cosine_similarity, normalize
+
+# 加载预训练的BERT分词器和模型  
+tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
+model = BertModel.from_pretrained('bert-base-chinese')
+
+# 准备文本  
+texts = ["今天天气真好", "今天天气不错"]
+
+# 编码文本  
+encoded_inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
+
+# 使用BERT模型获取文本的向量表示  
+with torch.no_grad():
+    outputs = model(**encoded_inputs)
+    last_hidden_states = outputs.last_hidden_state
+
+# 通常取[CLS] token的隐藏状态作为整个句子的表示  
+# 假设batch_size为文本的数量(在这个例子中是2)  
+cls_vectors = last_hidden_states[:, 0, :]  # 取每个序列的第一个token(即[CLS])  
+
+# 归一化向量  
+cls_vectors_normalized = normalize(cls_vectors, p=2, dim=1)
+
+# 计算两段文本之间的余弦相似度  
+# 注意:我们需要将向量reshape为(1, embedding_dim)来匹配cosine_similarity的输入要求  
+similarity = cosine_similarity(cls_vectors_normalized[0:1], cls_vectors_normalized[1:2])
+
+# 打印相似度  
+print(f"两段文本的相似度为: {similarity.item()}")
+
+ 

+ 3 - 3
TEAMModelOS.Function/TEAMModelOS.Function.csproj

@@ -5,9 +5,9 @@
     <OutputType>Exe</OutputType>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
-	<Version>5.2408.28</Version>
-	<AssemblyVersion>5.2408.28.1</AssemblyVersion>
-	<FileVersion>5.2408.28.1</FileVersion>
+	<Version>5.2409.4</Version>
+	<AssemblyVersion>5.2409.4.1</AssemblyVersion>
+	<FileVersion>5.2409.4.1</FileVersion>
 	<PackageId>TEAMModelOS.FunctionV4</PackageId>
 	<Authors>teammodel</Authors>
 	<Company>醍摩豆(成都)信息技术有限公司</Company>

+ 12 - 7
TEAMModelOS.SDK/Models/Cosmos/Common/ExamClassResult.cs

@@ -59,13 +59,18 @@ namespace TEAMModelOS.SDK.Models
         public List<int> fpc { get; set; } = new List<int>();
         public List<string> paper { get; set; }= new List<string>();
     }
-/*    public class PaperSimple {
-        public string id { get; set; }
-        public string name { get; set; }
-        public string code { get; set; }
-        public string blob { get; set; }
-        public string scope { get; set; }
-    }*/
+    //ExamClassResult 學生作答紀錄
+    public class ExamClassResultStudentAnswerArray : ExamClassResult
+    {
+        public List<List<string>> studentAnswersArray { get; set; }
+    }
+    /*    public class PaperSimple {
+            public string id { get; set; }
+            public string name { get; set; }
+            public string code { get; set; }
+            public string blob { get; set; }
+            public string scope { get; set; }
+        }*/
     public class ClassInfo {
         public string id { get; set; }
         public string name { get; set; }

+ 2 - 2
TEAMModelOS.SDK/Models/Cosmos/Common/LessonRecord.cs

@@ -579,7 +579,7 @@ namespace TEAMModelOS.SDK.Models
     public class ExamData 
     {
         public ExamInfo exam { get; set; }
-       public List<ExamClassResult> examClassResult { get; set; }
+        public List<ExamClassResultStudentAnswerArray> examClassResult { get; set; } = new List<ExamClassResultStudentAnswerArray>();
     }
     /// <summary>
     /// IRS.json
@@ -903,7 +903,7 @@ namespace TEAMModelOS.SDK.Models
         /// <summary>
         /// 參與率分布圖數據(HT畫圖表用)
         /// </summary>
-        public List<string> engagmentDistribution { get; set; } = new List<string>();
+        public List<double> engagmentDistribution { get; set; } = new List<double>();
     }
 
     public class QuizSummaryList

+ 3 - 3
TEAMModelOS.SDK/Models/Service/ExamService.cs

@@ -143,13 +143,13 @@ namespace TEAMModelOS.SDK.Models.Service
                         {
                             double totalScore = info.papers.SelectMany(x => x.point).Sum();
                             var classResult = classResults.Where(x => x.examId.Equals(info.id)).ToList();
-                            var classScores = classResult.GroupBy(x => x.info.id).Select(c => new { classId = c.Key, average = Math.Round(c.ToList().Sum(z => z.average) / info.subjects.Count / totalScore,2) }).ToList();
+                            var classScores = classResult.GroupBy(x => x.info.id).Select(c => new { classId = c.Key, average = Math.Round(c.ToList().Sum(z => z.average) / info.subjects.Count / totalScore,4) }).ToList();
                             List<(string className, double average)> classMore = new();
                             foreach (var cs in classScores)
                             {
                                 classMore.Add((cs.classId, cs.average));
                             }
-                            var gradeScores = Math.Round(info.average / totalScore,2);
+                            var gradeScores = Math.Round(info.average / totalScore,4);
                             List<(string sname, double scores, string classId)> stus = new();
                             foreach (RMember member in rmembers)
                             {
@@ -162,7 +162,7 @@ namespace TEAMModelOS.SDK.Models.Service
                                         scroe += result.sum[index];
                                     }
                                 }
-                                var persent = Math.Round(scroe * 1.0 / totalScore, 2);
+                                var persent = Math.Round(scroe * 1.0 / totalScore, 4);
                                 stus.Add((member.id, persent, member.classId));
                             }
                             grades.Add((info.name, classMore, gradeScores, stus,info.startTime));

+ 3 - 3
TEAMModelOS.SDK/TEAMModelOS.SDK.csproj

@@ -1,9 +1,9 @@
 <Project Sdk="Microsoft.NET.Sdk">
 	<PropertyGroup>
 		<TargetFramework>net8.0</TargetFramework>
-		<Version>5.2408.28</Version>
-		<AssemblyVersion>5.2408.28.1</AssemblyVersion>
-		<FileVersion>5.2408.28.1</FileVersion>
+		<Version>5.2409.4</Version>
+		<AssemblyVersion>5.2409.4.1</AssemblyVersion>
+		<FileVersion>5.2409.4.1</FileVersion>
 		<PackageReleaseNotes>发版</PackageReleaseNotes>
 	</PropertyGroup>
 

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

@@ -2773,6 +2773,8 @@ const LANG_EN_US = {
         imported2: 'Key concepts',
         imported3: 'Add and import',
         imported4: 'Key concepts',
+        treeName: 'Key concept tree name',
+        addTree: 'New key concept Tree',
     },
     // 活动模块
     learnActivity: {
@@ -8036,6 +8038,8 @@ const LANG_EN_US = {
         Warning03: 'Are you sure you want to delete the group?',
         notice: 'hint',
         Warning04: 'Are you sure you want to delete the schedule?',
+        enterReview: 'Enter review',
+        Approved: 'Approved',
     },
     activity: {
         scoreWord: {

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

@@ -2772,6 +2772,8 @@ const LANG_ZH_CN = {
         imported2: '个知识点',
         imported3: '新增并导入',
         imported4: '个知识点',
+        treeName: '知识点树名',
+        addTree: '新增知识点树',
     },
     // 活动模块
     learnActivity: {
@@ -8037,6 +8039,8 @@ const LANG_ZH_CN = {
         Warning03: '确定要删除组别?',
         notice: '提示',
         Warning04: '确定要删除时程?',
+        enterReview: '進入批閱',
+        Approved: '已批阅',
     },
     activity: {
         scoreWord: {

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

@@ -2775,6 +2775,8 @@ const LANG_ZH_TW = {
         imported2: '個知識點',
         imported3: '新增並匯入',
         imported4: '個知識點',
+        treeName: '知識點樹名',
+        addTree: '新增知識點樹',
     },
     // 活动模块
     learnActivity: {
@@ -8037,6 +8039,8 @@ const LANG_ZH_TW = {
         Warning03: '確定要刪除組別?',
         notice: '提示',
         Warning04: '確定要刪除時程?',
+        enterReview: '進入批閱',
+        Approved: '已批閱',
     },
     activity: {
         scoreWord: {

+ 4 - 0
TEAMModelOS/ClientApp/src/api/htcommunity.js

@@ -40,5 +40,9 @@ export default {
     jointCourseFind: function (data) {
         return post('/joint/course/find', data)
     },
+    // 取得教師BlobSas
+    jointTblobsas: function (data) {
+        return post('/joint/tblobsas', data)
+    },
 	
 }

+ 8 - 0
TEAMModelOS/ClientApp/src/api/knowledge.js

@@ -43,4 +43,12 @@ export default {
     modifyNodeName: function(data) {
         return post('/knowledge/modify', data)
     },
+    // 删除知识点结构
+    deleteList: function(data) {
+        return post('/knowledge/delete', data)
+    },
+    // 删除所有旧知识点
+    clearOld: function(data) {
+        return post('/knowledge/clear-old', data)
+    },
 }

+ 10 - 0
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -1419,6 +1419,16 @@
 										permission: "",
 										menuName: "personalBank",
 										isShow: true
+									},
+									{
+										icon: "iconfont icon-k-point",
+										name: this.$t("system.menu.kdBack"),
+										router: "/home/personalKnowledge",
+										tag: "",
+										role: "admin|teacher",
+										permission: "",
+										menuName: "personalKnowledge",
+										isShow: true
 									}
 								]
 							},

+ 167 - 60
TEAMModelOS/ClientApp/src/common/BaseQuickPaper.vue

@@ -1,47 +1,55 @@
 <template>
 	<div class="quick-paper-container">
 		<p style="float: right; cursor: pointer; color: #4d9cdc" @click="onChangeMode">{{ buildMode === "type" ? $t("evaluation.quickPaper.mode1") : $t("evaluation.quickPaper.mode2") }}</p>
-		<p class="title">
-			{{ $t("evaluation.quickPaper.info") }}
-			<Checkbox v-model="isSecret" style="margin-left: 20px" v-if="isSchool">
-				<Tooltip :content="$t('evaluation.secretTip2')" class="common-toolTip" placement="bottom-end" max-width="200">
-					<span>{{ $t("evaluation.secretTip1") }}</span>
-				</Tooltip>
-			</Checkbox>
-		</p>
-		<Input v-model="paperInfo.name" v-special-char :placeholder="$t('evaluation.quickPaper.tip1')" />
-		<div v-if="isSchool && schoolInfo">
-			<Select v-model="paperInfo.periodIndex" @on-change="onPeriodChange" style="width: 200px; margin-right: 15px; margin-top: 15px">
-				<Option v-for="(period, index) in schoolInfo.period" :value="index" :key="index">
-					{{ period.name }}
-				</Option>
-			</Select>
-			<Select v-model="paperInfo.gradeIndex" multiple :max-tag-count="2" :placeholder="$t('evaluation.newExercise.gradePlaceholder')" style="width: 250px; margin-right: 15px; margin-top: 15px">
-				<Option v-for="(grade, index) in gradeList" :value="index" :key="grade">
-					{{ grade }}
-				</Option>
-			</Select>
-			<Select v-model="paperInfo.subjectIndex" style="width: 250px; margin-right: 15px; margin-top: 15px">
-				<Option v-for="(subject, index) in subjectList" :value="index" :key="index">
-					{{ subject.name }}
-				</Option>
-			</Select>
-		</div>
-		<p class="title">
-			{{ $t("evaluation.quickPaper.attachments") }}
-			<Upload action="" style="display: inline-block" :format="['jpg', 'jpeg', 'png', 'pdf']" :max-size="2048" multiple :before-upload="handleBeforeUpload" :show-upload-list="false">
-				<span class="btn-upload">{{ $t("evaluation.quickPaper.upload") }}</span>
-			</Upload>
-		</p>
-		<div class="img-list">
-			<div v-if="!attachments.length" style="margin: 20px 30px; color: #9999">{{ $t("evaluation.quickPaper.empty") }}</div>
-			<div class="img-item" v-for="(img, imgIndex) in attachments" :key="imgIndex">
-				<img :src="img" alt="" srcset="" />
-				<p style="text-align: center; cursor: pointer; font-size: 12px">
-					<span @click="doPreview(img)">{{ $t("evaluation.quickPaper.preview") }}</span>
-					<span style="margin-left: 10px" @click="doDelete(imgIndex)">{{ $t("evaluation.quickPaper.del") }}</span>
-				</p>
+		<template v-if="!isMarkModel">
+			<p class="title">
+				{{ $t("evaluation.quickPaper.info") }}
+				<Checkbox v-model="isSecret" style="margin-left: 20px" v-if="isSchool">
+					<Tooltip :content="$t('evaluation.secretTip2')" class="common-toolTip" placement="bottom-end" max-width="200">
+						<span>{{ $t("evaluation.secretTip1") }}</span>
+					</Tooltip>
+				</Checkbox>
+			</p>
+			<Input v-model="paperInfo.name" v-special-char :placeholder="$t('evaluation.quickPaper.tip1')" />
+			<div v-if="isSchool && schoolInfo">
+				<Select v-model="paperInfo.periodIndex" @on-change="onPeriodChange" style="width: 200px; margin-right: 15px; margin-top: 15px">
+					<Option v-for="(period, index) in schoolInfo.period" :value="index" :key="index">
+						{{ period.name }}
+					</Option>
+				</Select>
+				<Select v-model="paperInfo.gradeIndex" multiple :max-tag-count="2" :placeholder="$t('evaluation.newExercise.gradePlaceholder')" style="width: 250px; margin-right: 15px; margin-top: 15px">
+					<Option v-for="(grade, index) in gradeList" :value="index" :key="grade">
+						{{ grade }}
+					</Option>
+				</Select>
+				<Select v-model="paperInfo.subjectIndex" style="width: 250px; margin-right: 15px; margin-top: 15px">
+					<Option v-for="(subject, index) in subjectList" :value="index" :key="index">
+						{{ subject.name }}
+					</Option>
+				</Select>
+			</div>
+			<p class="title">
+				{{ $t("evaluation.quickPaper.attachments") }}
+				<Upload action="" style="display: inline-block" :format="['jpg', 'jpeg', 'png', 'pdf']" :max-size="2048" multiple :before-upload="handleBeforeUpload" :show-upload-list="false">
+					<span class="btn-upload">{{ $t("evaluation.quickPaper.upload") }}</span>
+				</Upload>
+			</p>
+			<div class="img-list">
+				<div v-if="!attachments.length" style="margin: 20px 30px; color: #9999">{{ $t("evaluation.quickPaper.empty") }}</div>
+				<div class="img-item" v-for="(img, imgIndex) in attachments" :key="imgIndex">
+					<img :src="img" alt="" srcset="" />
+					<p style="text-align: center; cursor: pointer; font-size: 12px">
+						<span @click="doPreview(img)">{{ $t("evaluation.quickPaper.preview") }}</span>
+						<span style="margin-left: 10px" @click="doDelete(imgIndex)">{{ $t("evaluation.quickPaper.del") }}</span>
+					</p>
+				</div>
 			</div>
+		</template>
+		<div class="manual-filter-item" v-else>
+			<span class="manual-filter-label">{{ $t("evaluation.filter.range") }}:</span>
+			<Tag color="blue">{{ condName.periodName }}</Tag>
+			<Tag color="geekblue">{{ condName.subjectName }}</Tag>
+			<Tag color="purple">{{ condName.gradeName }}</Tag>
 		</div>
 		<div class="type-quick-part" v-if="buildMode === 'type'">
 			<p class="title">{{ $t("evaluation.quickPaper.setItem") }}</p>
@@ -176,7 +184,23 @@
 			editPaper: {
 				type: Object,
 				default: () => {}
-			}
+			},
+			isMarkModel: {
+				type: Boolean,
+				default: false
+			},
+			subjectCode: {
+				type: String,
+				default: ""
+			},
+			periodCode: {
+				type: String,
+				default: ""
+			},
+			gradeCode: {
+				type: Array,
+				default: () => []
+			},
 		},
 		data() {
 			return {
@@ -533,7 +557,7 @@
 					this.$nextTick(() => {
 						if (this.buildMode === "type") {
 							if (type === "single") {
-								this.singleAns[index] = newStr;
+								this.$set(this.singleAns, index, newStr);
 							} else {
 								this.$set(this.multipleAns, index, newStr);
 							}
@@ -787,7 +811,7 @@
 			},
 			/* 保存试卷 */
 			async onConfirmSave() {
-				if (!this.paperInfo.name) {
+				if (!this.paperInfo.name && !this.isMarkModel) {
 					this.$Message.warning(this.$t("evaluation.paperList.emptyNameTip"));
 					console.log(this.paperInfo);
 					return;
@@ -797,6 +821,7 @@
 					if (this.buildMode === "type") {
 						if (this.paperInfo.items.every((i) => i.count < 1)) {
 							this.$Message.warning(this.$t("evaluation.quickPaper.tip10"));
+							if(this.isMarkModel) this.$emit('editLoadingChange')
 							return;
 						}
 						/* 判定答案数量与题目数量是否一致 */
@@ -808,46 +833,58 @@
 						})
 						if (singleItems.count && singleAnsNum.length !== singleItems.count) {
 							this.$Message.warning(this.$t("evaluation.quickPaper.tip11"));
+							if(this.isMarkModel) this.$emit('editLoadingChange')
 							return;
 						}
 						if (multipleItems.count && this.multipleAns.length !== multipleItems.count) {
 							this.$Message.warning(this.$t("evaluation.quickPaper.tip12"));
+							if(this.isMarkModel) this.$emit('editLoadingChange')
 							return;
 						}
 					}
 					if (this.buildMode === "order") {
 						if (!this.orderItemsArr.length) {
 							this.$Message.warning(this.$t("evaluation.quickPaper.tip10"));
+								if(this.isMarkModel) this.$emit('editLoadingChange')
 							return;
 						} else {
 							console.error(this.orderItemsArr);
 							if (this.orderItemsArr.some((i) => ["single", "multiple"].includes(i.type) && !i.answer)) {
 								this.$Message.warning(this.$t("evaluation.exerciseList.noAnswerTip"));
+								if(this.isMarkModel) this.$emit('editLoadingChange')
 								return;
 							}
 						}
 					}
-					let sasData = this.isSchool ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas();
-					let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.isSchool ? "school" : "private");
-					/* 检测试卷是否已存在 */
-					let isPaperExist = await this.isPaperExist(this.paperInfo.name, containerClient);
-					if (isPaperExist && (!this.isEditPaper || (this.isEditPaper && this.paperInfo.name !== this.oldPaperName))) {
-						this.$Modal.confirm({
-							title: this.$t("evaluation.newExercise.modalTip"),
-							content: this.$t("evaluation.paperList.isExistPaperTip"),
-							onOk: async () => {
-								this.doSavePaper(containerClient);
-							},
-							onCancel: () => {
-								this.$Message.warning(this.$t("evaluation.paperList.cancelSaveTip"));
-							}
-						});
+					/**
+					 * 阅卷专用下,无需检查附件、试卷名称,答案没问题后需给试题添加题目、选项等不存在的内容,不走以下逻辑
+					 */
+					if(this.isMarkModel) {
+						let arr = await this.saveExercise()
+						this.$emit("addFinish", arr)
 					} else {
-						this.doSavePaper(containerClient);
+						let sasData = this.isSchool ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas();
+						let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.isSchool ? "school" : "private");
+						/* 检测试卷是否已存在 */
+						let isPaperExist = await this.isPaperExist(this.paperInfo.name, containerClient);
+						if (isPaperExist && (!this.isEditPaper || (this.isEditPaper && this.paperInfo.name !== this.oldPaperName))) {
+							this.$Modal.confirm({
+								title: this.$t("evaluation.newExercise.modalTip"),
+								content: this.$t("evaluation.paperList.isExistPaperTip"),
+								onOk: async () => {
+									this.doSavePaper(containerClient);
+								},
+								onCancel: () => {
+									this.$Message.warning(this.$t("evaluation.paperList.cancelSaveTip"));
+								}
+							});
+						} else {
+							this.doSavePaper(containerClient);
+						}
 					}
 				}
 
-				if (!this.attachments.length) {
+				if (!this.isMarkModel && !this.attachments.length) {
 					this.$Modal.confirm({
 						title: this.$t('schoolBaseInfo.warmTips'),
 						content: this.$t('evaluation.markMode.tip1'),
@@ -862,6 +899,69 @@
 					saveFn()
 				}
 			},
+			/* 只保存为blob试题样式 */
+			async saveExercise() {
+				try {
+					let blobItemPromiseArr = []
+					if (this.buildMode === "type") {
+						this.paperInfo.items.forEach(async (typeItem) => {
+							if (typeItem.count) {
+								for (var i = 0; i < typeItem.count; i++) {
+									blobItemPromiseArr.push({
+										id: this.$tools.guid(),
+										question: this.typeMap[typeItem.type],
+										option: this.getItemOptions(typeItem),
+										type: typeItem.type,
+										level: 1,
+										explain: null,
+										answer: this.getItemAnswer(typeItem, i),
+										repair: [],
+										field: 1,
+										knowledge: [],
+										children: [],
+										score: typeItem.score,
+										code: this.isSchool ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+										answerType: typeItem.type === "subjective" ? this.subjectiveArr[i].answerType : 'text',
+										useAutoScore: typeItem.type === "subjective" ? this.subjectiveArr[i].useAutoScore : false,
+										answerLang: typeItem.type === "subjective" ? this.subjectiveArr[i].answerLang : 'en-US'
+									})
+								}
+							}
+						})
+					} else {
+						this.orderItemsArr.forEach((item) => {
+							blobItemPromiseArr.push({
+								id: this.$tools.guid(),
+								question: this.typeMap[item.type],
+								option: this.getItemOptions(item),
+								type: item.type,
+								level: 1,
+								explain: null,
+								answer: this.getOrderItemAnswer(item),
+								repair: [],
+								field: 1,
+								knowledge: [],
+								children: [],
+								score: item.score,
+								code: this.isSchool ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+								answerType: item.answerType,
+								useAutoScore: item.useAutoScore,
+								answerLang: item.answerLang
+							})
+						})
+					}
+					blobItemPromiseArr.forEach(item => {
+						if(item.type === '') {
+						}
+						item.option.forEach(option => {
+							option.value = option.code
+						})
+					})
+					return blobItemPromiseArr
+				} catch (error) {
+					return []
+				}
+			},
 			/* 保存数据流程 */
 			async doSavePaper(containerClient) {
 				this.$parent.$parent.quickLoading = true;
@@ -1034,6 +1134,13 @@
 				this.singleAns = Array(arr.length).fill('')
 				return arr
 			},
+			condName() {
+				return {
+					periodName: this.schoolInfo?.period ? this.schoolInfo.period.find((i) => i.id === this.periodCode).name : '',
+					subjectName: this.subjectList.length ? this.subjectList.find((i) => i.id === this.subjectCode).name : '',
+					gradeName: this.gradeCode.length ? this.gradeCode.map((i) => this.gradeList[i]).join(",") : this.$t("evaluation.filter.allGrades")
+				}
+			},
 		}
 	};
 </script>

+ 7 - 8
TEAMModelOS/ClientApp/src/components/evaluation/SyllabusPicker.vue

@@ -155,6 +155,10 @@ export default {
             type: Boolean,
             default: false
         },
+        volumeInfo: {
+            type: Object,
+            default: undefined
+        },
     },
     data() {
         return {
@@ -199,14 +203,6 @@ export default {
         },
     },
     methods: {
-        onNodeClick(data, node) {
-            console.log(data)
-            this.onNodeClick(data)
-            // this.questionList = data.quesList
-            /* if (!data) return
-            this.curNode = node
-            this.curData = data */
-        },
         /**
          * 题干展开与收缩
          * @param index
@@ -278,6 +274,9 @@ export default {
                             jsonData.exercise.id = jsonData.id
                             jsonData.exercise.pid = jsonData.pid
                             jsonData.exercise.blob = blob
+                            jsonData.exercise.code = this.volumeInfo?.code
+                            jsonData.exercise.scope = this.volumeInfo?.scope
+                            jsonData.exercise.nodeId = data.id //课纲单元id,后续保存试卷时用到
                             jsonData.exercise = await this.$evTools.doAddHost(jsonData.exercise, null, null, true)
                             r(jsonData.exercise)
                         }))

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

@@ -739,6 +739,15 @@ export const routes = [{
             activeName: 'newKnowledge'
         }
     },
+    // 个人知识点管理
+    {
+        path: 'personalKnowledge',
+        name: 'personalKnowledge',
+        component: () => import('@/view/knowledge-point/index/page.vue'),
+        meta: {
+            activeName: 'personalKnowledge'
+        }
+    },
     // 创建校本评测
     {
         path: 'createSchoolEva',

+ 4 - 0
TEAMModelOS/ClientApp/src/view/Home.vue

@@ -460,6 +460,10 @@
 					let schoolVersions = JSON.parse(decodeURIComponent(localStorage.school_profile || "{}", "utf-8"));
 					let isSchoolRoute = !this.teacherRoutes.includes(this.$route.path)									
 					this.isBaseMgtExpire = !schoolVersions.productSum.service.find((i) => i.prodCode === "IPDYZYLC") && isSchoolRoute && this.inGlobalSite;
+					// 統構平台頁面不用學校服務授權
+					if(this.$route.name==="htExamMark" || this.$route.name==="htMgtHome" || this.$route.name==="htMgtExam"){
+						this.isBaseMgtExpire = false;
+					}					
 				},
 				// 深度观察监听
 				deep: true,

+ 24 - 18
TEAMModelOS/ClientApp/src/view/coursemgt/NewCusMgt.vue

@@ -475,24 +475,30 @@
 				});
 			},
 			async deleteClass(data) {
-				this.isLoading = true
-				let tasks = {
-					school: this.$store.state.userInfo.schoolCode,
-					year: this.filterYear,
-					courseId: this.curCourseItem.id,
-					semesterId: this.curPeriod.semesters[this.filterSemetsterIndex].id,
-					type: data.type,
-					groupId: data.id,
-					teacherId: this.leftTchs[this.activeTchIndex].id
-				}
-				let info = await this.deleteTasks([tasks])
-				if(info?.error) {
-					this.$Message.warning(this.$t("evaluation.deleteFail"));
-				} else {
-					this.$Message.success(this.$t("teachermgmt.message.info1"));
-					this.onCourseClick(this.curCourseItem, this.activeCourseIndex);
-				}
-				this.isLoading = false
+				this.$Modal.confirm({
+					title: `${this.$t('teachContent.props1')}${data.name}?`,
+					// content: `${this.$t("cusMgt.delContent")}${this.curCourseItem.name}?`,
+					onOk: async () => {
+						this.isLoading = true
+						let tasks = {
+							school: this.$store.state.userInfo.schoolCode,
+							year: this.filterYear,
+							courseId: this.curCourseItem.id,
+							semesterId: this.curPeriod.semesters[this.filterSemetsterIndex].id,
+							type: data.type,
+							groupId: data.id,
+							teacherId: this.leftTchs[this.activeTchIndex].id
+						}
+						let info = await this.deleteTasks([tasks])
+						if(info?.error) {
+							this.$Message.warning(this.$t("evaluation.deleteFail"));
+						} else {
+							this.$Message.success(this.$t("teachermgmt.message.info1"));
+							this.onCourseClick(this.curCourseItem, this.activeCourseIndex);
+						}
+						this.isLoading = false
+					}
+				})
 			},
 			/* 课程导入成功 */
 			importCusOk() {

+ 1 - 1
TEAMModelOS/ClientApp/src/view/coursemgt/components/AddTask.vue

@@ -19,7 +19,7 @@
 			</template>
 			<template slot-scope="{ row, index }" slot="assistants">
 				<Select v-model="row.assistants" transfer filterable :placeholder="$t('user.noSet')" multiple @on-change="onAssistantChange(row,index)">
-					<Option v-for="(item, index) in allTeachers" :value="item.id" :key="index" :disabled="item.id === row.instructorId" :label="item.name">{{ item.name }}({{ item.id }})</Option>
+					<Option v-for="(item, index) in allTeachers" :value="item.id" :key="index" :disabled="item.id === row.instructorId" :label="item.name">{{ item.name }}(<span v-if="item.iname">{{ item.iname }} - </span>{{ item.id }})</Option>
 				</Select>
 			</template>
 		</Table>

+ 6 - 0
TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue

@@ -1272,6 +1272,12 @@ export default {
                     false).then(res => {
                       r(200)
                     })
+                } else if (item.blob && item.blob.includes('syllabus/')) {
+                  let blobCntr = item.scope == 'school' ? schoolBlob : privateBlob
+                  containerClient.copyFolder('paper/' + paperItem.name + '/',
+                    'syllabus/' + item.nodeId + '/' + item.id, blobCntr, null, false).then(res => {
+                    r(200)
+                  })
                 } else {
                   const itemJsonFile = await this.$evTools
                     .createBlobItem(item)

+ 8 - 0
TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.vue

@@ -78,6 +78,9 @@
         <TabPane :label="$t('evaluation.cpTip3')" name="name5" tab="newExerciseTab">
           <ManualCreateNew ref="syllabusPicker" :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode" isAddModel></ManualCreateNew>
         </TabPane>
+        <TabPane :label="$t('evaluation.quickPaper.title')" name="name6" tab="newExerciseTab" v-if="isMarkMode">
+          <BaseQuickPaper ref="quickPaperRef" :subjectCode="subjectCode" :periodCode="periodCode" :gradeCode="gradeCode" :isMarkModel="isMarkMode" @addFinish="onAddNewFinish" @editLoadingChange="editLoadingChange"></BaseQuickPaper>
+        </TabPane>
       </Tabs>
       <div slot="footer">
         <Button @click="addNewModal = false">{{$t('evaluation.cancel')}}</Button>
@@ -302,10 +305,15 @@ export default {
       } else if (this.curModalTab === 'name5') {
         let newItems = this.$refs.syllabusPicker.shoppingQuestionList
         this.onAddNewFinish(newItems)
+      } else if (this.curModalTab === 'name6') {
+        this.$refs.quickPaperRef.onConfirmSave()
       } else {
         this.$refs.newEdit.getContent(this.$refs.newEdit.exersicesType)
       }
     },
+    editLoadingChange() {
+      this.editLoading = false
+    },
     onTabChange(val) {
       this.curModalTab = val
       // if (val === 'name1') {

+ 83 - 4
TEAMModelOS/ClientApp/src/view/htcommunity/htExamMark.vue

@@ -1,6 +1,16 @@
 <template>
     <div id="app" class="app-container">
-        
+        <el-table :data="activities" class="table-main" border v-loading="mainLoading">
+            <el-table-column prop="index" :label="this.$t('htcommunity.serialNumber')" min-width="50"></el-table-column>
+            <el-table-column prop="name" :label="this.$t('htcommunity.activityName')"></el-table-column>
+            <el-table-column prop="time" :label="this.$t('htcommunity.schedule')" min-width="150"></el-table-column>                       
+            <el-table-column :label="this.$t('htcommunity.eventExam')">
+                <template slot-scope="scope">
+                    <el-button type="text" @click="viewExams(scope.row)">{{$t("htcommunity.enterReview")}}</el-button>
+                    <!-- <el-button type="text" @click="return false">{{$t("htcommunity.view")}}</el-button> -->
+                </template>
+            </el-table-column>
+        </el-table> 
     </div>
 </template>
 
@@ -12,18 +22,87 @@ import excel from '@/utils/excel.js'
 export default {
   data() {
     return {
-      
+       mainLoading:false,
+       activities: [
+        // {
+        //   index: "1",
+        //   name: "活動1",
+        //   time: "2024-07-01~2024-07-31",
+        //   admin: "管理員A"
+        // }
+        ],
     };
   },
   methods: {
+    // 取得列表資料
+    getList() {
+      //先清除列表
+      this.activities.splice(0, this.activities.length);
+      try {
+        this.mainLoading = true;
+        let param = {           
+          isMarking: true 
+        };
+        //  取得列表資料API
+        this.$api.htcommunity.jointEventFind(param).then(
+          res => {
+            if (res) {
+              let index = 1;
+              res.data.forEach(item => {
+                // 組別批閱人是否有目前登入者存在                  
+                let newGroups = [];
+                item.groups.forEach((groupItem, index) => {
+                  let assistant = groupItem.assistants.find(assistantsItem => {
+                    return assistantsItem == this.$store.state.userInfo.TEAMModelId
+                  })
+                  if (assistant) {
+                    newGroups.push(groupItem);
+                  }
+                })
+                item.groups = newGroups;
+                item.isExamMark = true;
+                //if (newGroups.length > 0) {// 當該活動的評量的組別有目前登入者為批閱人  則把該活動加入列表
+                  let activity = {
+                    id: item.id,
+                    index: index,
+                    name: item.name,
+                    time: this.$jsFn.secondTimeFormat(item.startTime) + "~" + this.$jsFn.secondTimeFormat(item.endTime),
+                    admin: item.admin[0],
+                    originalData: item
+                  }
+                  this.activities.push(activity);
+                  index++;
+                //}
+              });
+            } else {
+            }
+          },
+          err => { console.log("API error : " + err); }
+        )
+      } catch (error) {
+        console.log("API error : " + error);
+      } finally {
+        this.mainLoading = false;
+      }    
+    },
+    //查看活動評量
+    viewExams(activity) {            
+      this.$router.push({
+					name: "htMgtExam",
+					params: {
+						data: activity.originalData,
+            parentPage:"htExamMark"
+					}
+				});
+    },
     
 
   },
   created () {
     
   },
-  mounted(){
-    
+  mounted(){    
+    this.getList();
   }
 };
 </script>

+ 20 - 25
TEAMModelOS/ClientApp/src/view/htcommunity/htMgtExam.vue

@@ -6,12 +6,7 @@
 			@click="goBack">
 			<Icon type="md-arrow-round-back" />
 			{{ $t('cusMgt.rcd.rtn') }}
-		</div>
-		<!-- <div style="margin: 10px">			
-			<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
-				<el-tab-pane v-for="item in propSchedules" :key="item.id" :label="item.name" :name="item.id" />				
-			</el-tabs>
-		</div> -->
+		</div>		
 		<div style="margin: 10px">
 			<el-select v-model="filter.schedule"  style="margin-right: 30px;" @change="findEvaluation">
 				<el-option v-for="item in propSchedules" :key="item.id" :label="item.name" :value="item.id" />
@@ -30,7 +25,7 @@
 					</b>
 					<div class="action-icon-wrap" v-if="!isSearch">
 						<Icon type="md-add" class="to-create-icon" @click="goToCreate"
-							:title="$t('learnActivity.mgtScEv.create')" v-if="evAuthStatus" />
+							:title="$t('learnActivity.mgtScEv.create')" v-show="isNotExamMark" />
 						<!-- <Icon type="md-trash" v-show="htEvaListShow.length" class="to-create-icon"
 							:title="$t('learnActivity.mgtScEv.delete')" @click="deleteEvaluation" v-if="evAuthStatus" /> -->
 						<!-- 筛选 -->
@@ -73,7 +68,7 @@
 							{{ item.name }}
 							<!-- 修改评测名称 -->
 							<Icon type="md-create" class="edit-end-time" @click="editEvName(index)"
-								:title="$t('learnActivity.mgtScEv.edName')" />
+								:title="$t('learnActivity.mgtScEv.edName')" v-show="isNotExamMark" />
 						</p>
 						<div class="all-tag-box">
 							<div class="tags-wrap">
@@ -160,7 +155,7 @@
 					<!-- 评测试卷 -->
 					<span
 						:class="curBarIndex == 1 ? 'evalustion-bar-item line-bottom-active line-bottom' : 'evalustion-bar-item line-bottom'"
-						@click="selectBar(1)">
+						@click="selectBar(1)" v-show="isNotExamMark">
 						{{ $t("learnActivity.mgtScEv.tab2") }}
 					</span>
 					<!-- 阅卷设置-->
@@ -309,12 +304,12 @@ export default {
 				}
 			};
 		},
-		evAuthStatus() {
-			if (this.scope == "school") {
-				return this.$access.can("admin.*|schoolAc-upd");
+		isNotExamMark() {			
+			if (this.$route.params.data.isExamMark) {
+				return false;
 			} else {
 				return true;
-			}
+			}			
 		}
 	},
 	mounted() {
@@ -324,16 +319,14 @@ export default {
 				name: "htMgtHome"
 			});
 		}
-		// 分組頁籤設定
-		//this.propGroups.push({id:"",name:"All"});
+		// 分組下拉選單設定		
 		this.$route.params.data.groups.forEach(item => {
 			this.propGroups.push(item);
 		});
 		this.filter.group = this.propGroups[0].id;
-		// 時程頁籤設定
-		//this.propSchedules.push({id:"","name":"All"});
+		// 時程下拉選單設定		
 		this.$route.params.data.schedule.forEach(item => {
-            if (item.type === "exam" && (item.examType == "regular" || item.examType == "custom")) {
+            if (item.type === "exam" && item.examType == "regular" ) {
 				this.propSchedules.push(item);
 			}
 		});
@@ -358,11 +351,13 @@ export default {
 		// this.findEvaluation(true);
 	},
 	methods: {
-		goBack() {
-			this.$router.go(-1)
+		goBack() {			
+			this.$router.push({
+					name: this.$route.params.parentPage,					
+				});
+			//this.$router.go(-1)
 		},
-		handleReachBottom() {
-			debugger
+		handleReachBottom() {			
 			return this.findEvaluation();
 		},
 		closeKeySearch() {
@@ -938,7 +933,8 @@ export default {
 				params: {
 					data: this.$route.params.data,
 					jointGroupId: this.filter.group,
-					jointScheduleId: this.filter.schedule
+					jointScheduleId: this.filter.schedule,
+					parentPage: this.$route.params.parentPage
 				}
 			});
 
@@ -993,8 +989,7 @@ export default {
 		},
 		//筛选评测
 		filterEv() {
-			this.ctnToken = "";
-			debugger
+			this.ctnToken = "";			
 			this.findEvaluation(true);
 		},
 		/**获取mode对应的label */

+ 45 - 45
TEAMModelOS/ClientApp/src/view/htcommunity/htMgtHome.vue

@@ -301,49 +301,43 @@ export default {
       this.groupsLoading = true;      
       console.log(scope);
       // 先檢查有輸入
-      if (scope.row.groupJudge) {
-        //if (scope.row.groupJudge.indexOf(0) == 0) scope.row.groupJudge = scope.row.groupJudge.substr(1) //開頭有0要去0
-       // 先檢查有沒有重複的tmdID
-        // const judgeCount = {};
-        // this.groups.forEach(group => {
-        //   if (group.groupJudge) {
-        //     if (!judgeCount[group.groupJudge]) {
-        //       judgeCount[group.groupJudge] = 0;
-        //     }
-        //     judgeCount[group.groupJudge]++;
-        //   }
-        // });
-
-        // this.groups.forEach(group => {
-        //   if (group.groupJudge) {
-        //     group.valid = judgeCount[group.groupJudge] > 1;
-        //   } else {
-        //     group.valid = false;
-        //   }
-        // });
-
-
-        
-        // 向CoreID取得使用者
-        this.$store.dispatch('user/getUserFromCoreId', [scope.row.groupJudge]).then(
-          (res) => {
-            if (res.code == 1 && res.data.length > 0) {
-              scope.row.valid = true;
-            }
-            else {
-              this.$message({
-                showClose: true,
-                message: this.$t('htcommunity.accountNotExist'),
-                type: 'error'
-              });
-              scope.row.valid = false;
+      if (scope.row.groupJudge) {        
+        // 多筆需轉換成陣列傳入
+        let gjarr = [];
+        if (scope.row.groupJudge) {
+          gjarr = scope.row.groupJudge.split(',');
+        }
+        if (gjarr.length > 0) {
+          // 向CoreID取得使用者
+          this.$store.dispatch('user/getUserFromCoreId', gjarr).then(
+            //  this.$store.dispatch('user/getUserFromCoreId', ["1595321354","1695106"]).then(
+            (res) => {
+              if (res.code == 1 && res.data.length > 0) {
+                scope.row.valid = true;
+              }
+              else {
+                this.$message({
+                  showClose: true,
+                  message: this.$t('htcommunity.accountNotExist'),
+                  type: 'error'
+                });
+                scope.row.valid = false;
+              }
+              this.groupsLoading = false;
+            },
+            (err) => {
+              //this.$Message.error('user/getUserFromCoreId API error!')
             }
-            this.groupsLoading = false;
-          },
-          (err) => {
-            //this.$Message.error('user/getUserFromCoreId API error!')
-          }
-        );
+          );
+        } else {
+          this.$message({
+            showClose: true,
+            message: this.$t('htcommunity.pleaseEnterAccount'),
+            type: 'error'
+          });
+          scope.row.valid = false;
+          this.groupsLoading = false;
+        }                    
       } else {
         this.$message({
           showClose: true,
@@ -399,7 +393,7 @@ export default {
         let group = {
           id: item.id,
           groupName: item.name,
-          groupJudge: item.assistants[0],
+          groupJudge: item.assistants.toString(),
           valid: false
         }
         this.newActivity.groups.push(group);
@@ -539,7 +533,8 @@ export default {
       this.$router.push({
 					name: "htMgtExam",
 					params: {
-						data: activity.originalData
+						data: activity.originalData,
+            parentPage:"htMgtHome"
 					}
 				});
     },
@@ -579,10 +574,15 @@ export default {
                 };
 
                 this.newActivity.groups.forEach(item => {
+                  // 多筆需轉換成陣列傳入
+                  let gjarr = [];
+                  if(item.groupJudge){
+                    gjarr = item.groupJudge.split(',');
+                  }
                   let upItem = {
                     id: item.id,
                     name: item.groupName,
-                    assistants: [item.groupJudge]
+                    assistants: gjarr
                   }
                   dataGroup.groups.push(upItem);
                 });

+ 108 - 27
TEAMModelOS/ClientApp/src/view/knowledge-point/index/page.vue

@@ -24,9 +24,13 @@
                     <div class="ns-header-content" v-if="!isSearchSubject">
                         <span>
                             <Icon type="md-bookmark" color="#40A8F0" size="16" />
-                            <span style="margin-left:5px">{{ $t("knowledge.subject") }}</span>
+                            <span style="margin-left:5px">{{ isSchool ? $t("knowledge.subject") : $t('knowledge.treeName') }}</span>
+                        </span>
+                        <span>
+                            <Icon type="ios-search" color="#40A8F0" size="14" v-show="subjectList.length" style="cursor:pointer" @click="isSearchSubject = true" />
+                            <Icon type="md-add" color="#40A8F0" size="14" v-show="!isSchool" style="cursor:pointer; margin-left: 5px;" @click="isAddPointList = true" />
+                            <Icon type="md-trash" color="#40A8F0" size="14" v-show="!isSchool" style="cursor:pointer; margin-left: 5px;" @click="deleteKnowledgeName()" />
                         </span>
-                        <Icon type="ios-search" color="#40A8F0" size="14" v-show="subjectList.length" style="cursor:pointer" @click="isSearchSubject = true" />
                     </div>
                     <div class="ns-header-search" v-else>
                         <Input v-special-char icon="ios-close" v-model="searchSubject" :placeholder="$t('knowledge.searchSubject')" autofocus style="width: 100%" @on-click="onSearchSubjectClose" @on-change="onSearchSubjectChange" @on-enter="onSearchSubjectChange" />
@@ -135,9 +139,9 @@
                             </span>
                             <div>
                                 <!-- <Button class="btn-compose-block" size="small" v-if="($access.can('admin.*|knowledge-upd')) && checkedPointList.length" @click="onComposeBlock">{{$t("knowledge.makeBlock")}}</Button> -->
-                                <Icon type="ios-create" v-if="($access.can('admin.*|knowledge-upd')) && subjectList.length" color="#40A8F0" size="14" style="cursor:pointer;margin-right:10px" @click="batchUploadModal = true" />
+                                <Icon type="ios-create" v-if="(isSchool ? ($access.can('admin.*|knowledge-upd')) : true) && subjectList.length" color="#40A8F0" size="14" style="cursor:pointer;margin-right:10px" @click="batchUploadModal = true" />
                                 <!-- <Icon type="md-trash" v-if="($access.can('admin.*|knowledge-upd')) && checkedPointList.length" color="#40A8F0" size="14" style="cursor:pointer;margin-right:10px" @click="doBatchDelete" /> -->
-                                <Icon type="md-add" v-if="($access.can('admin.*|knowledge-upd')) && subjectList.length" color="#40A8F0" size="14" style="cursor:pointer;margin-right:10px" @click="onAddPoint()" />
+                                <Icon type="md-add" v-if="(isSchool ? ($access.can('admin.*|knowledge-upd')) : true) && subjectList.length" color="#40A8F0" size="14" style="cursor:pointer;margin-right:10px" @click="onAddPoint()" />
                             </div>
                         </div>
                     </div>
@@ -186,6 +190,22 @@
                 </AddPoint>
             </div>
         </Modal>
+        <!-- 新增知识点学科列表弹窗 -->
+        <Modal v-model="isAddPointList" width="560" footer-hide class="point-modal">
+            <div class="modal-header" slot="header">
+                {{ $t('knowledge.addTree') }}
+            </div>
+            <div class="modal-content">
+                <Form label-position="top" class="form-container">
+                    <FormItem :label="$t('cusMgt.name')">
+                        <Input v-special-char v-model="sujName" />
+                    </FormItem>
+                    <FormItem>
+                        <Button type="primary" @click="saveKnowledgeName()" :loading="isLoading">{{ $t("knowledge.confirm") }}</Button>
+                    </FormItem>
+                </Form>
+            </div>
+        </Modal>
         <!-- 组成知识块弹窗 -->
         <Modal v-model="batchUploadModal" width="680" footer-hide class="point-modal tree-upload-modal">
             <Loading v-if="isParsing"></Loading>
@@ -236,11 +256,9 @@ export default {
             originSubjectList: [],
             searchSubject: '',
             activeSubjectIndex: 0,
-            countArr: [],
             knowledgeTrees: undefined,
             subTreeRoot: [],
             subTree: [],
-            syllabusList: [],
             defaultProps: {
                 children: 'children',
                 label: 'name'
@@ -250,6 +268,7 @@ export default {
             tabIndex: 0,
             curBlockPoints: [],
             isAddPoint: false,
+            isAddPointList: false,
             isEditPoint: false,
             addMode: 0, //新增(1) 删除(2)
             originPointList: [],
@@ -291,10 +310,15 @@ export default {
             filePointNum: 0,
             uploadShow: false,
             guid: '',
+            sujName: '',
         }
     },
     created() {
-        this.initSchoolData()
+        if(this.isSchool) {
+            this.initSchoolData()
+        } else {
+            this.getPointList()
+        }
         this.hostName = this.$evTools.getBlobHost()
     },
     mounted() {
@@ -310,6 +334,9 @@ export default {
                 return this.checkedPointList.map(item => item).indexOf(item) > -1
             }
         },
+        isSchool() {
+            return this.$route.name === "newKnowledge"
+        },
     },
     watch: {
         '$store.state.user.curPeriod': {
@@ -345,12 +372,17 @@ export default {
         },
         getPointList() {
             this.isLoading = true
+            this.originSubjectList = []
+            this.subjectList = []
+            this.knowledgeTrees = undefined
+            this.subTreeRoot = []
+            this.subTree = []
             let params = {
-                periodId: this.currentParams.periodId,
-                scope: 'school'
+                periodId: this.isSchool ? this.currentParams.periodId : '',
+                scope: this.isSchool ? 'school' : 'private'
             }
             this.$api.knowledge.getPointList(params).then(res => {
-                if(res.datas) {
+                if(res.datas && res.datas.length) {
                     this.originSubjectList = res.datas
                     this.subjectList = res.datas
                     this.readPoint()
@@ -362,10 +394,14 @@ export default {
         readPoint() {
             this.isLoading = true
             let params = {
-                periodId: this.currentParams.periodId,
-                scope: 'school',
-                subjectId: this.subjectList[this.activeSubjectIndex].subjectId,
-                school_code: this.currentParams.school_code
+                periodId: this.isSchool ? this.currentParams.periodId : '',
+                scope: this.isSchool ? 'school' : 'private',
+                school_code: this.isSchool ? this.currentParams.school_code : ''
+            }
+            if(this.isSchool) {
+                params.subjectId = this.subjectList[this.activeSubjectIndex].subjectId
+            } else {
+                params.id = this.subjectList[this.activeSubjectIndex].id
             }
             this.$api.knowledge.getPointInfo(params).then(res => {
                 this.knowledgeTrees = res.knowledgeTrees.length ? res.knowledgeTrees[0] : undefined
@@ -423,10 +459,10 @@ export default {
                         this.schoolParams = {
                             schoolCode: this.originData.id,
                             subjectId: this.subjectList[index].subjectId,
-                            period: this.currentParams.periodId
+                            period: this.currentParams?.periodId
                         }
                         this.currentSubjectIndex = index
-                        this.currentParams.subjectId = this.subjectList[index].subjectId
+                        this.currentParams.subjectId = this.subjectList[index]?.subjectId
                         this.activeSubjectIndex = index
                         this.isLoadBlocks = true
                         this.updated = false
@@ -448,7 +484,7 @@ export default {
                 this.schoolParams = {
                     schoolCode: this.originData.id,
                     subjectId: this.subjectList[index].id,
-                    period: this.currentParams.periodId
+                    period: this.currentParams?.periodId
                 }
                 this.currentSubjectIndex = index
                 this.currentParams.subjectId = this.subjectList[index].id
@@ -520,9 +556,9 @@ export default {
                 let params = {
                     old_new: this.oldNew,
                     id: this.knowledgeTrees.id, //知识点架构id(knowledgeTrees[0].id)
-                    subjectId: this.subjectList[this.activeSubjectIndex].subjectId,
-                    scope: 'school',
-                    owner: this.currentParams.school_code, //学校简码、个人简码
+                    subjectId: this.isSchool ? this.subjectList[this.activeSubjectIndex].subjectId: '',
+                    scope: this.isSchool ? 'school' : 'private',
+                    owner: this.isSchool ? this.currentParams.school_code : this.$store.state.userInfo.TEAMModelId, //学校简码、个人简码
                     updateRel: 0, //是否更新关联知识点(课纲,试题关联)
                 }
                 this.$api.knowledge.modifyNodeName(params).then(res => {
@@ -542,13 +578,13 @@ export default {
                 if(!this.guid) this.guid = this.$tools.guid()
                 let params = {
                     knowledgeTree: {
-                        id: this.knowledgeTrees?.id || this.guid, //id为空,表示新增,不为空,表示更新
-                        owner: this.currentParams.school_code,
-                        scope: 'school',
-                        periodId: this.currentParams.periodId,
-                        subjectId: this.subjectList[this.activeSubjectIndex].subjectId,
+                        id: this.isSchool ? (this.knowledgeTrees?.id || this.guid) : this.subjectList[this.activeSubjectIndex].id, //id为空,表示新增,不为空,表示更新
+                        owner: this.isSchool ? this.currentParams.school_code : this.$store.state.userInfo.TEAMModelId,
+                        scope: this.isSchool ? 'school' : 'private',
+                        periodId: this.isSchool ? this.currentParams.periodId : '',
+                        subjectId: this.isSchool ? this.subjectList[this.activeSubjectIndex].subjectId : '',
                         name: this.subTreeRoot.length ? this.knowledgeTrees.name : this.subjectList[this.activeSubjectIndex].name,
-                        tree: this.subTree,
+                        tree: this.subTree || [],
                     }
                 }
                 console.log('111111111111111', params);
@@ -577,6 +613,51 @@ export default {
             this.filePointNum = 0
             this.uploadShow = false
         },
+        saveKnowledgeName() {
+            let params = {
+                knowledgeTree: {
+                    id: this.$tools.guid(), //id为空,表示新增,不为空,表示更新
+                    owner: this.$store.state.userInfo.TEAMModelId,
+                    scope: 'private',
+                    periodId: '',
+                    subjectId: '',
+                    name: this.sujName,
+                    tree: [],
+                }
+            }
+            this.$api.knowledge.upsertPoint(params).then(res => {
+                if(res.knowledgeTree) {
+                    this.$Message.success(this.$t("teachermgmt.addOk"))
+                    this.isAddPointList = false
+                    this.getPointList()
+                } else {
+                    this.$Message.warning(this.$t("teachermgmt.addErr"))
+                }
+            })
+        },
+        deleteKnowledgeName() {
+            this.$Modal.confirm({
+					title: `${this.$t('teachContent.props1')}${this.subjectList[this.activeSubjectIndex].name}?`,
+					// content: `${this.$t("cusMgt.delContent")}${this.curCourseItem.name}?`,
+					onOk: async () => {
+                        let params = {
+                            id: this.subjectList[this.activeSubjectIndex].id,
+                            owner: this.$store.state.userInfo.TEAMModelId,
+                            scope: 'private',
+                        }
+                        this.$api.knowledge.deleteList(params).then(res => {
+                            if(res.code === 200) {
+                                this.$Message.success(this.$t("teachermgmt.message.info1"))
+                                this.activeSubjectIndex = 0
+                                this.getPointList()
+                            } else {
+                                this.$Message.warning(this.$t("evaluation.deleteFail"))
+                            }
+                        })
+                    }
+            })
+            
+        },
         doBatchDelete() {},
         // 新增知识点事件
         onAddPoint(node, data) {
@@ -619,7 +700,7 @@ export default {
                     children.splice(index, 1);
                     this.updated = true
                     this.addMode = 2
-                    this.$Message.success(this.$t('knowledge.delSuccess'))
+                    // this.$Message.success(this.$t('knowledge.delSuccess'))
                 }
             })
         },

+ 1 - 1
TEAMModelOS/ClientApp/src/view/learnactivity/ManualCreateNew.vue

@@ -144,7 +144,7 @@
 				<!-- <Loading :top="100" v-show="isLoading"></Loading> -->
 				<ExerciseList v-if="manualFilter.source === 'quesBank'" ref="exList" :propsList="questionList" @pageScroll="doScroll" @on-question-change="selectQuestion"></ExerciseList>
 				<BasePaperItemPicker v-else-if="manualFilter.source === 'paper'" ref="paperItemList" :paperList="paperList" @on-question-change="selectQuestion" />
-				<SyllabusPicker v-else-if="manualFilter.source === 'syllabus'" ref="syllabusTree" :treeOrigin="treeOrigin" @on-question-change="selectQuestion" />
+				<SyllabusPicker v-else-if="manualFilter.source === 'syllabus'" ref="syllabusTree" :treeOrigin="treeOrigin" :volumeInfo="volumeList[manualFilter.volume - 1]" @on-question-change="selectQuestion" />
                 <EmptyData style="margin-top: 120px" v-if="!isLoading && !questionList.length && !paperList.length && !treeOrigin.length" :textContent="$t('evaluation.addTip5')"></EmptyData>
 				<!-- <div class="page-wrap">
                     <Page :current.sync="pageNum" :total="totalNum" show-total :page-size="pageSize" size="small" show-sizer @on-change="getCurrentPageData" />

+ 46 - 21
TEAMModelOS/ClientApp/src/view/learnactivity/byStu/htByStuMark.vue

@@ -103,6 +103,11 @@ export default {
     QuAndScore
   },
   props: {
+    creatorId: {
+      type: String,
+      default: '',
+      required: true
+    },
     examId: {
       type: String,
       default: '',
@@ -135,6 +140,7 @@ export default {
   data() {
     let _this = this
     return {
+      blob_sas:"",
       originalStatus: false,
       isComplete: false,
       exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
@@ -157,7 +163,7 @@ export default {
       },
       isUpd: false
     }
-  },
+  },  
   computed: {
     scoreTotal() {
       if (this.studentAnswer && this.studentAnswer.scores) {
@@ -169,12 +175,12 @@ export default {
       }
       return 0
     },
-  },
+  },  
   watch: {
     examInfo: {
       immediate: true,
       handler(n, o) {
-        if (n) {
+        if (n) {          
           this.config.examInfo = n
           this.config.subjectId = this.subjectId
         }
@@ -208,9 +214,16 @@ export default {
           if (this.studentAnswer.answers.length) {
             let sourceBlob = this.studentAnswer.answers[0].replace('ans.json', 'source')
             this.$set(this.studentAnswer, 'sourceBlob', sourceBlob)
-            try {
-              let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+            try {// 一進入批閱分數畫面
+              // 不同老師的班級 取不同blob授權
+              let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+                return item.id == this.creatorId
+              })              
+                 
+              let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
               let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+              // 替換老師的醍摩豆ID
+              blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
               /**
                * 1、批注逻辑调整,每个题目的批注单独保存成图片;
                * 2、这里就直接读取原始作答数据;
@@ -272,7 +285,7 @@ export default {
       }
     },
   },
-  methods: {
+  methods: {    
     handleInitPaper(newPaper) {
       this.paperInfo = this._.cloneDeep(newPaper)
       let that = this
@@ -386,9 +399,15 @@ export default {
       if (!this.studentAnswer.sourceBlob) {
         this.$Message.warning(this.$t('learnActivity.score.noStuAnswer'))
         return
-      }
-      let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+      }      
+      // 不同老師的班級 取不同blob授權
+      let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+        return item.id == this.creatorId
+      })  
+      let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
       let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+      // 替換老師的醍摩豆ID
+      blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
       let host = blobUrl.substring(0, blobUrl.lastIndexOf('/'))
       let cont = blobUrl.substring(blobUrl.lastIndexOf('/') + 1)
       let blobTool = new BlobTool(host, cont, '?' + sas, this.examInfo.scope)
@@ -447,14 +466,14 @@ export default {
       this.examInfo.stuLists
       let requestData = {
         "id": this.examId,
-        "code": this.examInfo.owner == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+        "code": this.examInfo.owner == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
         "point": [this.studentAnswer.scores],
         "studentId": [{
           id: this.studentAnswer.id,
           type: this.studentAnswer.type
         }],
         "classId": this.studentAnswer.classId,
-        "school": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+        "school": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
         // "school": this.$store.state.userInfo.schoolCode,
         "subjectId": this.subjectId
       }
@@ -479,7 +498,7 @@ export default {
         content: this.$t('learnActivity.score.delMarkContent'),
         onOk: () => {
           let mark = this.studentAnswer.mark[index]?.find(item => {
-            return item.tmdId == this.$store.state.userInfo.TEAMModelId
+            return item.tmdId == this.creatorId
           })
           let blob = mark ? mark.mark : ""
           //保存批注数据
@@ -488,14 +507,14 @@ export default {
             "studentId": this.studentAnswer.id,
             "subjectId": this.subjectId,
             "classId": this.studentAnswer.classId,
-            "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
-            "tmdId": this.$store.state.userInfo.TEAMModelId,
+            "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
+            "tmdId": this.creatorId,
             "index": index,//题号
             "blob": blob
           }).then(
             res => {
               this.$Message.success(this.$t('learnActivity.mark.deleteOk'))
-              let i = this.studentAnswer.mark[index].findIndex(item => item.tmdId === this.$store.state.userInfo.TEAMModelId)
+              let i = this.studentAnswer.mark[index].findIndex(item => item.tmdId === this.creatorId)
               if (i > -1) {
                 this.studentAnswer.mark[index].splice(i, 1)
                 this.$set(this.studentAnswer.mark, index, this._.cloneDeep(this.studentAnswer.mark[index]))
@@ -517,7 +536,7 @@ export default {
       if (this.studentAnswer.mark && this.studentAnswer.mark[quIndex]) {
         let d = this._.cloneDeep(this.studentAnswer.mark[quIndex])
         let markData = d.find(item => {
-          return item.tmdId == this.$store.state.userInfo.TEAMModelId
+          return item.tmdId == this.creatorId
         })
         if (markData) {
           let blob = markData.mark
@@ -529,10 +548,16 @@ export default {
       } else {
         fileName = this.$jsFn.uuid() + '.png'
       }
-      let markPng = this.$jsFn.dataURLtoFile(markInfo.base64, fileName)
+      let markPng = this.$jsFn.dataURLtoFile(markInfo.base64, fileName)           
+      // 不同老師的班級 取不同blob授權 
+      let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+        return item.id == this.creatorId
+      })
       //保存批注图片
-      let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+      let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
       let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+      // 替換老師的醍摩豆ID
+      blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
       let host = blobUrl.substring(0, blobUrl.lastIndexOf('/'))
       let cont = blobUrl.substring(blobUrl.lastIndexOf('/') + 1)
       let blobTool = new BlobTool(host, cont, '?' + sas, this.examInfo.scope)
@@ -542,7 +567,7 @@ export default {
           if (!hasMarked) {
             this.studentAnswer.mark[quIndex].push({
               mark: path + '/' + fileName,
-              tmdId: this.$store.state.userInfo.TEAMModelId
+              tmdId: this.creatorId
             })
           }
           //保存批注数据
@@ -551,12 +576,12 @@ export default {
             "studentId": this.studentAnswer.id,
             "subjectId": this.subjectId,
             "classId": this.studentAnswer.classId,
-            "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
-            "tmdId": this.$store.state.userInfo.TEAMModelId,
+            "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
+            "tmdId": this.creatorId,
             "index": quIndex,//题号
             "mark": {
               sc: 0,//分数
-              tmdId: this.$store.state.userInfo.TEAMModelId,
+              tmdId: this.creatorId,
               mark: path + '/' + fileName,//批注BLOB地址
               identity: 'admin',//老师身份,这是是固定管理员身份
               index: quIndex //题号

+ 918 - 0
TEAMModelOS/ClientApp/src/view/learnactivity/htByQuMark.vue

@@ -0,0 +1,918 @@
+<template>
+    <div class="qu-mark-container" ref="mathJaxContainer">
+        <Loading :top="200" type="1" style="text-align:center" v-show="dataLoading"></Loading>
+        <div class="scoring-paper-header">
+            <!-- 保存分数 -->
+            <span :class="['base-info-btn',isUpd ? 'base-info-btn-active' : '']" type="success" @click="saveScore" style="margin-right:25px">
+                <Icon type="ios-albums-outline" />
+                {{$t('learnActivity.score.saveScore')}}
+            </span>
+            <!-- 显示答案 -->
+            <span class="base-info-btn" @click="showAnswer = !showAnswer">
+                <Icon :type="showAnswer ? 'md-eye-off':'md-eye'" />
+                {{ showAnswer ? $t('learnActivity.score.hideAns') : $t('learnActivity.score.showAns')}}
+            </span>
+            <!-- 显示题目 -->
+            <span class="base-info-btn" @click="showQu = !showQu">
+                <Icon :type="showQu ? 'md-eye-off':'md-eye'" />
+                {{ showQu ? $t('learnActivity.score.hideQu') : $t('learnActivity.score.showQu')}}
+            </span>
+            <!-- 全部满分 -->
+            <span class="base-info-btn" @click="batchScore(questionInfo.score)">
+                <Icon type="ios-create" />
+                {{$t('learnActivity.score.fastScore1')}}
+            </span>
+            <!-- 全部零分 -->
+            <span class="base-info-btn" @click="batchScore(0)">
+                <Icon type="ios-create" />
+                {{$t('learnActivity.score.fastScore2')}}
+            </span>
+            <!-- 题号列表 -->
+            <div class="qu-lable-wrap">
+                <span class="base-info-item" style="white-space: nowrap;">
+                    {{$t('learnActivity.score.quIndex')}}
+                </span>
+                <span :class="['qu-order-tag-def', quIndex == index ? 'qu-order-tag-active' : '' ]" v-for="(item,index) in quNoList" :key="index" @click="selectQu(index)">
+                    {{item.label}}
+                </span>
+            </div>
+        </div>
+
+        <!-- 题目显示区域 -->
+        <div class="question-wrap" v-show="showQu">
+            <p class="qu-info-title" style="display:block">{{$t('learnActivity.score.question')}}</p>
+            <!-- 题号 -->
+            <span>{{ quNoList && quNoList[quIndex] ? quNoList[quIndex].label : '' }} .</span>
+            <!-- 题干 -->
+            <span v-html="questionInfo.question"></span>
+            <!-- 选项 -->
+            <div v-for="(option,optionIndex) in questionInfo.option" :key="'op' + optionIndex" class="item-options">
+                <div style="margin-top:10px">
+                    <span class="item-option-order">{{String.fromCharCode(64 + parseInt(optionIndex+1))}} :</span>
+                    <span class="item-option-text" v-html="option.value"></span>
+                </div>
+            </div>
+        </div>
+        <!-- 参考答案区域 -->
+        <div class="solution-wrap" v-show="showAnswer">
+            <!-- 答案展示部分 -->
+            <div class="item-explain">
+                <span class="qu-info-title">{{$t('learnActivity.score.quAns')}}</span>
+                <div class="item-explain-details">
+                    <!-- 问答题答案 -->
+                    <div v-if="questionInfo.type === 'subjective'">
+                        <span v-for="(answer,index) in questionInfo.answer" :key="index" v-html="questionInfo.answer.length ? answer : $t('learnActivity.score.noAnswer')"></span>
+                    </div>
+                    <!-- 填空题答案 -->
+                    <div v-else-if="questionInfo.type === 'complete'">
+                        <span :class="[ questionInfo.type === 'complete' ? 'item-answer-item':'']" v-for="(answer,index) in questionInfo.answer" :key="index" v-html="answer"></span>
+                    </div>
+                    <!-- 其余题型答案 -->
+                    <div v-else>
+                        <span v-for="(answer,index) in questionInfo.answer" :key="index" v-html="answer"></span>
+                    </div>
+                </div>
+            </div>
+            <!-- 解析部分 -->
+            <div class="item-explain">
+                <span class="qu-info-title">{{$t('learnActivity.score.anaLabel')}}</span>
+                <div class="item-explain-details">
+                    <span v-html="questionInfo.explain || $t('learnActivity.score.noAna')"></span>
+                </div>
+            </div>
+            <!-- 知识点部分 -->
+            <div class="item-explain">
+                <span class="qu-info-title">{{$t('learnActivity.score.kdLabel')}}</span>
+                <div class="item-explain-details">
+                    <span v-if="!questionInfo.knowledge || (_.compact(questionInfo.knowledge).length)">{{$t('learnActivity.score.noKd')}}</span>
+                    <div v-else>
+                        <span v-for="(point,index) in questionInfo.knowledge" :key="index" class="item-point-tag">
+                            {{ point }}
+                        </span>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <Divider dashed v-show="showAnswer || showQu" />
+        <!-- 学生作答显示区域 -->
+        <div class="answer-wrap">
+            <div @click="stuIndex = index" :class="['stu-ans-item',stuIndex === index ? 'stu-ans-item-active':'']" v-for="(item,index) in studentsData" :key="item.id+index">
+                <!-- 打分区域 -->
+                <div class="set-score-box">
+                    <template v-if="item.data">
+                        <InputNumber @on-change="setUpdStatus(item)" v-model="item.data[quIndex]" :formatter="value => value < 0 ? '' : `${value}${$t('learnActivity.score.scoreUnit')}`" :parser="value => value.replace($t('learnActivity.score.scoreUnit'), '')" :min="0" :max="questionInfo.score" :placeholder="$t('learnActivity.score.zeroScore1')" style="width:60px" />
+                    </template>
+                    <!-- 快速打分 满分 零分 -->
+                    <div style="display:flex;justify-content: space-evenly;margin-top:5px;">
+                        <span class="fast-score-tag" :title="$t('learnActivity.score.fullScore')" @click="fastSetScore(index, questionInfo.score)">
+                            <!-- 题目分数 -->
+                            {{questionInfo.score || 10}}
+                        </span>
+                        <span class="fast-score-tag" :title="$t('learnActivity.score.zeroScore')" style="background:#ed4014" @click="fastSetScore(index, 0)">
+                            0
+                        </span>
+                    </div>
+                    <!-- 加分 减分 -->
+                    <div style="display:flex;justify-content: space-evenly;margin-top:5px;">
+                        <span class="fast-score-tag" @click="scoreStepChange(index, item.data[quIndex], 'up')">
+                            <Icon type="md-add" />
+                        </span>
+                        <span class="fast-score-tag" @click="scoreStepChange(index, item.data[quIndex], 'down')" style="background:#ed4014">
+                            <Icon type="md-remove" />
+                        </span>
+                    </div>
+                    <!-- 批注!!! -->
+                    <div class="redundant-mark-btn" v-if="quNoList.length && quNoList[quIndex] && !quNoList[quIndex].disabled" @click="markStuAnswer()">
+                        <span style="margin-left:5px;">{{$t('learnActivity.score.mark')}}</span>
+                    </div>
+                </div>
+                <!-- 作答信息显示区域 -->
+                <div class="stu-answer-box">
+                    <div>
+                        <span class="stu-name">
+                            {{item.name}}
+                        </span>
+                        <div class="mark-action-box" v-if="quNoList.length && quNoList[quIndex] && !quNoList[quIndex].disabled">
+                            <!-- 查看批注 -->
+                            <template v-if="item.mark && item.mark[quIndex]">
+                                <span v-show="viewMark[item.id]" class="mark-action-item" style="background:white;color:black" @click="hindMark(item.id)">
+                                    <span style="margin-left:5px;">{{$t('learnActivity.score.orgImg')}}</span>
+                                </span>
+                                <span class="mark-action-item" v-for="(markItem,markIndex) in item.mark[quIndex]" :key="markItem.tmdId" style="background:#19be6b" @click="showMark(item.id, markItem.mark)">
+                                    <!-- <span style="margin-left:5px;">{{$t('learnActivity.score.mark')+(markIndex+1)}}</span> -->
+                                    <span v-if="markItem.tmdId != $store.state.userInfo.TEAMModelId" style="margin-left:5px;">
+                                        {{$t('learnActivity.score.mark')+(markIndex+1)}}
+                                    </span>
+                                    <!-- 我的批注可以删除 -->
+                                    <span v-else style="margin-left:5px;">
+                                        {{$t('learnActivity.score.myMark')}}
+                                        <Icon type="md-close" @click.stop="delMark(item.id)" />
+                                    </span>
+                                </span>
+                            </template>
+                            <span class="mark-action-item" style="background:#ed4014" @click="markStuAnswer()">
+                                <Icon type="md-create" />
+                                <span style="margin-left:5px;">{{$t('learnActivity.score.mark')}}</span>
+                            </span>
+                        </div>
+                    </div>
+                    <!-- 原始答案 -->
+                    <template v-if="!viewMark[item.id]">
+                        <div class="answer-box" v-if="item.answer && item.answer[quIndex].length">
+                            <span v-html="item.answer[quIndex].toString()" v-if="(questionInfo.type === 'subjective' && (!questionInfo.answerType || questionInfo.answerType === 'text')) || questionInfo.type != 'subjective'"></span>
+                            <audio v-else-if="questionInfo.answerType === 'audio'" controls>
+                                <source :src="item.answer[quIndex].toString()">
+                                {{$t('teachContent.notAudio')}}
+                            </audio>
+                            <img v-else-if="questionInfo.answerType === 'image'" :src="item.answer[quIndex].toString()" alt="" style="max-width: 90%;">
+                            <div v-else class="answer-link">
+                                <Icon type="ios-link" />
+                                <span class="name" @click="onDownload(item.answer[quIndex].toString(), item.id)">{{ item.answer[quIndex].toString() }}</span>
+                            </div>
+                        </div>
+                        <div v-else class="answer-box" style="color:#ed4014">
+                            {{$t('learnActivity.score.noStuAns')}}
+                        </div>
+                    </template>
+                    <!-- 批注数据 -->
+                    <div v-else class="answer-box">
+                        <img :src="viewMark[item.id]" style="max-width:100%">
+                    </div>
+                </div>
+            </div>
+        </div>
+        <Modal v-model="markStatus" fullscreen :title="$t('learnActivity.score.mark')" class-name="mark-modal" @on-ok="saveMark()" @on-cancel="closeModal">
+            <BaseMark2 v-if="markStatus" defaultStatus="line" :bgImg="markBg" @onMarkChange="getMarkData"></BaseMark2>
+        </Modal>
+        <!-- 用来单独渲染学生作答数据,提高tocanvas 的效率 -->
+        <iframe id="byQuIframe" :srcdoc="answerOrMark"></iframe>
+    </div>
+</template>
+<script>
+import html2canvas from 'html2canvas'
+import BaseMark2 from '@/components/mark/BaseMark.vue'
+import BlobTool from '@/utils/blobTool.js'
+export default {
+    components: {
+        BaseMark2
+    },
+    props: {
+        creatorId: {
+            type: String,
+            default: '',
+            required: true
+        },
+        examId: {
+            type: String,
+            default: '',
+            required: true
+        },
+        examInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            },
+            required: true
+        },
+        subjectId: {
+            type: String,
+            default: '',
+            required: true
+        },
+        classId: {
+            type: String,
+            default: '',
+            required: true
+        },
+        stusInfo: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        stusMark: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        paper: {
+            type: Object,
+            default: () => { }
+        },
+        defaultQuIndex: {
+            type: Number,
+            default: 0
+        }
+
+    },
+    data() {
+        return {
+            preSaveList: {},//修改了分数,但是还未保存的学生
+            viewMark: {}, //记录当前查阅批注情况
+            markBg: '',
+            markStatus: false,
+            studentsData: [],
+            dataLoading: false,
+            showAnswer: false,
+            showQu: false,
+            paperInfo: {
+                item: []
+            },
+            quIndex: 0,
+            stuIndex: 0
+        }
+    },
+    methods: {
+        selectQu(index) {
+            this.viewMark = {}
+            if (this.isUpd) {
+                this.saveScore()
+            }
+            this.quIndex = index
+        },
+        /**
+         * score 分数
+         * isAll 是否所有 false -> 筛选未作答
+         */
+        batchScore(score, isAll = true) {
+            this.studentsData.forEach((student, index) => {
+                if (isAll) {
+                    this.fastSetScore(index, score)
+                } else {
+                    if (!student.answer || student.answer[this.quIndex] === this.$t('learnActivity.score.noStuAns')) {
+                        this.fastSetScore(index, score)
+                    }
+                }
+            })
+        },
+        /**
+         * index 学生索引
+         * score 当前分数
+         * type  加减分up/down
+         */
+        scoreStepChange(index, score, type) {
+            if (type === 'up') {
+                if (score < this.questionInfo.score) {
+                    this.fastSetScore(index, ++score)
+                }
+            } else if (type === 'down') {
+                if (score > 0) {
+                    this.fastSetScore(index, --score)
+                }
+            }
+        },
+        /**
+         * index 学生索引
+         * score 快速设置分数
+         */
+        fastSetScore(index, score) {
+            this.$set(this.studentsData[index].data, this.quIndex, score)
+            this.setUpdStatus(this.studentsData[index])
+        },
+        setUpdStatus(student) {            
+            this.$set(this.preSaveList, student.id, student)
+        },
+        hindMark(stuId) {
+            this.$delete(this.viewMark, stuId)
+        },
+        showMark(stuId, blob) {
+            console.log(stuId, blob)            
+            // 不同老師的班級 取不同blob授權
+            let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+                return item.id == this.creatorId
+            })
+            let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
+            let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+            // 替換老師的醍摩豆ID
+            blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
+            let markUrl = blobUrl + '/' + blob + '?' + sas
+            this.$set(this.viewMark, stuId, markUrl)
+        },
+        closeModal() {
+            this.markStatus = false
+            let answerIframe = document.getElementById('byQuIframe')
+            answerIframe.onload = () => { }
+        },
+        delMark(sId) {
+            console.log(sId)
+            console.log(this.studentsData[this.stuIndex])
+            console.log(sId)
+            this.$Modal.confirm({
+                title: this.$t('learnActivity.score.delMark'),
+                content: this.$t('learnActivity.score.delMarkContent'),
+                onOk: () => {
+                    let mark = this.studentsData[this.stuIndex]?.mark[this.quIndex]?.find(item => {
+                        return item.tmdId == this.creatorId
+                    })
+                    let blob = mark ? mark.mark : ""
+                    //保存批注数据
+                    this.$api.learnActivity.delAnswer({
+                        "id": this.examId,
+                        "studentId": this.studentsData[this.stuIndex].id,
+                        "subjectId": this.subjectId,
+                        "classId": this.classId,
+                        "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
+                        "tmdId": this.creatorId,
+                        "index": this.quIndex,//题号
+                        "blob": blob
+                    }).then(
+                        res => {
+                            this.$Message.success(this.$t('learnActivity.mark.deleteOk'))
+                            let i = this.studentsData[this.stuIndex]?.mark[this.quIndex]?.findIndex(item => {
+                                return item.tmdId == this.creatorId
+                            })
+                            console.log(i, this.studentsData)
+                            if (i > -1) {
+                                this.studentsData[this.stuIndex].mark[this.quIndex].splice(i, 1)
+                                this.$delete(this.viewMark, this.studentsData[this.stuIndex].id)
+                            }
+                        },
+                        err => {
+                            this.$Message.error(this.$t('learnActivity.mark.deleteErr'))
+                        }
+                    )
+                }
+            })
+        },
+        saveMark() {
+            if (!this.markData.base64) {
+                return
+            }
+            // 判断当前教师是否已有批注
+            let fileName, hasMarked
+            if (this.studentsData[this.stuIndex].mark && this.studentsData[this.stuIndex].mark[this.quIndex]) {
+                let d = this._.cloneDeep(this.studentsData[this.stuIndex].mark[this.quIndex])
+                let markData = d.find(item => {
+                    return item.tmdId == this.creatorId
+                })
+                if (markData) {
+                    let blob = markData.mark
+                    fileName = blob.substring(blob.lastIndexOf('/') + 1, blob.length)
+                    hasMarked = true
+                } else {
+                    fileName = this.$jsFn.uuid() + '.png'
+                }
+            } else {
+                fileName = this.$jsFn.uuid() + '.png'
+            }
+            let markPng = this.$jsFn.dataURLtoFile(this.markData.base64, fileName)            
+             // 不同老師的班級 取不同blob授權
+             let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+                return item.id == this.creatorId
+            })
+            //保存批注图片
+            let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
+            let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+            // 替換老師的醍摩豆ID
+            blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
+            let host = blobUrl.substring(0, blobUrl.lastIndexOf('/'))
+            let cont = blobUrl.substring(blobUrl.lastIndexOf('/') + 1)
+            let blobTool = new BlobTool(host, cont, '?' + sas, this.examInfo.scope)
+            let path = `exam/${this.examId}/${this.subjectId}/${this.studentsData[this.stuIndex].id}`
+            blobTool.upload(markPng, {
+                path
+            }).then(
+                res => {
+                    if (!hasMarked) {
+                        this.studentsData[this.stuIndex].mark[this.quIndex].push({
+                            mark: path + '/' + fileName,
+                            tmdId: this.creatorId
+                        })
+                    }
+                    this.markStatus = false
+                    //保存批注数据
+                    this.$api.learnActivity.upsertAnswer({
+                        "id": this.examId,
+                        "studentId": this.studentsData[this.stuIndex].id,
+                        "subjectId": this.subjectId,
+                        "classId": this.classId,
+                        "code": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
+                        "tmdId": this.creatorId,
+                        "index": this.quIndex,//题号
+                        "mark": {
+                            sc: 0,//分数
+                            tmdId: this.creatorId,
+                            mark: path + '/' + fileName,//批注BLOB地址
+                            identity: 'admin',//老师身份,这是是固定管理员身份
+                            index: this.quIndex //题号
+                        }
+                    }).then(
+                        res => {
+                            this.$Message.success(this.$t('learnActivity.score.markOk'))
+                            this.$set(this.viewMark, this.studentsData[this.stuIndex].id, '')
+                            setTimeout(() => {
+                                this.showMark(this.studentsData[this.stuIndex].id, path + '/' + fileName)
+                            })
+                        },
+                        err => {
+                            this.$Message.error(this.$t('learnActivity.score.markErr'))
+                        }
+                    )
+                },
+                err => {
+                    this.$Message.error(this.$t('learnActivity.mark.saveErr'))
+                }
+            )
+        },
+        getMarkData(data) {
+            this.markData = data
+        },
+        // 批注学生作答数据
+        markStuAnswer() {
+            this.$nextTick(() => {
+                let answerIframe = document.getElementById('byQuIframe')
+                answerIframe.srcdoc = this.answerOrMark
+                answerIframe.onload = () => {
+                    answerIframe.style.width = '850px'
+                    answerIframe.contentWindow.document.body.style.margin = '0px'
+                    answerIframe.contentWindow.document.body.style.padding = '0px'
+                    answerIframe.contentWindow.document.body.style.minWidth = '600px'
+                    answerIframe.contentWindow.document.body.style.minHeight = '240px'
+                    answerIframe.contentWindow.document.body.style.height = 'fit-content'
+                    answerIframe.contentWindow.document.body.style.width = 'fit-content'
+                    let bodyWidth = answerIframe.contentWindow.document.body.clientWidth
+                    bodyWidth = bodyWidth == 0 ? 600 : bodyWidth
+                    answerIframe.style.width = (bodyWidth + 20) + 'px'
+                    answerIframe.contentWindow.document.body.style.backgroundColor = '#f5f5f5'
+                    html2canvas(answerIframe.contentWindow.document.body, {
+                        useCORS: true
+                    }).then(
+                        (canvas) => {
+                            this.markBg = canvas.toDataURL()
+                        },
+                        err => {
+                            console.log('转换失败', err)
+                        }
+                    )
+                }
+            })
+            this.markStatus = true
+        },
+        saveScore() {
+            if (!this.isUpd) {
+                this.$Message.warning(this.$t('learnActivity.score.noUpd'))
+                return
+            }
+            let saveList = []
+            for (const key in this.preSaveList) {
+                saveList.push(this.preSaveList[key])
+            }            
+            let requestData = {
+                "id": this.examId,
+                //"code": this.examInfo.owner == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                "code": this.examInfo.owner == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
+                "point": saveList.map(item => item.data),
+                "studentId": saveList.map(item => {
+                    return {
+                        id: item.id,
+                        type: item.type
+                    }
+                }),
+                "classId": this.classId,
+                "school": this.examInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.creatorId,
+                "subjectId": this.subjectId
+            }
+            let d = this._.cloneDeep(saveList)
+            this.$api.learnActivity.UpsertAllRecord(requestData).then(res => {
+                if (res.error == null) {
+                    this.successTips()
+                    this.$emit('updScore', d)
+                    //TODO 清空预保存学生
+                    this.preSaveList = {}
+                } else {
+                    this.$Message.error(this.$t('learnActivity.score.saveSocreErr'))
+                }
+            })
+        },
+        successTips() {
+            this.$Message.success(this.$t('learnActivity.score.saveScoreOk'))
+        },
+        async getStuAnswer() {
+            try {
+                //debugger
+                // 不同老師的班級 取不同blob授權
+                let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+                    return item.id == this.creatorId
+                })
+                let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
+                let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+                // 替換老師的醍摩豆ID
+                blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
+                this.studentsData.forEach(async (student, index) => {
+                    /**
+                     * 1、批注逻辑调整,每个题目的批注单独保存成图片;
+                     * 2、这里就直接读取原始作答数据;
+                     * 3、在渲染的时候再判断当前题目是否有批注数据。
+                     */
+                    if (student.ansBlob && student.ansBlob.length) {
+                        let answerUrl = student.ansBlob[0]
+                        let urlPrefix = `${blobUrl}/exam/${answerUrl.replace('/ans.json','')}`
+                        let fullUrl = `${blobUrl}/exam/${answerUrl}?${sas}`
+                        let ansRes = await this.$jsFn.handleStudentAnswer(fullUrl, urlPrefix, sas)
+                        // 问答题:课中的地址需截取文件名称重新拼接地址,因此统一重新拼接处理
+                        ansRes = ansRes.map((item, index) => {
+                            if(this.quNoList[index].type === 'subjective' && this.quNoList[index].answerType && this.quNoList[index].answerType != 'text' && item.length) {
+                                let name = item[0].substr(item[0].lastIndexOf(`/${student.id}/`) + (student.id.length + 2))
+                                item = this.quNoList[index].answerType === 'file' ? name : [`${blobUrl}/exam/${this.examId}/${this.subjectId}/${student.id}/${name}?${sas}`]
+                            }
+                            return item
+                        })
+                        this.$set(student, 'answer', ansRes)
+                    }
+
+                })
+
+            } catch (e) {
+                this.$Message.error(this.$t('learnActivity.score.answerDataErr'))
+            }
+        },
+        getFileName(url, stuId) {
+            return url.substr(url.lastIndexOf(`/${stuId}/`) + (stuId.length + 2))
+        },
+        /* 下载 */
+        async onDownload(answer, stuId) {            
+            // 不同老師的班級 取不同blob授權
+            let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+                return item.id == this.creatorId
+            })
+            let sas = this.examInfo?.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
+            let blobUrl = this.examInfo?.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+            // 替換老師的醍摩豆ID
+            blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
+            let url = `${blobUrl}/exam/${this.examId}/${this.subjectId}/${stuId}/${answer}?${sas}`
+            this.$tools.doDownloadByUrl(url, answer)
+        },
+    },
+    watch: {
+        stusInfo: {
+            handler(n, o) {
+                this.studentsData = this._.cloneDeep(n)
+                if (this.studentsData.length) {
+                    this.getStuAnswer()
+                }
+            },
+            deep: true,
+            immediate: true
+        },
+        paper: {
+            handler(newValue, oldValue) {
+                this.paperInfo = newValue
+            },
+            deep: true,
+            immediate: true
+        },
+        defaultQuIndex: {
+            handler(n, o) {
+                this.quIndex = this.defaultQuIndex
+            }
+        },
+        showQu : {
+            handler(n, o) {
+                if(n) {
+                    window.MathJax.startup.promise.then(() => {
+                        window.MathJax.typesetPromise([this.$refs.mathJaxContainer])
+                    })
+                }
+            }
+        },
+    },
+    computed: {
+        isUpd() {
+            let keys = Object.keys(this.preSaveList)
+            console.log(keys)
+            return !!keys.length
+        },
+        //当前题目信息
+        questionInfo() {
+            if (this.paperInfo.item && this.paperInfo.item.length) {
+                let data
+                for (let item of this.paperInfo.item) {
+                    if (item.index != undefined) {
+                        if (item.index == this.quIndex) {
+                            data = this._.cloneDeep(item)
+                            break
+                        }
+                    } else if (item.children) {
+                        item.children.forEach(child => {
+                            if (child.index == this.quIndex) {
+                                data = this._.cloneDeep(child)
+                            }
+                        })
+                        if (data) {
+                            break
+                        }
+                    }
+                }
+                return data ? data : {}
+            }
+            return {}
+        },
+        //试卷题号列表
+        quNoList() {
+            if (this.paperInfo.item && this.paperInfo.item.length) {
+                let objectiveQu = ['single', 'multiple', 'judge']
+                let data = []
+                let realIndex = 0
+                this.paperInfo.item.forEach((item, index) => {
+                    if (item.children.length) {
+                        item.children.forEach((childItem, childIndex) => {
+                            let i = realIndex++
+                            data.push({
+                                label: (index + 1) + '-' + (childIndex + 1),
+                                value: i,
+                                score: item.score,
+                                disabled: objectiveQu.includes(childItem.type),
+                                type: childItem.type,
+                                answerType: childItem?.answerType || undefined
+                            })
+                        })
+                    } else {
+                        let i = realIndex++
+                        data.push({
+                            label: (index + 1) + '',
+                            value: i,
+                            score: item.score,
+                            disabled: objectiveQu.includes(item.type),
+                            type: item.type,
+                            answerType: item?.answerType || undefined
+                        })
+                    }
+                })
+                return data
+            }
+            return []
+        },
+        answerOrMark() {
+            //如果当前老师已经批注过,再次批注就复用前面的批注数据
+            if (this.studentsData[this.stuIndex] && this.studentsData[this.stuIndex].mark && this.studentsData[this.stuIndex].mark[this.quIndex]) {
+                let d = this._.cloneDeep(this.studentsData[this.stuIndex].mark[this.quIndex])
+                let markData = d.find(item => {
+                    return item.tmdId == this.creatorId
+                })
+                if (markData) {                    
+                    // 不同老師的班級 取不同blob授權
+                    let blob_sasItem = this.examInfo.Blob_sas.find(item => {
+                        return item.id == this.creatorId
+                    })
+                    let sas = this.examInfo.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : blob_sasItem.sas
+                    let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+                    // 替換老師的醍摩豆ID
+                    blobUrl = blobUrl.replace(this.$store.state.userInfo.TEAMModelId, this.creatorId);
+                    let url = blobUrl + '/' + markData.mark + '?' + sas
+                    let h = `<img src=\"${url}\"/>`
+                    return [h]
+                }
+            }
+            if(this.studentsData[this.stuIndex] && this.studentsData[this.stuIndex].answer) {
+                if(this.questionInfo.type === 'subjective' && !['text', 'file'].includes(this.questionInfo.answerType) && this.studentsData[this.stuIndex].answer[this.quIndex].length) {
+                    let name = this.studentsData[this.stuIndex].answer[this.quIndex][0].substr(this.studentsData[this.stuIndex].answer[this.quIndex][0].lastIndexOf(`/${this.studentsData[this.stuIndex].id}/`) + (this.studentsData[this.stuIndex].id.length + 2))
+                    let name1 = name.substring(0, name.lastIndexOf('?'))
+                    let h = `<img src=\"${this.studentsData[this.stuIndex].answer[this.quIndex][0]}\"/>`
+                    return this.questionInfo.answerType === 'audio' ? [name1] : [h]
+                } else {
+                    return this.studentsData[this.stuIndex].answer[this.quIndex]
+                }
+            } else {
+                return this.$t('learnActivity.score.noStuAns')
+            }
+            // return this.studentsData[this.stuIndex] && this.studentsData[this.stuIndex].answer ? this.studentsData[this.stuIndex].answer[this.quIndex] : this.$t('learnActivity.score.noStuAns')
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.redundant-mark-btn {
+    background: #ed4014;
+    margin: auto;
+    margin-top: 5px;
+    color: white;
+    border-radius: 4px;
+    width: 55px;
+    display: none;
+    cursor: pointer;
+    user-select: none;
+}
+.stu-name {
+    color: #00ad25;
+}
+.scoring-paper-header {
+    position: sticky;
+    top: 52px;
+    background: white;
+    z-index: 99;
+    padding: 10px 0px;
+}
+.fast-score-tag {
+    background: #19be6b;
+    color: white;
+    width: 20px;
+    height: 20px;
+    border-radius: 4px;
+    font-size: 12px;
+    display: none;
+    line-height: 18px;
+    cursor: pointer;
+}
+.solution-wrap {
+    padding: 10px;
+}
+.question-wrap {
+    padding: 10px;
+}
+#byQuIframe {
+    position: fixed;
+    top: 9990px;
+    width: 850px;
+    background: #f5f5f5;
+}
+.qu-info-title {
+    color: #00ad25;
+    margin-left: -6px;
+    margin-top: 20px;
+    display: inline-block;
+}
+.mark-action-box {
+    float: right;
+    margin-top: -32px;
+    margin-right: -12px;
+    display: none;
+}
+.mark-action-item {
+    color: white;
+    padding: 5px 12px;
+    cursor: pointer;
+}
+.answer-box {
+    padding: 5px 15px 5px 0px;
+    margin-top: 7px;
+    min-height: 50px;
+    
+    .answer-link{
+        background-color: #e6e6e6;
+        border: 2px solid #e8e8e8;
+        padding: 2px 10px;
+        border-radius: 4px;
+        margin: 5px;
+        color: #0086E6;
+        cursor: pointer;
+        
+        &-active{
+            background-color: #1cc0f3;
+            color: #fff;
+        }
+    }
+}
+.answer-wrap {
+    width: 100%;
+    margin-top: 20px;
+}
+.stu-ans-item-active {
+    .fast-score-tag {
+        display: inline-block;
+    }
+    .stu-answer-box {
+        border-color: #01b4ef;
+    }
+    .mark-action-box {
+        display: inline-block;
+    }
+    .redundant-mark-btn {
+        display: inline-block;
+    }
+}
+.stu-ans-item {
+    margin-top: 10px;
+    padding: 10px;
+    display: flex;
+    width: 100%;
+    min-height: 130px;
+    &:hover .fast-score-tag {
+        display: inline-block;
+    }
+    &:hover .stu-answer-box {
+        border-color: #01b4ef;
+    }
+    &:hover .mark-action-box {
+        display: inline-block;
+    }
+    &:hover .redundant-mark-btn {
+        display: inline-block;
+    }
+}
+.set-score-box {
+    text-align: center;
+    border-right: 1px dashed #e7e7e7;
+    margin-right: 10px;
+    width: 80px;
+}
+.stu-answer-box {
+    flex: 1;
+    border: 2px solid transparent;
+    padding: 5px 10px;
+}
+.qu-no-value {
+    font-size: 14px;
+    font-weight: 600;
+    color: #515a6e;
+    width: 40px;
+    height: 24px;
+    line-height: 24px;
+    display: inline-block;
+    border-radius: 12px;
+    text-align: center;
+    box-shadow: 0px 0px 5px #515a6e;
+    cursor: pointer;
+    margin-bottom: 10px;
+    margin-right: 10px;
+    user-select: none;
+}
+.qu-no-active {
+    color: #2d8cf0;
+    box-shadow: 0px 0px 5px #2d8cf0;
+}
+.qu-mark-container {
+    width: 100%;
+    // padding: 0px 15px 15px 0px;
+    min-height: 600px;
+    background: white;
+}
+.base-info-btn {
+    margin-left: 5px;
+    margin-right: 10px;
+    float: right;
+    user-select: none;
+    cursor: pointer;
+}
+.base-info-btn-active {
+    color: #2d8cf0;
+}
+.qu-order-tag-def {
+    display: inline-block;
+    height: 18px;
+    border-radius: 4px;
+    background-color: #e5e5e5;
+    color: black;
+    cursor: pointer;
+    margin-right: 5px;
+    line-height: 16px;
+    text-align: center;
+    font-size: 12px;
+    padding: 2px 6px;
+    margin-bottom: 5px;
+}
+.qu-order-tag-active {
+    background-color: #19be6b;
+    color: white;
+    box-shadow: 0px 0px 5px #aaa;
+}
+.qu-lable-wrap {
+    border-bottom: 1px dashed #c3c3c34d;
+    padding: 0px 15px 20px 15px;
+}
+</style>
+<style lang="less">
+.set-score-box .ivu-input-number-handler-wrap {
+    display: none;
+}
+</style>

+ 7 - 5
TEAMModelOS/ClientApp/src/view/learnactivity/htCreateEva.vue

@@ -60,13 +60,13 @@
                                 <Checkbox v-model="evaluationInfo.publish" :true-value="$GLOBAL.PUBLISH_TYPE()[0].value" :false-value="$GLOBAL.PUBLISH_TYPE()[1].value" @on-change="publishChange">
                                     <span style="margin-left:5px;user-select: none;">{{$GLOBAL.PUBLISH_TYPE()[0].label}}</span>
                                 </Checkbox>
-                            </FormItem> -->
+                            </FormItem> 
 							<FormItem :label="$t('learnActivity.createEv.startTime') + '(' + $t('learnActivity.noStartTimeTip') + ')'">
 								<DatePicker :options="dateOpt" type="datetime" format="yyyy/MM/dd HH:mm" v-model="startTime" split-panels :placeholder="$t('learnActivity.createEv.sTimeHolder')" style="width: 100%" @on-change="getDate($event, 0)"></DatePicker>
 							</FormItem>
 							<FormItem :label="$t('learnActivity.createEv.endTime')" prop="endTime">
 								<DatePicker :options="dateOpt1" type="datetime" format="yyyy/MM/dd HH:mm" v-model="endTime" split-panels :placeholder="$t('learnActivity.createEv.eTimeHolder')" style="width: 100%" @on-change="getDate($event, 1)"></DatePicker>
-							</FormItem>
+							</FormItem>-->
 							<FormItem>
 								<Checkbox v-model="evaluationInfo.isCompletion">{{ $t("learnActivity.createEv.completeScore") }}</Checkbox>
 							</FormItem>
@@ -432,11 +432,12 @@
 						if (++count == requestData.papers.length) {
 							this.$Message.success(this.$t("learnActivity.createEv.publishOk"));
 							this.isLoading = false;
-							// let route = this.mode + 'Evaluation'
+							// let route = this.mode + 'Evaluation'							
 							this.$router.push({
 								name: "htMgtExam",
 								params: {
-									data: this.$route.params.data
+									data: this.$route.params.data,
+									parentPage: this.$route.params.parentPage
 								}
 							});
 						}
@@ -485,7 +486,8 @@
 							this.$router.push({
 								name: "htMgtExam",
 								params: {
-									data: this.$route.params.data
+									data: this.$route.params.data,
+									parentPage: this.$route.params.parentPage
 								}
 							});							
 						} else {						

+ 66 - 15
TEAMModelOS/ClientApp/src/view/learnactivity/tabs/htAnswerTable.vue

@@ -107,6 +107,10 @@
 						<span class="stu-status-tag" @click="getStuScore(row, 0)" :style="{ background: row.statusColor }">
 							{{ row.statusText }}
 						</span>
+						<!-- 已批閱 -->
+						<span v-show="row.isApproved"  style="margin-left: 10px;color: #ed4014;" >
+							{{ $t("htcommunity.Approved") }}
+						</span>
 					</template>
 					<Loading slot="loading" :top="-50"></Loading>
 				</Table>
@@ -152,9 +156,13 @@
 					</span>
 				</div>
 				<!-- 按人批阅 -->
-				<ByStuMark ref="byStuMark" v-if="markType == 'byStu'" :examInfo="examInfo" :defaultIndex="defaultIndex" :paper="paperInfo" :studentInfo="chooseStudent" :subjectId="subjectIdForMark" @updScore="updScore" @nextStu="getNextStu" :examId="examIdForMark"></ByStuMark>
+				<ByStuMark ref="byStuMark" v-if="markType == 'byStu'" :examInfo="examInfo" :defaultIndex="defaultIndex" :paper="paperInfo" 
+				:studentInfo="chooseStudent" :subjectId="subjectIdForMark" @updScore="updScore" @nextStu="getNextStu" :examId="examIdForMark"
+				:creatorId="teacherSelectValue"></ByStuMark>
 				<!-- 按题批阅 -->
-				<ByQuMark v-else-if="markType == 'byQu'" @updScore="updScore" :paper="paperInfo" :examInfo="examInfo" ref="byQuMark" :stusInfo="studentScore" :classId="chooseClass" :subjectId="subjectIdForMark" :examId="examIdForMark" ></ByQuMark>
+				<ByQuMark v-else-if="markType == 'byQu'" @updScore="updScore" :paper="paperInfo" :examInfo="examInfo" ref="byQuMark" 
+				:stusInfo="studentScore" :classId="chooseClass" :subjectId="subjectIdForMark" :examId="examIdForMark" 
+				:creatorId="teacherSelectValue"></ByQuMark>
 			</div>
 		</vuescroll>
 		
@@ -256,7 +264,7 @@
 	import StuReport from "../StuReport.vue";
 	import CorrectRate from "../echarts/CorrectRate.vue";
 	import ByStuMark from "../byStu/htByStuMark.vue";
-	import ByQuMark from "../ByQuMark.vue";
+	import ByQuMark from "../htByQuMark.vue";
 	import excel from "@/utils/excel.js";
 	export default {
 		components: {
@@ -282,6 +290,7 @@
 		},
 		data() {
 			return {
+				markResult:[],
 				examIdForMark:"",
 				subjectIdForMark:"",
 				groupSelectValue: "",
@@ -353,7 +362,7 @@
 						slot: "status",
 						align: "center",
 						fixed: "right",
-						width: 130
+						width: 200
 					}
 				],
 				tableColumn: [],
@@ -394,7 +403,7 @@
 				// else {
 				// 	data = this.examClassLilst;
 				// }
-				if(this.examInfo.stuLists){				
+				if(this.examInfo.stuLists && this.examInfo.stuLists.length > 0){				
 				  data = this.examInfo.stuLists[0].courseLists[0].groupLists;
 				}
 				// data.forEach((item) => {
@@ -453,7 +462,7 @@
 				return c ? c.name : "";
 			}
 		},
-		created() {
+		created() {			
 			this.$store.dispatch("user/getSchoolProfile").then((res) => {
 				this.schoolBase = res.school_base;
 				this.schoolClassList = res.school_classes;
@@ -467,7 +476,7 @@
 		watch: {
 			examInfo: {
 				handler(n, o) {					
-					if (!n) return;
+					if (!n) return;					
 					if (n.subjects && n.subjects.length) {
 						this.chooseSubject = n.subjects[0].id;
 					}
@@ -505,6 +514,7 @@
 					//这里统一获取名单数据(教学班、行政班、个人名单)
 					//let ids = n.stuLists?.length ? n.stuLists : n.classes;
 					let ids = [];
+					let tmids = [];
 					if (n.stuLists) {
 						// 活動評量的架構為"stuLists": ["courseLists": ["groupLists": []]]  所以要分三層取id
 						n.stuLists.forEach(item => {
@@ -513,8 +523,13 @@
 									ids.push(group.id);
 								})
 							})
+							// 取出所有老師醍摩豆ID
+							tmids.push(item.creatorId);
 						})
 					}
+					if (tmids && tmids.length > 0) {
+						this.getBlob_sas(tmids);
+					}
 					if (!ids) return; 
 					let params = {
 						schoolId: this.$store.state.userInfo.schoolCode,
@@ -526,13 +541,15 @@
 							this.examClassLilst = res.groups;
 							this.examStuList = res.members;
 							// 老師 > 課程 > 課程名單 三聯下拉選單設定
-					this.bindTeacherSelectData();
-					this.teacherSelectValue = this.teacherSelectData[0].id;
-				    this.changeTeacherSelectData();
-				    this.courseSelectValue = this.courseSelectData[0].id;
-				    this.changeCourseSelectData();
-					this.groupSelectValue = this.groupSelectData[0].id;
-					this.changeGroupSelectData();
+							this.bindTeacherSelectData();
+							if(this.teacherSelectData[0]){
+							this.teacherSelectValue = this.teacherSelectData[0].id;
+							this.changeTeacherSelectData();
+							this.courseSelectValue = this.courseSelectData[0].id;
+							this.changeCourseSelectData();
+							this.groupSelectValue = this.groupSelectData[0].id;
+							this.changeGroupSelectData();
+							}
 						},
 						(err) => {
 							// this.$Message.error('API Error')
@@ -590,6 +607,20 @@
 			}
 		},
 		methods: {
+			// 取得blob授權
+			async getBlob_sas(tmidsarr) {
+				let param = { tmids: tmidsarr };
+				try {
+					//  取得blob授權
+					let res = await this.$api.htcommunity.jointTblobsas(param);
+					if (res) {						
+						this.examInfo.Blob_sas = res;
+					} else {
+					}
+				} catch (err) {
+					console.log("API error: " + err);
+				}
+			},
 			// 設定老師下拉選單
 			bindTeacherSelectData(){
 				this.teacherSelectData = [];
@@ -617,11 +648,15 @@
 				// 取得課程的班級  // 根據目前選中的課程班級  設定 examId 跟 subjectId
 				let stu = this.examInfo.stuLists.find((item) =>  item.creatorId == this.teacherSelectValue );
 				let course = stu.courseLists.find((item) =>  item.courseId == this.courseSelectValue );
-				course.groupLists.forEach(item =>{
+				if(course){
+					course.groupLists.forEach(item =>{
 					this.groupSelectData.push({name: item.name, id: item.id});
 				})
 				this.examIdForMark = course.examId;
 				this.subjectIdForMark = course.subjectId;
+
+				}
+				
 			},
 			// 課程下拉選單切換值 
 			changeGroupSelectData(){	
@@ -825,6 +860,8 @@
 			},
 			toggleScoreStatus() {
 				this.checkScoreSave(this.handleScoreStatus);
+				// 批閱儲存之後刷新資料
+				this.getStudentAnswer();		
 			},
 			handleScoreStatus() {
 				this.isMarkView = !this.isMarkView;
@@ -1205,6 +1242,10 @@
 			    this.$api.htcommunity.commonExamFindSummaryRecord(requestData).then(
 					(res) => {
 						if (res.examClassResults && res.examClassResults.length) {
+							// 已批閱資料
+							if(res.markResult){
+								this.markResult = res.markResult;
+							}
 							if (this.timer) clearInterval(this.timer);
 							this.paperInfo[this.chooseClass]["studentAns"] = res.examClassResults[0];
 							let classStu = {
@@ -1322,6 +1363,16 @@
 								score.mark = studentAns.mark[i];
 								let { status, statusText, statusColor } = this.getStatusInfo(studentAns.studentScores[i], studentAns.status[i]);
 								score.status = status;
+								// 加上已批閱功能 如果學生id存在而且有分數 => 已批閱					 			
+								score.isApproved = false;
+								if (this.markResult && this.markResult.length > 0) {									
+									let markStuIndex = this.markResult[0].studentIds.findIndex(item => {										
+										return item === studentData.students[k].id
+									})
+									if(this.markResult[0].points[markStuIndex]&&this.markResult[0].points[markStuIndex].length>0){
+										score.isApproved = true ;
+									}									
+								}																											
 								score.statusText = statusText;
 								score.statusColor = statusColor;
 								this.studentScore.push(score);

+ 1 - 0
TEAMModelOS/ClientApp/src/view/student-account/class/ClassMgt.vue

@@ -1255,6 +1255,7 @@ export default {
             handler(n, o) {
                 if (this.classList.length > 0) {
                     this.updateBefore = JSON.stringify(this.classList[0])
+                    this.filterByPeriod()
                 }
             },
             deep: true,

+ 59 - 39
TEAMModelOS/Controllers/Analysis/ArtAnalysisController.cs

@@ -65,8 +65,8 @@ namespace TEAMModelOS.Controllers.Analysis
 
         [ProducesDefaultResponseType]
         [HttpPost("statistics")]
-        [Authorize(Roles = "IES")]
-        [AuthToken(Roles = "teacher,admin")]
+        //[Authorize(Roles = "IES")]
+        //[AuthToken(Roles = "teacher,admin")]
         public async Task<IActionResult> getAnalysis(JsonElement request)
         {
             var client = _azureCosmos.GetCosmosClient();
@@ -134,8 +134,8 @@ namespace TEAMModelOS.Controllers.Analysis
                 double min = subjectScore.name.Min(s => Math.Abs(s));
                 double total = subjectScore.name.Sum();
                 double average = Math.Round(total / stus.Count, 2);
-                double excellent = Math.Round(subjectScore.name.Where(s => s >= 80).Count() * 1.0 / stus.Count, 2);
-                double pass = Math.Round(subjectScore.name.Where(s => s >= 60).Count() * 1.0 / stus.Count, 2);
+                double excellent = Math.Round(subjectScore.name.Where(s => s >= 80).Count() * 1.0 / stus.Count, 4);
+                double pass = Math.Round(subjectScore.name.Where(s => s >= 60).Count() * 1.0 / stus.Count, 4);
                 double powSum = 0;
                 foreach (var sc in subjectScore.name)
                 {
@@ -145,7 +145,13 @@ namespace TEAMModelOS.Controllers.Analysis
 
                 //获取本次评测所有科目结算结果
                 List<ExamResult> examResults = new();
+                List<ExamClassResult> classResults = [];
                 ExamInfo info = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<ExamInfo>(examId.ToString(), new PartitionKey($"Exam-{code}"));
+                var query = $"select value(c) from c where c.examId = '{examId}' and c.subjectId = '{subjectId}' ";
+                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<ExamClassResult>(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{code}") }))
+                {
+                    classResults.Add(item);
+                }
                 (List<RMember> tchList, List<RGroupList> classLists) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, classIds, code.GetString(), null,-1,info.startTime);
                 //获取评测ID
                 //var examId = arts[0].settings.SelectMany(s => s.task).Where(a => a.type == 1 && a.subject.Equals(subjectId.GetString())).FirstOrDefault().acId;
@@ -170,8 +176,8 @@ namespace TEAMModelOS.Controllers.Analysis
                     (string subId, List<(string name, List<string> kno)> values) = await getKnowledge(info.papers[index].code, client, subjectId.GetString(), info.papers[index].periodId);
                     knos = values;
                 }
-                var query = $"select c.id,c.name,c.subjectId,c.studentScores,c.studentIds,c.paper,c.classes,c.sRate,c.average,c.standard,c.lostStus,c.record,c.phc,c.plc from c where c.examId = '{examId}' and c.subjectId = '{subjectId}' ";
-                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<ExamResult>(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamResult-{examId}") }))
+                var queryClassResult = $"select c.id,c.name,c.subjectId,c.studentScores,c.studentIds,c.paper,c.classes,c.sRate,c.average,c.standard,c.lostStus,c.record,c.phc,c.plc from c where c.examId = '{examId}' and c.subjectId = '{subjectId}' ";
+                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryIteratorSql<ExamResult>(queryText: queryClassResult, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamResult-{examId}") }))
                 {
                     examResults.Add(item);
                 }
@@ -179,7 +185,7 @@ namespace TEAMModelOS.Controllers.Analysis
                 List<KeyValuePair<string, List<(string name, double score, double point, string sId)>>> stuPersent = new();
                 (KeyValuePair<string, List<string>> key1, KeyValuePair<string, List<string>> key2, KeyValuePair<string, List<(string name, double score, double average)>> key3,
                     KeyValuePair<string, List<(string name, double score)>> key4, KeyValuePair<string, List<(string name, double score, double point, string cId)>> key5,
-                    KeyValuePair<string, List<(string name, double score, double point, string sId)>> key6) = DoKnowledgePoint(examResults[0], info);
+                    KeyValuePair<string, List<(string name, double score, double point, string sId)>> key6, KeyValuePair<string, double> key7) = DoKnowledgePoint(examResults[0], info);
                 //KeyValuePair<string, List<(string id, double sta, double pass, string stu)>> key = DoSubjectScatter(examResults[0]);
                 pointPersent.Add(key3);
                 stuPersent.Add(key6);
@@ -249,7 +255,7 @@ namespace TEAMModelOS.Controllers.Analysis
                 {
                     x.name,
                     x.score,
-                    persent = Math.Round(x.score > 0 ? x.av / x.score : 0, 2),
+                    persent = Math.Round(x.score > 0 ? x.av / x.score : 0, 4),
                     dimension = setting.dimensions.Where(s => s.blocks.Contains(x.name)).Select(x => x.dimension)
                 });
                 //获取维度得分率
@@ -257,36 +263,41 @@ namespace TEAMModelOS.Controllers.Analysis
                 List<(string claId, double score, double point, string dim)> claDims = new();
                 foreach (var dimm in setting.dimensions)
                 {
-                    double dimScore = 0;
-                    double dimPoint = 0;
                     if (dimm.subjectBind.Equals(subjectId.GetString()))
                     {
+
+
                         foreach (var stuDim in stuBlock)
                         {
+                            double dimSScore = 0;
+                            double dimSPoint = 0;
                             foreach (var dd in stuDim.block)
                             {
                                 if (dimm.blocks.Contains(dd.Key))
                                 {
-                                    dimScore += dd.score;
-                                    dimPoint += dd.point;
+                                    dimSScore += dd.score;
+                                    dimSPoint += dd.point;
                                 }
                             }
-                            stuDims.Add((stuDim.Key, dimScore, dimPoint, dimm.dimension));
+                            stuDims.Add((stuDim.Key, dimSScore, dimSPoint, dimm.dimension));
                         }
                         foreach (var claDim in claBlock)
                         {
+                            double dimCScore = 0;
+                            double dimCPoint = 0;
                             foreach (var dd in claDim.block)
                             {
                                 if (dimm.blocks.Contains(dd.Key))
                                 {
-                                    dimScore += dd.score;
-                                    dimPoint += dd.point;
+                                    dimCScore += dd.score;
+                                    dimCPoint += dd.point;
                                 }
                             }
-                            claDims.Add((claDim.Key, dimScore, dimPoint, dimm.dimension));
+                            claDims.Add((claDim.Key, dimCScore, dimCPoint, dimm.dimension));
                         }
                     }
-                }
+                }              
+
                 var dim = setting.dimensions.Where(q => q.subjectBind.Equals(subjectId.GetString())).Select(x => new
                 {
                     x.dimension,
@@ -295,14 +306,14 @@ namespace TEAMModelOS.Controllers.Analysis
                         persent =
                              blockScore.Where(z => z.name.Equals(c)).Sum(v => v.score) > 0 ?
                              blockScore.Where(z => z.name.Equals(c)).Sum(v => v.av) / blockScore.Where(z => z.name.Equals(c)).Sum(v => v.score) : 0
-                    }).Sum(o => o.persent) / x.blocks.Count, 2)
+                    }).Sum(o => o.persent) , 4)
                 });
 
 
                 var kno = key4.Value.Select(x => new
                 {
                     x.name,
-                    x.score,
+                    persent = x.score,
                     block = knos.Where(v => null != v.kno && v.kno.Contains(x.name)).Select(x => x.name)
                 });
                 //学生信息
@@ -316,22 +327,22 @@ namespace TEAMModelOS.Controllers.Analysis
                     examResults[0].classes.Where(c => c.id.Equals(s.cd)).FirstOrDefault()?.gradeId,
                     //key.Value.Where(c => c.id.Equals(s.sIds))?.FirstOrDefault().sta,
                     //key.Value.Where(c => c.id.Equals(s.sIds))?.FirstOrDefault().pass,
-                   // key.Value.Where(c => c.id.Equals(s.sIds))?.FirstOrDefault().stu,
+                    // key.Value.Where(c => c.id.Equals(s.sIds))?.FirstOrDefault().stu,
                     kno = key6.Value.Where(c => c.sId.Equals(s.sIds))?.Select(z => new
                     {
                         z.name,
-                        persent = z.point > 0 ? Math.Round(z.score / z.point, 2) : 0,
+                        persent = z.point > 0 ? Math.Round(z.score / z.point, 4) : 0,
                         block = knos.Where(v => null != v.kno && v.kno.Contains(z.name))?.Select(x => x.name)
                     }),
                     block = stuBlock.Where(c => c.Key.Equals(s.sIds)).FirstOrDefault()?.block.Select(x => new
                     {
                         name = x.Key,
-                        persent = x.point > 0 ? Math.Round(x.score / x.point, 2) : 0
+                        persent = x.point > 0 ? Math.Round(x.score / x.point, 4) : 0
                     }),
                     dim = stuDims.Where(c => c.stuId.Equals(s.sIds))?.Select(z => new
                     {
                         name = z.dim,
-                        persent = z.point > 0 ? Math.Round(z.score / z.point, 2) : 0
+                        persent = z.point > 0 ? Math.Round(z.score / z.point, 4) : 0
                     })
                 });
                 List<(string cId, double sc, double max, double min, double excellent, double pass, double count)> clsInfo = new();
@@ -341,24 +352,25 @@ namespace TEAMModelOS.Controllers.Analysis
                     name = x.Key.className,
                     max = x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Count > 0 ? x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Max(s => Math.Abs((double)s)) : 0,
                     min = x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Count > 0 ? x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Min(s => Math.Abs((double)s)) : 0,
-                    excellent = x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Count > 0 ? Math.Round(x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Where(s => s >= 80).Count() * 1.0 / x.ToList().Count, 2) : 0,
-                    pass = x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Count > 0 ? Math.Round(x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Where(s => s >= 60).Count() * 1.0 / x.ToList().Count, 2) : 0,
+                    excellent = x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Count > 0 ? Math.Round(x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Where(s => s >= 80).Count() * 1.0 / x.ToList().Count, 4) : 0,
+                    pass = x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Count > 0 ? Math.Round(x.ToList().Where(p => p.score > 0).Select(z => z.score).ToList().Where(s => s >= 60).Count() * 1.0 / x.ToList().Count, 4) : 0,
                     score = x.ToList().Count > 0 ? Math.Round((double)x.ToList().Sum(z => z.score) * 1.0 / x.ToList().Count, 2) : 0,
+                    persent = x.ToList().Count > 0 ? Math.Round((double)x.ToList().Sum(z => z.score) * 1.0 / x.ToList().Count / key7.Value, 4) : 0,
                     kno = key5.Value.Where(c => c.cId.Equals(x.Key.classId)).Select(z => new
                     {
                         z.name,
-                        persent = Math.Round(z.point > 0 ? z.score / x.ToList().Count / z.point : 0, 2),
+                        persent = Math.Round(z.point > 0 ? z.score / z.point : 0, 4),
                         block = knos.Where(v => null != v.kno && v.kno.Contains(z.name)).Select(x => x.name)
                     }),
                     block = claBlock.Count() > 0 ? claBlock.Where(c => c.Key.Equals(x.Key.classId))?.FirstOrDefault().block.Select(z => new
                     {
                         name = z.Key,
-                        persent = Math.Round(z.point > 0 ? z.score / x.ToList().Count / z.point : 0, 2)
+                        persent = Math.Round(z.point > 0 ? z.score / z.point : 0, 4)
                     }) : null,
                     dim = claDims.Where(c => c.claId.Equals(x.Key.classId)).Select(z => new
                     {
                         name = z.dim,
-                        persent = Math.Round(z.point > 0 ? z.score / x.ToList().Count / z.point : 0, 2)
+                        persent = Math.Round(z.point > 0 ? z.score / z.point : 0, 4)
                     }),
                     examResults[0].classes.Where(c => c.id.Equals(x.Key.classId))?.FirstOrDefault().gradeId
                 });
@@ -369,10 +381,11 @@ namespace TEAMModelOS.Controllers.Analysis
                     id = x.gradeId,
                     name = perMore[int.Parse(x.gradeId)],
                     score = x.list.Any() ? Math.Round((double)(x.list.Sum() / x.list.Count()), 2) : 0,
+                    persent = x.list.Any() ? Math.Round((double)(x.list.Sum() / x.list.Count()) / key7.Value, 4) : 0,
                     max = x.list.Any() ? x.list.Max(s => Math.Abs((double)s)) : 0,
                     min = x.list.Any() ? x.list.Min(s => Math.Abs((double)s)) : 0,
-                    excellent = x.list.Any() ? Math.Round(x.list.Where(s => s >= 80).Count() * 1.0 / x.list.Count(), 2) : 0,
-                    pass = x.list.Any() ? Math.Round(x.list.Where(s => s >= 60).Count() * 1.0 / x.list.Count(), 2) : 0
+                    excellent = x.list.Any() ? Math.Round(x.list.Where(s => s >= 80).Count() * 1.0 / x.list.Count(), 4) : 0,
+                    pass = x.list.Any() ? Math.Round(x.list.Where(s => s >= 60).Count() * 1.0 / x.list.Count(), 4) : 0
                 });
                 //获奖次数
                 List<ArtAttachment> artAttachments = new();
@@ -405,14 +418,19 @@ namespace TEAMModelOS.Controllers.Analysis
                      url = _azureStorage.GetBlobContainerClient($"{code}").GetBlobClient($"/art/{id}/{subjectId}.json").Uri.ToString();
                  }*/
                 var realCount = stus.Count - info.lostStu.Count;
-                /*if (classIds.Count == 1)
+                if (classIds.Count == 1)
                 {
-                    return Ok(new { count = tchList.Count, scount = realCount, max, min, average, excellent, pass, pow, blk, kno, dim, optCount, students, cInfo , gscore });
+                    var realClassCount = classResults.Where(c => c.info.id.Equals(classIds[0])).SelectMany(z => z.status).Count(k => k == 0);
+                    return Ok(new { count = tchList.Count, scount = realClassCount, max, min, average, excellent, pass, pow, blk, kno, dim, optCount, students, cInfo, gscore });
+                }
+                else if (classIds.Count == 0)
+                {
+                    return Ok(new { count = tchList.Count, scount = realCount, max, min, average, excellent, pass, pow, blk, kno, dim, optCount });
                 }
                 else {
-                    return Ok(new { count = tchList.Count, scount = realCount, max, min, average, excellent, pass, pow, blk, kno, dim, optCount, cInfo  });
-                }*/
-                return Ok(new { count = tchList.Count, scount = realCount, max, min, average, excellent, pass, pow, blk, kno, dim, optCount, students, cInfo, gscore });
+                    return Ok(new { count = tchList.Count, scount = realCount, max, min, average, excellent, pass, pow, blk, kno, dim, optCount,cInfo });
+                }
+                //return Ok(new { count = tchList.Count, scount = realCount, max, min, average, excellent, pass, pow, blk, kno, dim, optCount, gscore });
             }
             catch (Exception e)
             {
@@ -472,7 +490,7 @@ namespace TEAMModelOS.Controllers.Analysis
         }
         //获取知识点得分率
         private static (KeyValuePair<string, List<string>>, KeyValuePair<string, List<string>>, KeyValuePair<string, List<(string name, double score, double average)>>,
-            KeyValuePair<string, List<(string name, double score)>>, KeyValuePair<string, List<(string name, double score, double point, string cId)>>, KeyValuePair<string, List<(string name, double score, double point, string sId)>>) DoKnowledgePoint(ExamResult exam, ExamInfo info)
+            KeyValuePair<string, List<(string name, double score)>>, KeyValuePair<string, List<(string name, double score, double point, string cId)>>, KeyValuePair<string, List<(string name, double score, double point, string sId)>>, KeyValuePair<string, double> key7) DoKnowledgePoint(ExamResult exam, ExamInfo info)
         {
 
             HashSet<string> knowledge = new HashSet<string>();
@@ -507,7 +525,7 @@ namespace TEAMModelOS.Controllers.Analysis
             }
             else
             {
-                return (default, default, default, default, default, default);
+                return (default, default, default, default, default, default,default);
             }
             point = info.papers[index].point;
             result = exam.studentScores;
@@ -554,7 +572,8 @@ namespace TEAMModelOS.Controllers.Analysis
                         var itemPersent = kno.Count > 0 ? 1 / Convert.ToDouble(kno.Count) : 0;
                         OnePoint += point[n] * itemPersent;
                         foreach (string id in exam.studentIds) {
-                            scores += exam.studentScores[index][n] * itemPersent;
+                            int stuIndex = exam.studentIds.IndexOf(id);
+                            scores += exam.studentScores[stuIndex][n] * itemPersent;
                         }
                     }
                     n++;
@@ -620,8 +639,9 @@ namespace TEAMModelOS.Controllers.Analysis
             KeyValuePair<string, List<(string name, double score)>> key4 = new(exam.subjectId, pointTScore);
             KeyValuePair<string, List<(string name, double score, double point, string cId)>> key5 = new(exam.subjectId, classInfo);
             KeyValuePair<string, List<(string name, double score, double point, string sId)>> key6 = new(exam.subjectId, stuInfo);
+            KeyValuePair<string, double> key7 = new(exam.subjectId, TotalPoint);
             //KeyValuePair<string, List<double>> key3 = new KeyValuePair<string, List<double>>(exam.subjectId, allPer);          
-            return (key1, key2, key3, key4, key5, key6);
+            return (key1, key2, key3, key4, key5, key6,key7);
         }
         private KeyValuePair<string, List<(string id, double sta, double pass, string stu)>> DoSubjectScatter(ExamResult e)
         {

+ 42 - 15
TEAMModelOS/Controllers/Both/KnowledgeController.cs

@@ -46,7 +46,7 @@ namespace TEAMModelOS.Controllers.Both
 #if !DEBUG
         [Authorize(Roles = "IES")]
 #endif
-        [AuthToken(Roles = "admin", Permissions = "knowledge-upd")]
+        [AuthToken(Roles = "admin,teacher", Permissions = "knowledge-upd")]
         public IActionResult ItemImport([FromForm] IFormFile file)
         {
             var tokenAuth = HttpContext.GetAuthTokenInfo();
@@ -142,8 +142,9 @@ namespace TEAMModelOS.Controllers.Both
 #if !DEBUG
 
         [Authorize(Roles = "IES")]
-       [AuthToken(Roles = "teacher,admin,student", Permissions = "knowledge-read,knowledge-upd")]
-#endif 
+#endif
+        [AuthToken(Roles = "teacher,admin,student", Permissions = "knowledge-read,knowledge-upd")]
+
         public async Task<IActionResult> ReadKnowledge(JsonElement json)
         {
             var client = _azureCosmos.GetCosmosClient();
@@ -151,7 +152,7 @@ namespace TEAMModelOS.Controllers.Both
             string scope = "school";
             if (json.TryGetProperty("scope", out JsonElement _scope)) 
             {
-                scope=$"{scope}";
+                scope=$"{_scope}";
             }
             List<Knowledge> knowledges = new List<Knowledge>();
             string code = string.Empty;
@@ -192,7 +193,8 @@ namespace TEAMModelOS.Controllers.Both
             }
             else {
                 code=$"Knowledge-{tokenAuth.id}";
-                StringBuilder sql = new StringBuilder($"select value(c) from c");
+                json.TryGetProperty("id", out JsonElement _kid);
+                StringBuilder sql = new StringBuilder($"select value(c) from c where c.id ='{_kid}'");
                 await foreach (var item in client.GetContainer(Constant.TEAMModelOS,Constant.Teacher).GetItemQueryIteratorSql<Knowledge>(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{code}") }))
                 {
                     item.blocks.ForEach(x =>
@@ -208,14 +210,36 @@ namespace TEAMModelOS.Controllers.Both
             return Ok(new { knowledgeTrees = KnowledgeService. KnowledgeTranslate(knowledges) });
         }
 
-      
+
+
+        [ProducesDefaultResponseType]
+        [HttpPost("delete")]
+#if !DEBUG
+
+        [Authorize(Roles = "IES")]
+        #endif
+       [AuthToken(Roles = "teacher,admin,student", Permissions = "knowledge-read,knowledge-upd")]
+
+        public async Task<IActionResult> DeleteKnowledge(JsonElement json) 
+        {
+            try {
+                var id = json.GetProperty("id").GetString();
+                var owner = json.GetProperty("owner").GetString();
+                var scope = json.GetProperty("scope").GetString();
+                string key = $"KnowledgeNew:Count:{owner}";
+                await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).DeleteItemStreamAsync(id, new PartitionKey($"Knowledge-{owner}"));
+                await _azureRedis.GetRedisClient(8).HashDeleteAsync(key, id);
+                return Ok(new { code =200});
+            } catch (Exception ex) { return Ok(new { code = 400 }); }
+        }
         [ProducesDefaultResponseType]
         [HttpPost("modify")]
 #if !DEBUG
 
         [Authorize(Roles = "IES")]
-        [AuthToken(Roles = "admin", Permissions = "knowledge-upd")]
-#endif 
+#endif
+        [AuthToken(Roles = "admin,teacher", Permissions = "knowledge-upd")]
+
         public async Task<IActionResult> ModifyKnowledgeNode(JsonElement json)
         {
             List<TagOldNew> old_new = null;
@@ -298,8 +322,9 @@ namespace TEAMModelOS.Controllers.Both
 #if !DEBUG
 
         [Authorize(Roles = "IES")]
-        [AuthToken(Roles = "admin", Permissions = "knowledge-upd")]
 #endif
+        [AuthToken(Roles = "admin,teacher", Permissions = "knowledge-upd")]
+
         public async Task<IActionResult> ClearOld(JsonElement json) 
         {
 
@@ -327,8 +352,9 @@ namespace TEAMModelOS.Controllers.Both
 #if !DEBUG
 
         [Authorize(Roles = "IES")]
-        [AuthToken(Roles = "admin", Permissions = "knowledge-upd")]
-#endif 
+#endif
+        [AuthToken(Roles = "admin,teacher", Permissions = "knowledge-upd")]
+
         public async Task<IActionResult> UpsertKnowledge(JsonElement json) 
         {
             KnowledgeTreeDto knowledge = json.GetProperty("knowledgeTree").ToObject<KnowledgeTreeDto>();
@@ -564,16 +590,16 @@ namespace TEAMModelOS.Controllers.Both
                     sql= $" select value c from c ";
                 }
                 var result =  await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<Knowledge>(sql, $"Knowledge-{userid}");
-                if (result.list.IsNotEmpty()) 
+                if (result.list.IsNotEmpty())
                 {
-                    foreach (var item in result.list) 
+                    foreach (var item in result.list)
                     {
-                       var dto= KnowledgeService.KnowledgeTranslate(item);
+                        var dto = KnowledgeService.KnowledgeTranslate(item);
 
                         //处理知识点,知识块计数问题
                         string key = $"KnowledgeNew:Count:{item.owner}";
                         string filed = item.id;
-                        var count = new { pcount = dto. points.Count(), bcount = dto.blocks.Count(), name = item.name };
+                        var count = new { pcount = dto.points.Count(), bcount = dto.blocks.Count(), name = item.name };
                         await _azureRedis.GetRedisClient(8).HashSetAsync(key, filed, count.ToJsonString());
                         KnowledgeCount record = new KnowledgeCount
                         {
@@ -587,6 +613,7 @@ namespace TEAMModelOS.Controllers.Both
                         datas.Add(record);
                     }
                 }
+               
             }
             return Ok(new { datas });
         }

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

@@ -4503,11 +4503,7 @@ namespace TEAMModelOS.Controllers.Client
         {
             public List<List<List<string>>> studentAnswersArray { get; set; }
         }
-        //ExamClassResult 學生作答紀錄
-        public class ExamClassResultStudentAnswerArray : ExamClassResult
-        {
-            public List<List<string>> studentAnswersArray { get; set; }
-        }
+       
         //get-teacher-info API輸出 schools
         private class GetTeacherInfoApiSchool
         {

+ 30 - 19
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -379,9 +379,9 @@ namespace TEAMModelOS.Controllers
         }
 
         [ProducesDefaultResponseType]
-        [AuthToken(Roles = "teacher,admin")]
+        //[AuthToken(Roles = "teacher,admin")]
         [HttpPost("get-exam-point")]
-        [Authorize(Roles = "IES")]
+        //[Authorize(Roles = "IES")]
         public async Task<IActionResult> getExamPoint(JsonElement request)
         {
             try
@@ -397,7 +397,9 @@ namespace TEAMModelOS.Controllers
                 List<string> clds = classId.ToObject<List<string>>().ToList();
                 List<string> clas = classId.ToObject<List<string>>().ToList();
                 List<ExamSubject> subs = [];
+                List<PaperSimple> paperSimples = [];
                 List<(string id,string type)> source = [];
+                List<(string id, double score)> averages = [];
                 //获取学校基本信息
                 School schoolBase = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<School>(code.GetString(), new PartitionKey("Base"));
                 var period = schoolBase.period.Find(x => x.id.Equals($"{periodId}"));
@@ -437,7 +439,7 @@ namespace TEAMModelOS.Controllers
                 string classInfos = string.Join(" or ", strs);
 
                 (List<RMember> members, List<RGroupList> groups) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, clds, $"{code}");
-                    var queryExam = $"select c.id,c.subjects,c.source from c where c.period.id = '{periodId}' and ({classInfos}) " +
+                    var queryExam = $"select c.id,c.subjects,c.source,c.average,c.papers from c where c.period.id = '{periodId}' and ({classInfos}) " +
                     $"and c.qamode <> 2 and c.progress = 'finish' and c.scope = 'school' and c.startTime > {semesterData.date.ToUnixTimeMilliseconds()} and c.startTime < {semesterData.nextSemester.ToUnixTimeMilliseconds()} ";
                     await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIteratorSql(queryText: queryExam, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Exam-{code}") }))
                     {
@@ -453,13 +455,21 @@ namespace TEAMModelOS.Controllers
                                 {
                                     subs.AddRange(subject.ToObject<List<ExamSubject>>());
                                 }
+                                if (account.TryGetProperty("papers", out JsonElement papers))
+                                {
+                                    paperSimples.AddRange(papers.ToObject<List<PaperSimple>>());
+                                }
                                 source.Add((account.GetProperty("id").GetString(), account.GetProperty("source").GetString()));
+                                averages.Add((account.GetProperty("id").GetString(), account.GetProperty("average").GetDouble()));
                             }
                         }
                     }
                     subs = subs.Where((x, i) => subs.FindIndex(z => z.id == x.id) == i).ToList();
                 List<ExamClassResult> classResults = new();
                 List<ExamClassResult> results = new();
+
+                var point = paperSimples.FirstOrDefault().point.Sum();
+                var gradeExamAverageScore = Math.Round(averages.Select(c => c.score).Sum() / averages.Count,2);
                 if (clas.Count == 0)
                 {
                     var queryClass = $"select value(c) from c where  c.examId in ({string.Join(",", ids.Select(o => $"'{o}'"))})";
@@ -494,10 +504,11 @@ namespace TEAMModelOS.Controllers
                         x.Key,
                         cc = x.ToList().Count,
                         students = x.ToList().SelectMany(z => z.studentIds).Distinct().ToList(),
+                        classRate = Math.Round(x.ToList().Select(z => z.average).Sum() / x.ToList().Count,2)
                         //score = x.ToList().Select(k => k.sum).Aggregate((current, next) => current.Zip(next, (a, b) => a + b).ToList()),
                         //gRate = Math.Round( x.ToList().SelectMany(k => k.sum).Sum() / x.ToList().Count / members.Count, 2)
                     });
-                List<(string sub, List<(string id, double score)> subScore,int cc)> stuScore = [];
+                List<(string sub, List<(string id, double score)> subScore,int cc,double average)> stuScore = [];
                 foreach (var es in examScore) {
                     List<(string id, double score)> subScore = [];
                     foreach (var sId in es.students) {
@@ -505,7 +516,7 @@ namespace TEAMModelOS.Controllers
                         var totalScore = classResults.Where(c => c.subjectId.Equals(es.Key) && c.studentIds.Contains(sId)).ToList().Select(z => z.sum[z.studentIds.IndexOf(sId)]).Sum();
                         subScore.Add((sId, Math.Round(totalScore / count,2)));
                     }
-                    stuScore.Add((es.Key, subScore,es.cc));
+                    stuScore.Add((es.Key, subScore,es.cc,es.classRate));
                 }
 
 
@@ -517,17 +528,17 @@ namespace TEAMModelOS.Controllers
                     classRate = x.ToList().GroupBy(z => z.info.id).Select(m => new { 
                         classId = m.Key,
                         className = groups.Where(k => k.id.Equals(m.Key)).FirstOrDefault().name,
-                        rate = Math.Round(m.ToList().SelectMany(j => j.sum).Sum() / m.ToList().Count /m.ToList().SelectMany(z => z.studentIds).Distinct().ToList().Count,2)
-                    }),
+                        rate = Math.Round(m.ToList().Select(k => k.average).Sum() / m.ToList().Count / point,4)
+                    })
                     //score = x.ToList().Select(k => k.sum).Aggregate((current, next) => current.Zip(next, (a, b) => a + b).ToList()),
-                    gRate = Math.Round(x.ToList().SelectMany(k => k.sum).Sum() / x.ToList().Count / members.Count, 2)
+                    //gRate = Math.Round(x.ToList().SelectMany(k => k.sum).Sum() / x.ToList().Count / members.Count, 2)
                 }) ;
                 var gradeRate = examAllScore.Select(x => new
                 {
                     subjectId = x.Key,
                     subjectName = subs.Where(z => z.id.Equals(x.Key)).FirstOrDefault().name,
-                    x.classRate,
-                    x.gRate
+                    x.classRate
+                    //x.gRate
                 });
 
                 if (request.TryGetProperty("studentId", out JsonElement studentId) && !string.IsNullOrWhiteSpace($"{studentId}"))
@@ -536,22 +547,22 @@ namespace TEAMModelOS.Controllers
                     {
                         subjectId = x.sub,
                         subjectName = subs.Where(z => z.id.Equals(x.sub)).FirstOrDefault().name,
-                        classRate = Math.Round(x.subScore.Select(z => z.score).Sum() / x.cc / x.subScore.Count, 2),
+                        classRate = Math.Round(x.average / point, 4),
                         rank = x.subScore.OrderByDescending(x => x.score).Select(z => z.id).ToList().IndexOf(studentId.GetString()) + 1,
                         stuRate = x.subScore.OrderByDescending(x => x.score).Select(z => new
                         {
                             z.id,
                             members.Where(s => s.id.Equals(z.id)).FirstOrDefault().name,
-                            rate = Math.Round(z.score, 2)
+                            rate = Math.Round(z.score / point, 4)
                         }).ToList().Take(10),
                         sper = x.subScore.Where(c => c.id.Equals(studentId.GetString())).Select(z => new
                         {
                             z.id,
                             members.Where(s => s.id.Equals(z.id)).FirstOrDefault().name,
-                            rate = Math.Round(z.score, 2)
+                            rate = Math.Round(z.score / point, 4)
                         })
                     });
-                    return Ok(new { orderScore, typeCount, gradeRate, pCount = members.Count, code = 200 });
+                    return Ok(new { orderScore, typeCount, gradeRate, pCount = members.Count, gradeExamAverageScore, point, code = 200 });
                 }
                 else
                 {
@@ -559,14 +570,14 @@ namespace TEAMModelOS.Controllers
                     {
                         subjectId = x.sub,
                         subjectName = subs.Where(z => z.id.Equals(x.sub)).FirstOrDefault().name,
-                        classRate = Math.Round(x.subScore.Select(z => z.score).Sum() / x.cc / x.subScore.Count,2),
+                        classRate = Math.Round(x.average / point,4),
                         stuRate = x.subScore.OrderByDescending(x => x.score).Select(z => new
                         {
                             z.id,
-                            rate = Math.Round(z.score , 2)
+                            rate = Math.Round(z.score / point, 4)
                         }).ToList().Take(10)
                     });
-                    return Ok(new { orderScore, typeCount, gradeRate, pCount = members.Count, code = 200 });
+                    return Ok(new { orderScore, typeCount, gradeRate, pCount = members.Count, gradeExamAverageScore, point, code = 200 });
                 }
 
             }
@@ -1960,7 +1971,7 @@ namespace TEAMModelOS.Controllers
                             });
                             await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(exam, id.ToString(), new PartitionKey($"Exam-{code}"));
                             //若為統測評量,紀錄修改分數內容
-                            if (string.IsNullOrWhiteSpace(exam.jointExamId)) await upsertExamClassResultMark(result, userid, username, sIds, ans);
+                            if (!string.IsNullOrWhiteSpace(exam.jointExamId)) await upsertExamClassResultMark(result, userid, username, sIds, ans);
                         }
                     }
                     else
@@ -1969,7 +1980,7 @@ namespace TEAMModelOS.Controllers
                         exam.updateTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                         await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(exam, id.ToString(), new PartitionKey($"Exam-{code}"));
                         //若為統測評量,紀錄修改分數內容
-                        if (string.IsNullOrWhiteSpace(exam.jointExamId)) await upsertExamClassResultMark(result, userid, username, sIds, ans);
+                        if (!string.IsNullOrWhiteSpace(exam.jointExamId)) await upsertExamClassResultMark(result, userid, username, sIds, ans);
                     }
                     classResult = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(result, result.id, new PartitionKey($"{result.code}"));
                     // await Task.WhenAll(tasks);

+ 32 - 7
TEAMModelOS/Controllers/School/KnowledgesController.cs

@@ -21,6 +21,9 @@ using Microsoft.AspNetCore.Authorization;
 using TEAMModelOS.SDK;
 using static TEAMModelOS.SDK.ValidatorHelper;
 using System.ComponentModel.DataAnnotations;
+using static SKIT.FlurlHttpClient.Wechat.TenpayV3.Models.CreateApplyForSubjectApplymentRequest.Types;
+using TEAMModelOS.SDK.Models.Service;
+using DocumentFormat.OpenXml.Drawing.Charts;
 
 namespace TEAMModelOS.Controllers
 {
@@ -326,18 +329,40 @@ namespace TEAMModelOS.Controllers
         public async Task<IActionResult> Find(JsonElement request)
         {
             var client = _azureCosmos.GetCosmosClient();
-            request.TryGetProperty("periodId", out JsonElement periodId);
-            if (!request.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
-            if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
-            string code = $"Knowledge-{school_code}-{subjectId}";
+            request.TryGetProperty("scope", out JsonElement _scope);
+           
+           
+            string scope = "school";
+            if (_scope.ValueKind.Equals(JsonValueKind.String)) 
+            {
+                scope =$"{_scope}";
+            }
             StringBuilder sql = new StringBuilder($"select value(c) from c");
-            if (periodId.ValueKind.Equals(JsonValueKind.String))
+            string code = "";
+            if (scope.Equals("school"))
             {
-                sql.Append($" where c.periodId = '{periodId}'");
+                request.TryGetProperty("periodId", out JsonElement periodId);
+                if (!request.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
+                if (!request.TryGetProperty("school_code", out JsonElement school_code)) return BadRequest();
+                code = $"Knowledge-{school_code}-{subjectId}";
+                if (periodId.ValueKind.Equals(JsonValueKind.String))
+                {
+                    sql.Append($" where c.periodId = '{periodId}'");
+                }
             }
+            else {
+                if (!request.TryGetProperty("owner", out JsonElement owner)) return BadRequest();
+                request.TryGetProperty("kid", out JsonElement kid);
+                code = $"Knowledge-{owner}";
+                if (kid.ValueKind.Equals(JsonValueKind.String))
+                {
+                    sql.Append($" where c.id = '{kid}'");
+                }
+            }
+           
             List<Knowledge> knowledges = new List<Knowledge>();
             List<string> keywords = new List<string>();
-            await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIteratorSql<Knowledge>(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{code}") }))
+            await foreach (var item in client.GetContainer(Constant.TEAMModelOS, scope.Equals("school")?Constant.School:Constant.Teacher).GetItemQueryIteratorSql<Knowledge>(queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{code}") }))
             {
                 item.blocks.ForEach(x =>
                 {

+ 37 - 23
TEAMModelOS/Controllers/Student/OverallEducationController.cs

@@ -541,7 +541,11 @@ namespace TEAMModelOS.Controllers
                         
                         classDimensionScore.intelligenceLevels= classData.list.GroupBy(x => x.level).Select(x => new CodeLong { code = x.Key, value = x.Count() }).ToList();
 
-                       
+                        classDimensionScore.avg_intelligence =Math.Round(classData.list.Average(x => x.intelligence), 2);
+                        classDimensionScore.avg_virtue = Math.Round(classData.list.Average(x => x.virtue), 2);
+                        classDimensionScore.avg_labour = Math.Round(classData.list.Average(x => x.labour), 2);
+                        classDimensionScore.avg_sports = Math.Round(classData.list.Average(x => x.sports), 2);
+                        classDimensionScore.avg_art = Math.Round(classData.list.Average(x => x.art), 2);
                     }
                     else {
                         classDimensionScore = new ClassDimensionScore() { classId = c.id };
@@ -678,19 +682,19 @@ namespace TEAMModelOS.Controllers
                         var student =  students.Find(x => x.id.Equals($"{_studentId}"));
                         ExamScore exam3Score = new ExamScore();
                         exam3Score.name = item.name;
-                        exam3Score.gradeScore=item.total;
+                        exam3Score.gradeRate=item.total;
                         exam3Score.xdatas= new List<CodeDouble> 
                         { 
                             new CodeDouble 
                             {
                                 code = student.id,
                                 name= student.name,
-                                value=item.students.Find(x=>x.studentId.Equals(student.id)).scores,
+                                value= item.students.Find(x=>x.studentId.Equals(student.id)).scores,
                                 rank = item.students.OrderByDescending(x => x.scores).Select(z => z.studentId).ToList().IndexOf(student.id) + 1,
                                 time = item.time
                             } 
                         };
-                        exam3Score.classScore=item.classMore.Find(x=>x.classId.Equals(student.classId)).average;
+                        exam3Score.classRate=item.classMore.Find(x=>x.classId.Equals(student.classId)).average;
                         exam3Scores.Add(exam3Score);
                     }
                     else
@@ -700,8 +704,8 @@ namespace TEAMModelOS.Controllers
                             Class clazz = classes.First();
                             ExamScore exam3Score = new ExamScore();
                             exam3Score.name = item.name;
-                            exam3Score.gradeScore=item.total;
-                            exam3Score.classScore=item.classMore.Find(x => x.classId.Equals(clazz.id)).average;
+                            exam3Score.gradeRate=item.total;
+                            exam3Score.classRate=item.classMore.Find(x => x.classId.Equals(clazz.id)).average;
                             exam3Score.xdatas= item.students.Where(y=>y.classId.Equals(clazz.id)).Select(x=>new CodeDouble { 
                                 code=x.studentId,
                                 name= students.Find(z=>z.id.Equals(x.studentId))?.name,
@@ -716,8 +720,8 @@ namespace TEAMModelOS.Controllers
                         {
                             ExamScore exam3Score = new ExamScore();
                             exam3Score.name = item.name;
-                            exam3Score.gradeScore=item.total;
-                            exam3Score.classScore=-1;
+                            exam3Score.gradeRate =item.total;
+                            exam3Score.classRate =-1;
                             exam3Score.xdatas= item.classMore.Select(x => new CodeDouble { 
                                 code=x.classId, 
                                 name= grade_classes.Find(z => z.id.Equals(x.classId))?.name, 
@@ -730,8 +734,8 @@ namespace TEAMModelOS.Controllers
                 }
                 var examScores = exam3Scores.Select(x => new { 
                     x.name,
-                    x.gradeScore,
-                    x.classScore,
+                    x.gradeRate,
+                    x.classRate,
                     data = x.xdatas.Where(c => !string.IsNullOrEmpty(c.name)).Select(z => new { 
                         z.code,
                         z.name,
@@ -748,7 +752,7 @@ namespace TEAMModelOS.Controllers
                     exam3Scores.ForEach(z => {
                         var stu =  z.xdatas.Find(z => z.code.Equals($"{_studentId}"));
                         if (stu!=null) {
-                            stuExamScores.Add(new ExamDimensionScore { examName=z.name, classScore=z.classScore,gradeScore=z.gradeScore,stuScore=stu.value });
+                            stuExamScores.Add(new ExamDimensionScore { examName=z.name, classRate=z.classRate,gradeRate=z.gradeRate,stuScore=stu.value });
                         }
                     });
                     var student = students.Find(z => z.id.Equals($"{_studentId}"));
@@ -809,7 +813,7 @@ namespace TEAMModelOS.Controllers
                             var stu = z.xdatas.Find(z => z.code.Equals(student.id));
                             if (stu!=null)
                             {
-                                stuExamScores.Add(new ExamDimensionScore { examName=z.name, classScore=z.classScore, gradeScore=z.gradeScore, stuScore=stu.value });
+                                stuExamScores.Add(new ExamDimensionScore { examName=z.name, classRate=z.classRate, gradeRate=z.gradeRate, stuScore=stu.value });
                             }
                         });
                         studentDimension=new StudentDimensionScore
@@ -846,7 +850,11 @@ namespace TEAMModelOS.Controllers
                 }
                 return Ok(new
                 {
-                   
+                    avg_intelligence= dimensions.IsNotEmpty()?Math.Round(dimensions.Average(x => x.intelligence),2):0,
+                    avg_virtue = dimensions.IsNotEmpty() ? Math.Round(dimensions.Average(x => x.virtue),2):0,
+                    avg_labour = dimensions.IsNotEmpty() ? Math.Round(dimensions.Average(x => x.labour), 2) : 0,
+                    avg_sports = dimensions.IsNotEmpty() ? Math.Round(dimensions.Average(x => x.sports), 2) : 0,
+                    avg_art = dimensions.IsNotEmpty() ? Math.Round(dimensions.Average(x => x.art), 2) : 0,
                     examScores,
                     sports_count60,
                     sports_count90,
@@ -865,9 +873,9 @@ namespace TEAMModelOS.Controllers
                     grade_tscore_avg,
                     grade_gscore_avg,
 
-                    grade_prate_avg= grade_pscore_avg,
-                    grade_trate_avg= grade_tscore_avg,
-                    grade_zrate_avg= grade_gscore_avg,
+                    grade_prate_avg = grade_pscore_avg,
+                    grade_trate_avg = grade_tscore_avg,
+                    grade_zrate_avg = grade_gscore_avg,
                     grade_xrate_avg = 60,
                     grade_crate_avg = 60,
 
@@ -878,10 +886,10 @@ namespace TEAMModelOS.Controllers
                     prate_avg = pscore_avg,
                     trate_avg = tscore_avg,
                     zrate_avg = gscore_avg,
-                    xrate_avg =60,
-                    crate_avg=60,
+                    xrate_avg = 60,
+                    crate_avg = 60,
                     classDimensions,
-                    dimensions=dimensions.OrderByDescending(x => x.score),
+                    dimensions = dimensions.OrderByDescending(x => x.score),
                     data_count,
                     classCount = classes.Count,
                     rate90,
@@ -914,8 +922,8 @@ namespace TEAMModelOS.Controllers
         }
         public class ExamScore { 
 
-            public double gradeScore { get; set; }
-            public double classScore { get; set; }
+            public double gradeRate { get; set; }
+            public double classRate { get; set; }
             public string name { get; set; }
             public List<CodeDouble> xdatas = new List<CodeDouble>();
         }
@@ -951,8 +959,8 @@ namespace TEAMModelOS.Controllers
         {
             public string examName {  get; set; }
             public double stuScore { get; set; }
-            public double classScore { get;set;  }
-            public double gradeScore { get; set;  }
+            public double classRate { get;set;  }
+            public double gradeRate { get; set;  }
         }
         public class ClassDimensionScore
         {
@@ -978,6 +986,12 @@ namespace TEAMModelOS.Controllers
             public string level { get; set; }
             // 等级人数
             public List<CodeLong> intelligenceLevels { get; set; } = new List<CodeLong>();
+
+            public double avg_intelligence { get; set; }
+            public double avg_virtue { get; set; }
+            public double avg_labour { get; set; }
+            public double avg_sports { get; set; }
+            public double avg_art { get; set; }
         }
         public class StudentDimensionScore
         {

+ 2 - 2
TEAMModelOS/Filter/RequestAuditFilter.cs

@@ -222,12 +222,12 @@ namespace TEAMModelOS.Filter
                 var httpclient=    _httpClient.CreateClient();
                 httpclient.Timeout=  TimeSpan.FromSeconds(10); 
 //#if DEBUG
-//                var  response = await httpclient.PostAsJsonAsync("http://cdhabook.teammodel.cn:8806/api/http-log", data);
+//                var  response = await httpclient.PostAsJsonAsync("http://52.130.252.100:8806/api/http-log", data);
 
 //#else
           
 //#endif
-                _=  httpclient.PostAsJsonAsync("http://cdhabook.teammodel.cn:8806/api/http-log", data);
+                _=  httpclient.PostAsJsonAsync("http://52.130.252.100:8806/api/http-log", data);
             }
             catch (Exception ex)
             {

+ 113 - 0
TEAMModelOS/Properties/ServiceDependencies/teammodelos-test - Web Deploy/profile.arm.json

@@ -0,0 +1,113 @@
+{
+  "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
+  "contentVersion": "1.0.0.0",
+  "metadata": {
+    "_dependencyType": "compute.appService.windows"
+  },
+  "parameters": {
+    "resourceGroupName": {
+      "type": "string",
+      "defaultValue": "TEAMModelChengdu",
+      "metadata": {
+        "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
+      }
+    },
+    "resourceGroupLocation": {
+      "type": "string",
+      "defaultValue": "",
+      "metadata": {
+        "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support."
+      }
+    },
+    "resourceName": {
+      "type": "string",
+      "defaultValue": "test",
+      "metadata": {
+        "description": "Name of the main resource to be created by this template."
+      }
+    },
+    "resourceLocation": {
+      "type": "string",
+      "defaultValue": "[parameters('resourceGroupLocation')]",
+      "metadata": {
+        "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
+      }
+    }
+  },
+  "variables": {
+    "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
+    "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]"
+  },
+  "resources": [
+    {
+      "type": "Microsoft.Resources/resourceGroups",
+      "name": "[parameters('resourceGroupName')]",
+      "location": "[parameters('resourceGroupLocation')]",
+      "apiVersion": "2019-10-01"
+    },
+    {
+      "type": "Microsoft.Resources/deployments",
+      "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
+      "resourceGroup": "[parameters('resourceGroupName')]",
+      "apiVersion": "2019-10-01",
+      "dependsOn": [
+        "[parameters('resourceGroupName')]"
+      ],
+      "properties": {
+        "mode": "Incremental",
+        "template": {
+          "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+          "contentVersion": "1.0.0.0",
+          "resources": [
+            {
+              "location": "[parameters('resourceLocation')]",
+              "name": "[parameters('resourceName')]",
+              "type": "Microsoft.Web/sites",
+              "apiVersion": "2015-08-01",
+              "tags": {
+                "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty"
+              },
+              "dependsOn": [
+                "[variables('appServicePlan_ResourceId')]"
+              ],
+              "kind": "app",
+              "properties": {
+                "name": "[parameters('resourceName')]",
+                "kind": "app",
+                "httpsOnly": true,
+                "reserved": false,
+                "serverFarmId": "[variables('appServicePlan_ResourceId')]",
+                "siteConfig": {
+                  "metadata": [
+                    {
+                      "name": "CURRENT_STACK",
+                      "value": "dotnetcore"
+                    }
+                  ]
+                }
+              },
+              "identity": {
+                "type": "SystemAssigned"
+              }
+            },
+            {
+              "location": "[parameters('resourceLocation')]",
+              "name": "[variables('appServicePlan_name')]",
+              "type": "Microsoft.Web/serverFarms",
+              "apiVersion": "2015-08-01",
+              "sku": {
+                "name": "S1",
+                "tier": "Standard",
+                "family": "S",
+                "size": "S1"
+              },
+              "properties": {
+                "name": "[variables('appServicePlan_name')]"
+              }
+            }
+          ]
+        }
+      }
+    }
+  ]
+}

+ 4 - 4
TEAMModelOS/TEAMModelOS.csproj

@@ -80,11 +80,11 @@
 		<SpaRoot>ClientApp\</SpaRoot>
 		<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
 		<UserSecretsId>078b5d89-7d90-4f6a-88fc-7d96025990a8</UserSecretsId>
-		<Version>5.2408.28</Version>
-		<AssemblyVersion>5.2408.28.1</AssemblyVersion>
-		<FileVersion>5.2408.28.1</FileVersion>
+		<Version>5.2409.4</Version>
+		<AssemblyVersion>5.2409.4.1</AssemblyVersion>
+		<FileVersion>5.2409.4.1</FileVersion>
 		<Description>TEAMModelOS(IES5)</Description>
-		<PackageReleaseNotes>IES版本说明版本切换标记5.2408.28.1</PackageReleaseNotes>
+		<PackageReleaseNotes>IES版本说明版本切换标记5.2409.4.1</PackageReleaseNotes>
 		<PackageId>TEAMModelOS</PackageId>
 		<Authors>teammodel</Authors>
 		<Company>醍摩豆(成都)信息技术有限公司</Company>

+ 2 - 2
TEAMModelOS/appsettings.Development.json

@@ -18,7 +18,7 @@
     "IdTokenSalt": "8263692E2213497BB55E74792B7900B4",
     "HttpTrigger": "https://teammodelosfunction-test.chinacloudsites.cn/api/",
     //"HttpTrigger": "http://localhost:7071/api/"
-    "Version": "5.2408.28.1"
+    "Version": "5.2409.4.1"
   },
   "Azure": {
     // 测试站数据库
@@ -29,7 +29,7 @@
       "ConnectionString": "AccountEndpoint=https://cdhabookdep-free.documents.azure.cn:443/;AccountKey=JTUVk92Gjsx17L0xqxn0X4wX2thDPMKiw4daeTyV1HzPb6JmBeHdtFY1MF1jdctW1ofgzqkDMFOtcqS46by31A==;"
     },
     "Redis": {
-      "ConnectionString": "52.130.252.100:6379,password=habook,ssl=false,abortConnect=False,writeBuffer=10240"
+      "ConnectionString": "163.228.141.122:6379,password=cdhabook,ssl=false,abortConnect=False,writeBuffer=10240"
     },
     "ServiceBus": {
       "ConnectionString": "Endpoint=sb://coreservicebuscn.servicebus.chinacloudapi.cn/;SharedAccessKeyName=TEAMModelOS;SharedAccessKey=xO8HcvXXuuEkuFI0KlV5uXs8o6vyuVqTR+ASbPGMhHo=",

+ 1 - 1
TEAMModelOS/appsettings.json

@@ -18,7 +18,7 @@
     "Exp": 86400,
     "IdTokenSalt": "8263692E2213497BB55E74792B7900B4",
     "HttpTrigger": "https://teammodelosfunction.chinacloudsites.cn/api/",
-    "Version": "5.2408.28.1"
+    "Version": "5.2409.4.1"
   },
   "Azure": {
     "Storage": {