Browse Source

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

OnePsycho 2 years ago
parent
commit
d2ca5a83cb
60 changed files with 886 additions and 1574 deletions
  1. 1 1
      TEAMModelBI/Controllers/BIAbility/AbilityMgmtController.cs
  2. 1 1
      TEAMModelBI/Controllers/BIAbility/AbilityTaskMgmtController.cs
  3. 45 58
      TEAMModelBI/Controllers/BIHome/AnalyseFileController.cs
  4. 1 1
      TEAMModelBI/Controllers/BIHome/HomeStatisController.cs
  5. 2 2
      TEAMModelBI/Controllers/BIHome/OnLineController.cs
  6. 1 1
      TEAMModelBI/Controllers/BINormal/AppCompanyController.cs
  7. 1 1
      TEAMModelBI/Controllers/BISchool/AreaRelevantController.cs
  8. 1 1
      TEAMModelBI/Controllers/BISchool/BatchAreaController.cs
  9. 1 1
      TEAMModelBI/Controllers/BISchool/BatchSchoolController.cs
  10. 1 1
      TEAMModelBI/Controllers/BISchool/LessonController.cs
  11. 1 1
      TEAMModelBI/Controllers/BISchool/ProductController.cs
  12. 1 1
      TEAMModelBI/Controllers/BISchool/RoomController.cs
  13. 4 4
      TEAMModelBI/Controllers/BISchool/SchoolController.cs
  14. 1 1
      TEAMModelBI/Controllers/BIStudent/StuActivityController.cs
  15. 1 1
      TEAMModelBI/Controllers/BIStudent/StudentController.cs
  16. 1 1
      TEAMModelBI/Controllers/BITable/BIOpenApiController.cs
  17. 1 1
      TEAMModelBI/Controllers/BITable/CompanyController.cs
  18. 1 1
      TEAMModelBI/Controllers/BITable/CompanyUserController.cs
  19. 1 1
      TEAMModelBI/Controllers/BITeacher/TeacherController.cs
  20. 13 64
      TEAMModelBI/Controllers/BITest/TestController.cs
  21. 42 40
      TEAMModelBI/Controllers/Census/ActivitySticsController.cs
  22. 1 1
      TEAMModelBI/Controllers/Census/BlobLogController.cs
  23. 1 1
      TEAMModelBI/Controllers/Census/ItemSticsController.cs
  24. 1 1
      TEAMModelBI/Controllers/Census/LessonSticsController.cs
  25. 1 1
      TEAMModelBI/Controllers/Census/PaperController.cs
  26. 1 1
      TEAMModelBI/Controllers/Census/ProductStatisController.cs
  27. 1 1
      TEAMModelBI/Controllers/Census/SchoolController.cs
  28. 1 1
      TEAMModelBI/Controllers/Census/StudyController.cs
  29. 1 1
      TEAMModelBI/Controllers/Core/BlobController.cs
  30. 1 1
      TEAMModelBI/Controllers/DingDingStruc/DDStructController.cs
  31. 1 1
      TEAMModelBI/Controllers/DingDingStruc/TableDingDingInfoController.cs
  32. 1 1
      TEAMModelBI/Controllers/LoginController.cs
  33. 1 1
      TEAMModelBI/Controllers/OperateRecord/OperateLogController.cs
  34. 1 1
      TEAMModelBI/Tool/Context/BIConst.cs
  35. 26 1
      TEAMModelOS.SDK/DI/AzureSignalR/AzureSignalRExtensions.cs
  36. 9 9
      TEAMModelOS.SDK/DI/AzureSignalR/AzureSignalRFactory.cs
  37. 35 1
      TEAMModelBI/Models/RecordM/RecAppGWInfo.cs
  38. 164 0
      TEAMModelOS.SDK/Models/Service/BI/BILogAnalyseService.cs
  39. 105 32
      TEAMModelOS.SDK/Models/Service/StudentService.cs
  40. 6 3
      TEAMModelOS/ClientApp/public/lang/en-US.js
  41. 6 3
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  42. 6 3
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  43. 1 1
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  44. 1 1
      TEAMModelOS/ClientApp/src/router/routes.js
  45. 25 16
      TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.less
  46. 194 116
      TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.vue
  47. 0 417
      TEAMModelOS/ClientApp/src/view/classrecord/ClassRecordNew.less
  48. 0 635
      TEAMModelOS/ClientApp/src/view/classrecord/ClassRecordNew.vue
  49. 2 1
      TEAMModelOS/ClientApp/src/view/classrecord/eventchart/DataCount.vue
  50. 10 2
      TEAMModelOS/ClientApp/src/view/classrecord/eventchart/Receive.vue
  51. 6 6
      TEAMModelOS/ClientApp/src/view/mgtPlatform/MgtPlatform.vue
  52. 33 52
      TEAMModelOS/ClientApp/src/view/mycourse/MyCourse.vue
  53. 48 46
      TEAMModelOS/ClientApp/src/view/mycourse/student/Student.vue
  54. 1 0
      TEAMModelOS/ClientApp/src/view/teachermgmt/components/mgt/TeacherMgt.vue
  55. 15 0
      TEAMModelOS/Controllers/OpenApi/Business/BizTeacherController.cs
  56. 31 29
      TEAMModelOS/Controllers/OpenApi/IRS/TianboController.cs
  57. 16 0
      TEAMModelOS/Controllers/OpenApi/OpenApiService.cs
  58. 1 1
      TEAMModelOS/Controllers/OpenApi/OpenSchool/ScGroupListController.cs
  59. 8 1
      TEAMModelOS/Controllers/Student/StudentController.cs
  60. 2 1
      TEAMModelOS/appsettings.Development.json

+ 1 - 1
TEAMModelBI/Controllers/BIAbility/AbilityMgmtController.cs

@@ -17,8 +17,8 @@ using TEAMModelOS.SDK.Models.Cosmos.BI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelBI.Filter;
 using TEAMModelBI.Tool.Extension;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.DI.BIAzureStorage;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.BIAbility
 {

+ 1 - 1
TEAMModelBI/Controllers/BIAbility/AbilityTaskMgmtController.cs

@@ -18,8 +18,8 @@ using TEAMModelBI.Filter;
 using TEAMModelOS.SDK.Services;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.SDK.Extension;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.DI.BIAzureStorage;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.BIAbility
 {

+ 45 - 58
TEAMModelBI/Controllers/BIHome/AnalyseFileController.cs

@@ -10,8 +10,7 @@ using System.Linq;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
-using TEAMModelBI.Models.RecordM;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models.Cosmos.BI;
@@ -114,85 +113,73 @@ namespace TEAMModelBI.Controllers.BIHome
         public async Task<IActionResult> GetOSLog(JsonElement jsonElement) 
         {
             jsonElement.TryGetProperty("site", out JsonElement site);
-            DateTimeOffset dateTime = DateTimeOffset.UtcNow;
-            int year = dateTime.Year;
-            int month = dateTime.Month;
-            int day = dateTime.Day;
-            int hour = dateTime.Hour;
 
             var blobClient = _azureStorage.GetBlobContainerClient($"insights-logs-applicationgatewayfirewalllog",name:BIConst.LogChina);
             if ($"{site}".Equals(BIConst.Global)) 
             {
-                blobClient = _azureStorage.GetBlobContainerClient($"insights-logs-applicationgatewayfirewalllog", name: BIConst.Global);
+                blobClient = _azureStorage.GetBlobContainerClient($"insights-logs-applicationgatewayfirewalllog", name: BIConst.LogGlobal);
             }
+            
+            //List<ReadContent> contents = new();
+            List<RecCnt> recCnts = new();
+            List<string> urls = new();
+
             //地址:    y={year}/m={month}/d={day}/h={hour}/m=00/PT1H.json
             string logName = "resourceId=/SUBSCRIPTIONS/73B7F9EF-D8B7-4444-9E8D-D80B43BF3CD4/RESOURCEGROUPS/TEAMMODELCHENGDU/PROVIDERS/MICROSOFT.NETWORK/APPLICATIONGATEWAYS/OSFIREWARE";
             await foreach (BlobItem blobItem in blobClient.GetBlobsAsync(BlobTraits.None, BlobStates.None, logName))
             {
-                BlobClient tempBlobClient = blobClient.GetBlobClient(blobItem.Name);
 
+                StringBuilder visits = new("[");
 
-                StreamReader streamReader = new StreamReader(new FileStream(blobItem.Name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.UTF8);
-                if (await blobClient.ExistsAsync())
+                BlobClient tempBlobClient = blobClient.GetBlobClient(blobItem.Name);
+                BlobDownloadInfo download = tempBlobClient.Download();
+                var content = download.Content;
+                string text;
+                using (var streamReader = new StreamReader(content))
                 {
-                }
-            }
-
-
-
-
-
-
+                    while ((text = streamReader.ReadLine()) != null)
+                    {
+                        if (streamReader.EndOfStream)
+                            visits.Append($"{text.ToString()}");
+                        else
+                            visits.Append($"{text.ToString()},");
+                    }
 
+                    visits.Append("]");
+                    streamReader.Close();
+                }
 
+                string input = visits.ToString();
+                List<AGInfo> aGInfos = input.ToObject<List<AGInfo>>();
+                DateTimeOffset dtime = DateTimeOffset.UtcNow;
+                string cHour = dtime.ToString("yyyyMMddHH");
+                string cDay = dtime.ToString("yyyyMMdd");
+                if (aGInfos.Count > 0)
+                {
+                    cHour = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMddHH")).First();
+                    cDay = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMdd")).First();
+                }
 
-            return Ok(new { state = 200 });
-        }
+                RecCnt saveCnts = new();
 
+                List<RecAppGWInfo> recInfo = aGInfos.Select(s => new RecAppGWInfo { hour = cHour, ip = s.properties.clientIp, api = s.properties.requestUri.Split("?").ToList().Count() > 1 ? s.properties.requestUri.Split("?").ToList()[0] : s.properties.requestUri, hostName = s.properties.hostname }).ToList();
 
+                List<RecApiCnt> apiCnt = recInfo.GroupBy(a => a.api).Select(g => new RecApiCnt { api = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), ip = g.Select(i => i.ip).Distinct().ToList() }).ToList();
+                saveCnts.apiCnt = apiCnt;
 
-        public record StatisNameCnt 
-        {
-            public string name { get; set; }
-            public int cnt { get; set; }
-        }
+                List<RecIpCnt> ipCnt = recInfo.GroupBy(a => a.ip).Select(g => new RecIpCnt { ip = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), api = g.Select(i => i.api).Distinct().ToList() }).ToList();
+                saveCnts.ipCnt = ipCnt;
+                recCnts.Add(saveCnts);
 
-        public record AGInfo 
-        {
-            //public string resourceId { get; set; }
+                ////保存存至Blob文件
+                var url = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(saveCnts.ToJsonString(), $"visitCnt/{cDay}", $"{cHour}.json");
 
-            public string operationName { get; set; }
-            public string time { get; set; }
-            public string category { get; set; }
-            public Properties properties { get; set; }
-        }
+                urls.Add(url);
+            }
 
-        public record Properties 
-        {
-            //public string instanceId { get; set; }
-            public string clientIp { get; set; }
-            public string clientPort { get; set; }
-            public string requestUri { get; set; }
-            public string ruleSetType { get; set; }
-            public string ruleSetVersion { get; set; }
-            public string ruleId { get; set;}
-            public string ruleGroup { get; set; }
-            //public string message { get; set; }
-            public string action { get; set; }
-            public string site { get; set; }
-            //public Datails datails { get; set; }
-            public string hostname { get; set; }
-            public string transactionId { get; set; }
+            return Ok(new { state = 200, recCnts, urls });
         }
 
-        public record Datails 
-        {
-            public string message { get; set; }
-            public string data { get; set; }
-            public string file { get; set; }
-            public string line { get; set; }
-
-        }
 
     }
 }

+ 1 - 1
TEAMModelBI/Controllers/BIHome/HomeStatisController.cs

@@ -14,7 +14,7 @@ using System.Text;
 using StackExchange.Redis;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.BIHome
 {

+ 2 - 2
TEAMModelBI/Controllers/BIHome/OnLineController.cs

@@ -11,12 +11,12 @@ using System.Linq;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
-using TEAMModelBI.Models.RecordM;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;
+using TEAMModelOS.SDK.Models.Cosmos.BI;
 using TEAMModelOS.SDK.Models.Table;
 
 namespace TEAMModelBI.Controllers.BIHome

+ 1 - 1
TEAMModelBI/Controllers/BINormal/AppCompanyController.cs

@@ -12,9 +12,9 @@ using System.Threading.Tasks;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelBI.Filter;
 using TEAMModelBI.Models.Extension;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models.Cosmos.BI;

+ 1 - 1
TEAMModelBI/Controllers/BISchool/AreaRelevantController.cs

@@ -11,10 +11,10 @@ using System.Threading.Tasks;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelBI.Filter;
 using TEAMModelBI.Models;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.Models;
 using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.Context.Constant;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;

+ 1 - 1
TEAMModelBI/Controllers/BISchool/BatchAreaController.cs

@@ -20,7 +20,6 @@ using TEAMModelOS.SDK.Models.Service;
 using TEAMModelBI.Filter;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.DI.BIAzureStorage;
 using Azure.Storage.Blobs.Models;
 using Azure;
@@ -29,6 +28,7 @@ using System.Net.Http;
 using System.Net.Http.Json;
 using System.Net;
 using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.BISchool
 {

+ 1 - 1
TEAMModelBI/Controllers/BISchool/BatchSchoolController.cs

@@ -26,9 +26,9 @@ using TEAMModelBI.Filter;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelBI.Tool;
 using TEAMModelBI.Models;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.BISchool
 {

+ 1 - 1
TEAMModelBI/Controllers/BISchool/LessonController.cs

@@ -10,7 +10,7 @@ using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;

+ 1 - 1
TEAMModelBI/Controllers/BISchool/ProductController.cs

@@ -6,8 +6,8 @@ using Microsoft.Extensions.Options;
 using System.Collections.Generic;
 using System.Text.Json;
 using System.Threading.Tasks;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;

+ 1 - 1
TEAMModelBI/Controllers/BISchool/RoomController.cs

@@ -9,9 +9,9 @@ using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelBI.Filter;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;

+ 4 - 4
TEAMModelBI/Controllers/BISchool/SchoolController.cs

@@ -18,11 +18,11 @@ using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelBI.Filter;
 using TEAMModelBI.Models;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.Tool.CosmosBank;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.Models;
 using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.Context.Constant;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
@@ -1662,7 +1662,7 @@ namespace TEAMModelBI.Controllers.BISchool
             int lessAll = 0;  //所以课例
             int lessLastdayCnt = 0;  //昨天的课例
             int lessDayCnt = 0;    //今天的课例
-            int lessLastYarCnt = 0; //去年的课例
+            int lessLastYearCnt = 0; //去年的课例
             int lessYearCnt = 0; //去年的课例
 
             int interAll = 0;    //所有互动总数
@@ -1692,7 +1692,7 @@ namespace TEAMModelBI.Controllers.BISchool
             lessDayCnt = await CommonFind.GetSqlValueCount(cosmosClient, "School", lessDaySql);
 
             string lessLastYarSql = $"{strSql} and {scSql} and c.startTime >= { lastYears} and c.startTime <= {lastYeare} ";
-            lessLastYarCnt = await CommonFind.GetSqlValueCount(cosmosClient, "School", lessLastYarSql);
+            lessLastYearCnt = await CommonFind.GetSqlValueCount(cosmosClient, "School", lessLastYarSql);
 
             string lessYearSql = $"{strSql} and {scSql} and c.startTime >= { years} and c.startTime <= {yeare} ";
             lessYearCnt = await CommonFind.GetSqlValueCount(cosmosClient, "School", lessYearSql);
@@ -1729,7 +1729,7 @@ namespace TEAMModelBI.Controllers.BISchool
             string yearActSql = $" and {scSql} and c.createTime >= {years} and c.createTime <= {yeare}";
             yearActCn = await ActivityWay.GetCnt(cosmosClient, yearActSql);
 
-            return Ok(new { state = RespondCode.Ok, lessAll, lessLastdayCnt, lessDayCnt, lessLastYarCnt, lessYearCnt, interAll, lastDayInterCnt, interCnt, lastYarInterCnt, yearInterCnt, actAllCnt, lastActCnt, actCnt, lastYearActCn, yearActCn });
+            return Ok(new { state = RespondCode.Ok, lessAll, lessLastdayCnt, lessDayCnt, lessLastYearCnt, lessYearCnt, interAll, lastDayInterCnt, interCnt, lastYarInterCnt, yearInterCnt, actAllCnt, lastActCnt, actCnt, lastYearActCn, yearActCn });
         }
 
         /// <summary>

+ 1 - 1
TEAMModelBI/Controllers/BIStudent/StuActivityController.cs

@@ -7,8 +7,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text.Json;
 using System.Threading.Tasks;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;

+ 1 - 1
TEAMModelBI/Controllers/BIStudent/StudentController.cs

@@ -5,8 +5,8 @@ using Microsoft.Extensions.Options;
 using System.Collections.Generic;
 using System.Text.Json;
 using System.Threading.Tasks;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Models;
 

+ 1 - 1
TEAMModelBI/Controllers/BITable/BIOpenApiController.cs

@@ -9,9 +9,9 @@ using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelBI.Filter;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models.Cosmos.BI;

+ 1 - 1
TEAMModelBI/Controllers/BITable/CompanyController.cs

@@ -12,9 +12,9 @@ using System.Threading.Tasks;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelBI.Filter;
 using TEAMModelBI.Models.Extension;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models.Cosmos.BI;

+ 1 - 1
TEAMModelBI/Controllers/BITable/CompanyUserController.cs

@@ -6,8 +6,8 @@ using System.Collections.Generic;
 using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.Models;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.Context.Constant;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;

+ 1 - 1
TEAMModelBI/Controllers/BITeacher/TeacherController.cs

@@ -9,7 +9,7 @@ using TEAMModelOS.Models;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.BITeacher
 {

+ 13 - 64
TEAMModelBI/Controllers/BITest/TestController.cs

@@ -45,9 +45,10 @@ using System.Net;
 using TEAMModelBI.Tool.CosmosBank;
 using System.Diagnostics;
 using StackExchange.Redis;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelOS.SDK.Models.Service.BI;
+using TEAMModelOS.SDK.Context.BI;
+using TEAMModelOS.SDK.Context.Constant;
 
 namespace TEAMModelBI.Controllers.BITest
 {
@@ -1437,73 +1438,21 @@ namespace TEAMModelBI.Controllers.BITest
         }
 
 
-
-        List<string> schools = new()
-        {
-            "dghznx",
-            "cxhhlx",
-            "xjaxx",
-            "zjqxx",
-            "xnygxx",
-            "lxxcfx",
-            "gxjrxx",
-            "cdgxsx",
-            "yzxx",
-            "kjyxx",
-            "wjylxx",
-            "dsgjxx",
-            "ydzt",
-            "xndbxx",
-            "sqtszx",
-            "cdxczx",
-            "ghsyzx",
-            "lqsx",
-            "cdxxps",
-            "xcfx",
-            "xyqxx",
-            "xncbyy",
-            "cdlqjk",
-            "lqdmxx",
-            "lqyx",
-            "pclxxx",
-            "cdfzx",
-            "xnblxx",
-            "ghsx",
-            "khdycz",
-            "khbmxx",
-            "khdecz",
-            "khzx",
-            "khhbzx",
-            "gxxcxx",
-            "khhtxx",
-            "khdxxx",
-            "khsyxx"
-        };
-
-        public async Task SetTeacherSchool(AzureCosmosFactory _azureCosmos)
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="jsonElement"></param>
+        /// <returns></returns>
+        [HttpPost("get-loganalyse")]
+        public async Task<IActionResult> GetLogAnalyse(JsonElement jsonElement) 
         {
-            var cosmosClient = _azureCosmos.GetCosmosClient();
-            foreach (var item in schools)
-            {
-                School school = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemAsync<School>($"{item}", new PartitionKey("Base"));
+            if (!jsonElement.TryGetProperty("path", out JsonElement path)) return BadRequest();
+            var (an ,saveUrl) = await BILogAnalyseService.GetPathAnalyse(_azureStorage, $"{path}");
 
-                List<Teacher> teachers = new();
-                await foreach (var tch in cosmosClient.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Teacher>(queryText: $"SELECT value(c) FROM c join s in c.schools where s.schoolId='dghznx' and c.code='Base'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
-                {
-                    teachers.Add(tch);
-                }
-
-                if (teachers.Count > 0)
-                {
-                    foreach (var teacher in teachers)
-                    {
-                        teacher.schools.ForEach(tch => { if (tch.schoolId.Equals(item)) { tch.areaId = school.areaId; } });
-                        await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, teacher.id, new PartitionKey("Base"));
-                    }
-                }
-            }
+            return Ok(new { state = RespondCode.Ok, an, saveUrl });
         }
 
+
         public class linqTest
         {
             public string id{ get; set; }

+ 42 - 40
TEAMModelBI/Controllers/Census/ActivitySticsController.cs

@@ -16,8 +16,8 @@ using TEAMModelOS.SDK.Extension;
 using TEAMModelBI.Models;
 using TEAMModelOS.SDK;
 using TEAMModelBI.Tool.CosmosBank;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.SDK.Models.Cosmos.BI;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.Census
 {
@@ -47,25 +47,25 @@ namespace TEAMModelBI.Controllers.Census
         /// <returns></returns>
         [ProducesDefaultResponseType]
         [HttpPost("get-count")]
-        public async Task<IActionResult> GetCount(JsonElement jsonElement) 
+        public async Task<IActionResult> GetCount(JsonElement jsonElement)
         {
             jsonElement.TryGetProperty("tmdId", out JsonElement tmdId);
-            if(!jsonElement.TryGetProperty("term", out JsonElement term)) return BadRequest();
+            if (!jsonElement.TryGetProperty("term", out JsonElement term)) return BadRequest();
             jsonElement.TryGetProperty("site", out JsonElement site);
             long start = 0, end = 0;
-            if (bool.Parse($"{term}") == true) 
+            if (bool.Parse($"{term}") == true)
             {
                 (start, end) = TimeHelper.GetTermStartOrEnd(DateTime.Now);
             }
             var cosmosClient = _azureCosmos.GetCosmosClient();
             if ($"{site}".Equals(BIConst.Global))
                 cosmosClient = _azureCosmos.GetCosmosClient(name: BIConst.Global);
-             List<object> activityCount = new List<object>();
+            List<object> activityCount = new List<object>();
 
             if (!string.IsNullOrEmpty($"{tmdId}"))
             {
                 List<string> schoolIds = await CommonFind.FindSchoolIds(cosmosClient, $"{tmdId}");
-                
+
                 foreach (var itemId in schoolIds)
                 {
                     School school = new();
@@ -92,7 +92,7 @@ namespace TEAMModelBI.Controllers.Census
                     activityCount.Add(tempCount);
                 }
             }
-            else 
+            else
             {
                 foreach (var type in StaticValue.activityTypes)
                 {
@@ -116,7 +116,7 @@ namespace TEAMModelBI.Controllers.Census
         /// </summary>
         /// <returns></returns>
         [HttpPost("get-allactivity")]
-        public async Task<IActionResult> GetAllActivity(JsonElement jsonElement) 
+        public async Task<IActionResult> GetAllActivity(JsonElement jsonElement)
         {
             try
             {
@@ -128,7 +128,7 @@ namespace TEAMModelBI.Controllers.Census
                 foreach (var type in StaticValue.activityTypes)
                 {
                     string querySql = $"SELECT Count(c.id) as totals FROM c where c.pk='{type}'  ";
-                    
+
                     long totals = await CommonFind.FindTotals(cosmosClient, querySql, new List<string>() { "Common" });
 
                     KeyValuePair<string, long> valuePair = new KeyValuePair<string, long>(type, totals);
@@ -192,7 +192,7 @@ namespace TEAMModelBI.Controllers.Census
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [HttpPost("get-termactivity")]
-        public async Task<IActionResult> GetTermActivity(JsonElement jsonElement) 
+        public async Task<IActionResult> GetTermActivity(JsonElement jsonElement)
         {
             jsonElement.TryGetProperty("tmdId", out JsonElement tmdId);
             jsonElement.TryGetProperty("site", out JsonElement site);
@@ -217,7 +217,7 @@ namespace TEAMModelBI.Controllers.Census
                     }
                     ActivityCount activityCount = new ActivityCount() { id = schoolId, name = school.name != null ? school.name : schoolId };
 
-                    foreach (var type in StaticValue.activityTypes) 
+                    foreach (var type in StaticValue.activityTypes)
                     {
                         string activitySql = $"SELECT COUNT(c.id) AS totals  FROM c WHERE c.pk='{type}' AND c.school='{school}' and c.createTime >= {start} and c.createTime <= {end}";
                         long totals = await CommonFind.FindTotals(cosmosClient, activitySql, new List<string>() { "Common" });
@@ -251,7 +251,7 @@ namespace TEAMModelBI.Controllers.Census
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [HttpPost("get-area")]
-        public async Task<IActionResult> GetAreaActovoty(JsonElement jsonElement) 
+        public async Task<IActionResult> GetAreaActovoty(JsonElement jsonElement)
         {
             if (!jsonElement.TryGetProperty("areaId", out JsonElement areaId)) return BadRequest();
             jsonElement.TryGetProperty("site", out JsonElement site);
@@ -277,7 +277,7 @@ namespace TEAMModelBI.Controllers.Census
                 ActivityCount tempCount = new ActivityCount() { id = school.id, name = school.name != null ? school.name : school.id };
                 foreach (var type in StaticValue.activityTypes)
                 {
-                    StringBuilder sqlTxt = new($"select COUNT(c.id) AS totals from c where c.pk='{type}' and c.school='{school.id}' ");                
+                    StringBuilder sqlTxt = new($"select COUNT(c.id) AS totals from c where c.pk='{type}' and c.school='{school.id}' ");
                     long totals = await CommonFind.FindTotals(cosmosClient, sqlTxt.ToString(), new List<string>() { "Common" });
 
                     switch (type)
@@ -286,13 +286,13 @@ namespace TEAMModelBI.Controllers.Census
                             exemAreaCount += totals;
                             break;
                         case "Survey":
-                             surveyAreaCount += totals;
+                            surveyAreaCount += totals;
                             break;
                         case "Vote":
-                             voteAreaCount += totals;
+                            voteAreaCount += totals;
                             break;
                         case "Homework":
-                             homeworkAreaCount += totals;
+                            homeworkAreaCount += totals;
                             break;
                     }
 
@@ -310,7 +310,7 @@ namespace TEAMModelBI.Controllers.Census
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [HttpPost("get-areastics")]
-        public async Task<IActionResult> GetAreaStics(JsonElement jsonElement) 
+        public async Task<IActionResult> GetAreaStics(JsonElement jsonElement)
         {
             if (!jsonElement.TryGetProperty("areaId", out JsonElement areaId)) return BadRequest();
             jsonElement.TryGetProperty("site", out JsonElement site);
@@ -335,7 +335,7 @@ namespace TEAMModelBI.Controllers.Census
             {
                 schools.Add(item);
             }
-            
+
             int countArea = 0;//区级人员
             int appraiseArea = 0;//区级评审人员
             int areaSize = 0;   //区级空间
@@ -344,7 +344,7 @@ namespace TEAMModelBI.Controllers.Census
             long voteAreaCount = 0;   //投票活动
             long homeworkAreaCount = 0;  //作业活动
             long weekActivity = 0; //周活动数量
-            long termActivity=0;   //学期活动
+            long termActivity = 0;   //学期活动
             int basics = 0; //基础版数
             int standard = 0; //标准版数
             int major = 0;   //专业版数
@@ -359,7 +359,7 @@ namespace TEAMModelBI.Controllers.Census
             double teachCount = 0;    //课例教师
 
             List<SchoolLesson> schoolLessons = new();  //学校课例集合 
-            List<SchoolInfo> schoolInfos = new();            
+            List<SchoolInfo> schoolInfos = new();
             List<LessonRecord> records = new();//所有的课程记录
 
             List<string> scIds = schools.Select(x => x.id).ToList();
@@ -389,14 +389,14 @@ namespace TEAMModelBI.Controllers.Census
                 }
 
                 //查询学校的总学时
-                totalTime += await CommonFind.FindTotals(cosmosClient, "SELECT sum(c.totalTime) as totals FROM c", "Teacher", $"TeacherTrain-{school.id}");
+                totalTime += await CommonFind.GetSqlValueCount(cosmosClient, "Teacher", "SELECT value(sum(c.totalTime)) FROM c", $"TeacherTrain-{school.id}");
                 //查询学校参训人数
-                int tempCount = await CommonFind.FindTotals(cosmosClient, $"select array_length(c.members) as totals from c where c.type='yxtrain'", "School", $"GroupList-{school.id}");
+                int tempCount = await CommonFind.GetSqlValueCount(cosmosClient, "School", $"select value(array_length(c.members)) from c where c.type='yxtrain'", $"GroupList-{school.id}");
                 //学校学生人数
-                long stuCount = await CommonFind.FindTotals(cosmosClient, $"select count(c.id) as totals from c", "Student", $"Base-{school.id}");
+                long stuCount = await CommonFind.GetSqlValueCount(cosmosClient, "Student", $"select value(count(c.id)) from c", $"Base-{school.id}");
 
                 //查询是否有服务
-                int serCount = await CommonFind.FindTotals(cosmosClient, $"select array_length(c.service) as totals from c where c.id='{school.id}'", "School", "ProductSum");
+                int serCount = await CommonFind.GetSqlValueCount(cosmosClient, "School", $"select value(array_length(c.service)) from c where c.id='{school.id}'", "ProductSum");
                 if (serCount > 0)
                     major += 1;
                 else if (school.size >= 300 && school.scale >= 500)
@@ -410,7 +410,7 @@ namespace TEAMModelBI.Controllers.Census
                 }
 
                 //课例
-                int lessCount = await CommonFind.FindTotals(cosmosClient, "select count(c.id) totals from c", "School", $"LessonRecord-{school.id}");
+                int lessCount = await CommonFind.GetSqlValueCount(cosmosClient, "School", "select value(count(c.id)) from c", $"LessonRecord-{school.id}");
                 SchoolLesson schoolLesson = new()
                 {
                     id = school.id,
@@ -419,27 +419,28 @@ namespace TEAMModelBI.Controllers.Census
                     count = lessCount
                 };
 
-                SchoolInfo schoolInfo = new() { 
-                    id = school.id, 
+                SchoolInfo schoolInfo = new()
+                {
+                    id = school.id,
                     name = school.name,
                     picture = school.picture,
                     teacherCount = count,
-                    studentCount = stuCount, 
-                    appraiseCount = appraise, 
-                    trainCount = tempCount 
+                    studentCount = stuCount,
+                    appraiseCount = appraise,
+                    trainCount = tempCount
                 };
 
                 ActivityCount tempActivity = new() { id = school.id, name = school.name != null ? school.name : school.id };
                 foreach (var type in StaticValue.activityTypes)
                 {
-                    long totals = await CommonFind.FindTotals(cosmosClient, $"select COUNT(c.id) AS totals from c where c.pk='{type}' and c.school='{school.id}' ", new List<string>() { "Common" });
+                    long totals = await CommonFind.GetSqlValueCount(cosmosClient, "Common", $"select value(COUNT(c.id)) from c where c.pk='{type}' and c.school='{school.id}' ");
 
-                    string weekSql= $"select count(c.id) as totals from c where c.pk='{type}' and c.school='{school.id}' and c.createTime>={weekStart}   and c.createTime<={weekEnd}";
-                    long weekActCount = await CommonFind.FindTotals(cosmosClient, weekSql, new List<string>() { "Common" });
+                    string weekSql = $"select value(count(c.id)) from c where c.pk='{type}' and c.school='{school.id}' and c.createTime>={weekStart}   and c.createTime<={weekEnd}";
+                    long weekActCount = await CommonFind.GetSqlValueCount(cosmosClient, "Common", weekSql);
                     weekActivity += weekActCount;
 
-                    string termSql = $"select count(c.id) as totals from c where c.pk='{type}' and c.school='{school.id}' and c.createTime>={termStart}   and c.createTime<={termEnd}";
-                    long termActCount = await CommonFind.FindTotals(cosmosClient, termSql, new List<string>() { "Common" });
+                    string termSql = $"select value(count(c.id)) from c where c.pk='{type}' and c.school='{school.id}' and c.createTime>={termStart}   and c.createTime<={termEnd}";
+                    long termActCount = await CommonFind.GetSqlValueCount(cosmosClient, "Common", termSql);
                     termActivity += termActCount;
 
                     switch (type)
@@ -487,6 +488,7 @@ namespace TEAMModelBI.Controllers.Census
             }
 
             return Ok(new { state = 200, schoolCount = schools.Count, countArea, weekActivity, termActivity, totalTime, appraiseArea, examAreaCount, surveyAreaCount, voteAreaCount, homeworkAreaCount, major, standard, basics, oeCount, dayLess, weekLess, monthLess, termLess, teachCount, allLess = records.Count, schools = schoolInfos, schoolLessons });
+
         }
 
         /// <summary>
@@ -535,10 +537,10 @@ namespace TEAMModelBI.Controllers.Census
             stuCount = await CommonFind.FindTotals(cosmosClient, $"SELECT count(c.id) as totals FROM c ", "Student", "Base");
             allSize = await CommonFind.FindTotals(cosmosClient, $"SELECT sum(c.size) as totals FROM c ", "School", "Base");
 
-            foreach (var area in areaInfos) 
+            foreach (var area in areaInfos)
             {
                 List<RecSchool> recSchools = new();
-                await foreach (var school in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<RecSchool>(queryText: $"select c.id,c.name,c.picture,c.type,c.size,c.scale from c where c.areaId='{area.id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") })) 
+                await foreach (var school in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<RecSchool>(queryText: $"select c.id,c.name,c.picture,c.type,c.size,c.scale from c where c.areaId='{area.id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
                 {
                     recSchools.Add(school);
                 }
@@ -567,7 +569,7 @@ namespace TEAMModelBI.Controllers.Census
                 long totals = await CommonFind.FindTotals(cosmosClient, sqlTxt, new List<string>() { "Common" });
 
                 string weekSql = $"select COUNT(c.id) AS totals from c where c.pk='{type}' and c.createTime>={weekStart} and c.createTime<={weekStart} ";
-                weekActivity  += await CommonFind.FindTotals(cosmosClient, weekSql,new List<string> { "Common" });
+                weekActivity += await CommonFind.FindTotals(cosmosClient, weekSql, new List<string> { "Common" });
 
                 string termSql = $"select COUNT(c.id) AS totals from c where c.pk='{type}' and c.createTime>={termStart} and c.createTime<={termEnd} ";
                 termActivity += await CommonFind.FindTotals(cosmosClient, termSql, new List<string> { "Common" });
@@ -578,7 +580,7 @@ namespace TEAMModelBI.Controllers.Census
 
             allLess = await CommonFind.FindTotals(cosmosClient, "select count(c.id) as totals from c where c.pk='LessonRecord'", new List<string>() { "School", "Teacher" });
             weekLess = await CommonFind.FindTotals(cosmosClient, $"select count(c.id) as totals from c where c.pk='LessonRecord' and c.startTime>={weekStart} and c.startTime <={weekEnd}", new List<string>() { "School", "Teacher" });
-            termLess = await CommonFind.FindTotals(cosmosClient, $"select count(c.id) as totals from c where c.pk='LessonRecord' and c.startTime>={termStart} and c.startTime <={termEnd}", new List<string>() { "School","Teacher" });
+            termLess = await CommonFind.FindTotals(cosmosClient, $"select count(c.id) as totals from c where c.pk='LessonRecord' and c.startTime>={termStart} and c.startTime <={termEnd}", new List<string>() { "School", "Teacher" });
 
             totalTime = await CommonFind.FindTotals(cosmosClient, "select sum(c.totalTime) as totals from c where c.pk='TeacherTrain'", new List<string>() { "Teacher" });
             resourceCount = await CommonFind.FindTotals(cosmosClient, "select count(c.id) as totals from c where c.pk='Bloblog'", new List<string>() { "School", "Teacher" });
@@ -645,7 +647,7 @@ namespace TEAMModelBI.Controllers.Census
             public string standard { get; set; }
             public string standardName { get; set; }
             public int schoolCount { get; set; }
-            public int techCount { get; set;}
+            public int techCount { get; set; }
             public int stuCount { get; set; }
         }
 

+ 1 - 1
TEAMModelBI/Controllers/Census/BlobLogController.cs

@@ -9,8 +9,8 @@ using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 

+ 1 - 1
TEAMModelBI/Controllers/Census/ItemSticsController.cs

@@ -12,7 +12,7 @@ using System;
 using System.Text;
 using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Extension;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.Census
 {

+ 1 - 1
TEAMModelBI/Controllers/Census/LessonSticsController.cs

@@ -17,7 +17,7 @@ using System.Text;
 using TEAMModelBI.Tool;
 using MathNet.Numerics.LinearAlgebra.Double;
 using TEAMModelBI.Tool.CosmosBank;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.Census
 {

+ 1 - 1
TEAMModelBI/Controllers/Census/PaperController.cs

@@ -9,8 +9,8 @@ using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.Models;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;

+ 1 - 1
TEAMModelBI/Controllers/Census/ProductStatisController.cs

@@ -8,8 +8,8 @@ using System.Linq;
 using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;

+ 1 - 1
TEAMModelBI/Controllers/Census/SchoolController.cs

@@ -10,9 +10,9 @@ using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.Models;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.Tool.CosmosBank;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;

+ 1 - 1
TEAMModelBI/Controllers/Census/StudyController.cs

@@ -6,8 +6,8 @@ using System.Collections.Generic;
 using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.Tool;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Models;
 

+ 1 - 1
TEAMModelBI/Controllers/Core/BlobController.cs

@@ -9,9 +9,9 @@ using System.Net.Http;
 using System.Threading.Tasks;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelBI.Filter;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.Models;
 using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 

+ 1 - 1
TEAMModelBI/Controllers/DingDingStruc/DDStructController.cs

@@ -20,7 +20,7 @@ using TEAMModelBI.Controllers.BISchool;
 using TEAMModelOS.SDK.Models.Cosmos.BI;
 using Microsoft.Azure.Cosmos.Table;
 using TEAMModelOS.SDK;
-using TEAMModelBI.Tool.Context;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.DingDingStruc
 {

+ 1 - 1
TEAMModelBI/Controllers/DingDingStruc/TableDingDingInfoController.cs

@@ -25,9 +25,9 @@ using TEAMModelBI.Filter;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelBI.Models;
 using TEAMModelBI.Tool.CosmosBank;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.DI.BIAzureStorage;
 using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.DingDingStruc
 {

+ 1 - 1
TEAMModelBI/Controllers/LoginController.cs

@@ -35,10 +35,10 @@ using TEAMModelOS.SDK;
 using Microsoft.AspNetCore.Hosting;
 using TEAMModelBI.Tool;
 using TEAMModelBI.DI.BIAzureStorage;
-using TEAMModelBI.Tool.Context;
 using TEAMModelOS.SDK.Models.Table;
 using TEAMModelOS.SDK.Context.Constant;
 using TEAMModelBI.Models;
+using TEAMModelOS.SDK.Context.BI;
 //using static DingTalk.Api.Response.OapiV2UserGetResponse;
 
 namespace TEAMModelBI.Controllers

+ 1 - 1
TEAMModelBI/Controllers/OperateRecord/OperateLogController.cs

@@ -15,8 +15,8 @@ using TEAMModelOS.SDK.Models.Table;
 using TEAMModelBI.Filter;
 using TEAMModelBI.Tool.Extension;
 using TEAMModelOS.SDK.Extension;
-using TEAMModelBI.Tool.Context;
 using TEAMModelBI.DI.BIAzureStorage;
+using TEAMModelOS.SDK.Context.BI;
 
 namespace TEAMModelBI.Controllers.OperateRecord
 {

+ 1 - 1
TEAMModelBI/Tool/Context/BIConst.cs

@@ -1,4 +1,4 @@
-namespace TEAMModelBI.Tool.Context
+namespace TEAMModelOS.SDK.Context.BI
 {
     public class BIConst
     {

+ 26 - 1
TEAMModelOS.SDK/DI/AzureSignalR/AzureSignalRExtensions.cs

@@ -6,11 +6,36 @@ using System.Threading;
 using System.Threading.Tasks;
 using System.Linq;
 using StackExchange.Redis;
+using System.Collections.Concurrent;
+using Microsoft.Azure.SignalR.Management;
 
 namespace TEAMModelOS.SDK.DI
 {
     public static class AzureSignalRExtensions
     {
-       
+        private static ConcurrentDictionary<string, ServiceHubContext> ServiceHubContexts { get; } = new();
+
+
+
+        /// <summary>
+        /// µo°e«H®§¦Ü¹ï¦C©Î¥DÃD
+        /// </summary>       
+        /// <param name="name">QueueName or TopicName</param>
+        /// <param name="message">°T®§</param>
+        /// <returns></returns>
+        public static ServiceHubContext GetHubContext(this ServiceManager sm, string name)
+        {
+            try
+            {
+                ServiceHubContext hub = ServiceHubContexts.GetOrAdd(name, (x) =>
+                sm.CreateHubContextAsync(name, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult());
+
+                return hub;
+            }
+            catch
+            {
+                throw;
+            }
+        }
     }
 }

+ 9 - 9
TEAMModelOS.SDK/DI/AzureSignalR/AzureSignalRFactory.cs

@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using StackExchange.Redis;
 using System;
+using System.Collections.Concurrent;
 
 namespace TEAMModelOS.SDK.DI
 {
@@ -10,6 +11,7 @@ namespace TEAMModelOS.SDK.DI
     {
         private readonly IServiceProvider _services;
         private readonly IOptionsMonitor<AzureSignalRFactoryOptions> _optionsMonitor;
+        private ConcurrentDictionary<string, ServiceManager> ServiceManagers { get; } = new();
         private readonly ILogger _logger;
 
         public AzureSignalRFactory(IServiceProvider services, IOptionsMonitor<AzureSignalRFactoryOptions> optionsMonitor, ILogger<AzureSignalRFactory> logger)
@@ -20,18 +22,16 @@ namespace TEAMModelOS.SDK.DI
         }
 
         public ServiceManager GetServiceManager(string name = "Default")
-        {            
+        {
             try
             {
+                var serviceManager = ServiceManagers.GetOrAdd(name, x =>
                 // TODO(Mickey) : Signalr SDK 1.10.0之後,aud會無法擴充,會變成asrs.u.aud,會造成無法使用restAPI,故先降回舊版
-                var serviceManager = new ServiceManagerBuilder()
-                     .WithOptions(option =>
-                     {
-                         option.ConnectionString = _optionsMonitor.Get(name).SignalRConnectionString;
-                         option.ServiceTransportType = ServiceTransportType.Persistent;
-                         // option.ServiceTransportType = ServiceTransportType.Persistent
-                     })
-                     .BuildServiceManager();
+                new ServiceManagerBuilder().WithOptions(option =>
+                {
+                    option.ConnectionString = _optionsMonitor.Get(name).SignalRConnectionString;
+                    option.ServiceTransportType = ServiceTransportType.Persistent;
+                }).BuildServiceManager());
 
                 return serviceManager;
             }

+ 35 - 1
TEAMModelBI/Models/RecordM/RecAppGWInfo.cs

@@ -1,6 +1,6 @@
 using System.Collections.Generic;
 
-namespace TEAMModelBI.Models.RecordM
+namespace TEAMModelOS.SDK.Models.Cosmos.BI
 {
     /// <summary>
     /// 防火墙网关信息
@@ -49,4 +49,38 @@ namespace TEAMModelBI.Models.RecordM
         public string api { get; set; }
         public List<string> ip { get; set; }
     }
+
+    /// <summary>
+    /// 读取日志数据结构
+    /// </summary>
+    public record AGInfo
+    {
+        //public string resourceId { get; set; }
+
+        public string operationName { get; set; }
+        public string time { get; set; }
+        public string category { get; set; }
+        public Properties properties { get; set; }
+    }
+
+    /// <summary>
+    /// 读取日志数据结构
+    /// </summary>
+    public record Properties
+    {
+        //public string instanceId { get; set; }
+        public string clientIp { get; set; }
+        public string clientPort { get; set; }
+        public string requestUri { get; set; }
+        public string ruleSetType { get; set; }
+        public string ruleSetVersion { get; set; }
+        public string ruleId { get; set; }
+        public string ruleGroup { get; set; }
+        //public string message { get; set; }
+        public string action { get; set; }
+        public string site { get; set; }
+        //public Datails datails { get; set; }
+        public string hostname { get; set; }
+        public string transactionId { get; set; }
+    }
 }

+ 164 - 0
TEAMModelOS.SDK/Models/Service/BI/BILogAnalyseService.cs

@@ -0,0 +1,164 @@
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Context.BI;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.Extension;
+using TEAMModelOS.SDK.Models.Cosmos.BI;
+
+namespace TEAMModelOS.SDK.Models.Service.BI
+{
+    public static class BILogAnalyseService
+    {
+
+        /// <summary>
+        /// 读取全部的防火墙日志文件并分析保存至
+        /// </summary>
+        /// <param name="_azureStorage"></param>
+        /// <param name="site"></param>
+        /// <returns></returns>
+        public static async Task<(List<RecCnt> recCnts ,List<string> saveUrls)> GetAllLogAnalyse(AzureStorageFactory _azureStorage,string site = null) 
+        {
+            var blobClient = _azureStorage.GetBlobContainerClient($"insights-logs-applicationgatewayfirewalllog", name: BIConst.LogChina);
+            if ($"{site}".Equals(BIConst.Global))
+            {
+                blobClient = _azureStorage.GetBlobContainerClient($"insights-logs-applicationgatewayfirewalllog", name: BIConst.Global);
+            }
+
+            List<RecCnt> recCnts = new();
+            List<string> urls = new();
+            //地址:    y={year}/m={month}/d={day}/h={hour}/m=00/PT1H.json
+            string logName = "resourceId=/SUBSCRIPTIONS/73B7F9EF-D8B7-4444-9E8D-D80B43BF3CD4/RESOURCEGROUPS/TEAMMODELCHENGDU/PROVIDERS/MICROSOFT.NETWORK/APPLICATIONGATEWAYS/OSFIREWARE";
+            await foreach (BlobItem blobItem in blobClient.GetBlobsAsync(BlobTraits.None, BlobStates.None, logName))
+            {
+
+                StringBuilder visits = new("[");
+
+                BlobClient tempBlobClient = blobClient.GetBlobClient(blobItem.Name);
+                BlobDownloadInfo download = tempBlobClient.Download();
+                var content = download.Content;
+                string text;
+                using (var streamReader = new StreamReader(content))
+                {
+                    while ((text = streamReader.ReadLine()) != null)
+                    {
+                        if (streamReader.EndOfStream)
+                            visits.Append($"{text.ToString()}");
+                        else
+                            visits.Append($"{text.ToString()},");
+                    }
+
+                    visits.Append("]");
+                    streamReader.Close();
+                }
+
+                string input = visits.ToString();
+                List<AGInfo> aGInfos = input.ToObject<List<AGInfo>>();
+                DateTimeOffset dtime = DateTimeOffset.UtcNow;
+                string cHour = dtime.ToString("yyyyMMddHH");
+                string cDay = dtime.ToString("yyyyMMdd");
+                if (aGInfos.Count > 0)
+                {
+                    cHour = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMddHH")).First();
+                    cDay = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMdd")).First();
+                }
+
+                RecCnt saveCnts = new();
+
+                List<RecAppGWInfo> recInfo = aGInfos.Select(s => new RecAppGWInfo { hour = cHour, ip = s.properties.clientIp, api = s.properties.requestUri.Split("?").ToList().Count() > 1 ? s.properties.requestUri.Split("?").ToList()[0] : s.properties.requestUri, hostName = s.properties.hostname }).ToList();
+
+                List<RecApiCnt> apiCnt = recInfo.GroupBy(a => a.api).Select(g => new RecApiCnt { api = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), ip = g.Select(i => i.ip).Distinct().ToList() }).ToList();
+                saveCnts.apiCnt = apiCnt;
+
+                List<RecIpCnt> ipCnt = recInfo.GroupBy(a => a.ip).Select(g => new RecIpCnt { ip = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), api = g.Select(i => i.api).Distinct().ToList() }).ToList();
+                saveCnts.ipCnt = ipCnt;
+                recCnts.Add(saveCnts);
+
+                ////保存存至Blob文件
+                var url = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(saveCnts.ToJsonString(), $"visitCnt/{cDay}", $"{cHour}.json");
+
+                urls.Add(url);
+            }
+
+            return (recCnts, urls);
+        }
+
+        /// <summary>
+        /// 通过路径获取日志文件并分析结果
+        /// </summary>
+        /// <param name="_azureStorage"></param>
+        /// <param name="path"></param>
+        /// <param name="site"></param>
+        /// <returns></returns>
+        public static async Task<(List<RecCnt> recCnts, List<string> saveUrls)> GetPathAnalyse(AzureStorageFactory _azureStorage,string path, string site = null)
+        {
+            var blobClient = _azureStorage.GetBlobContainerClient($"insights-logs-applicationgatewayfirewalllog", name: BIConst.LogChina);
+            if ($"{site}".Equals(BIConst.Global))
+            {
+                blobClient = _azureStorage.GetBlobContainerClient($"insights-logs-applicationgatewayfirewalllog", name: BIConst.LogGlobal);
+            }
+
+            List<RecCnt> recCnts = new();
+            List<string> urls = new();
+
+            string logName = "resourceId=/SUBSCRIPTIONS/73B7F9EF-D8B7-4444-9E8D-D80B43BF3CD4/RESOURCEGROUPS/TEAMMODELCHENGDU/PROVIDERS/MICROSOFT.NETWORK/APPLICATIONGATEWAYS/OSFIREWARE";
+            await foreach (BlobItem blobItem in blobClient.GetBlobsAsync(BlobTraits.None, BlobStates.None, path))
+            {
+                StringBuilder visits = new("[");
+                //BlobClient tempBlobClient = blobClient.GetBlobClient(blobItem.Name);
+                //BlobDownloadInfo download = tempBlobClient.Download();
+                BlobDownloadInfo download = blobClient.GetBlobClient(blobItem.Name).Download();
+
+                var content = download.Content;
+                string text;
+                using (var streamReader = new StreamReader(content))
+                {
+                    while ((text = streamReader.ReadLine()) != null)
+                    {
+                        if (streamReader.EndOfStream)
+                            visits.Append($"{text.ToString()}");
+                        else
+                            visits.Append($"{text.ToString()},");
+                    }
+
+                    visits.Append("]");
+                    streamReader.Close();
+                }
+
+                string input = visits.ToString();
+                List<AGInfo> aGInfos = input.ToObject<List<AGInfo>>();
+                DateTimeOffset dtime = DateTimeOffset.UtcNow;
+                string cHour = dtime.ToString("yyyyMMddHH");
+                string cDay = dtime.ToString("yyyyMMdd");
+                if (aGInfos.Count > 0)
+                {
+                    cHour = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMddHH")).First();
+                    cDay = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMdd")).First();
+                }
+
+                RecCnt saveCnts = new();
+
+                List<RecAppGWInfo> recInfo = aGInfos.Select(s => new RecAppGWInfo { hour = cHour, ip = s.properties.clientIp, api = s.properties.requestUri.Split("?").ToList().Count() > 1 ? s.properties.requestUri.Split("?").ToList()[0] : s.properties.requestUri, hostName = s.properties.hostname }).ToList();
+
+                List<RecApiCnt> apiCnt = recInfo.GroupBy(a => a.api).Select(g => new RecApiCnt { api = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), ip = g.Select(i => i.ip).Distinct().ToList() }).ToList();
+                saveCnts.apiCnt = apiCnt;
+
+                List<RecIpCnt> ipCnt = recInfo.GroupBy(a => a.ip).Select(g => new RecIpCnt { ip = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), api = g.Select(i => i.api).Distinct().ToList() }).ToList();
+                saveCnts.ipCnt = ipCnt;
+                recCnts.Add(saveCnts);
+
+                ////保存存至Blob文件
+                var url = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(saveCnts.ToJsonString(), $"visitCnt/{cDay}", $"{cHour}.json");
+
+                urls.Add(url);
+            }
+
+            return (recCnts, urls);
+        }
+    }
+}

+ 105 - 32
TEAMModelOS.SDK/Models/Service/StudentService.cs

@@ -816,8 +816,7 @@ namespace TEAMModelOS.SDK
                     writerNew.Flush();
                     if (!string.IsNullOrWhiteSpace(stud.Value.imei))
                     {
-                        Imei imei = new Imei { id = stud.Value.imei, code = "Imei", pk = "Imei", stuid = stud.Key, school = schoolId };
-                        await cosmosContainer.UpsertItemAsync(imei, new PartitionKey($"Imei"));
+                        await upsertImei(stud.Key, stud.Value.imei, schoolId, "keep", cosmosContainer);
                     }
                     var response = await cosmosContainer.CreateItemStreamAsync(memoryStream, new PartitionKey($"Base-{schoolId}"));
 
@@ -917,6 +916,90 @@ namespace TEAMModelOS.SDK
             }
             return (null, null, null);
         }
+        /// <summary>
+        /// grand_type 
+        /// 为import:如果导入则已最新,没导入则不动这个字段的值,维持现状
+        /// 为create:如果有值,则绑定,并删除stuid关联的别的imei.
+        /// 为update 
+        /// 为delete
+        /// </summary>
+        /// <param name="stuid"></param>
+        /// <param name="imei"></param>
+        /// <param name="schoolId"></param>
+        /// <param name="grand_type"></param>
+        /// <param name="cosmosContainer"> 
+        /// clean 当传入的电子学生证值为空,判断要强制清除关联的学生电子学生证 ,
+        /// keep 保持现状,导入时
+        /// delete 因学生被删除,强制删除电子学生证。</param>
+        /// <returns></returns>
+        public static async Task upsertImei(string stuid ,string imeiid,string schoolId,string grand_type, CosmosContainer cosmosContainer) {
+            List<Imei> imeis = new List<Imei>();
+            string sql = $"select value c from c where c.stuid='{stuid}'  and c.school='{schoolId}' ";
+            await foreach (var item in cosmosContainer.GetItemQueryIterator<Imei>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("Imei") }))
+            {
+                imeis.Add(item);
+            }
+            switch (grand_type)
+            {
+                case "clean":
+                case "keep":
+                    bool notin = true;
+                    List<Imei> update = new List<Imei>();
+                    List<Imei> delete = new List<Imei>();
+                    imeis.ForEach(x => {
+                        //如果传入的电子学生证id不存在,且是单个更新或创建,则解除学生id的电子学生证绑定,并删除该电子学生证。
+                        if (string.IsNullOrWhiteSpace(imeiid)) {
+                            if (grand_type.Equals("clean") )
+                            {
+                                delete.Add(x);
+                            }// 如果是导入模式,且电子学生证没有值则不动。
+                        }
+                        else {
+                            //如果电子学生证有值则,解除之前的所有与当前电子学生证id不符的绑定。
+                            if (!x.id.Equals(imeiid))
+                            {
+                                x.stuid = null;
+                                x.school = null;
+                                delete.Add(x);
+                            }
+                            else
+                            {
+                                //存在且正确绑定
+                                notin = false;
+                            }
+                        }
+                        
+                    });
+                    //当前学生还未有任何电子学生证,且有新的电子学生证id传入,则需要捞出电子学生证,绑定,如果没有捞出,则新建电子学生证,并绑定
+                    if (notin && !string.IsNullOrWhiteSpace(imeiid))
+                    {
+                        var imeiResponse = await cosmosContainer.ReadItemStreamAsync(imeiid, new PartitionKey("Imei"));
+                        if (imeiResponse.Status == 200)
+                        {
+                            Imei imeiDb = JsonDocument.Parse(imeiResponse.Content).RootElement.Deserialize<Imei>();
+                            imeiDb.stuid = stuid;
+                            imeiDb.school = schoolId;
+                            await cosmosContainer.ReplaceItemAsync(imeiDb, imeiDb.id, new PartitionKey("Imei"));
+                        }
+                        else
+                        {
+                            Imei imei = new Imei { id = imeiid, code = "Imei", pk = "Imei", stuid = stuid, school = schoolId };
+                            await cosmosContainer.CreateItemAsync(imei, new PartitionKey($"Imei"));
+                        };
+                    }
+                    if (delete.Any())
+                    {
+                        await cosmosContainer.DeleteItemsStreamAsync(delete.Select(x => x.id).ToList(), "Imei");
+                    }
+                    break;
+                case "delete":
+                    if (imeis.Any()) {
+                        await cosmosContainer.DeleteItemsStreamAsync(imeis.Select(x => x.id).ToList(), "Imei");
+                    }
+                    break;
+            }
+            
+        }
 
         /// <summary>
         /// 單純建立單一學生
@@ -993,17 +1076,15 @@ namespace TEAMModelOS.SDK
                 }
                 writer.WriteEndObject();
                 writer.Flush();
-                if (!string.IsNullOrWhiteSpace(studCreateInfo.imei))
-                {
-                    Imei imei = new Imei { id = studCreateInfo.imei, code = "Imei", pk = "Imei", stuid = studCreateInfo.id, school = schoolId };
-                    await _azureCosmos
-                                .GetCosmosClient()
-                                .GetContainer(Constant.TEAMModelOS, "Student").UpsertItemAsync(imei, new PartitionKey($"Imei"));
-                }
+                
                 var response = await _azureCosmos
                                 .GetCosmosClient()
                                 .GetContainer(Constant.TEAMModelOS, "Student")
                                 .CreateItemStreamAsync(stream, new PartitionKey($"Base-{schoolId}"));
+                //更新电子学生证、
+                await upsertImei(studCreateInfo.id, studCreateInfo.imei, schoolId, "keep", _azureCosmos
+                               .GetCosmosClient()
+                               .GetContainer(Constant.TEAMModelOS, "Student"));
                 if (response.Status == (int)HttpStatusCode.Created || response.Status == (int)HttpStatusCode.OK) return true;
                 if (response.Status == (int)HttpStatusCode.Conflict) return false;
                 else
@@ -1011,6 +1092,7 @@ namespace TEAMModelOS.SDK
                     await _dingDing.SendBotMsg($"IES5,{_option.Location},StudentController/createStudent()\nCosmosDB Create response status = {response.Status}\nID:{studCreateInfo.id}", GroupNames.醍摩豆服務運維群組);
                     return false;
                 }
+                
             }
             catch (Exception ex)
             {
@@ -1488,9 +1570,11 @@ namespace TEAMModelOS.SDK
                     string id = string.Empty;
                     try
                     {
+                       
                         JsonElement student = students.Current;
                         id = student.GetProperty("id").GetString();
                         var ret = await container.DeleteItemStreamAsync(id, new PartitionKey($"Base-{schoolId}"));
+                        await upsertImei(id, null, schoolId, "delete", container);
                         if (ret.Status == (int)HttpStatusCode.NoContent) sucIds.Add(id);
                         await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<GroupList>(queryText: $"select value(c) from c join A0 in c.members where A0.id = '{id}' and A0.code='{schoolId}' ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"GroupList-{schoolId}") }))
                         {
@@ -1751,7 +1835,7 @@ namespace TEAMModelOS.SDK
         /// <param name="students"></param>
         /// <returns></returns>
         public static async Task<(List<object> studs, Dictionary<string, List<string>> classDuplNos, List<string> nonexistentIds, List<string> errorIds, Dictionary<string, List<string>> errorNos, List<string> errorClassId)>
-            updateStudents(AzureCosmosFactory _azureCosmos, DingDing _dingDing, Option _option, string schoolId, JsonElement.ArrayEnumerator students)
+            updateStudents(AzureCosmosFactory _azureCosmos, DingDing _dingDing, Option _option, string schoolId, JsonElement.ArrayEnumerator students,bool cleanImei)
         {
             try
             {
@@ -2091,23 +2175,7 @@ namespace TEAMModelOS.SDK
                                                     writer.WriteString("irs", studentInfos[id].irs);
                                                 }
                                                 break;
-                                            case bool _ when element.Name.Equals("imei", StringComparison.Ordinal):
-                                                //移除座號的話會給空的
-                                                if (studentInfos[id].imei != null && studentInfos[id].imei.Length == 0)
-                                                {
-                                                    writer.WriteNull("imei");
-                                                    tmpData.imei = null;
-                                                }
-                                                else if (string.IsNullOrWhiteSpace(studentInfos[id].imei))
-                                                {
-                                                    element.WriteTo(writer);
-                                                    tmpData.imei = element.Value.GetString();
-                                                }
-                                                else
-                                                {
-                                                    writer.WriteString("imei", studentInfos[id].imei);
-                                                }
-                                                break;
+                                            
                                             case bool _ when element.Name.StartsWith("_", StringComparison.Ordinal):
                                                 break;
                                             default:
@@ -2158,12 +2226,17 @@ namespace TEAMModelOS.SDK
                                     
                                     writer.WriteEndObject();
                                     writer.Flush();
-                                    if (!string.IsNullOrWhiteSpace(studentInfos[id].imei))
+                                    //编辑是是否要明确清除电子学生证。
+                                    if (cleanImei)
                                     {
-                                        Imei imei = new Imei { id = studentInfos[id].imei, code = "Imei", pk = "Imei", stuid = id, school = schoolId };
-                                        await _azureCosmos
-                                                    .GetCosmosClient()
-                                                    .GetContainer(Constant.TEAMModelOS, "Student").UpsertItemAsync(imei, new PartitionKey($"Imei"));
+                                        await upsertImei(id, studentInfos[id].imei, schoolId, "clean", _azureCosmos
+                                                         .GetCosmosClient()
+                                                         .GetContainer(Constant.TEAMModelOS, "Student"));
+                                    }
+                                    else {
+                                        await upsertImei(id, studentInfos[id].imei, schoolId, "keep", _azureCosmos
+                                                       .GetCosmosClient()
+                                                       .GetContainer(Constant.TEAMModelOS, "Student"));
                                     }
                                     try
                                     {

+ 6 - 3
TEAMModelOS/ClientApp/public/lang/en-US.js

@@ -1147,7 +1147,7 @@ const LANG_EN_US = {
             gCount: 'Group No.',
             taskCount: 'Task No.',
             colctCount: 'Work No.',
-            pushCount: 'Push Time',
+            pushCount: '推送資源數',
             totalScore: 'Total Points',
             examCount: 'No. of Test',
             quCount: 'IRS Question No.',
@@ -2818,7 +2818,7 @@ const LANG_EN_US = {
         totalPoint: 'Total Points',
         collateTaskCount: 'Task No.',
         collateCount: 'Work No.',
-        pushCount: 'Push No.',
+        pushCount: '推送資源數',
         score: 'Interactive Score',
         interactionCount: 'Interactive Qs',
         clientInteractionCount: 'Response',
@@ -5501,7 +5501,10 @@ const LANG_EN_US = {
             'dashboard-read': 'View Data Board',
             'research': 'Lesson Management',
             'research-read': 'View Lesson Management',
-            'research-upd': 'Modify Lesson Management'
+            'research-upd': 'Modify Lesson Management',
+            'link': '資源平臺',
+            'link-read': '查看資源平臺',
+            'link-upd': '管理(修改)資源平臺'
         },
         modal: {
             text1: 'The authorization of this account has been changed, do you want to give this account a more appropriate title?',

+ 6 - 3
TEAMModelOS/ClientApp/public/lang/zh-CN.js

@@ -1147,7 +1147,7 @@ const LANG_ZH_CN = {
             gCount: '小组数',
             taskCount: '任务总数',
             colctCount: '作品总数',
-            pushCount: '推送数',
+            pushCount: '推送资源数',
             totalScore: '总计分',
             examCount: '测验总数',
             quCount: '互动题数',
@@ -2818,7 +2818,7 @@ const LANG_ZH_CN = {
         totalPoint: '总记分',
         collateTaskCount: '任务总数',
         collateCount: '作品总数',
-        pushCount: '推送数',
+        pushCount: '推送资源数',
         score: '总互动分',
         interactionCount: '互动题数',
         clientInteractionCount: '互动总数',
@@ -5501,7 +5501,10 @@ const LANG_ZH_CN = {
             'dashboard-read': '数据看板查看权限',
             'research': '课例中心',
             'research-read': '课例中心查看权限',
-            'research-upd': '课例中心管理(修改)权限'
+            'research-upd': '课例中心管理(修改)权限',
+            'link': '资源平台',
+            'link-read': '查看资源平台',
+            'link-upd': '管理(修改)资源平台'
         },
         modal: {
             text1: '此账号权限已变更,是否要给此账号更加合适的职称',

+ 6 - 3
TEAMModelOS/ClientApp/public/lang/zh-TW.js

@@ -1147,7 +1147,7 @@ const LANG_ZH_TW = {
             gCount: '小組數',
             taskCount: '任務總數',
             colctCount: '作品總數',
-            pushCount: '推送數',
+            pushCount: '推送資源數',
             totalScore: '總計分',
             examCount: '測驗總數',
             quCount: '互動題數',
@@ -2818,7 +2818,7 @@ const LANG_ZH_TW = {
         totalPoint: '總記分',
         collateTaskCount: '任務總數',
         collateCount: '作品總數',
-        pushCount: '推送數',
+        pushCount: '推送資源數',
         score: '總互動分',
         interactionCount: '互動題數',
         clientInteractionCount: '互動總數',
@@ -5501,7 +5501,10 @@ const LANG_ZH_TW = {
             'dashboard-read': '校園大數據查看權限',
             'research': '課堂紀錄',
             'research-read': '課堂紀錄查看權限',
-            'research-upd': '課堂紀錄管理(修改)權限'
+            'research-upd': '課堂紀錄管理(修改)權限',
+            'link': '資源平臺',
+            'link-read': '查看資源平臺',
+            'link-upd': '管理(修改)資源平臺'
         },
         modal: {
             text1: '此帳號權限已變更,是否要給此帳號更加合適的職稱',

+ 1 - 1
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -628,7 +628,7 @@ export default {
           router: '/home/mgtPlatform',
           tag: '',
           role: 'admin',
-          permission: '',
+          permission: 'link-read|link-upd',
           menuName: 'mgtPlatform',
           child: [],
           isShow: this.IES5Menu

+ 1 - 1
TEAMModelOS/ClientApp/src/router/routes.js

@@ -1252,7 +1252,7 @@ export const routes = [{
 	{
 		path: 'classRecord',
 		name: 'classRecord',
-		component: resolve => require(['@/view/classrecord/ClassRecordNew.vue'], resolve),
+		component: resolve => require(['@/view/classrecord/ClassRecord.vue'], resolve),
 		meta: {
 			activeName: 'myCourse'
 		}

+ 25 - 16
TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.less

@@ -9,7 +9,7 @@
 
 .class-record-container {
     width: 100%;
-    background: #f2f2f2;
+    background: #ffffff;
     height: 100%;
 }
 
@@ -41,12 +41,9 @@
 }
 
 .class-content {
-    width: ~"calc(100% - 20px)";
-    display: flex;
-    flex-direction: row;
-    height: 450px;
+    width: 100%;
     margin-left: 10px;
-    margin-top: 15px;
+    margin-top: 7px;
     position: relative;
     &:hover .cus-page-wrap {
         opacity: 1;
@@ -63,18 +60,19 @@
 }
 
 .video-player-box {
-    width: 50%;
+    width: ~"calc(100% - 15px)";
     box-shadow: 0px 0px 10px 2px #d8d8d8;
-    height: 450px;
+    max-height: 450px;
     display: flex;
     position: relative;
     background: #fff;
 }
 .courseware-wrap {
-    width: ~"calc(50% - 15px)";
+    width: ~"calc(100% - 15px)";
     margin-right: 15px;
     position: relative;
-    height: 450px;
+    max-height: 450px;
+    margin-top: 10px;
     background: #fff;
     box-shadow: 0px 0px 10px 2px #d8d8d8;
 
@@ -112,19 +110,20 @@
     height: 36px;
     background: rgba(43, 51, 63, 0.7);
     text-align: center;
-    opacity: 0;
+    opacity: 1;
     transition: opacity 1s;
     border-right: 2px solid black;
 }
 
 .content-title {
-    border-left: 8px solid #1CC0F3;
-    padding-left: 14px;
+    border-left: 4px solid #1CC0F3;
+    padding-left: 4px;
     line-height: 20px;
     font-size: 20px;
-    margin-top: 25px;
+    margin-top: 10px;
     margin-bottom: 6px;
-    width: 100%;
+    margin-left: 10px;
+    width: fit-content;
 }
 .e-note-tag{
     font-size: 14px;
@@ -178,6 +177,7 @@
     border: 1px solid #f0f0f0;
     margin-bottom: 10px;
     position: relative;
+    background: #f9f9f9;
     min-height: 300px;
 }
 
@@ -250,7 +250,7 @@
 .page-item {
     display: flex;
     position: relative;
-    background: white;
+    // background: white;
     padding: 10px 70px 10px 20px;
     .record-data-wrap {
         width: 100%;
@@ -405,4 +405,13 @@
     left: 0px;
     color: #ff9900;
     width: 100%;
+}
+.rcd-split-pane{
+    height: ~"calc(100% - 60px)";
+}
+.top-wrap{
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
 }

+ 194 - 116
TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.vue

@@ -1,130 +1,132 @@
 <template>
     <div class="class-record-container">
-        <vuescroll ref="pagewrap">
-            <div>
-                <!--头部信息-->
-                <div class="class-record-header">
-                    <span class="back-page" @click="goBack">
-                        <Icon type="md-arrow-round-back" />
-                        {{$t('cusMgt.rcd.rtn')}}
-                    </span>
-                    <span class="course-name">
-                        {{recordInfo.name}}
-                    </span>
-                    <div style="display:inline-block;margin-left:30px">
-                        <span class="label-text">
-                            {{$t('cusMgt.rcd.ctime')}}
-                        </span>
-                        <span class="label-value">
-                            {{$jsFn.timeFormat(recordInfo.startTime)}}
-                        </span>
-                    </div>
-                    <div class="action-wrap">
-                        <!-- 电子笔记 -->
-                        <span class="e-note-tag" @click="viewENote">
-                            <Icon type="ios-paper" />
-                            {{$t('cusMgt.rcd.enote')}}
-                        </span>
-                        <!-- 苏格拉底报告 -->
-                        <!-- <span class="e-note-tag" @click="viewReport">
-                            {{$t('cusMgt.rcd.sokrateRpt')}}
-                        </span> -->
-                        <!-- 表格下载 -->
-                        <span class="e-note-tag" @click="downloadData">
-                            <Icon type="md-download" />
-                            {{$t('cusMgt.rcd.exportData')}}
-                        </span>
-                        <!-- 数据统计 -->
-                        <span class="e-note-tag" v-show="hasVideo" @click="isShowVd = !isShowVd">
-                            <Icon :type="isShowVd ? 'md-podium' : 'logo-youtube'" />
-                            {{isShowVd ? $t('cusMgt.rcd.dataCount') : $t('cusMgt.rcd.videoData')}}
-                        </span>
-                    </div>
-                </div>
+        <!--头部信息-->
+        <div class="class-record-header">
+            <span class="back-page" @click="goBack">
+                <Icon type="md-arrow-round-back" />
+                {{$t('cusMgt.rcd.rtn')}}
+            </span>
+            <span class="course-name">
+                {{recordInfo.name}}
+            </span>
+            <div style="display:inline-block;margin-left:30px">
+                <span class="label-text">
+                    {{$t('cusMgt.rcd.ctime')}}
+                </span>
+                <span class="label-value">
+                    {{$jsFn.timeFormat(recordInfo.startTime)}}
+                </span>
+            </div>
+            <div class="action-wrap">
+                <!-- 下载学生作品 -->
+                <span class="e-note-tag" @click="downloadStuWork">
+                    <Icon type="md-download" />
+                    {{$t('cusMgt.rcd.dlStuWrk')}}
+                </span>
+                <!-- 电子笔记 -->
+                <span class="e-note-tag" @click="viewENote">
+                    <Icon type="ios-paper" />
+                    {{$t('cusMgt.rcd.enote')}}
+                </span>
+                <!-- 表格下载 -->
+                <span class="e-note-tag" @click="downloadData">
+                    <Icon type="md-download" />
+                    {{$t('cusMgt.rcd.exportData')}}
+                </span>
+                <!-- 数据统计 -->
+                <span class="e-note-tag" v-show="hasVideo" @click="isShowVd = !isShowVd">
+                    <Icon :type="isShowVd ? 'md-podium' : 'logo-youtube'" />
+                    {{isShowVd ? $t('cusMgt.rcd.dataCount') : $t('cusMgt.rcd.videoData')}}
+                </span>
+            </div>
+        </div>
+        <Split v-model="split1">
+            <div slot="left" class="rcd-split-pane">
                 <!--上课内容-->
-                <div :class="isShowBar ? 'class-content mouse-over-status':'class-content'" @mousemove="isShowBar = true" @mouseleave="isShowBar = false">
-                    <!-- 暂时没有HTEX数据 -->
-                    <!-- <div class="courseware-wrap">
-                        <MyHTEXRender :page="curPage" isInner @onPageChange="getCurHTEX" url="https://teammodelstorage.blob.core.chinacloudapi.cn/0-public/testdata/%E3%80%8A%E5%9B%9B%E8%BE%B9%E5%BD%A2%E3%80%8B%E8%AF%BE%E4%BB%B6%20-%20TEST/index.json"></MyHTEXRender>
-                    </div> -->
-                    <div class="courseware-wrap">
-                        <img :src="curImg" alt="" class="course-cur-img">
-                        <div class="cus-page-wrap">
-                            <Page :total="pageList.length" :current.sync="curPage" :page-size="1" size="small" @on-change="getCurHTEX" />
+                <vuescroll>
+                    <!-- <div :class="isShowBar ? 'class-content mouse-over-status':'class-content mouse-over-status'" @mousemove="isShowBar = true" @mouseleave="isShowBar = false"> -->
+                    <div class="class-content mouse-over-status">
+                        <video-player2 v-if="hasVideo" v-show="isShowVd" @on-vd-error="videoError" class="video-player-box" :markers="markers" ref="videoPlayer" :options="playerOptions" :playsinline="true" @getCurPage="getCurPage">
+                        </video-player2>
+                        <div v-show="!isShowVd" class="video-player-box" style="padding:25px 0px">
+                            <Alert v-show="!hasVideo" class="no-video-tips" type="warning" show-icon>
+                                {{$t('cusMgt.rcd.noVideo')}}
+                            </Alert>
+                            <DataCount :rcdInfo="recordInfo"></DataCount>
                         </div>
+                        <Carousel class="courseware-wrap" loop>
+                            <CarouselItem v-for="(item) in pageList" :key="item.id">
+                                <img :src="item.img" alt="" class="course-cur-img">
+                            </CarouselItem>
+                        </Carousel>
+                        <!-- <div class="courseware-wrap">
+                            <img :src="curImg" alt="" class="course-cur-img">
+                            <div class="cus-page-wrap">
+                                <Page :total="pageList.length" :current.sync="curPage" :page-size="1" size="small" @on-change="getCurHTEX" />
+                            </div>
+                        </div> -->
                     </div>
-                    <video-player2 v-if="hasVideo" v-show="isShowVd" @on-vd-error="videoError" class="video-player-box" :markers="markers" ref="videoPlayer" :options="playerOptions" :playsinline="true" @getCurPage="getCurPage">
-                    </video-player2>
-                    <div v-show="!isShowVd" class="video-player-box" style="padding:25px 0px">
-                        <Alert v-show="!hasVideo" class="no-video-tips" type="warning" show-icon>
-                            {{$t('cusMgt.rcd.noVideo')}}
-                        </Alert>
-                        <DataCount :rcdInfo="recordInfo"></DataCount>
-                    </div>
-                </div>
-
-                <div class="cus-data-wrap">
-                    <!--课堂互动记录-->
+                </vuescroll>
+            </div>
+            <div slot="right" class="rcd-split-pane">
+                <!--课堂互动记录-->
+                <div class="top-wrap">
                     <h2 class="content-title">
                         {{$t('cusMgt.rcd.rcdLabel')}}
                     </h2>
-                    <div class="interaction-record-wrap">
-                        <!-- <div class="interaction-type-wrap">
-                            <span style="border-bottom:5px solid #efefef;display:block; margin-bottom:10px;padding-top:5px;">
-                                <Icon size="22" :class="typeIndex == 'all' ? 'type-icon-active type-icon':'type-icon'" custom="iconfont icon-basic-setting" @click="filterType('all')" :title="$t('cusMgt.rcd.allRcd')" />
-                            </span>
-                            <span :class="typeIndex == 'irs' ? 'type-icon-active type-icon':'type-icon'" style="font-size:32px;line-height:30px;" @click="filterType('irs')" :title="$t('cusMgt.rcd.qustion')">Q</span>
-                            <Icon :class="typeIndex == 'msg' ? 'type-icon-active type-icon':'type-icon'" custom="iconfont icon-message" style="font-size:24px;" @click="filterType('msg')" :title="$t('cusMgt.rcd.message')" />
-                            <Icon :class="typeIndex == 'img' ? 'type-icon-active type-icon':'type-icon'" type="ios-image" @click="filterType('img')" :title="$t('cusMgt.rcd.image')" />
-                            <Icon :class="typeIndex == 'file' ? 'type-icon-active type-icon':'type-icon'" type="md-document" @click="filterType('file')" :title="$t('cusMgt.rcd.file')" />
-                            <Icon :class="typeIndex == 'link' ? 'type-icon-active type-icon':'type-icon'" type="ios-link" @click="filterType('link')" :title="$t('cusMgt.rcd.link')" />
-                        </div> -->
-                        <!-- <vuescroll ref="datawrap"> -->
-                        <template v-for="(item,index) in pageList">
-                            <div v-if="item.pageData && item.pageData.length" class="page-item" :key="index" :id="'page'+(item.page)">
-                                <div class="page-info-wrap">
-                                    <span class="page-tag" @click="toVideo(index+1,$event)" :ref="'page'+(index+1)">
-                                        {{`${$t('cusMgt.rcd.cw')}${item.page}${$t('cusMgt.rcd.page')}`}}
-                                    </span>
-                                    <!-- 课件缩略图 -->
-                                    <!-- <img class="page-mini-img" @click="openViewer(item.img)" :src="item.img" /> -->
-                                    <!-- <Timeline style="margin-top:10px;margin-left:-5px">
+                    <div style="margin-top:5px;margin-right:3px">
+                        <RadioGroup v-model="filterType" size="small" @on-change="handleFilter">
+                            <Radio v-for="item in filterInte" :key="item.value" :label="item.value" border>
+                                {{item.text}}
+                                {{item.value == 'all' ? '' : `(${item.count})`}}
+                            </Radio>
+                        </RadioGroup>
+                    </div>
+                </div>
+                <vuescroll ref="pagewrap">
+                    <div class="cus-data-wrap">
+                        <div class="interaction-record-wrap">
+                            <template v-for="(item,index) in pageListShow">
+                                <div v-if="item.pageData && item.pageData.length" class="page-item" :key="index" :id="'page'+(item.page)">
+                                    <div class="page-info-wrap">
+                                        <span class="page-tag" @click="toVideo(index+1,$event)" :ref="'page'+(index+1)">
+                                            {{`${$t('cusMgt.rcd.cw')}${item.page}${$t('cusMgt.rcd.page')}`}}
+                                        </span>
+                                        <!-- 课件缩略图 -->
+                                        <img class="page-mini-img" @click="openViewer(item.img)" :src="item.img" />
+                                        <!-- <Timeline style="margin-top:10px;margin-left:-5px">
                                             <TimelineItem v-for="(rtItem, rtIndex) in item.pageData" :key="rtIndex +''+index">
                                                 <Icon type="md-arrow-dropright" slot="dot" />
                                                 <p class="event-tag" @click="toEvent(rtItem)">{{rtItem.eventName}}</p>
                                             </TimelineItem>
                                         </Timeline> -->
-                                </div>
-                                <div class="record-data-wrap">
-                                    <!-- <EmptyData v-if="!item.pageData.length" :textContent="$t('cusMgt.rcd.pageNoRcd')"></EmptyData> -->
-                                    <!-- <p v-if="!item.pageData.length" style="color:#c0c0c0;padding-top:4px">
-                                        {{$t('cusMgt.rcd.pageNoRcd')}}
-                                    </p> -->
-                                    <!-- <template v-else> -->
-                                    <!-- 互动数据 -->
-                                    <div v-for="event in item.pageData" :key="event.Time">
-                                        <!-- 即问即答 -->
-                                        <PopQues class="event-item" v-if="event.Event === 'PopQuesLoad' || event.Event === 'ReAtmpAnsStrt'" :evtType="event.Event" :irsData="event.data"></PopQues>
-                                        <!-- 抢权 -->
-                                        <Buzr class="event-item student-event" v-else-if="event.Event === 'BuzrAns'" :buzrData="event.data" :students="baseData.student"></Buzr>
-                                        <!-- 推送 -->
-                                        <Push class="event-item" v-else-if="event.Event === 'FastPgPush'" :pushData="event.data"></Push>
-                                        <!-- 作品收集 -->
-                                        <Receive :recordInfo="recordInfo" class="student-event event-item" v-else-if="event.Event === 'WrkSpaceLoad'" :rcvData="event.data" :students="baseData.student"></Receive>
-                                        <!-- 随机挑人 -->
-                                        <Pick class="event-item student-event" :pickData="event.data" v-else-if="event.Event === 'PickupResult'" :students="baseData.student"></Pick>
-                                        <!-- 课中评测 -->
-                                        <Exam class="event-item" :examInfo="event.data" :recordInfo="recordInfo" v-else-if="event.Event === 'SPQStrt'"></Exam>
                                     </div>
-                                    <!-- </template> -->
+                                    <div class="record-data-wrap">
+                                        <!-- 互动数据 -->
+                                        <div v-for="event in item.pageData" :key="event.Time">
+                                            <!-- 即问即答 -->
+                                            <PopQues class="event-item" v-if="event.Event === 'PopQuesLoad' || event.Event === 'ReAtmpAnsStrt'" :evtType="event.Event" :irsData="event.data"></PopQues>
+                                            <!-- 抢权 -->
+                                            <Buzr class="event-item student-event" v-else-if="event.Event === 'BuzrAns'" :buzrData="event.data" :students="baseData.student"></Buzr>
+                                            <!-- 推送 -->
+                                            <Push class="event-item" v-else-if="event.Event === 'FastPgPush'" :pushData="event.data"></Push>
+                                            <!-- 作品收集 -->
+                                            <Receive :recordInfo="recordInfo" class="student-event event-item" v-else-if="event.Event === 'WrkSpaceLoad'" :rcvData="event.data" :students="baseData.student"></Receive>
+                                            <!-- 作品回帖 -->
+                                            <WrkCmp :recordInfo="recordInfo" class="student-event event-item" v-else-if="event.Event === 'WrkCmp'" :cmpData="event.data" :students="baseData.student"></WrkCmp>
+                                            <!-- 随机挑人 -->
+                                            <Pick class="event-item student-event" :pickData="event.data" v-else-if="event.Event === 'PickupResult'" :students="baseData.student"></Pick>
+                                            <!-- 课中评测 -->
+                                            <Exam class="event-item" :examInfo="event.data" :recordInfo="recordInfo" v-else-if="event.Event === 'SPQStrt'"></Exam>
+                                        </div>
+                                    </div>
                                 </div>
-                            </div>
-                        </template>
-                        <!-- </vuescroll> -->
+                            </template>
+                        </div>
                     </div>
-                </div>
+                </vuescroll>
             </div>
-        </vuescroll>
+        </Split>
         <div v-if="openImageViewer" class="image-viewer" @click="closeViewer()">
             <Icon type="md-close" class="close-icon" @click="closeViewer()" />
             <img :src="viewUrl" class="animated fadeIn" @click.stop />
@@ -134,6 +136,7 @@
     </div>
 </template>
 <script>
+import WrkCmp from './eventchart/WrkCmp.vue'
 import PopQues from './eventchart/PopQues.vue'
 import Buzr from './eventchart/Buzr.vue'
 import Pick from './eventchart/Pick.vue'
@@ -142,12 +145,41 @@ import Exam from './eventchart/Exam.vue'
 import Receive from './eventchart/Receive.vue'
 import DataCount from './eventchart/DataCount.vue'
 import CountTo from 'vue-count-to'
+import BlobTool from '@/utils/blobTool.js'
 export default {
     components: {
-        PopQues, Pick, Push, Receive, DataCount, CountTo, Buzr, Exam
+        PopQues, Pick, Push, Receive, DataCount, CountTo, Buzr, Exam, WrkCmp
     },
     data() {
         return {
+            filterType: 'all',
+            filterInte: [
+                {
+                    value: 'all',
+                    text: this.$t('cusMgt.rcd.filter1'),
+                },
+                {
+                    value: 'FastPgPush',
+                    text: this.$t('cusMgt.rcd.filter2'),
+                    count: 0
+                },
+                {
+                    value: 'WrkSpaceLoad',
+                    text: this.$t('cusMgt.rcd.filter3'),
+                    count: 0
+                },
+                {
+                    value: 'Other',
+                    text: this.$t('cusMgt.rcd.filter4'),
+                    count: 0
+                },
+                {
+                    value: 'SPQStrt',
+                    text: this.$t('cusMgt.rcd.filter5'),
+                    count: 0
+                }
+            ],
+            split1: 0.4,
             backPage: undefined,
             baseData: {}, //base.json
             pushData: [],//push.json
@@ -163,6 +195,7 @@ export default {
             pageIds: [],
             pageEvents: [],
             pageList: [],
+            pageListShow: [],
             videoUrl: '',
             videoPoster: '',
             recordInfo: {},
@@ -200,6 +233,11 @@ export default {
         }
     },
     methods: {
+        // 下载学生作品
+        downloadStuWork() {
+            const containerClient = BlobTool.CreateBlobTool(this.recordInfo.scope)
+            containerClient.downloadFolder(`records/${this.recordInfo.id}/Clients`, this.$t('cusMgt.rcd.stuWrk'))
+        },
         //下载统计表格
         downloadData() {
             let blobInfo = this.recordInfo.scope === 'school' ? this.$store.state.user.schoolProfile : this.$store.state.user.userProfile
@@ -313,9 +351,9 @@ export default {
                 this.baseUrl = {}
             }
             let pgids = this.pageIds
-
             //这里需要判断录制开始的pageid
-            let startInfo = this.pageEvents.findLast(item => item.Event === 'EzsStartRecord')
+            // let startInfo = this.pageEvents?.findLast(item => item.Event === 'EzsStartRecord')
+            let startInfo = this._.findLast(this.pageEvents, item => item.Event === 'EzsStartRecord') //排查兼容性问题
             let startId = startInfo ? startInfo.Pgid : ''
             let startIndex = 0
             if (startId) {
@@ -345,18 +383,23 @@ export default {
                     switch (rlt) {
                         case 'irs':
                             e.data = this.irsData.find(i => i.pageID == e.Pgid)
+                            this.filterInte[3].count++
                             break
                         case 'push':
                             e.data = this.pushData.find(p => p.pageId == e.Pgid || p.pageID == e.Pgid) //数据兼容 pageId 有些大写有些小写
+                            this.filterInte[1].count++
                             break
                         case 'task':
                             e.data = this.taskData.find(t => t.pageID == e.Pgid)
+                            this.filterInte[2].count++
                             break
                         case 'timeline':
                             e.data = this._.cloneDeep(e)
+                            this.filterInte[3].count++
                             break
                         case 'exam':
                             e.data = this._.cloneDeep(e)
+                            this.filterInte[4].count++
                             break
                         default:
                             break
@@ -365,6 +408,15 @@ export default {
                 this.pageList.push(page)
             })
             console.log(this.pageList)
+            this.pageListShow = this.pageList
+            console.log('互动数据', this.pageList)
+            this.pageList.forEach((pageInfo, pageIndex) => {
+                pageInfo.pageData.forEach(e => {
+                    if (e.Event == 'WrkCmp') {
+                        console.log(pageIndex, e)
+                    }
+                })
+            })
             let pageEvent = this.pageEvents.filter(item => item.Event === 'PgJump' || item.Event === 'PgAdd' || item.Event === 'DiscussStart')
             let page = this.markers.length
             pageEvent.forEach((item, index) => {
@@ -457,8 +509,27 @@ export default {
             if (pageInfo) this.$refs.videoPlayer.player.currentTime(pageInfo.time)
         },
         //互动类型筛选
-        filterType(type) {
-            this.typeIndex = type
+        handleFilter() {
+            if (this.filterType == 'all') {
+                this.pageListShow = this.pageList
+            } else {
+                let data = this._.cloneDeep(this.pageList)
+                let events = ['FastPgPush', 'WrkSpaceLoad', 'SPQStrt',]
+                this.pageListShow = data.map(item => {
+                    if (item.pageData.length) {
+                        item.pageData = item.pageData.filter(event => {
+                            if (this.filterType == 'Other') {
+                                return !events.includes(event.Event)
+                            } else {
+                                return event.Event == this.filterType
+                            }
+                        })
+
+                    }
+                    return item
+                })
+            }
+
         },
         //查看图片
         openViewer(url) {
@@ -493,7 +564,7 @@ export default {
         this.hiTeachEvent = this.$GLOBAL.HI_TEACH_EVENT()
         //正式站先暂时不放出课中评测
         // if (this.$store.state.config.srvAdrType == 'product') {
-        //     this.$delete(this.hiTeachEvent,'SPQStrt')
+        //     this.$delete(this.hiTeachEvent, 'SPQStrt')
         // }
         this.events = Object.keys(this.hiTeachEvent)
         this.fnEvents = this.events.filter(key => this.hiTeachEvent[key].type === 'fn')
@@ -529,6 +600,13 @@ export default {
 @import "./ClassRecord.less";
 </style>
 <style>
+.top-wrap .ivu-radio-wrapper-checked {
+    color: white;
+    background: #2d8cf0;
+}
+.top-wrap .ivu-radio {
+    display: none;
+}
 .class-content .vjs-progress-holder {
     font-size: 10px !important;
 }

+ 0 - 417
TEAMModelOS/ClientApp/src/view/classrecord/ClassRecordNew.less

@@ -1,417 +0,0 @@
-@first-bgColor: #141414;
-@second-bgColor: #1b1b1b;
-@third-bgColor: #222222;
-@borderColor: #424242;
-@primary-textColor: #fff; //文本主颜色
-@second-textColor: #a5a5a5; //文本副级颜色
-@primary-fontSize: 14px;
-@second-fontSize: 15px;
-
-.class-record-container {
-    width: 100%;
-    background: #ffffff;
-    height: 100%;
-}
-
-.class-record-header {
-    width: 100%;
-    box-shadow: 0px 2px 5px #e9e9e9;
-    padding: 15px 15px;
-    background: white;
-    .course-name {
-        padding: 4px 10px;
-        margin-right: 10px;
-        border-radius: 4px;
-        font-size: 15px;
-        font-weight: bold;
-        border: 1px solid;
-    }
-
-    .record-name {
-        font-size: 18px;
-        font-weight: bold;
-        vertical-align: top;
-    }
-
-    .label-value {
-        color: #1CC0F3;
-        font-weight: bold;
-        margin-right: 30px;
-    }
-}
-
-.class-content {
-    width: 100%;
-    margin-left: 10px;
-    margin-top: 7px;
-    position: relative;
-    &:hover .cus-page-wrap {
-        opacity: 1;
-    }
-
-    &:hover .cur-page-tag {
-        opacity: 0;
-    }
-}
-.cus-data-wrap{
-    width: ~"calc(100% - 20px)";
-    margin-left: 10px;
-    margin-top: 5px;
-}
-
-.video-player-box {
-    width: ~"calc(100% - 15px)";
-    box-shadow: 0px 0px 10px 2px #d8d8d8;
-    max-height: 450px;
-    display: flex;
-    position: relative;
-    background: #fff;
-}
-.courseware-wrap {
-    width: ~"calc(100% - 15px)";
-    margin-right: 15px;
-    position: relative;
-    max-height: 450px;
-    margin-top: 10px;
-    background: #fff;
-    box-shadow: 0px 0px 10px 2px #d8d8d8;
-
-    .full-screen-icon {
-        float: right;
-        margin-right: 20px;
-        margin-top: -20px;
-        cursor: pointer;
-    }
-
-    .cur-page-tag {
-        position: absolute;
-        left: 20px;
-        bottom: 5px;
-        width: 30px;
-        height: 30px;
-        font-size: 18px;
-        line-height: 30px;
-        display: block;
-        background: rgba(255,153,0,0.9);
-        border-radius: 50%;
-        text-align: center;
-        opacity: 1;
-        transition: opacity 0.2s;
-        color: white;
-        box-shadow: 0px 0px 5px rgb(255, 153, 0);
-    }
-}
-.cus-page-wrap {
-    left: 0px;
-    bottom: 0px;
-    position: absolute;
-    padding: 6px;
-    width: 100%;
-    height: 36px;
-    background: rgba(43, 51, 63, 0.7);
-    text-align: center;
-    opacity: 1;
-    transition: opacity 1s;
-    border-right: 2px solid black;
-}
-
-.content-title {
-    border-left: 4px solid #1CC0F3;
-    padding-left: 4px;
-    line-height: 20px;
-    font-size: 20px;
-    margin-top: 10px;
-    margin-bottom: 6px;
-    margin-left: 10px;
-    width: fit-content;
-}
-.e-note-tag{
-    font-size: 14px;
-    padding: 2px 5px;
-    margin-left: 30px;
-    vertical-align: top;
-    cursor: pointer;
-    user-select: none;
-    &:hover{
-        color: #2db7f5;
-    }
-}
-
-.page-content {
-    width: 10%;
-    height: 500px;
-    border: 1px solid @borderColor;
-    background: #515558;
-    height: 450px;
-}
-
-.page-filter-icon {
-    position: absolute;
-    color: white;
-    left: -28px;
-    top: 5px;
-    cursor:pointer;
-    font-size:18px;
-}
-.page-info-item {
-    color: white;
-    border-bottom: 1px solid @borderColor;
-    padding: 5px;
-    cursor: pointer;
-    transition: background 0.2s;
-
-    &:hover {
-        background: white;
-        color: #1CC0F3;
-    }
-
-    .page-time {
-        float: right;
-    }
-}
-
-.interaction-record-wrap {
-    width: 100%;
-    box-shadow: 5px 5px 500px #f0f0f0 inset;
-    border-radius: 4px;
-    border: 1px solid #f0f0f0;
-    margin-bottom: 10px;
-    position: relative;
-    background: #f9f9f9;
-    min-height: 300px;
-}
-
-.class-file-wrap {
-    width: 100%;
-    box-shadow: 5px 5px 500px #404040 inset;
-    border-radius: 4px;
-    border: 1px solid #404040;
-    margin-bottom: 10px;
-    padding: 20px 30px 20px 30px;
-    color: white;
-    justify-content: space-between;
-    display: flex;
-    align-items: center;
-
-    li {
-        margin: 5px 0px;
-    }
-}
-
-.upload-class-file-wrap {
-    text-align: center;
-    color: #a5a5a5;
-    margin-right: 30px;
-    cursor: pointer;
-
-    &:hover {
-        color: #1cc0f3;
-    }
-
-    .upload-class-file-icon {
-        margin: auto;
-        font-size: 60px;
-    }
-
-    .upload-class-file-text {
-        display: block;
-        margin-top: 5px;
-    }
-}
-
-
-.interaction-type-wrap {
-    position: absolute;
-    padding: 10px 0px;
-    width:50px;
-    right: 0px;
-    top: 0px;
-    text-align: center;
-    background: white;
-    z-index:999;
-    box-shadow: 0px 0px 5px rgba(0,0,0,0.1);
-    .type-icon {
-        font-size: 30px;
-        // color: white;
-        display: block;
-        cursor: pointer;
-        margin-bottom: 15px;
-
-        &:hover {
-            color: #1CC0F3;
-        }
-    }
-
-    .type-icon-active {
-        color: #1cc0f3 !important;
-    }
-}
-
-.page-item {
-    display: flex;
-    position: relative;
-    // background: white;
-    padding: 10px 70px 10px 20px;
-    .record-data-wrap {
-        width: 100%;
-        // padding-left: 15px;
-        padding-top: 5px;
-        border-bottom: 1px dashed #e0e0e0;
-    }
-}
-.page-info-wrap{
-    margin-right: 10px;
-    max-width: 160px;
-    padding-right: 15px;
-    // border-right: 1px dashed #e0e0e0;
-}
-
-.page-tag {
-    border: 1px solid #1cc0f3;
-    color: #1CC0F3;
-    white-space: initial;
-    display: block;
-    font-size: 14px;
-    width: 110px;
-    padding: 3px 10px;
-    border-radius: 4px;
-    cursor: pointer;
-    height: fit-content;
-    transition: background 0.3s;
-    margin-right: 10px;
-    user-select: none;
-    &:hover{
-        color: white;
-        background: #1cc0f3;
-    }
-}
-.page-mini-img{
-    width:110px;
-    margin-top:10px;
-    cursor:pointer;
-    border: 1px solid transparent;
-    border-color:#cccccc;
-}
-
-
-.question-time {
-    position: absolute;
-    left: 0px;
-    bottom: -24px;
-}
-
-.image-viewer {
-    background-color: rgba(0, 0, 0, 0.8);
-    z-index: 9999;
-    width: 100%;
-    height: 100%;
-    position: fixed;
-    top: 0;
-    left: 0;
-    overflow-y: scroll;
-    overflow-x: hidden;
-    text-align: center;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-}
-.htex-viewer {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    align-items: center;
-
-    .viewer-page-wrap {
-        margin-top: -36px;
-        z-index: 99;
-        padding: 6px;
-        color: white;
-        width: 100%;
-        background: rgba(43, 51, 63, 0.7);
-    }
-}
-
-.image-viewer img {
-    max-width: 50%;
-    margin: 5% auto;
-    border: none;
-}
-.close-icon {
-    position: absolute;
-    right: 15px;
-    top: 10px;
-    font-size: 20px;
-    color: white;
-    cursor: pointer;
-}
-.course-cur-img{
-    max-width: 100%;
-    max-height: 100%;
-}
-.sokrate-report{
-    max-width: 100%;
-    max-height: 100%;
-    margin: auto;
-    display: block;
-}
-.event-tag{
-    display: inline-block;
-    background: white;
-    padding: 1px 8px;
-    margin-bottom: 5px;
-    border: 1px solid #e0e0e0;
-    margin-left: -5px;
-    color: #555555;
-    cursor: pointer;
-    font-size: 12px;
-    &:hover{
-        background: #2db7f5;
-        color: white;
-    }
-}
-.page-event-list{
-
-}
-.event-item{
-    border: 1px dashed transparent;
-    margin-bottom: 15px;
-    padding: 5px 5px;
-}
-.event-item:hover{
-    border: 1px dashed #e0e0e0;
-}
-.student-event{
-    display: flex;
-    justify-content: end;
-}
-.back-page{
-    margin-right: 15px;
-    cursor: pointer;
-    user-select: none;
-}
-.action-wrap{
-    float: right;
-
-}
-.toggle-view{
-    position: absolute;
-    right: 5px;
-    bottom: 5px;
-    z-index: 9999;
-}
-.no-video-tips{
-    position: absolute;
-    top: 0px;
-    left: 0px;
-    color: #ff9900;
-    width: 100%;
-}
-.rcd-split-pane{
-    height: ~"calc(100% - 60px)";
-}
-.top-wrap{
-    width: 100%;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-}

+ 0 - 635
TEAMModelOS/ClientApp/src/view/classrecord/ClassRecordNew.vue

@@ -1,635 +0,0 @@
-<template>
-    <div class="class-record-container">
-        <!--头部信息-->
-        <div class="class-record-header">
-            <span class="back-page" @click="goBack">
-                <Icon type="md-arrow-round-back" />
-                {{$t('cusMgt.rcd.rtn')}}
-            </span>
-            <span class="course-name">
-                {{recordInfo.name}}
-            </span>
-            <div style="display:inline-block;margin-left:30px">
-                <span class="label-text">
-                    {{$t('cusMgt.rcd.ctime')}}
-                </span>
-                <span class="label-value">
-                    {{$jsFn.timeFormat(recordInfo.startTime)}}
-                </span>
-            </div>
-            <div class="action-wrap">
-                <!-- 下载学生作品 -->
-                <span class="e-note-tag" @click="downloadStuWork">
-                    <Icon type="md-download" />
-                    {{$t('cusMgt.rcd.dlStuWrk')}}
-                </span>
-                <!-- 电子笔记 -->
-                <span class="e-note-tag" @click="viewENote">
-                    <Icon type="ios-paper" />
-                    {{$t('cusMgt.rcd.enote')}}
-                </span>
-                <!-- 表格下载 -->
-                <span class="e-note-tag" @click="downloadData">
-                    <Icon type="md-download" />
-                    {{$t('cusMgt.rcd.exportData')}}
-                </span>
-                <!-- 数据统计 -->
-                <span class="e-note-tag" v-show="hasVideo" @click="isShowVd = !isShowVd">
-                    <Icon :type="isShowVd ? 'md-podium' : 'logo-youtube'" />
-                    {{isShowVd ? $t('cusMgt.rcd.dataCount') : $t('cusMgt.rcd.videoData')}}
-                </span>
-            </div>
-        </div>
-        <Split v-model="split1">
-            <div slot="left" class="rcd-split-pane">
-                <!--上课内容-->
-                <vuescroll>
-                    <!-- <div :class="isShowBar ? 'class-content mouse-over-status':'class-content mouse-over-status'" @mousemove="isShowBar = true" @mouseleave="isShowBar = false"> -->
-                    <div class="class-content mouse-over-status">
-                        <video-player2 v-if="hasVideo" v-show="isShowVd" @on-vd-error="videoError" class="video-player-box" :markers="markers" ref="videoPlayer" :options="playerOptions" :playsinline="true" @getCurPage="getCurPage">
-                        </video-player2>
-                        <div v-show="!isShowVd" class="video-player-box" style="padding:25px 0px">
-                            <Alert v-show="!hasVideo" class="no-video-tips" type="warning" show-icon>
-                                {{$t('cusMgt.rcd.noVideo')}}
-                            </Alert>
-                            <DataCount :rcdInfo="recordInfo"></DataCount>
-                        </div>
-                        <div class="courseware-wrap">
-                            <img :src="curImg" alt="" class="course-cur-img">
-                            <div class="cus-page-wrap">
-                                <Page :total="pageList.length" :current.sync="curPage" :page-size="1" size="small" @on-change="getCurHTEX" />
-                            </div>
-                        </div>
-                    </div>
-                </vuescroll>
-            </div>
-            <div slot="right" class="rcd-split-pane">
-                <!--课堂互动记录-->
-                <div class="top-wrap">
-                    <h2 class="content-title">
-                        {{$t('cusMgt.rcd.rcdLabel')}}
-                    </h2>
-                    <div style="margin-top:5px;margin-right:3px">
-                        <RadioGroup v-model="filterType" size="small" @on-change="handleFilter">
-                            <Radio v-for="item in filterInte" :key="item.value" :label="item.value" border>
-                                {{item.text}}
-                                {{item.value == 'all' ? '' : `(${item.count})`}}
-                            </Radio>
-                        </RadioGroup>
-                    </div>
-                </div>
-                <vuescroll ref="pagewrap">
-                    <div class="cus-data-wrap">
-                        <div class="interaction-record-wrap">
-                            <template v-for="(item,index) in pageListShow">
-                                <div v-if="item.pageData && item.pageData.length" class="page-item" :key="index" :id="'page'+(item.page)">
-                                    <div class="page-info-wrap">
-                                        <span class="page-tag" @click="toVideo(index+1,$event)" :ref="'page'+(index+1)">
-                                            {{`${$t('cusMgt.rcd.cw')}${item.page}${$t('cusMgt.rcd.page')}`}}
-                                        </span>
-                                        <!-- 课件缩略图 -->
-                                        <img class="page-mini-img" @click="openViewer(item.img)" :src="item.img" />
-                                        <!-- <Timeline style="margin-top:10px;margin-left:-5px">
-                                            <TimelineItem v-for="(rtItem, rtIndex) in item.pageData" :key="rtIndex +''+index">
-                                                <Icon type="md-arrow-dropright" slot="dot" />
-                                                <p class="event-tag" @click="toEvent(rtItem)">{{rtItem.eventName}}</p>
-                                            </TimelineItem>
-                                        </Timeline> -->
-                                    </div>
-                                    <div class="record-data-wrap">
-                                        <!-- 互动数据 -->
-                                        <div v-for="event in item.pageData" :key="event.Time">
-                                            <!-- 即问即答 -->
-                                            <PopQues class="event-item" v-if="event.Event === 'PopQuesLoad' || event.Event === 'ReAtmpAnsStrt'" :evtType="event.Event" :irsData="event.data"></PopQues>
-                                            <!-- 抢权 -->
-                                            <Buzr class="event-item student-event" v-else-if="event.Event === 'BuzrAns'" :buzrData="event.data" :students="baseData.student"></Buzr>
-                                            <!-- 推送 -->
-                                            <Push class="event-item" v-else-if="event.Event === 'FastPgPush'" :pushData="event.data"></Push>
-                                            <!-- 作品收集 -->
-                                            <Receive :recordInfo="recordInfo" class="student-event event-item" v-else-if="event.Event === 'WrkSpaceLoad'" :rcvData="event.data" :students="baseData.student"></Receive>
-                                            <!-- 作品回帖 -->
-                                            <WrkCmp :recordInfo="recordInfo" class="student-event event-item" v-else-if="event.Event === 'WrkCmp'" :cmpData="event.data" :students="baseData.student"></WrkCmp>
-                                            <!-- 随机挑人 -->
-                                            <Pick class="event-item student-event" :pickData="event.data" v-else-if="event.Event === 'PickupResult'" :students="baseData.student"></Pick>
-                                            <!-- 课中评测 -->
-                                            <Exam class="event-item" :examInfo="event.data" :recordInfo="recordInfo" v-else-if="event.Event === 'SPQStrt'"></Exam>
-                                        </div>
-                                    </div>
-                                </div>
-                            </template>
-                        </div>
-                    </div>
-                </vuescroll>
-            </div>
-        </Split>
-        <div v-if="openImageViewer" class="image-viewer" @click="closeViewer()">
-            <Icon type="md-close" class="close-icon" @click="closeViewer()" />
-            <img :src="viewUrl" class="animated fadeIn" @click.stop />
-        </div>
-        <!--返回顶部-->
-        <BackToTop @on-to-top="handleToTop"></BackToTop>
-    </div>
-</template>
-<script>
-import WrkCmp from './eventchart/WrkCmp.vue'
-import PopQues from './eventchart/PopQues.vue'
-import Buzr from './eventchart/Buzr.vue'
-import Pick from './eventchart/Pick.vue'
-import Push from './eventchart/Push.vue'
-import Exam from './eventchart/Exam.vue'
-import Receive from './eventchart/Receive.vue'
-import DataCount from './eventchart/DataCount.vue'
-import CountTo from 'vue-count-to'
-import BlobTool from '@/utils/blobTool.js'
-export default {
-    components: {
-        PopQues, Pick, Push, Receive, DataCount, CountTo, Buzr, Exam, WrkCmp
-    },
-    data() {
-        return {
-            filterType: 'all',
-            filterInte: [
-                {
-                    value: 'all',
-                    text: this.$t('cusMgt.rcd.filter1'),
-                },
-                {
-                    value: 'FastPgPush',
-                    text: this.$t('cusMgt.rcd.filter2'),
-                    count: 0
-                },
-                {
-                    value: 'WrkSpaceLoad',
-                    text: this.$t('cusMgt.rcd.filter3'),
-                    count: 0
-                },
-                {
-                    value: 'Other',
-                    text: this.$t('cusMgt.rcd.filter4'),
-                    count: 0
-                },
-                {
-                    value: 'SPQStrt',
-                    text: this.$t('cusMgt.rcd.filter5'),
-                    count: 0
-                }
-            ],
-            split1: 0.4,
-            backPage: undefined,
-            baseData: {}, //base.json
-            pushData: [],//push.json
-            irsData: [],//irs.json
-            taskData: [],//task.json
-            fnEvents: [],//功能事件
-            events: [],//事件ID
-            hiTeachEvent: [],//需要解析的事件信息
-            isShowVd: true,
-            hasVideo: true,
-            eventClick: false,
-            sokratesRecords: [],
-            pageIds: [],
-            pageEvents: [],
-            pageList: [],
-            pageListShow: [],
-            videoUrl: '',
-            videoPoster: '',
-            recordInfo: {},
-            isShowBar: false,
-            markers: [],
-            curPage: 1,
-            viewUrl: '',
-            openImageViewer: false,
-            typeIndex: 'all',
-            playerOptions: {
-                height: "450px",
-                controlBar: {
-                    durationDisplay: true, // 总时间
-                    currentTimeDisplay: true,
-
-                },
-                html5: {
-                    nativeControlsForTouch: true,
-                },
-                inactivityTimeout: 1,
-                nativeVideoTracks: false,
-                playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度
-                autoplay: true, //如果true,浏览器准备好时开始回放。
-                controls: true, //控制条
-                preload: 'auto', //视频预加载
-                muted: false, //默认情况下将会消除任何音频。
-                loop: false, //导致视频一结束就重新开始。
-                language: 'zh-CN',
-                //aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
-                //fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
-                sources: [],
-                poster: '',
-                // notSupportedMessage: '此视频暂无法播放,请稍后再试' //允许覆盖Video.js无法播放媒体源时显示的默认信息。
-            }
-        }
-    },
-    methods: {
-        // 下载学生作品
-        downloadStuWork() {
-            const containerClient = BlobTool.CreateBlobTool(this.recordInfo.scope)
-            containerClient.downloadFolder(`records/${this.recordInfo.id}/Clients`, this.$t('cusMgt.rcd.stuWrk'))
-        },
-        //下载统计表格
-        downloadData() {
-            let blobInfo = this.recordInfo.scope === 'school' ? this.$store.state.user.schoolProfile : this.$store.state.user.userProfile
-            let url = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/summary.xlsx?${blobInfo.blob_sas}`
-            const downloadRes = async () => {
-                let response = await fetch(url); // 内容转变成blob地址
-                let blob = await response.blob(); // 创建隐藏的可下载链接
-                let objectUrl = window.URL.createObjectURL(blob);
-                let a = document.createElement('a');
-                a.href = objectUrl;
-                a.download = this.recordInfo.name + '.xlsx';
-                a.click()
-                a.remove();
-            }
-            downloadRes();
-        },
-        videoError() {
-            this.hasVideo = false
-            this.isShowVd = false
-        },
-        //返回顶部
-        handleToTop() {
-            this.$refs['pagewrap'].scrollTo(
-                {
-                    y: '0'
-                },
-                300
-            )
-            // this.$refs['datawrap'].scrollTo(
-            //     {
-            //         y: '0'
-            //     },
-            //     300
-            // )
-        },
-        //跳转到对应事件
-        toEvent(event) {
-            console.log(event)
-            this.eventClick = true
-            this.curPage = event.pageIndex + 1
-            this.$refs.videoPlayer.player.currentTime(event.Time)
-        },
-        //根据SokratesRecords.json处理page数据
-        async getPageList() {
-            this.pageList = []
-            this.markers = []
-            let blobInfo = this.recordInfo.scope == 'school' ? this.$store.state.user.schoolProfile : this.$store.state.user.userProfile
-            // 这里需要兼容原来没有TimeLine.json的课例(优先度读timeLine.json,没有则读SokratesRecords.json)
-            let url = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/TimeLine.json?${blobInfo.blob_sas}`
-            let hasTimeLine = true
-            let dataErr = false
-            try {
-                let res = await this.$tools.getFile(url)
-                this.sokratesRecords = JSON.parse(res)
-                this.pageIds = this.sokratesRecords.PgIdList || []
-                this.pageEvents = this.sokratesRecords.events || []
-            } catch (e) {
-                hasTimeLine = false
-            }
-            //读取 timeLine.json 失败,则读取 SokratesRecords.json
-            if (!hasTimeLine) {
-                url = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/Sokrates/SokratesRecords.json?${blobInfo.blob_sas}`
-                try {
-                    let res = await this.$tools.getFile(url)
-                    let resJson = JSON.parse(res)
-                    // 处理成timeLine数据格式
-                    let pageidEvent = resJson.find(item => item.Event == 'PgidList')
-                    this.pageIds = pageidEvent && pageidEvent.PgIdList ? pageidEvent.PgIdList : []
-                    this.pageEvents = resJson.filter(item => this.events.includes(item.Event))
-                    this.sokratesRecords = {
-                        events: this.pageEvents,
-                        PgIdList: this.pageIds
-                    }
-                } catch (e) {
-                    //timeLine 和 SokratesRecords都没有找到
-                    dataErr = true
-                }
-            }
-            // 数据异常
-            if (dataErr) {
-                this.$Message.error(this.$t('cusMgt.rcd.dataErr'))
-                return
-            }
-            // 时间轴数据正常
-            //获取Push.json、IRS.json、Task.json、Base.json数据
-            try {
-                let pushUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/Push.json?${blobInfo.blob_sas}`
-                this.pushData = JSON.parse(await this.$tools.getFile(pushUrl) || '[]')
-                this.pushData.forEach(item => {
-                    item.pageUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}${item.pageMeta}?${blobInfo.blob_sas}`
-                })
-            } catch (e) {
-                this.pushData = []
-            }
-            try {
-                let irsUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/IRS.json?${blobInfo.blob_sas}`
-                this.irsData = JSON.parse(await this.$tools.getFile(irsUrl) || '[]')
-            } catch (e) {
-                this.irsData = []
-            }
-            try {
-                let taskUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/Task.json?${blobInfo.blob_sas}`
-                this.taskData = JSON.parse(await this.$tools.getFile(taskUrl) || '[]')
-            } catch (e) {
-                this.taskData = []
-            }
-            try {
-                let baseUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/IES/base.json?${blobInfo.blob_sas}`
-                this.baseData = JSON.parse(await this.$tools.getFile(baseUrl) || '{}')
-            } catch (e) {
-                this.baseUrl = {}
-            }
-            let pgids = this.pageIds
-            //这里需要判断录制开始的pageid
-            // let startInfo = this.pageEvents?.findLast(item => item.Event === 'EzsStartRecord')
-            let startInfo = this._.findLast(this.pageEvents, item => item.Event === 'EzsStartRecord') //排查兼容性问题
-            let startId = startInfo ? startInfo.Pgid : ''
-            let startIndex = 0
-            if (startId) {
-                startIndex = pgids.findIndex(item => item === startId)
-                //第一页视频标记
-                this.markers.push({
-                    time: startInfo.Time,
-                    text: `${this.$t('cusMgt.rcd.di')}${1}${this.$t('cusMgt.rcd.page')}`,
-                    page: 1
-                })
-            }
-            if (startIndex > -1) {
-                pgids = pgids.slice(startIndex)
-            }
-            pgids.forEach((item, index) => {
-                let page = {}
-                page.id = item
-                page.img = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/Memo/${item}.jpg?${blobInfo.blob_sas}`
-                page.page = index + 1
-                //当前页面对应的功能事件
-                page.pageData = this.pageEvents.filter(record => record.Pgid === item && this.fnEvents.includes(record.Event))
-                page.pageData.forEach(e => {
-                    e.pageIndex = index
-                    e.eventName = this.hiTeachEvent[e.Event]?.text
-                    let rlt = this.hiTeachEvent[e.Event]?.relation
-                    e.relation = rlt
-                    switch (rlt) {
-                        case 'irs':
-                            e.data = this.irsData.find(i => i.pageID == e.Pgid)
-                            this.filterInte[3].count++
-                            break
-                        case 'push':
-                            e.data = this.pushData.find(p => p.pageId == e.Pgid || p.pageID == e.Pgid) //数据兼容 pageId 有些大写有些小写
-                            this.filterInte[1].count++
-                            break
-                        case 'task':
-                            e.data = this.taskData.find(t => t.pageID == e.Pgid)
-                            this.filterInte[2].count++
-                            break
-                        case 'timeline':
-                            e.data = this._.cloneDeep(e)
-                            this.filterInte[3].count++
-                            break
-                        case 'exam':
-                            e.data = this._.cloneDeep(e)
-                            this.filterInte[4].count++
-                            break
-                        default:
-                            break
-                    }
-                })
-                this.pageList.push(page)
-            })
-            console.log(this.pageList)
-            this.pageListShow = this.pageList
-            console.log('互动数据', this.pageList)
-            this.pageList.forEach((pageInfo, pageIndex) => {
-                pageInfo.pageData.forEach(e => {
-                    if (e.Event == 'WrkCmp') {
-                        console.log(pageIndex, e)
-                    }
-                })
-            })
-            let pageEvent = this.pageEvents.filter(item => item.Event === 'PgJump' || item.Event === 'PgAdd' || item.Event === 'DiscussStart')
-            let page = this.markers.length
-            pageEvent.forEach((item, index) => {
-                if (item.JumpTo != item.JumpFrom) {
-                    this.markers.push({
-                        time: item.Time,
-                        text: `${this.$t('cusMgt.rcd.di')}${item.JumpTo}${this.$t('cusMgt.rcd.page')}`,
-                        page: item.JumpTo
-                    })
-                }
-            })
-            console.log(this.markers)
-        },
-        //查看苏格拉底报告
-        viewReport() {
-            this.$router.push({
-                name: 'teachCenter',
-                query: { id: this.recordInfo.id, name: this.recordInfo.name }
-            })
-        },
-        //查看电子笔记
-        viewENote() {
-            let eNote
-            if (this.recordInfo.eNote) {
-                eNote = this.recordInfo.eNote
-            } else {
-                let sasInfo = {}
-                let blobInfo = this.recordInfo.scope === 'school' ? this.$store.state.user.schoolProfile : this.$store.state.user.userProfile
-                sasInfo.sas = '?' + blobInfo.blob_sas
-                sasInfo.name = this.recordInfo.scope ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
-                sasInfo.url = blobInfo.blob_uri.slice(0, blobInfo.blob_uri.lastIndexOf(sasInfo.name) - 1)
-
-                eNote = `${sasInfo.url}/${sasInfo.name}/records/${this.recordInfo.id}/Note.pdf${sasInfo.sas}`
-            }
-            window.open('/web/viewer.html?file=' + encodeURIComponent(eNote))
-            // if (this.recordInfo.eNote) {
-            // } else {
-            //     this.$Message.warning(this.$t('cusMgt.rcd.noNote'))
-            // }
-        },
-        //点击视频切片
-        getCurPage(page) {
-            // this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
-            this.$refs.videoPlayer.player.play()
-            this.curPage = page
-        },
-        //点击课件page
-        getCurHTEX(page) {
-            if (this.eventClick) {
-                this.eventClick = false
-                return
-            }
-            //视频时间定位
-            let pageInfo = this.markers.find(item => {
-                return item.page == page
-            })
-            if (pageInfo) {
-                this.$refs.videoPlayer.player.currentTime(pageInfo.time)
-                this.$refs.videoPlayer.player.play()
-            } else {
-                // this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
-            }
-        },
-        //点击互动记录页面tag
-        toVideo(page, e) {
-            //页面滚动
-            // let dataLoacation = this.$refs["datawrap"].getPosition()
-            // let pageLocaltion = this.$refs["pagewrap"].getPosition()
-            // let y = e.pageY - 665 + pageLocaltion.scrollTop + dataLoacation.scrollTop
-            this.$nextTick(() => {
-                // this.$refs["pagewrap"].scrollTo(
-                //     {
-                //         x: 0,
-                //         y: 0
-                //     }
-                // )
-                // this.$refs["datawrap"].scrollTo(
-                //     {
-                //         x: 0,
-                //         y: y
-                //     }
-                // )
-            })
-            // 教材页面切换
-            this.curPage = page
-            //视频时间定位
-            let pageInfo = this.markers.find(item => {
-                return item.page == page
-            })
-            if (pageInfo) this.$refs.videoPlayer.player.currentTime(pageInfo.time)
-        },
-        //互动类型筛选
-        handleFilter() {
-            if (this.filterType == 'all') {
-                this.pageListShow = this.pageList
-            } else {
-                let data = this._.cloneDeep(this.pageList)
-                let events = ['FastPgPush', 'WrkSpaceLoad', 'SPQStrt',]
-                this.pageListShow = data.map(item => {
-                    if (item.pageData.length) {
-                        item.pageData = item.pageData.filter(event => {
-                            if (this.filterType == 'Other') {
-                                return !events.includes(event.Event)
-                            } else {
-                                return event.Event == this.filterType
-                            }
-                        })
-
-                    }
-                    return item
-                })
-            }
-
-        },
-        //查看图片
-        openViewer(url) {
-            this.openImageViewer = true
-            this.viewUrl = url
-        },
-        closeViewer() {
-            this.openImageViewer = false
-        },
-        goBack() {
-            if (this.backPage) {
-                this.$router.push({
-                    name: this.backPage,
-                    record: this.recordInfo
-                })
-            } else {
-                this.$router.go(-1)
-            }
-        },
-    },
-    computed: {
-        curImg() {
-            console.log(this.curPage)
-            if (this.pageList[this.curPage - 1]) {
-                return this.pageList[this.curPage - 1].img
-            } else {
-                return ""
-            }
-        }
-    },
-    created() {
-        this.hiTeachEvent = this.$GLOBAL.HI_TEACH_EVENT()
-        //正式站先暂时不放出课中评测
-        if (this.$store.state.config.srvAdrType == 'product') {
-            this.$delete(this.hiTeachEvent, 'SPQStrt')
-        }
-        this.events = Object.keys(this.hiTeachEvent)
-        this.fnEvents = this.events.filter(key => this.hiTeachEvent[key].type === 'fn')
-        console.log(this.events, this.fnEvents)
-        this.recordInfo = this.$route.params.record || (sessionStorage.getItem('record') ? JSON.parse(sessionStorage.getItem('record')) : undefined)
-        console.log(this.recordInfo)
-        if (!this.recordInfo) {
-            this.$router.go(-1)
-        } else {
-            sessionStorage.setItem('record', JSON.stringify(this.recordInfo))
-            //对接Blob数据
-            let blobInfo = this.recordInfo.scope == 'school' ? this.$store.state.user.schoolProfile : this.$store.state.user.userProfile
-            this.videoUrl = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/Record/CourseRecord.mp4?${blobInfo.blob_sas}`
-            this.videoPoster = `${blobInfo.blob_uri}/records/${this.recordInfo.id}/Record/CoverImage.jpg?${blobInfo.blob_sas}`
-            this.playerOptions.poster = this.videoPoster
-            this.playerOptions.sources.push({
-                src: this.videoUrl
-            })
-            this.getPageList()
-        }
-    },
-    beforeRouteEnter(to, from, next) {
-        next(vm => {
-            if (from.name == 'homePage') {
-                vm.backPage = 'course'
-            }
-            console.log(arguments)
-        })
-    }
-}
-</script>
-<style lang="less" scoped>
-@import "./ClassRecordNew.less";
-</style>
-<style>
-.top-wrap .ivu-radio-wrapper-checked {
-    color: white;
-    background: #2d8cf0;
-}
-.top-wrap .ivu-radio {
-    display: none;
-}
-.class-content .vjs-progress-holder {
-    font-size: 10px !important;
-}
-.mouse-over-status .vjs-control-bar {
-    opacity: 1 !important;
-}
-
-.class-content .video-js .vjs-big-play-button {
-    top: 50%;
-    left: 50%;
-    margin-left: -40px;
-    margin-top: -20px;
-}
-
-.class-content .vjs_video_3-dimensions.vjs-fluid {
-    padding-top: 0;
-}
-
-.class-content .video-js.vjs-fluid,
-.class-content .video-js.vjs-16-9,
-.class-content .video-js.vjs-4-3 {
-    height: 450px;
-}
-
-.video-player-box .vjs-volume-panel {
-    display: none;
-}
-.video-player-box .ivu-alert {
-    border-radius: 0px;
-}
-</style>

+ 2 - 1
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/DataCount.vue

@@ -116,7 +116,8 @@ export default {
     padding: 20px 0px;
 }
 .data-item {
-    width: 150px;
+    min-width: 19%;
+    width: fit-content;
     padding: 10px;
     text-align: center;
 }

+ 10 - 2
TEAMModelOS/ClientApp/src/view/classrecord/eventchart/Receive.vue

@@ -43,7 +43,7 @@
                 {{ item.isGroupItem ? item.groupID : item.sName}}
             </p>
         </div>
-        <StudentClient></StudentClient>
+        <StudentClient class="receive-student"></StudentClient>
         <!--文件预览-->
         <div v-if="previewStatus" class="image-viewer">
             <div style="width:fit-content;position:relative;margin:auto;">
@@ -184,7 +184,7 @@ export default {
 }
 </script>
 <style lang="less" scoped>
-.name-text{
+.name-text {
     color: #2d8cf0;
 }
 .image-viewer {
@@ -214,7 +214,15 @@ export default {
     }
 }
 .receive-wrap {
+    position: relative;
+    padding-right: 45px !important;
     display: flex;
+    flex-wrap: wrap;
+}
+.receive-student {
+    position: absolute;
+    right: 0px;
+    top: 5px;
 }
 .receive-item {
     margin-right: 20px;

+ 6 - 6
TEAMModelOS/ClientApp/src/view/mgtPlatform/MgtPlatform.vue

@@ -18,7 +18,7 @@
                 </div>
                 <!-- 校级资源平台 -->
                 <div class="platform-item" v-for="(item,index) in schoolPlatform.links" :key="item.name" @click="openSchoolPlatform(index)">
-                    <span class="delete-platform-icon" @click.stop="delSchoolPlatform(index)" v-show="!isArea">
+                    <span class="delete-platform-icon" @click.stop="delSchoolPlatform(index)" v-show="!isArea" v-if="$access.can('admin.*|link-upd')">
                         <Icon type="md-close" class="add-member-icon" />
                     </span>
                     <span class="platform-type-label school-type">
@@ -30,7 +30,7 @@
                         {{item.name}}
                     </p>
                 </div>
-                <div class="add-platform-box" @click="addStatus = true" v-show="areaPlatform.links.length || schoolPlatform.links.length">
+                <div class="add-platform-box" @click="addStatus = true" v-show="areaPlatform.links.length || schoolPlatform.links.length" v-if="$access.can('admin.*|link-upd')">
                     <Icon type="md-add-circle" class="add-platform-icon" />
                     <p class="add-platform-text">{{$t('platform.addPlatform')}}</p>
                 </div>
@@ -122,12 +122,12 @@ export default {
     },
     methods: {
         openAreaPlatform(index) {
-			let url = this.areaPlatform.links[index].url
-			window.open(/^(http:|https:)/i.test(url) ? url : "http://" + url)
+            let url = this.areaPlatform.links[index].url
+            window.open(/^(http:|https:)/i.test(url) ? url : "http://" + url)
         },
         openSchoolPlatform(index) {
-			let url = this.schoolPlatform.links[index].url
-			window.open(/^(http:|https:)/i.test(url) ? url : "http://" + url)
+            let url = this.schoolPlatform.links[index].url
+            window.open(/^(http:|https:)/i.test(url) ? url : "http://" + url)
         },
         delAreaPlatform(index) {
             this.$Modal.confirm({

+ 33 - 52
TEAMModelOS/ClientApp/src/view/mycourse/MyCourse.vue

@@ -61,7 +61,7 @@
                                 </p>
                                 <p class="class-attr-item">
                                     <span class="attr-label">{{$t('cusMgt.stuCount')}}</span>
-                                    <span class="class-name">{{item.allStu ? item.allStu.length : 0}}{{$t('unit.text7')}}</span>
+                                    <span class="class-name">{{getStudenCount(item.classId || item.stulist)}}{{$t('unit.text7')}}</span>
                                 </p>
                                 <Icon size="25" custom="iconfont icon-qr-code" :class="['qr-code-icon', {'qr-code-icon-color': item.joinLock}]" @click="showQrCode(index)" v-if="listType == 'private'" :title="$t('cusMgt.qrCodeLabel')" />
                                 <span v-if="hasCCAuth" :class="['qr-code-icon', hasCCAuth ? 'cc-icon' : 'no-cc-auth']" @click="startCus(index)">
@@ -203,10 +203,6 @@
                 <Button :loading="btnLoading" @click="confirmCreateList" long type="primary" class="confirm-btn">{{ $t('syllabus.confirm') }}</Button>
             </div>
         </Modal>
-        <!-- 开始上课 -->
-        <!-- <div class="start-course">
-            开始<br />上课
-        </div> -->
         <!-- 创建名单 -->
         <Modal v-model="startCusStatus" width="600" footer-hide className="ed-name-modal">
             <div slot="header" class="modal-header">
@@ -312,6 +308,7 @@ export default {
             courseList: [],
             filterPeriod: '',
             courseInfo: {},
+            courseGroupList: [],//当前课程所有名单详细信息
             courseTypeClass: ['course-list-wrap'],
             addCusInfo: {
                 name: '',
@@ -342,7 +339,7 @@ export default {
         },
         curStuList() {
             let groupId = this.teaClassList[this.curClassIndex]?.classId || this.teaClassList[this.curClassIndex]?.stulist
-            return this.groupList.find(item => item.id == groupId)
+            return this.courseGroupList.find(item => item.id == groupId)
         },
         rcdParams() {
             return {
@@ -352,12 +349,14 @@ export default {
             }
         },
         students() {
-            if (this.teaClassList && this.teaClassList[this.curClassIndex]) {
-                return this.teaClassList[this.curClassIndex].allStu
-            }
-            else {
-                return []
+            if (this.teaClassList) {
+                let groupId = this.teaClassList[this.curClassIndex]?.classId || this.teaClassList[this.curClassIndex]?.stulist
+                if (groupId) {
+                    let group = this.courseGroupList.find(item => item.id == groupId)
+                    if (group) return group.members
+                }
             }
+            return
         },
         //查询成绩表所需参数(courseId, cId, code)
         gradeParams() {
@@ -369,7 +368,7 @@ export default {
             return data
         },
         courseListShow() {
-            if(!this.courseList.length){
+            if (!this.courseList.length) {
                 return []
             }
             let data = this.courseList.filter(item => item.scope == this.listType)
@@ -379,9 +378,9 @@ export default {
             if (data.length) {
                 return data
             } else {
-                setTimeout(()=>{
+                setTimeout(() => {
                     this.listType = this.listType == 'school' ? 'private' : 'school'
-                },100)
+                }, 100)
                 return []
             }
         },
@@ -421,6 +420,13 @@ export default {
         }, 100)
     },
     methods: {
+        getStudenCount(groupId) {
+            let group = this.courseGroupList.find(item => item.id == groupId)
+            if (group) {
+                return group.members?.length || 0
+            }
+            return 0
+        },
         setSourceId(value, selectData) {
             console.log(value)
             if (value.length) {
@@ -526,13 +532,7 @@ export default {
         },
         onSetIrs(data) {
             let { students } = data
-            // 当前名单对应的课程安排用于更新UI
-            let schedule = this.courseInfo.schedule.find(item => {
-                return item.stulist == this.teaClassList[this.curClassIndex].stulist && !item.classId //只有自定名单能添加学生,所以classId == ‘’
-            })
-            schedule.allStu = students
-
-            let stulist = this.groupList.find(item => {
+            let stulist = this.courseGroupList.find(item => {
                 return item.id == this.teaClassList[this.curClassIndex].stulist
             })
             if (stulist) {
@@ -540,7 +540,7 @@ export default {
             }
         },
         agreeJoinClass(status) {
-            let stulist = this.groupList.find(item => {
+            let stulist = this.courseGroupList.find(item => {
                 return item.id == this.teaClassList[this.curClassIndex].stulist
             })
             if (stulist) {
@@ -566,7 +566,7 @@ export default {
             let listId = this.teaClassList[index].stulist
             let listName = this.teaClassList[index].listName
             let cusName = this.courseInfo.name
-            let stulistInfo = this.groupList.find(item => {
+            let stulistInfo = this.courseGroupList.find(item => {
                 return item.id == listId
             })
             if (stulistInfo) {
@@ -635,6 +635,7 @@ export default {
                             students: s.students,
                             time: []
                         })
+                        this.courseListShow.push(this._.cloneDeep(s))
                         this.btnLoading = true
                         this.updCusInfo()
                         this.listId = ''
@@ -691,7 +692,7 @@ export default {
         },
         confirmRename() {
             let stulistId = this.teaClassList[this.curClassIndex].stulist
-            let stulist = this.groupList.find(item => {
+            let stulist = this.courseGroupList.find(item => {
                 return item.id == this.teaClassList[this.curClassIndex].stulist
             })
             if (stulist) {
@@ -999,13 +1000,15 @@ export default {
                         try {
                             //新版名单API支持同时获取各种名单详细信息
                             let allGroupIds = ids.concat(classIds)
+                            this.courseGroupList = []
                             if (allGroupIds.length) {
                                 let allGroups = await this.getListInfo([...allGroupIds])
                                 if (allGroups.groups) {
+                                    this.courseGroupList = allGroups.groups
                                     resSchedule.forEach(item => {
                                         //补充行政班信息
                                         if (item.classId) {
-                                            let classInfo = allGroups.groups.find(classItem => {
+                                            let classInfo = this.courseGroupList.find(classItem => {
                                                 return classItem.id == item.classId
                                             })
                                             item.classInfo = {
@@ -1013,19 +1016,15 @@ export default {
                                                 name: classInfo ? classInfo.name : this.$t('cusMgt.hasDelClass'),
                                                 year: classInfo ? classInfo.year : 0 //方便计算年级
                                             }
-                                            item.allStu = classInfo ? classInfo.members : []
-                                            item.fullStu = true
                                             item.joinLock = classInfo.joinLock
                                         }
                                         //补充教学班信息
                                         if (item.stulist) {
-                                            let listInfo = allGroups.groups.find(listItem => {
+                                            let listInfo = this.courseGroupList.find(listItem => {
                                                 return listItem.id == item.stulist
                                             })
                                             item.listName = listInfo ? listInfo.name : this.$t('cusMgt.hasDelClass')
-                                            item.allStu = listInfo ? listInfo.members : []
                                             item.listSchool = listInfo ? listInfo.school : undefined
-                                            item.fullStu = true
                                             item.joinLock = listInfo.joinLock
                                         }
                                         //统一数据格式
@@ -1049,15 +1048,8 @@ export default {
         },
         onAddStudent(data) {
             let { addStudents } = data
-            // 当前名单对应的课程安排用于更新UI
-            let schedule = this.courseInfo.schedule.find(item => {
-                return item.stulist == this.teaClassList[this.curClassIndex].stulist && !item.classId //只有自定名单能添加学生
-            })
-            if (schedule) {
-                schedule.allStu.unshift(...addStudents)
-            }
             //当前名单完整信息 用于更新名单API
-            let stulist = this.groupList.find(item => {
+            let stulist = this.courseGroupList.find(item => {
                 return item.id == this.teaClassList[this.curClassIndex].stulist
             })
             if (stulist) {
@@ -1067,20 +1059,8 @@ export default {
         },
         onDelStudent(data) {
             let { delIds } = data
-            // 当前名单对应的课程安排用于更新UI
-            let schedule = this.courseInfo.schedule.find(item => {
-                return item.stulist == this.teaClassList[this.curClassIndex].stulist && !item.classId //只有自定名单能添加学生
-            })
-            if (schedule) {
-                for (let i = 0; i < schedule.allStu.length; i++) {
-                    if (delIds.includes(schedule.allStu[i].id)) {
-                        schedule.allStu.splice(i, 1)
-                        i--
-                    }
-                }
-            }
             //当前名单完整信息 用于更新名单API
-            let stulist = this.groupList.find(item => {
+            let stulist = this.courseGroupList.find(item => {
                 return item.id == this.teaClassList[this.curClassIndex].stulist
             })
             if (stulist) {
@@ -1125,7 +1105,8 @@ export default {
                             students: res.list.members,
                             time: []
                         })
-                        this.groupList.push(res.list)
+                        this.groupList.push(this._.cloneDeep(res.list))
+                        this.courseGroupList.push(this._.cloneDeep(res.list))
                         this.updCusInfo()
                     }
                 },

+ 48 - 46
TEAMModelOS/ClientApp/src/view/mycourse/student/Student.vue

@@ -7,38 +7,38 @@
                     <Icon type="ios-arrow-down"></Icon>
                 </a>
                 <DropdownMenu slot="list">
-                    <DropdownItem>
-                        <span @click="generateQrcodes">
+                    <DropdownItem @click.native="generateQrcodes">
+                        <span>
                             <Icon type="md-download" size="16" />
                             {{$t('schoolBaseInfo.exportQrCode')}}
                         </span>
                     </DropdownItem>
-                    <DropdownItem>
-                        <span @click="generatePDF">
+                    <DropdownItem @click.native="generatePDF">
+                        <span>
                             <Icon type="md-download" size="16" />
                             {{$t('schoolBaseInfo.exportList')}}
                         </span>
                     </DropdownItem>
-                    <DropdownItem>
-                        <span class="action-item" @click="exportStudents">
+                    <DropdownItem @click.native="exportStudents">
+                        <span class="action-item">
                             <Icon type="md-download" size="16" />
                             {{$t('cusMgt.exportStu')}}
                         </span>
                     </DropdownItem>
-                    <DropdownItem v-if="stuList && stuList.scope == 'private'" class="action-item">
-                        <span @click="setIrsStatus = true">
+                    <DropdownItem @click.native="setIrsStatus = true" v-if="basicStuList && basicStuList.scope == 'private'" class="action-item">
+                        <span>
                             <Icon type="md-settings" size="16" />
                             {{$t('cusMgt.editStu')}}
                         </span>
                     </DropdownItem>
-                    <DropdownItem v-if="stuList && stuList.scope == 'private'" v-show="$store.state.userInfo.hasSchool">
-                        <span class="action-item" @click="addStuStatus = true">
+                    <DropdownItem @click.native="addStuStatus = true" v-if="basicStuList && basicStuList.scope == 'private'" v-show="$store.state.userInfo.hasSchool">
+                        <span class="action-item">
                             <Icon type="md-add" size="16" />
                             {{$t('cusMgt.addStu')}}
                         </span>
                     </DropdownItem>
-                    <DropdownItem v-if="stuList && stuList.scope == 'private'">
-                        <span class="action-item" @click="delStudents">
+                    <DropdownItem @click.native="delStudents" v-if="basicStuList && basicStuList.scope == 'private'">
+                        <span class="action-item">
                             <Icon type="md-trash" size="16" />
                             {{$t('cusMgt.delStuBatch')}}
                         </span>
@@ -247,8 +247,11 @@ export default {
             return !!this.classInfo.stulist
         },
         students() {
-            return this.classInfo.allStu || []
+            return this.basicStuList?.members || []
         },
+        basicStuList() {
+            return this._.cloneDeep(this.stuList)
+        }
     },
     methods: {
         setStuInfo(row, index) {
@@ -260,18 +263,18 @@ export default {
         },
         // 批量设置irs
         confirmFastSet() {
-            if (this.stuList) {
+            if (this.basicStuList) {
                 this.btnLoading = true
-                this.stuList.members.forEach((item, index) => {
+                this.basicStuList.members.forEach((item, index) => {
                     item.irs = (index + 1) + ''
                 })
-                this.$api.common.upsertGroupInfo(this.stuList).then(
+                this.$api.common.upsertGroupInfo(this.basicStuList).then(
                     res => {
                         this.$Message.success(this.$t('cusMgt.listSaveOk'))
                         //TODO 更新父组件数据
                         this.$emit('on-update-students', {
-                            stuListId: this.stuList.id,
-                            students: this.stuList.members
+                            stuListId: this.basicStuList.id,
+                            students: this._.cloneDeep(this.basicStuList.members)
                         })
                         this.setIrsStatus = false
                     },
@@ -284,7 +287,6 @@ export default {
             }
         },
         getSchoolName(school) {
-            debugger
             if (!school) {
                 return this.$t('cusMgt.listSchoolTips3')
             }
@@ -300,14 +302,14 @@ export default {
             if (this.selections.length > 0) {
                 let stuIds = []
                 let addStudents = []
-                if (this.classInfo?.allStu) {
-                    stuIds = this.classInfo.allStu.map(item => {
+                if (this.basicStuList?.members) {
+                    stuIds = this.basicStuList?.members.map(item => {
                         return item.id
                     })
                 }
                 this.selections.forEach(item => {
                     if (!stuIds.includes(item.id)) { //如果已经有学生,根据账号去重
-                        let irs = this.getDefIRS(this.stuList)
+                        let irs = this.getDefIRS(this.basicStuList)
                         let info = {
                             id: item.id,
                             code: this.$store.state.userInfo.schoolCode,
@@ -319,16 +321,16 @@ export default {
                             classId: item.classId
                         }
                         addStudents.push(info)
-                        this.stuList.members.push(info)
+                        this.basicStuList.members.push(info)
                     }
                 })
                 this.listLoading = true
-                this.$api.common.upsertGroupInfo(this.stuList).then(
+                this.$api.common.upsertGroupInfo(this.basicStuList).then(
                     res => {
                         this.$Message.success(this.$t('cusMgt.listSaveOk'))
                         //TODO 更新父组件数据
                         this.$emit('on-add-student', {
-                            stuListId: this.stuList.id,
+                            stuListId: this.basicStuList.id,
                             addStudents: addStudents
                         })
                     },
@@ -435,15 +437,15 @@ export default {
                 content: `${this.$t('cusMgt.delStuContent')}${row.name}?`,
                 loading: true,
                 onOk: () => {
-                    let index = this.stuList?.members?.findIndex(item => item.id == row.id)
+                    let index = this.basicStuList?.members?.findIndex(item => item.id == row.id)
                     if (index > -1) {
-                        this.stuList.members.splice(index, 1)
-                        this.$api.common.upsertGroupInfo(this.stuList).then(
+                        this.basicStuList.members.splice(index, 1)
+                        this.$api.common.upsertGroupInfo(this.basicStuList).then(
                             res => {
                                 this.$Message.success(this.$t('cusMgt.delOk'))
                                 //TODO 更新父组件数据
                                 this.$emit('on-del-student', {
-                                    stuListId: this.stuList.id,
+                                    stuListId: this.basicStuList.id,
                                     delIds: [row.id]
                                 })
                             },
@@ -472,19 +474,19 @@ export default {
                         let delIds = this.delSelection.map(item => {
                             return item.id
                         })
-                        if (this.stuList && this.stuList.members) {
-                            for (let i = 0; i < this.stuList.members.length; i++) {
-                                if (delIds.includes(this.stuList.members[i].id)) {
-                                    this.stuList.members.splice(i, 1)
+                        if (this.basicStuList && this.basicStuList.members) {
+                            for (let i = 0; i < this.basicStuList.members.length; i++) {
+                                if (delIds.includes(this.basicStuList.members[i].id)) {
+                                    this.basicStuList.members.splice(i, 1)
                                     i--
                                 }
                             }
-                            this.$api.common.upsertGroupInfo(this.stuList).then(
+                            this.$api.common.upsertGroupInfo(this.basicStuList).then(
                                 res => {
                                     this.$Message.success(this.$t('cusMgt.delOk'))
                                     //TODO 更新父组件数据
                                     this.$emit('on-del-student', {
-                                        stuListId: this.stuList.id,
+                                        stuListId: this.basicStuList.id,
                                         delIds: delIds
                                     })
                                 },
@@ -507,24 +509,24 @@ export default {
                 return
             }
             // //检查irs重复
-            let irsRep = this.stuList?.members?.some((item, index) => {
-                return index != rowIndex && item.irs == this.editIrs
+            let irsRep = this.basicStuList?.members?.some((item, index) => {
+                return row.id != item.id && item.irs == this.editIrs
             })
             if (irsRep) {
                 this.$Message.warning(this.$t('schoolBaseInfo.irsRep'))
                 return
             }
 
-            let student = this.stuList.members.find(item => item.id == row.id)
+            let student = this.basicStuList.members.find(item => item.id == row.id)
             if (student) {
                 student.irs = this.editIrs + ''
-                this.$api.common.upsertGroupInfo(this.stuList).then(
+                this.$api.common.upsertGroupInfo(this.basicStuList).then(
                     res => {
                         this.$Message.success(this.$t('cusMgt.listSaveOk'))
                         //TODO 更新父组件数据
                         this.$emit('on-update-students', {
-                            stuListId: this.stuList.id,
-                            students: this.stuList.members
+                            stuListId: this.basicStuList.id,
+                            students: this._.cloneDeep(this.basicStuList.members)
                         })
                         this.editIndex = -1
                     },
@@ -545,16 +547,16 @@ export default {
                 this.$Message.warning(this.$t('cusMgt.nicknameTips'))
                 return
             }
-            let student = this.stuList.members.find(item => item.id == row.id)
+            let student = this.basicStuList.members.find(item => item.id == row.id)
             if (student) {
                 student.nickname = this.nickname
-                this.$api.common.upsertGroupInfo(this.stuList).then(
+                this.$api.common.upsertGroupInfo(this.basicStuList).then(
                     res => {
                         this.$Message.success(this.$t('cusMgt.listSaveOk'))
                         //TODO 更新父组件数据
                         this.$emit('on-update-students', {
-                            stuListId: this.stuList.id,
-                            students: this.stuList.members
+                            stuListId: this.basicStuList.id,
+                            students: this._.cloneDeep(this.basicStuList.members)
                         })
                         this.editIndex = -1
                     },
@@ -572,7 +574,7 @@ export default {
             deep: true,
             immediate: true,
             handler(n, o) {
-                console.log(n)
+                console.log('班级信息:', n)
             }
         }
     }

+ 1 - 0
TEAMModelOS/ClientApp/src/view/teachermgmt/components/mgt/TeacherMgt.vue

@@ -1202,6 +1202,7 @@ export default {
                         research: 12,
                         train: 13,
                         analysis: 14,
+                        link: 15,
                     }
                     res.authoritylist.sort((a, b) => {
                         return sortMap[a.category] - sortMap[b.category]

+ 15 - 0
TEAMModelOS/Controllers/OpenApi/Business/BizTeacherController.cs

@@ -69,5 +69,20 @@ namespace TEAMModelBI.Controllers
             var responseData = await OpenApiService.GetTeacherInfo(_azureCosmos, _dingDing, id, school, json);
             return Ok(new { responseData });
         }
+        /// <summary>
+        /// 教师执教的班级和课程
+        /// </summary>
+        /// <param name="json"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("get-teacher-teach")]
+        [ApiToken(Auth = "1502", Name = "教师执教的班级和课程", RWN = "R", Limit = false)]
+        public async Task<IActionResult> GetTeacherTeach(JsonElement json)
+        {
+            var (id, school) = HttpContext.GetApiTokenInfo();
+            var responseData = await OpenApiService.GetTeacherTeach(_azureCosmos, _dingDing, id, school, json);
+            return Ok(new { responseData });
+        }
+
     }
 }

+ 31 - 29
TEAMModelOS/Controllers/OpenApi/IRS/TianboController.cs

@@ -13,6 +13,7 @@ using System.Net.Http;
 using Microsoft.Extensions.Configuration;
 using TEAMModelOS.SDK.Models.Service;
 using System.Threading;
+using TEAMModelOS.SDK.Extension;
 
 namespace TEAMModelOS.Controllers
 {
@@ -70,49 +71,50 @@ namespace TEAMModelOS.Controllers
 
                     if (!string.IsNullOrWhiteSpace(stu?.channel) && !string.IsNullOrWhiteSpace(stu?.userid))
                     {
-                        var serviceManager = _azureSignalR.GetServiceManager();
-                        var cancel = new CancellationToken();
-                        var hub = await serviceManager.CreateHubContextAsync($"C{stu.channel}", cancel);
-                        try
+                        // 處理單例復用,提高請求效率,避免ServiceTransportType.Persistent websocket斷線在Azure偶發的消息遺失
+                        // TODO 尚須注意釋放的問題,後續須處理
+                        var hub = _azureSignalR.GetServiceManager().GetHubContext($"C{stu.channel}");
+                        // 下面註解寫法會造成Azure上消息偶發遺失,Localhost不會發生
+                        //var serviceManager = _azureSignalR.GetServiceManager();                       
+                        //using var hub = await serviceManager.CreateHubContextAsync($"C{stu.channel}", CancellationToken.None);
+
+                        var con = content.GetString();
+                        var ans = con switch
                         {
-                            var con = content.GetString();
-                            var ans = con switch
-                            {
-                                string _ when con.Length == 1 => new string[] { con },
-                                string _ when con.Contains('+', StringComparison.OrdinalIgnoreCase) => con.Split('+'),
-                                string _ when con.Equals("true", StringComparison.OrdinalIgnoreCase) => new string[] { "A" },
-                                string _ when con.Equals("false", StringComparison.OrdinalIgnoreCase) => new string[] { "B" },
-                                _ => throw new ArgumentNullException(nameof(content))
-                            };
+                            string _ when con.Length == 1 => new string[] { con },
+                            string _ when con.Contains('+', StringComparison.OrdinalIgnoreCase) => con.Split('+'),
+                            string _ when con.Equals("true", StringComparison.OrdinalIgnoreCase) => new string[] { "A" },
+                            string _ when con.Equals("false", StringComparison.OrdinalIgnoreCase) => new string[] { "B" },
+                            _ => throw new ArgumentNullException(nameof(content))
+                        };
 
-                            var common = JsonSerializer.Serialize(new
-                            {
-                                action = "DirectIRS.Answer",
-                                clientType = "DI",
-                                sender = stu.stuid,
-                                timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
-                                waitReturn = false,
-                                payload = new { irsno = "", answer = ans }
-                            });
+                        var common = JsonSerializer.Serialize(new
+                        {
+                            action = "DirectIRS.Answer",
+                            clientType = "DI",
+                            sender = stu.stuid,
+                            timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
+                            waitReturn = false,
+                            payload = new { irsno = "", answer = ans }
+                        });
 
-                            await hub.Clients.User(stu.userid).SendCoreAsync("onMessage", new[]{new {
+                        await hub.Clients.User(stu.userid).SendCoreAsync("onMessage", new[]{new {
                                 connectionId = (string)null,
                                 to = (string)null,
                                 groupname = (string)null,
                                 sender = stu.stuid,
                                 text = common
                             }});
-                        }
-                        finally
-                        {
-                            hub.Dispose();
-                        }
+
                     }
+                    await _dingDing.SendBotMsg($"IES5,{_option.Location},tianbo/api/course/cardUploadData()\n{stu?.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
                 }
+
                 return Ok(new { code = 200, msg = "成功", data = (string)null });
             }
-            catch
+            catch (Exception ex)
             {
+                await _dingDing.SendBotMsg($"IES5,{_option.Location},tianbo/api/course/cardUploadData()\n{ex.Message}\n{ex.StackTrace}{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
                 return Ok(new { code = 200, msg = "成功", data = (string)null });
             }
 

+ 16 - 0
TEAMModelOS/Controllers/OpenApi/OpenApiService.cs

@@ -516,6 +516,22 @@ namespace TEAMModelOS.Controllers
             }
         }
 
+
+        /// <summary>
+        /// 教师执教的班级和课程
+        /// [ApiToken(Auth = "1502", Name = "教师详细信息", RWN = "R", Limit = false)]
+        /// </summary>
+        /// <param name="_azureCosmos"></param>
+        /// <param name="_dingDing"></param>
+        /// <param name="bizId"></param>
+        /// <param name="school"></param>
+        /// <param name="json"></param>
+        /// <returns></returns>
+        public static async Task<ResponseData<dynamic>> GetTeacherTeach(AzureCosmosFactory _azureCosmos, DingDing _dingDing, string bizId, string school, JsonElement json) {
+
+            return null;
+        }
+
         /// <summary>
         /// 教师详细信息
         /// [ApiToken(Auth = "1502", Name = "教师详细信息", RWN = "R", Limit = false)]

+ 1 - 1
TEAMModelOS/Controllers/OpenApi/OpenSchool/ScGroupListController.cs

@@ -134,7 +134,7 @@ namespace TEAMModelOS.Controllers
                     List<Student> webStudents = json.GetProperty("students").ToObject<List<Student>>();
                     webStudents.ForEach(x => x.periodId = $"{_periodId}");
                     List<Student> preStudents = await StudentService.GeStudentData(_azureCosmos, school, webStudents?.Select(x => x.id));
-                    var retUpdate = await StudentService.updateStudents(_azureCosmos, _dingDing, _option, school, json.GetProperty("students").EnumerateArray());
+                    var retUpdate = await StudentService.updateStudents(_azureCosmos, _dingDing, _option, school, json.GetProperty("students").EnumerateArray(),false);
                     await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, school, webStudents, preStudents);
                     return Ok(new { code = school, students = retUpdate.studs, retUpdate.classDuplNos, retUpdate.nonexistentIds, retUpdate.errorNos, retUpdate.errorClassId });
                 }

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

@@ -175,8 +175,15 @@ namespace TEAMModelOS.Controllers
                     case "update":
                         //更新學生資料,批量密碼重置,基本資訊更新(姓名、教室ID、性別、學年及座號)
                         webStudents = request.GetProperty("students").ToObject<List<Student>>();
+                        var cleanImei  = false;
+                         
+                        if (request.GetProperty("cleanImei").ValueKind.Equals(JsonValueKind.True))
+                        {
+                            cleanImei = true;
+                        }
+                        
                         preStudents = await StudentService.GeStudentData(_azureCosmos, schoolId.GetString(), webStudents?.Select(x => x.id));
-                        var retUpdate = await StudentService.updateStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray());
+                        var retUpdate = await StudentService.updateStudents(_azureCosmos, _dingDing, _option, schoolId.GetString(), request.GetProperty("students").EnumerateArray(), cleanImei);
                         await StudentService.CheckStudent(_serviceBus, _configuration, _azureCosmos, schoolId.GetString(), webStudents, preStudents);
                         return this.Ok(new { code = $"Base-{schoolId.GetString()}", students = retUpdate.studs, retUpdate.classDuplNos, retUpdate.nonexistentIds, retUpdate.errorNos, retUpdate.errorClassId });
                     case "delete":

+ 2 - 1
TEAMModelOS/appsettings.Development.json

@@ -36,7 +36,8 @@
       "GenPdfQueue": "dep-genpdf"
     },
     "SignalR": {
-      "ConnectionString": "Endpoint=https://channel.service.signalr.net;AccessKey=KrblW06tuA4a/GyqRPDU0ynFFmAWxbAvyJihHclSXbQ=;Version=1.0;"
+      //"ConnectionString": "Endpoint=https://channel.service.signalr.net;AccessKey=KrblW06tuA4a/GyqRPDU0ynFFmAWxbAvyJihHclSXbQ=;Version=1.0;",
+      "ConnectionString": "Endpoint=https://channel.signalr.azure.cn;AccessKey=AtcB7JYFNUbUXb1rGxa3PVksQ2X5YSv3JOHZR9J88tw=;Version=1.0;"
     }
   },
   "HaBookAuth": {