Преглед на файлове

Merge remote-tracking branch 'origin/TPE/feat/sutdOpt2' into TPE/feat/sutdOpt

Mickey преди 4 години
родител
ревизия
a7d348db6d
променени са 100 файла, в които са добавени 4390 реда и са изтрити 1617 реда
  1. 40 0
      TEAMModelFunction/ClassChangeServiceBus.cs
  2. 6 5
      TEAMModelFunction/MonitorCosmosDB.cs
  3. 1 1
      TEAMModelFunction/ExamTrigger.cs
  4. 48 7
      TEAMModelFunction/SurveyTrigger.cs
  5. 205 0
      TEAMModelFunction/TriggerVote.cs
  6. 0 79
      TEAMModelFunction/VoteTrigger.cs
  7. 14 10
      TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs
  8. 40 1
      TEAMModelOS.SDK/Extension/Utils.cs
  9. 88 0
      TEAMModelOS.SDK/Models/Cosmos/Common/ActivityData.cs
  10. 0 21
      TEAMModelOS.SDK/Models/Cosmos/Common/CommonData.cs
  11. 12 0
      TEAMModelOS.SDK/Models/Cosmos/Common/Inner/VoteRecord.cs
  12. 77 37
      TEAMModelOS.SDK/Models/Cosmos/School/Survey.cs
  13. 1 12
      TEAMModelOS.SDK/Models/Cosmos/Common/Syllabus.cs
  14. 76 46
      TEAMModelOS.SDK/Models/Cosmos/Common/Vote.cs
  15. 8 1
      TEAMModelOS.SDK/Models/Cosmos/School/Class.cs
  16. 23 13
      TEAMModelOS.SDK/Models/Cosmos/School/Knowledge.cs
  17. 23 23
      TEAMModelOS.SDK/Models/Cosmos/Student/VoteRecord.cs
  18. 1 1
      TEAMModelOS/ClientApp/package.json
  19. 1 1
      TEAMModelOS/ClientApp/public/index.html
  20. 8 1
      TEAMModelOS/ClientApp/src/api/uploadFile.js
  21. 2 2
      TEAMModelOS/ClientApp/src/api/index.js
  22. 253 0
      TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html
  23. 51 8
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css
  24. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.eot
  25. 1 1
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js
  26. 77 0
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.json
  27. 61 28
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.svg
  28. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf
  29. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff
  30. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2
  31. 1 1
      TEAMModelOS/ClientApp/src/boot-app.js
  32. 88 42
      TEAMModelOS/ClientApp/src/common/BaseCanvas.vue
  33. 398 0
      TEAMModelOS/ClientApp/src/common/BaseKonva.vue
  34. 10 8
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  35. 74 56
      TEAMModelOS/ClientApp/src/common/BaseMyCanvas.vue
  36. 1 1
      TEAMModelOS/ClientApp/src/common/BasePackage.vue
  37. 2 4
      TEAMModelOS/ClientApp/src/common/BaseUserPoptip.vue
  38. 3 2
      TEAMModelOS/ClientApp/src/common/UploadModal.vue
  39. 15 2
      TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.vue
  40. 1 1
      TEAMModelOS/ClientApp/src/components/homework/BaseHwTable.vue
  41. 11 11
      TEAMModelOS/ClientApp/src/components/questionnaire/BaseQnForm.vue
  42. 1 1
      TEAMModelOS/ClientApp/src/components/selflearn/ContentFileList.vue
  43. 3 15
      TEAMModelOS/ClientApp/src/components/student-analysis/total/BaseMyTable.vue
  44. 25 26
      TEAMModelOS/ClientApp/src/components/student-web/EventBasicInfo.vue
  45. 124 77
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReport.vue
  46. 51 96
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReportCharts/StudentScore.vue
  47. 4 4
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperTest.vue
  48. 3 1
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperView.vue
  49. 2 0
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventList.vue
  50. 22 22
      TEAMModelOS/ClientApp/src/components/vote/BaseVoteForm.vue
  51. 105 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/cusMgt.js
  52. 46 4
      TEAMModelOS/ClientApp/src/locale/lang/en-US/global.js
  53. 22 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/home.js
  54. 7 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/index.js
  55. 188 170
      TEAMModelOS/ClientApp/src/locale/lang/en-US/learnActivity.js
  56. 47 3
      TEAMModelOS/ClientApp/src/locale/lang/en-US/studentWeb.js
  57. 53 32
      TEAMModelOS/ClientApp/src/locale/lang/en-US/teachContent.js
  58. 106 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/cusMgt.js
  59. 5 5
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/evaluation.js
  60. 35 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/global.js
  61. 22 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/home.js
  62. 8 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/index.js
  63. 18 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/learnActivity.js
  64. 46 7
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/studentWeb.js
  65. 54 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/survey.js
  66. 25 4
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/teachContent.js
  67. 49 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/vote.js
  68. 107 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/cusMgt.js
  69. 5 5
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/evaluation.js
  70. 46 4
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/global.js
  71. 22 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/home.js
  72. 10 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/index.js
  73. 19 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/learnActivity.js
  74. 46 2
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/studentWeb.js
  75. 54 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/survey.js
  76. 26 5
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/teachContent.js
  77. 49 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/vote.js
  78. 1 1
      TEAMModelOS/ClientApp/src/router/routes.js
  79. 34 9
      TEAMModelOS/ClientApp/src/store/index.js
  80. 12 6
      TEAMModelOS/ClientApp/src/store/module/totalAnalysis.js
  81. 235 32
      TEAMModelOS/ClientApp/src/utils/blobTool.js
  82. 2 1
      TEAMModelOS/ClientApp/src/utils/editorTools.js
  83. 31 25
      TEAMModelOS/ClientApp/src/utils/evTools.js
  84. 6 6
      TEAMModelOS/ClientApp/src/utils/public.js
  85. 392 398
      TEAMModelOS/ClientApp/src/view/classmgt/ManageClass.vue
  86. 2 2
      TEAMModelOS/ClientApp/src/view/classrecord/ClassRecord.vue
  87. 33 37
      TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue
  88. 1 2
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue
  89. 1 1
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseCreateChild.vue
  90. 4 3
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseEditExercise.vue
  91. 52 31
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue
  92. 158 12
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseImport.vue
  93. 11 4
      TEAMModelOS/ClientApp/src/view/evaluation/components/BasePointPie.vue
  94. 1 1
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseRepair.vue
  95. 16 8
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreateExercises.vue
  96. 116 41
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue
  97. 1 1
      TEAMModelOS/ClientApp/src/view/evaluation/types/BaseConnector.vue
  98. 99 79
      TEAMModelOS/ClientApp/src/view/homepage/AcCountPie.vue
  99. 6 6
      TEAMModelOS/ClientApp/src/view/homepage/HomePage.less
  100. 0 0
      TEAMModelOS/ClientApp/src/view/homepage/HomePage.vue

+ 40 - 0
TEAMModelFunction/ClassChangeServiceBus.cs

@@ -0,0 +1,40 @@
+using Microsoft.Azure.WebJobs;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelFunction
+{
+    //public class ClassChangeServiceBus
+    //{
+    //    private readonly AzureCosmosFactory _azureCosmos;
+    //    private readonly DingDing _dingDing;
+    //    delegate void DoActivityTarget(string ids,string opt,string no,string source);
+    //    public ClassChangeServiceBus(AzureCosmosFactory azureCosmos, DingDing dingDing)
+    //    {
+    //        _azureCosmos = azureCosmos;
+    //        _dingDing = dingDing;
+    //    }
+    //    /// <summary>
+    //    /// 完善学生名单变更影响的活动
+    //    /// </summary>
+    //    /// <data msg>
+    //    /// "ids":["s111","t111"]//学生
+    //    /// "opt":"join/leave",//状态
+    //    /// "no":"CLASS001"//教室编号
+    //    /// "source":1/2  //学生名单数据来源 1是不同学校的学生账号,2是扫码加入的醍摩豆ID
+    //    /// </data>
+    //    /// <param name="msg"></param>
+    //    /// <returns></returns>
+    //    [FunctionName("ImproveActivity")]
+    //    public async Task ImproveActivity([ServiceBusTrigger("active-task", "classchange", Connection = "Azure:ServiceBus:ConnectionString")] string msg) {
+    //        try {
+    //            var json = JsonDocument.Parse(msg);
+
+    //        } catch (Exception e) { }
+    //    }
+    //}
+}

+ 6 - 5
TEAMModelFunction/MonitorCosmosDB.cs

@@ -21,14 +21,15 @@ namespace TEAMModelFunction
         private readonly AzureServiceBusFactory _serviceBus;
         private readonly AzureStorageFactory _azureStorage;
         private readonly DingDing _dingDing;
-
-        public MonitorCosmosDB(IHttpClientFactory clientFactory, AzureCosmosFactory azureCosmos, AzureServiceBusFactory azureServiceBus, AzureStorageFactory azureStorage, DingDing dingDing)
+        private readonly AzureRedisFactory _azureRedis;
+        public MonitorCosmosDB(IHttpClientFactory clientFactory, AzureCosmosFactory azureCosmos, AzureServiceBusFactory azureServiceBus, AzureStorageFactory azureStorage, DingDing dingDing, AzureRedisFactory azureRedis)
         {
             _clientFactory = clientFactory;
             _azureCosmos = azureCosmos;
             _serviceBus = azureServiceBus;
             _azureStorage = azureStorage;
             _dingDing = dingDing;
+            _azureRedis = azureRedis;
         }
 
         [FunctionName("Common")]
@@ -60,13 +61,13 @@ namespace TEAMModelFunction
                         switch (pk)
                         {
                             case "Exam":
-                                ExamTrigger.Trigger(_azureCosmos, _serviceBus, _azureStorage, _dingDing, client,input,code,stime,etime,school);
+                                TriggerExam.Trigger(_azureCosmos, _serviceBus, _azureStorage, _dingDing, client,input,code,stime,etime,school);
                                 break;
                             case "Vote":
-                                VoteTrigger.Trigger(_azureCosmos, _serviceBus, _azureStorage, _dingDing, client, input, code, stime, etime, school);
+                                TriggerVote.Trigger(_azureCosmos, _serviceBus, _azureStorage, _dingDing, client, input, code, stime, etime, school, _azureRedis);
                                 break;
                             case "Survey":
-                                SurveyTrigger.Trigger(_azureCosmos, _serviceBus, _azureStorage, _dingDing, client, input, code, stime, etime, school);
+                                TriggerSurvey.Trigger(_azureCosmos, _serviceBus, _azureStorage, _dingDing, client, input, code, stime, etime, school);
                                 break;
 
                         }

+ 1 - 1
TEAMModelFunction/ExamTrigger.cs

@@ -12,7 +12,7 @@ using TEAMModelOS.SDK.Models;
 
 namespace TEAMModelFunction
 {
-    public class ExamTrigger
+    public class TriggerExam
     {
         public static async void Trigger(AzureCosmosFactory _azureCosmos, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, DingDing _dingDing, 
             CosmosClient client, Document input ,string code,long stime,long etime, string school)

+ 48 - 7
TEAMModelFunction/SurveyTrigger.cs

@@ -9,19 +9,22 @@ using System.Threading.Tasks;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;
+using TEAMModelOS.SDK.Models.Cosmos;
 
 namespace TEAMModelFunction
 {
-   public class SurveyTrigger
+   public class TriggerSurvey
     {
         public static async void Trigger(AzureCosmosFactory _azureCosmos, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, DingDing _dingDing,
                CosmosClient client, Document input, string code, long stime, long etime, string school)
         {
             Survey survey = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<Survey>(input.Id, new Azure.Cosmos.PartitionKey($"{code}"));
-            //messageSurvey.ScheduledEnqueueTime = DateTimeOffset.FromUnixTimeMilliseconds(stime);
-            //string msgid = messageSurvey.MessageId;
             List<ChangeRecord> changeRecords = await _azureStorage.FindListByDict<ChangeRecord>(new Dictionary<string, object>() { { "RowKey", input.Id }, { "PartitionKey", survey.progress } });
-            //ChangeRecord surveyRecord = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<ChangeRecord>(input.Id, new Azure.Cosmos.PartitionKey($"{survey.progress}"));
+            if (survey.ttl >= 1)
+            {
+                //TODO  处理TTL删除业务
+                return;
+            }
             switch (survey.progress)
             {
                 case "pending":
@@ -33,7 +36,6 @@ namespace TEAMModelFunction
                         long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageSurvey, DateTimeOffset.FromUnixTimeMilliseconds(stime));
                         changeRecords[0].sequenceNumber = start;
                         await _azureStorage.SaveOrUpdate<ChangeRecord>(changeRecords[0]);
-                        //await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(surveyRecord, surveyRecord.id, new Azure.Cosmos.PartitionKey($"{surveyRecord.code}"));
                     }
                     else
                     {
@@ -46,10 +48,47 @@ namespace TEAMModelFunction
                             msgId = messageSurvey.MessageId
                         };
                         await _azureStorage.Save<ChangeRecord>(changeRecord);
-                        //await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(changeRecord, new Azure.Cosmos.PartitionKey($"{changeRecord.code}"));
                     }
                     break;
                 case "going":
+                    ActivityData data;
+                    if (survey.scope == "school" || survey.scope == "teacher")
+                    {
+                        data = new ActivityData
+                        {
+                            id = survey.id,
+                            code = $"Activity-{survey.owner}",
+                            type = "survey",
+                            name = survey.name,
+                            startTime = survey.startTime,
+                            endTime = survey.endTime,
+                            scode = survey.code,
+                            scope = survey.scope,
+                            classes = survey.classes,
+                            tmdids = survey.tmdids,
+                            progress = "going",
+                            owner = survey.owner
+                        };
+                        await client.GetContainer("TEAMModelOS", "School").UpsertItemAsync<ActivityData>(data, new Azure.Cosmos.PartitionKey(data.code));
+                    }
+                    else if (survey.scope == "private")
+                    {
+                        data = new ActivityData
+                        {
+                            id = survey.id,
+                            code = $"Activity-Common",
+                            type = "survey",
+                            name = survey.name,
+                            startTime = survey.startTime,
+                            endTime = survey.endTime,
+                            scode = survey.code,
+                            scope = survey.scope,
+                            classes = survey.classes,
+                            progress = "going",
+                            owner = survey.owner
+                        };
+                        await client.GetContainer("TEAMModelOS", "Teacher").UpsertItemAsync<ActivityData>(data, new Azure.Cosmos.PartitionKey(data.code));
+                    }
                     var messageSurveyEnd = new ServiceBusMessage(new { id = input.Id, progress = "finish", code = code }.ToJsonString());
                     messageSurveyEnd.ApplicationProperties.Add("name", "Survey");
                     if (changeRecords.Count > 0)
@@ -70,8 +109,10 @@ namespace TEAMModelFunction
                             msgId = messageSurveyEnd.MessageId
                         };
                         await _azureStorage.Save<ChangeRecord>(changeRecord);
-                        //await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(changeRecord, new Azure.Cosmos.PartitionKey($"{changeRecord.code}"));
                     }
+                    break;
+                case "finish":
+
                     break;
             }
         }

+ 205 - 0
TEAMModelFunction/TriggerVote.cs

@@ -0,0 +1,205 @@
+using Azure.Cosmos;
+using Azure.Messaging.ServiceBus;
+using Microsoft.Azure.Documents;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.Extension;
+using TEAMModelOS.SDK.Models;
+using TEAMModelOS.SDK.Models.Cosmos;
+
+namespace TEAMModelFunction
+{
+    public static class TriggerVote
+    {
+
+        public static async void Trigger(AzureCosmosFactory _azureCosmos, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, DingDing _dingDing,
+            CosmosClient client, Document input, string code, long stime, long etime, string school, AzureRedisFactory _azureRedis)
+        {
+            Vote vote = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<Vote>(input.Id, new Azure.Cosmos.PartitionKey($"{code}"));
+            List<ChangeRecord> voteRecords = await _azureStorage.FindListByDict<ChangeRecord>(new Dictionary<string, object>() { { "RowKey", input.Id }, { "PartitionKey", vote.progress } });
+            if (vote.ttl >= 1)
+            {
+                //TODO  处理TTL删除业务
+                _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Record:{vote.id}_{vote.code}");
+                _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Count:{vote.id}_{vote.code}");
+                return;
+            }
+            else {
+                switch (vote.progress)
+                {
+                    case "pending":
+                        var messageVote = new ServiceBusMessage(new { id = input.Id, progress = "going", code = code }.ToJsonString());
+                        messageVote.ApplicationProperties.Add("name", "Vote");
+                        if (voteRecords.Count > 0)
+                        {
+                            long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVote, DateTimeOffset.FromUnixTimeMilliseconds(stime));
+                            await _serviceBus.GetServiceBusClient().cancelMessage("active-task", voteRecords[0].sequenceNumber);
+                            voteRecords[0].sequenceNumber = start;
+                            await _azureStorage.SaveOrUpdate<ChangeRecord>(voteRecords[0]);
+                            //await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(voteRecord, voteRecord.id, new Azure.Cosmos.PartitionKey($"{voteRecord.code}"));
+                        }
+                        else
+                        {
+                            long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVote, DateTimeOffset.FromUnixTimeMilliseconds(stime));
+                            ChangeRecord changeRecord = new ChangeRecord
+                            {
+                                RowKey = input.Id,
+                                PartitionKey = "pending",
+                                sequenceNumber = start,
+                                msgId = messageVote.MessageId
+                            };
+                            await _azureStorage.Save<ChangeRecord>(changeRecord);
+                            //await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(changeRecord, new Azure.Cosmos.PartitionKey($"{changeRecord.code}"));
+                        }
+                        break;
+                    case "going":
+                        ActivityData data;
+                        if (vote.scope == "school" || vote.scope == "teacher")
+                        {
+                            data = new ActivityData
+                            {
+                                id = vote.id,
+                                code = $"Activity-{vote.owner}",
+                                type = "vote",
+                                name = vote.name,
+                                startTime = vote.startTime,
+                                endTime = vote.endTime,
+                                scode = vote.code,
+                                scope = vote.scope,
+                                classes = vote.classes,
+                                tmdids = vote.tmdids,
+                                progress= "going",
+                                owner =vote.owner
+                            };
+                            await client.GetContainer("TEAMModelOS", "School").UpsertItemAsync<ActivityData>(data, new Azure.Cosmos.PartitionKey(data.code));
+                        }
+                        else if (vote.scope == "private")
+                        {
+                            data = new ActivityData
+                            {
+                                id = vote.id,
+                                code = $"Activity-Common",
+                                type = "vote",
+                                name = vote.name,
+                                startTime = vote.startTime,
+                                endTime = vote.endTime,
+                                scode = vote.code,
+                                scope = vote.scope,
+                                progress = "going",
+                                classes = vote.classes,
+                                owner = vote.owner
+                                // tmdids = vote.tmdids
+                            };
+                            await client.GetContainer("TEAMModelOS", "Teacher").UpsertItemAsync<ActivityData>(data, new Azure.Cosmos.PartitionKey(data.code));
+                        }
+                        var messageVoteEnd = new ServiceBusMessage(new { id = input.Id, progress = "finish", code = code }.ToJsonString());
+                        messageVoteEnd.ApplicationProperties.Add("name", "Vote");
+                        if (voteRecords.Count > 0)
+                        {
+                            long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVoteEnd, DateTimeOffset.FromUnixTimeMilliseconds(etime));
+                            await _serviceBus.GetServiceBusClient().cancelMessage("active-task", voteRecords[0].sequenceNumber);
+                            voteRecords[0].sequenceNumber = end;
+                            await _azureStorage.SaveOrUpdate<ChangeRecord>(voteRecords[0]);
+                        }
+                        else
+                        {
+                            long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVoteEnd, DateTimeOffset.FromUnixTimeMilliseconds(etime));
+                            ChangeRecord changeRecord = new ChangeRecord
+                            {
+                                RowKey = input.Id,
+                                PartitionKey = "going",
+                                sequenceNumber = end,
+                                msgId = messageVoteEnd.MessageId
+                            };
+                            await _azureStorage.Save<ChangeRecord>(changeRecord);
+                        }
+                        break;
+                    case "finish":
+                        //获取投票活动的所有投票记录
+                        var records= await _azureRedis.GetRedisClient(8).HashGetAllAsync($"Vote:Record:{vote.id}_{vote.code}");
+                        //获取投票活动的选项及投票数
+                        var counts= _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Vote:Count:{vote.id}_{vote.code}");
+                        if (counts != null && counts.Length > 0) {
+                            foreach (var count in counts) {
+                                vote.options.ForEach(x => {
+                                    //重新赋值
+                                    if (x.code.Equals(count.Element.ToString())) {
+                                        x.count = (int)count.Score;
+                                    }
+                                });
+                            }
+                        }
+                        List<Task<string>> tasks = new List<Task<string>>();
+                        List<dynamic> recordsBlob = new List<dynamic>();
+                        foreach (var rcd in records) {
+                            var key = rcd.Name.ToString().Split("-")[0];
+                            var value = rcd.Value.ToString().ToObject<JsonElement>();
+                            recordsBlob.Add(new { key,value});
+                            tasks.Add(_azureStorage.UploadFileByContainer(vote.owner, value.ToJsonString(), "vote", $"{vote.id}/{key}.json"));
+                        }
+                        //处理活动方的记录
+                        string url = $"vote/{vote.id}/index.json";
+                        vote.recordUrl = url;
+                        tasks.Add(_azureStorage.UploadFileByContainer(vote.owner, recordsBlob.ToJsonString(), "vote", $"{vote.id}/index.json"));
+                        //处理投票者的记录
+                        await Task.WhenAll(tasks);
+                        if (vote.scope == "school" || vote.scope == "teacher")
+                        {
+                            await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Vote>(vote,vote.id, new Azure.Cosmos.PartitionKey(vote.code));
+                        }
+                        else if (vote.scope == "private")
+                        {
+                            await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Vote>(vote, vote.id, new Azure.Cosmos.PartitionKey(vote.code));
+                        }
+
+
+                        //更新结束状态
+                        if (vote.scope == "school" || vote.scope == "teacher")
+                        {
+                            data = new ActivityData
+                            {
+                                id = vote.id,
+                                code = $"Activity-{vote.owner}",
+                                type = "vote",
+                                name = vote.name,
+                                startTime = vote.startTime,
+                                endTime = vote.endTime,
+                                scode = vote.code,
+                                scope = vote.scope,
+                                classes = vote.classes,
+                                tmdids = vote.tmdids,
+                                progress = "finish",
+                                owner = vote.owner
+                            };
+                            await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<ActivityData>(data,data.id, new Azure.Cosmos.PartitionKey(data.code));
+                        }
+                        else if (vote.scope == "private")
+                        {
+                            //更新结束状态
+                            data = new ActivityData
+                            {
+                                id = vote.id,
+                                code = $"Activity-Common",
+                                type = "vote",
+                                name = vote.name,
+                                startTime = vote.startTime,
+                                endTime = vote.endTime,
+                                scode = vote.code,
+                                scope = vote.scope,
+                                progress = "finish",
+                                classes = vote.classes,
+                                owner = vote.owner
+                                // tmdids = vote.tmdids
+                            };
+                            await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<ActivityData>(data,data.id, new Azure.Cosmos.PartitionKey(data.code));
+                        }
+                        break;
+                }
+            }
+        }
+    }
+}

+ 0 - 79
TEAMModelFunction/VoteTrigger.cs

@@ -1,79 +0,0 @@
-using Azure.Cosmos;
-using Azure.Messaging.ServiceBus;
-using Microsoft.Azure.Documents;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
-using TEAMModelOS.SDK.DI;
-using TEAMModelOS.SDK.Extension;
-using TEAMModelOS.SDK.Models;
-
-namespace TEAMModelFunction
-{
-  public static  class VoteTrigger
-    {
-
-        public static async void Trigger(AzureCosmosFactory _azureCosmos, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, DingDing _dingDing,
-            CosmosClient client, Document input, string code, long stime, long etime, string school)
-        {
-            Vote vote = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<Vote>(input.Id, new Azure.Cosmos.PartitionKey($"{code}"));
-            List<ChangeRecord> voteRecords = await _azureStorage.FindListByDict<ChangeRecord>(new Dictionary<string, object>() { { "RowKey", input.Id }, { "PartitionKey", vote.progress } });
-            //ChangeRecord voteRecord = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<ChangeRecord>(input.Id, new Azure.Cosmos.PartitionKey($"{vote.progress}"));
-            switch (vote.progress)
-            {
-                case "pending":
-                    var messageVote = new ServiceBusMessage(new { id = input.Id, progress = "going", code = code }.ToJsonString());
-                    messageVote.ApplicationProperties.Add("name", "Vote");
-                    if (voteRecords.Count > 0)
-                    {
-                        long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVote, DateTimeOffset.FromUnixTimeMilliseconds(stime));
-                        await _serviceBus.GetServiceBusClient().cancelMessage("active-task", voteRecords[0].sequenceNumber);
-                        voteRecords[0].sequenceNumber = start;
-                        await _azureStorage.SaveOrUpdate<ChangeRecord>(voteRecords[0]);
-                        //await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(voteRecord, voteRecord.id, new Azure.Cosmos.PartitionKey($"{voteRecord.code}"));
-                    }
-                    else
-                    {
-                        long start = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVote, DateTimeOffset.FromUnixTimeMilliseconds(stime));
-                        ChangeRecord changeRecord = new ChangeRecord
-                        {
-                            RowKey = input.Id,
-                            PartitionKey = "pending",
-                            sequenceNumber = start,
-                            msgId = messageVote.MessageId
-                        };
-                        await _azureStorage.Save<ChangeRecord>(changeRecord);
-                        //await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(changeRecord, new Azure.Cosmos.PartitionKey($"{changeRecord.code}"));
-                    }
-                    break;
-                case "going":
-                    var messageVoteEnd = new ServiceBusMessage(new { id = input.Id, progress = "finish", code = code }.ToJsonString());
-                    messageVoteEnd.ApplicationProperties.Add("name", "Vote");
-                    if (voteRecords.Count > 0)
-                    {
-                        long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVoteEnd, DateTimeOffset.FromUnixTimeMilliseconds(etime));
-                        await _serviceBus.GetServiceBusClient().cancelMessage("active-task", voteRecords[0].sequenceNumber);
-                        voteRecords[0].sequenceNumber = end;
-                        await _azureStorage.SaveOrUpdate<ChangeRecord>(voteRecords[0]);
-                        //await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(voteRecord, voteRecord.id, new Azure.Cosmos.PartitionKey($"{voteRecord.code}"));
-                    }
-                    else
-                    {
-                        long end = await _serviceBus.GetServiceBusClient().SendScheduleMessageAsync("active-task", messageVoteEnd, DateTimeOffset.FromUnixTimeMilliseconds(etime));
-                        ChangeRecord changeRecord = new ChangeRecord
-                        {
-                            RowKey = input.Id,
-                            PartitionKey = "going",
-                            sequenceNumber = end,
-                            msgId = messageVoteEnd.MessageId
-                        };
-                        await _azureStorage.Save<ChangeRecord>(changeRecord);
-                        //await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(changeRecord, new Azure.Cosmos.PartitionKey($"{changeRecord.code}"));
-                    }
-                    break;
-            }
-        }
-    }
-}

+ 14 - 10
TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs

@@ -85,18 +85,22 @@ namespace TEAMModelOS.SDK.DI
             List<OptUrl> optUrls = new List<OptUrl>();
             try
             {
-               
-                foreach (var url in urls) {
-                    OptUrl optUrl = new OptUrl { url = url, size =0};
-                    var eurl = System.Web.HttpUtility.UrlDecode(url, Encoding.UTF8);
-                    var blob = client.GetBlobClient(eurl);
-                    if (blob.Exists()) {
-                        var props = await blob.GetPropertiesAsync();
-                        var size= props.Value.ContentLength;
-                        optUrl.size = size;
+                if (urls != null) {
+                    foreach (var url in urls)
+                    {
+                        OptUrl optUrl = new OptUrl { url = url, size = 0 };
+                        var eurl = System.Web.HttpUtility.UrlDecode(url, Encoding.UTF8);
+                        var blob = client.GetBlobClient(eurl);
+                        if (blob.Exists())
+                        {
+                            var props = await blob.GetPropertiesAsync();
+                            var size = props.Value.ContentLength;
+                            optUrl.size = size;
+                        }
+                        optUrls.Add(optUrl);
                     }
-                    optUrls.Add(optUrl);
                 }
+                
                 return optUrls;
             }
             catch

+ 40 - 1
TEAMModelOS.SDK/Extension/Utils.cs

@@ -3,6 +3,7 @@ using Microsoft.IdentityModel.Tokens;
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using System.Drawing.Drawing2D;
 using System.Drawing.Imaging;
 using System.IdentityModel.Tokens.Jwt;
 using System.IO;
@@ -144,14 +145,52 @@ namespace TEAMModelOS.SDK.Extension
 
                 return (true, "png", depth);
             }
-
+            
             if (fileheader.StartsWith("47,49,46,38,39,61,") || fileheader.StartsWith("47,49,46,38,37,61,"))
                 return (true, "gif", 0);
+
+            if (fileheader.StartsWith("1,0,0,0,"))
+                return (true, "emf", 0);
+
+            if (fileheader.StartsWith("1,0,9,0,0,3"))
+                return (true, "wmf", 0);
             //if (fileheader.StartsWith("4D,4D") || fileheader.StartsWith("49,49") || fileheader.StartsWith("46,4F,52,4D"))
             //    return (true, "tif");
             return (false, "", 0);
 
         }
+
+        /// <summary>
+        /// 轉換EMF為PNG格式
+        /// </summary>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        public static string ConvertEMFtoPNG(Stream stream) 
+        {
+            using var image = Image.FromStream(stream);
+
+            //GDI+報錯,Azure Web App沙箱不支持GDI+部分權限
+            //using var nbase64ms = new MemoryStream();
+            //image.Save(nbase64ms, ImageFormat.Png); //保存為PNG格式
+            //byte[] data = nbase64ms.ToArray();
+            //string base64 = Convert.ToBase64String(data);
+            //return base64;
+
+            //Azure Web App沙箱不支持GDI+渲染部分EMF指令,可以出圖但會是空白
+            var pngimage = new Bitmap(image.Width, image.Height);
+            using (var graphics = Graphics.FromImage(pngimage))
+            {
+                graphics.CompositingQuality = CompositingQuality.HighSpeed;
+                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
+                graphics.CompositingMode = CompositingMode.SourceCopy;
+                graphics.DrawImage(image, 0, 0, image.Width, image.Height);
+                using var nbase64ms = new MemoryStream();
+                pngimage.Save(nbase64ms, ImageFormat.Png); //保存為PNG格式
+                byte[] data = nbase64ms.ToArray();
+                string base64 = Convert.ToBase64String(data);
+                return base64;
+            }
+        }
         #endregion
 
 

+ 88 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/ActivityData.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Models.Cosmos
+{
+
+    /// <summary>
+    /// 
+    /// [
+    //      Activity|-----问卷调查
+    //        |-----投票活动
+    //        |-----评测活动
+    //        |-----作业活动
+    //        |-----学习活动
+    //]
+    //[
+    //    {
+    //        "type": "vote",//"vote"/"exam"/"homework"/"learn"/"survey"
+    //        "name": "第一届紫藤小学人气教师投票",
+    //        "startTime": 1608274766154,
+    //        "endTime": 1608912000000,
+    //        "scode": "Vote-hbcn",
+    //        "id": "aaaaaaaaaaaaaaaaaaa",
+    //        "code": "Activity-hbcn",
+    //        "pk": "Activity",
+    //        "scope":"private/school/teacher",
+    //        "classes":["S-C-00001","S-C-00002","S-C-00003","S-C-00004"],
+    //        "tmdids":["TMDID0001","TMDID0002","TMDID0003","TMDID0004",]//只有问卷调查和投票活动设置,且分区键必须为学校的
+    //},
+    //    {
+    //        "type": "vote",//"vote"/"exam"/"homework"/"learn"/"survey"
+    //        "name": "第一届醍摩豆杯人气教师投票",
+    //        "startTime": 1608274766154,
+    //        "endTime": 1608912000000,
+    //        "scode": "Vote-TMDID0001",
+    //        "id": "bbbbbbbbbbbbbbbbbbbbb",
+    //        "code": "Activity-Common",//教师个人发布的活动统一使用Common,用班级子查询
+    //        "pk": "Activity",
+    //        "scope":"private/school/teacher",
+    //        "classes":["P-C-00004","S-C-00001"],//如果是醍摩豆ID则直接搜寻加入的私人教师编码,如果是校内学生则需要多搜寻一次。
+    //        "tmdids":[]
+    //    }
+    //]
+    ///简单阐述说明:
+    /// 1.  对于学校产生的活动 即scope=school  或scope=teacher  则对应活动原本的数据的owner 则直接为学校的编码,且ActivityData的code为Activity-学校编码 ActivityData数据存在School表
+    ///     如果scope=private,则活动是老师个人活动,owner=tmdid  ,ActivityData的code则为Activity-Common ActivityData且数据存在Teacher表
+    ///     
+    /// 2.  学生端查询包含 tmdid登录者(可能包含加入学校的教师)以学生身份登入学生端,
+    ///     则需要知道 登录人加入的私人教室id  则输入条件是classes=['私人教室id'],!!!
+    ///     或者用id身份去查询是否存在于tmdids 表示是否是单独被邀请加入活动的。!!!但是这种情况只存在于加入学校的教师,因为私人教室不允许随意邀请别的tmdid,只能加入私人教室。
+    ///     如果是学校学生以学生身份登入,则需要知道该学生加入的学校的班级和私人教室的班级 则classes=['私人教室id','学校教室id']
+    ///     
+    /// 3.  教师端查询ActivityData,则 查询权限为班主任或任课教师等,且这种查询只会存在于校园内的活动,包含scope=school,teacher 发布对象为校园班级班级
+    ///     则需要知道班主任,任课教室管理的班级 则classes=["学校班级id1","学校班级id2"]
+    ///     查询条件 code=Activity-hbcn   classes=["学校班级id1","学校班级id2"]  任课教师的科目id
+    /// </summary>
+    public class ActivityData : CosmosEntity
+    {
+       
+        public ActivityData() {
+            pk = "Activity";
+        }
+        /// <summary>
+        /// 业务类型  vote投票 survey问卷 exam评测 learn学习活动 homework作业活动
+        /// </summary>
+        public string type { get; set; }
+        public string name { get; set; }
+        public long  startTime { get; set; }
+        public long endTime { get; set; }
+        /// <summary>
+        /// pending 待发布|going 已发布|finish 已结束
+        /// </summary>
+        public string progress { get; set; }
+        /// <summary>
+        /// 活动的分区键 Vote-hbcn/Vote-1606294378
+        /// </summary>
+        public string scode { get; set; }
+        /// <summary>
+        /// private/school/teacher
+        /// </summary>
+        public string scope { get; set; }
+        public List<string> classes { get; set; }
+        public List<string> tmdids { get; set; }
+        public string owner { get; set; }
+        public List<string> subjects { get; set; }
+    }
+}

+ 0 - 21
TEAMModelOS.SDK/Models/Cosmos/Common/CommonData.cs

@@ -1,21 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace TEAMModelOS.SDK.Models.Cosmos.Student
-{
-    public class CommonData : CosmosEntity
-    {
-        public CommonData() {
-            pk = "Common";
-        }
-        /// <summary>
-        /// 业务类型  vote投票 survey问卷 exam评测 learn学习活动 homework作业活动
-        /// </summary>
-        public string type { get; set; }
-        public string name { get; set; }
-        public string startTime { get; set; }
-        public string endTime { get; set; }
-        public string scode { get; set; }
-    }
-}

+ 12 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/Inner/VoteRecord.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Models.Cosmos.Common.Inner
+{
+    public class VoteRecord
+    {
+        public List<string> opt { get; set; }
+        public long time { get; set; }
+    }
+}

+ 77 - 37
TEAMModelOS.SDK/Models/Cosmos/School/Survey.cs

@@ -15,21 +15,46 @@ namespace TEAMModelOS.SDK.Models
     {
         public Survey() {
             pk = "Survey";
-            questions = new List<Questions>();
-            classes = new List<Classes>();
+            // questions = new List<Question>();
+            classes = new List<string>();
+            tmdids = new List<string>();
         }
-       
-        public string name { get; set; }   //测试问卷名称', // 问卷名称
-        public string description { get; set; }   //测试问卷描述', // 问卷描述
+        /// <summary>
+        /// 学校编码或教室tmdid
+        /// </summary>
+        [Required(ErrorMessage = "owner 必须设置")]
+        public string owner { get; set; }
+        /// <summary>
+        /// 问卷名称
+        /// </summary>
+        [Required(ErrorMessage = "name 必须设置")]
+        public string name { get; set; }
+        /// <summary>
+        ///  问卷描述
+        /// </summary>
+        public string description { get; set; }
         //public string type { get; set; }   //normal', // 问卷类型
-        public string school { get; set; }
+        /// <summary>
+        /// 创建者的id 
+        /// </summary>
+        [Required(ErrorMessage = "creatorId 必须设置")]
         public string creatorId { get; set; }
-        public int year { get; set; }
+        // public int year { get; set; }
+        /// <summary>
+        /// pending 待发布|going 已发布|finish 已结束
+        /// </summary>
+        [Required(ErrorMessage = "progress 必须设置")]
         public string progress { get; set; }
-        public List<Questions> questions { get; set; }
-        public List<string> targetClassIds { get; set; }
-        public int stuCount { get; set; }
-        public int status { get; set; } // 问卷状态(100:待发布 200:已发布 300:已结束)
+        public string scope { get; set; }
+
+        public List<string> tmdids { get; set; }
+        public List<string> classes { get; set; }
+        // public List<string> targetClassIds { get; set; }
+        //  public int stuCount { get; set; }
+        /// <summary>
+        /// 问卷状态(100:待发布 200:已发布 300:已结束)
+        /// </summary>
+        public int status { get; set; }
         /// <summary>
         /// 发布对象
         /// </summary>
@@ -39,7 +64,7 @@ namespace TEAMModelOS.SDK.Models
         /// <summary>
         /// 发布模式 0 立即发布 1 定时
         /// </summary>
-        public string publishModel { get; set; }
+        //public string publishModel { get; set; }
 
         /// <summary>
         /// 开始时间
@@ -51,39 +76,58 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public long endTime { get; set; }
         public long createTime { get; set; } // 问卷发布时间
-        public List<Classes> classes { get; set; }
 
+        /// <summary>
+        /// 更新时间
+        /// </summary>
+        public long updateTime { get; set; }
         //public long sequenceNumber { get; set; }
 
-        public string url { get; set; }
-        public string scope { get; set; }
+        public string blobUrl { get; set; }
+        //将问题放入Blob
+        //public List<Question> questions { get; set; }
 
     }
-    /*public class Item {
-        public string stem { get; set; }
-        /// <summary>
-        ///  Complete Single Multiple Subjective, Judge判断
-        /// </summary>
-        //public string type { get; set; }
-        public bool required { get; set; }
-        public int order { get; set; }
-        public string description { get; set; } = null;
-        public List<CodeValue> options { get; set; }
-        public List<CodeVal> result { get; set; }
-    }*/
-     public class Questions { 
+
+    /// <summary>
+    ///
+    /// </summary>
+    public class Question {
         public string qid { get; set; }
         public string question { get; set; }
-        public List<Options> option { get; set; }
+        public List<OptionSurvey> options { get; set; }
         public string type { get; set; }
-        public QuestionResult result { get; set; }
+        //public QuestionResult result { get; set; }
 
     }
-    public class Options { 
+
+    public class OptionSurvey{
+        /// <summary>
+        /// 选项编码
+        /// </summary>
         public string code { get; set; }
+        /// <summary>
+        /// 选项文本
+        /// </summary>
         public string value { get; set; }
-        public Result result { get; set; }
+        /// <summary>
+        /// 选项描述
+        /// </summary>
+        //public string desc { get; set; }
+        /// <summary>
+        /// 选择数
+        /// </summary>
+        public int? count { get; set; }
+        /// <summary>
+        /// 其他答案数量
+        /// </summary>
+        public int? other { get; set; }
     }
+
+
+
+
+
     public class QuestionResult
     {
         public double finish { get; set; }
@@ -105,7 +149,7 @@ namespace TEAMModelOS.SDK.Models
     public class AnswerRate {
         public string qid { get; set; }
         public double answerRate { get; set; }
-        public List<Options> option { get; set; }
+        public List<CodeValue> option { get; set; }
     }
     public class StudentInfo {
         public string id { get; set; }
@@ -122,8 +166,4 @@ namespace TEAMModelOS.SDK.Models
         public string qid { get; set; }
         public string answer { get; set; }
     }
-    /*public class CodeVal{
-        public string code { get; set; }
-        public int value { get; set; }
-    }*/
 }

+ 1 - 12
TEAMModelOS.SDK/Models/Cosmos/Common/Syllabus.cs

@@ -12,18 +12,7 @@ namespace TEAMModelOS.SDK.Models
     /// </summary>
     
     public class Syllabus : CosmosEntity
-    {
-        [Required(ErrorMessage = "{0} 必须填写")]
-        public string code { get; set; }
-        //[PartitionKey(name = "Syllabuses")]
-        public string pk { get; set; }
-        public int? ttl { get; set; }
-        /// <summary>
-        /// 
-        /// </summary>
-        [Required(ErrorMessage = "{0} 必须填写")]
-        public string id { get; set; }
-
+    {  
         /// <summary>
         /// 册别编码
         /// </summary>

+ 76 - 46
TEAMModelOS.SDK/Models/Cosmos/Common/Vote.cs

@@ -1,3 +1,4 @@
+
 using System;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
@@ -16,81 +17,110 @@ namespace TEAMModelOS.SDK.Models
         public Vote()
         {
             pk = "Vote";
-            options = new List<OptionsVote>();
+            options = new List<OptionVote>();
 
         }
-
+        /// <summary>
+        /// 学校编码或教师tmdid
+        /// </summary>
+        [Required(ErrorMessage = "owner 必须设置")]
+        public string owner { get; set; }
         /// <summary>
         /// 投票名称
         /// </summary>
+        [Required(ErrorMessage = "name 必须设置")]
         public string name { get; set; }
-        public string school { get; set; }
+        /// <summary>
+        /// 创建者的id 
+        /// </summary>
+        [Required(ErrorMessage = "creatorId 必须设置")]
         public string creatorId { get; set; }
-        public int year { get; set; }
+        /// <summary>
+        /// 投票描述
+        /// </summary>
+        public string description { get; set; }
+        /// <summary>
+        /// pending 待发布|going 已发布|finish 已结束
+        /// </summary>
+        //[Required(ErrorMessage = "progress 必须设置")]
         public string progress { get; set; }
-        public List<OptionsVote> options { get; set; }
+        /// <summary>
+        /// 投票选项
+        /// </summary>
+        public List<OptionVote> options { get; set; }
+        /// <summary>
+        /// //匿名投票,不公布投票人相关信息
+        /// </summary>
         public bool secret { get; set; }
-        public int selectMax { get; set; }
-        public int stuCount { get; set; }
+        /// <summary>
+        /// 投票周期/once一次,day天,week周,month月,年year等
+        /// </summary>
+        [Required(ErrorMessage = "times 必须设置")]
+        public string times { get; set; }
+        //周期内可投票数
+        public int voteNum { get; set; }
+        //周期内是否允许重复投相同一个选项。
+        public bool repeat { get; set; } 
+        /// <summary>
+        /// school|private
+        /// </summary>
+        [Required(ErrorMessage = "scope 必须设置")]
         public string scope { get; set; }
-        public List<string> targetClassIds { get; set; }
         /// <summary>
-        /// 发布模式 0 立即发布 1 定时
+        /// 参与投票的教师醍摩豆id
         /// </summary>
-        public string publishModel { get; set; }
-
+        public List<string> tmdids { get; set; }
+        /// <summary>
+        ///  scope 为school时 是学校的班级  为private 时是私人班级
+        /// </summary>
+        public List<string> classes { get; set; }
+        /// <summary>
+        /// (100:待发布 200:已发布 300:已结束)
+        /// </summary>
+        public int status { get; set; }
         /// <summary>
         /// 开始时间
         /// </summary>
         public long startTime { get; set; }
+        /// <summary>
+        /// 创建时间
+        /// </summary>
         public long createTime { get; set; }
-
         /// <summary>
         /// 结束时间
         /// </summary>
         public long endTime { get; set; }
-
-
         /// <summary>
-        /// 投票描述
+        /// 更新时间
         /// </summary>
-        public string description { get; set; }
-
-
-        /*        /// <summary>
-                /// 投票附件
-                /// </summary>
-                [ProtoMember(9)]
-                public List<ProcessRes> resource { get; set; }
-        */
-
+        public long updateTime { get; set; }
         /// <summary>
-        /// 状态 (100:待发布 200:已发布 300:已结束)
+        /// 投票记录
         /// </summary>
-        public int status { get; set; }
-
-
-        /*        /// <summary>
-                /// 模式
-                /// </summary>
-                [ProtoMember(11)]
-                public List<string> other { get; set; }*/
-
-        /*
-                public string url { get; set; }
-
-                /// <summary>
-                /// 选项
-                /// </summary>
-                [ProtoMember(12)]
-                public List<Option> option { get; set; }*/
-
-        //public long sequenceNumber { get; set; }
+        public string recordUrl { get; set; }
     }
-    public class OptionsVote
+    /// <summary>
+    /// 投票选项
+    /// </summary>
+    public class OptionVote
     {
+        /// <summary>
+        /// 投票编号
+        /// </summary>
         public string code { get; set; }
+        /// <summary>
+        /// 投票对象
+        /// </summary>
         public string value { get; set; }
+        /// <summary>
+        /// 投票对象描述
+        /// </summary>
         public string desc { get; set; }
+        /// <summary>
+        /// 得票数量
+        /// </summary>
+        public int? count { get; set; } 
     }
+    
 }
+

+ 8 - 1
TEAMModelOS.SDK/Models/Cosmos/School/Class.cs

@@ -43,6 +43,10 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public string openType { get; set; }
         public string scope { get; set; }
+        /// <summary>
+        /// 学生名单数据来源 1是不同学校的学生账号,2是扫码加入的醍摩豆ID
+        /// </summary>
+        public int source { get; set; }
         
     }
 }
@@ -54,7 +58,10 @@ public class Point
 }
 public class StudentSimple
 {
-
+    /// <summary>
+    /// 数据来源的分区键 如果是学校的编码 则是学校编码,如果是tmdid则是Base
+    /// </summary>
+    public string scode { get; set; }
     public string id { get; set; }
 
     public string name { get; set; }

+ 23 - 13
TEAMModelOS.SDK/Models/Cosmos/School/Knowledge.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Text;
 
 namespace TEAMModelOS.SDK.Models.Cosmos.School
@@ -11,18 +12,22 @@ namespace TEAMModelOS.SDK.Models.Cosmos.School
     {
         public Knowledge() {
             points = new List<string>();
-            blocks = new List<CodeValue>();
+            blocks = new List<Block>();
             pk = "Knowledge";
         }
-
+        [Required(ErrorMessage = "owner 必须设置")]
+        public string owner { get; set; }
+        [Required(ErrorMessage = "scope 必须设置")]
         public string scope { get; set; }
         /// <summary>
         /// 学段id
         /// </summary>
+       [Required(ErrorMessage = "periodId 必须设置")]
         public string periodId { get; set; }
         /// <summary>
         /// 学科id
         /// </summary>
+        [Required(ErrorMessage = "subjectId 必须设置")]
         public string subjectId { get; set; }
         /// <summary>
         /// 知识点
@@ -31,8 +36,13 @@ namespace TEAMModelOS.SDK.Models.Cosmos.School
         /// <summary>
         /// 知识块
         /// </summary>
-        public List<CodeValue> blocks { get; set; }
+        public List<Block> blocks { get; set; }
+
+    }
 
+    public class Block { 
+        public string name { get; set; }
+        public List<string> points { get; set; }
     }
     /*
     {
@@ -47,24 +57,24 @@ namespace TEAMModelOS.SDK.Models.Cosmos.School
         ],
         "block":[
             {
-                "code": "方程式" ,
-                "value":["一元一次方程","二元一次方程","一元二次方程","直线方程","三元一次方程","鸡兔同笼问题","微积分方程"]
+                "name": "方程式" ,
+                "points":["一元一次方程","二元一次方程","一元二次方程","直线方程","三元一次方程","鸡兔同笼问题","微积分方程"]
             },
             {
-                "code": "函数的特性" ,
-                "value": ["函数有界性","函数单调性","函数奇偶性","函数周期性","函数连续性","函数凹凸性"]
+                "name": "函数的特性" ,
+                "points": ["函数有界性","函数单调性","函数奇偶性","函数周期性","函数连续性","函数凹凸性"]
             },
             {
-                "code": "多项式函数" ,
-                "value": ["常函数","一次函数","二次函数","三次函数","四次函数","五次函数"]
+                "name": "多项式函数" ,
+                "points": ["常函数","一次函数","二次函数","三次函数","四次函数","五次函数"]
             },
             {
-                "code":  "基本初等函数" ,
-                "value": ["幂函数","指数函数","对数函数","三角函数","反三角函数","常数函数"]
+                "name":  "基本初等函数" ,
+                "points": ["幂函数","指数函数","对数函数","三角函数","反三角函数","常数函数"]
             },
             {
-                "code": "三角函数" ,
-                "value":["正弦函数","余弦函数","正切函数","余切函数","正割函数","余割函数","正矢函数","余矢函数","半正矢函数","半余矢函数","外正割函数","外余割函数"]
+                "name": "三角函数" ,
+                "points":["正弦函数","余弦函数","正切函数","余切函数","正割函数","余割函数","正矢函数","余矢函数","半正矢函数","半余矢函数","外正割函数","外余割函数"]
             }
         ]
     }

+ 23 - 23
TEAMModelOS.SDK/Models/Cosmos/Student/VoteRecord.cs

@@ -7,35 +7,35 @@ using TEAMModelOS.SDK.DI;
 
 namespace TEAMModelOS.SDK.Models
 {    
-    public class VoteRecord : CosmosEntity
-    {
+    //public class VoteRecord : CosmosEntity
+    //{
 
-        public VoteRecord()
-        {
-            classroom = new ClassroomItem();
-        }
+    //    public VoteRecord()
+    //    {
+    //        classroom = new ClassroomItem();
+    //    }
 
-        /// <summary>
-        /// 姓名
-        /// </summary>
-        public string name { get; set; }
+    //    /// <summary>
+    //    /// 姓名
+    //    /// </summary>
+    //    public string name { get; set; }
 
-        /// <summary>
-        /// 上课班级
-        /// </summary>
-        public ClassroomItem classroom { get; set; }
+    //    /// <summary>
+    //    /// 上课班级
+    //    /// </summary>
+    //    public ClassroomItem classroom { get; set; }
 
 
 
-        /// <summary>
-        /// 提交时间
-        /// </summary>
-        public long submitTime { get; set; }
+    //    /// <summary>
+    //    /// 提交时间
+    //    /// </summary>
+    //    public long submitTime { get; set; }
 
-        /// <summary>
-        /// 选项
-        /// </summary>
-        public string option { get; set; }
+    //    /// <summary>
+    //    /// 选项
+    //    /// </summary>
+    //    public string option { get; set; }
 
-    }
+    //}
 }

+ 1 - 1
TEAMModelOS/ClientApp/package.json

@@ -32,7 +32,6 @@
 		"file-saver": "^2.0.2",
 		"firebase": "^7.19.0",
 		"firestore": "^1.1.6",
-		"hammer-touchemulator": "0.0.2",
 		"html2canvas": "^1.0.0-rc.7",
 		"increase-memory-limit": "^1.0.7",
 		"js-sha1": "^0.6.0",
@@ -67,6 +66,7 @@
 		"vue-infinite-loading": "^2.4.4",
 		"vue-loading-overlay": "^3.3.3",
 		"vue-msal": "^2.0.0",
+		"vue-pdf": "^4.2.0",
 		"vue-router": "^3.0.6",
 		"vue-scroll": "^2.1.12",
 		"vue-server-renderer": "^2.6.12",

+ 1 - 1
TEAMModelOS/ClientApp/public/index.html

@@ -6,7 +6,7 @@
 		<meta name="viewport" content="width=device-width,initial-scale=1.0">
 		<link rel="icon" href="<%= BASE_URL %>favicon.ico">
 		<link id="theme" type="text/css" rel="stylesheet" href="<%= BASE_URL %>theme/dark-theme.css">
-		<title>醍摩豆IES5智慧大云</title>
+		<title>IES5 云平台</title>
 		<script type="text/javascript" id="MathJax-script" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script>
 		<script>
 			let cloudSetting = localStorage.getItem('cloudSetting')

+ 8 - 1
TEAMModelOS/ClientApp/src/api/uploadFile.js

@@ -31,5 +31,12 @@ export default {
 	deleteBlobs: function (data) {
         return post('/blob/delete-blobs', data)
     },
-
+    // 上传文件之前需要先检查文件是否存在
+	checkBlobs: function (data) {
+        return post('/blob/check-blobsize', data)
+    },
+    // 上传文件之后需要更新blob空间
+	updateSize: function (data) {
+        return post('/blob/update-blobsize', data)
+    }
 }

+ 2 - 2
TEAMModelOS/ClientApp/src/api/index.js

@@ -10,7 +10,7 @@ import stuAccount from './stuAccount'
 import syllabus from './syllabus'
 import knowledge from './knowledge'
 import teachContent from './teachContent'
-import uploadFile from './uploadFile'
+import blob from './blob'
 import courseMgmt from './courseMgmt'
 import login from './login'
 import schoolList from './schoolList'
@@ -41,7 +41,7 @@ export default {
     talMgmt,
     teachContent,
     // cloudApi,
-    uploadFile,
+    blob,
     courseMgmt,
     newEvaluation,
     login,

+ 253 - 0
TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html

@@ -30,6 +30,72 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe61a;</span>
+                <div class="name">链接</div>
+                <div class="code-name">&amp;#xe61a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe671;</span>
+                <div class="name">链接</div>
+                <div class="code-name">&amp;#xe671;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe61d;</span>
+                <div class="name">jpg</div>
+                <div class="code-name">&amp;#xe61d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe8b7;</span>
+                <div class="name">zip</div>
+                <div class="code-name">&amp;#xe8b7;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe64d;</span>
+                <div class="name">PDF</div>
+                <div class="code-name">&amp;#xe64d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe608;</span>
+                <div class="name">ppt</div>
+                <div class="code-name">&amp;#xe608;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe617;</span>
+                <div class="name">mp3</div>
+                <div class="code-name">&amp;#xe617;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66e;</span>
+                <div class="name">video</div>
+                <div class="code-name">&amp;#xe66e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ff;</span>
+                <div class="name">文件</div>
+                <div class="code-name">&amp;#xe6ff;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe601;</span>
+                <div class="name">word</div>
+                <div class="code-name">&amp;#xe601;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe61c;</span>
+                <div class="name">xlsx</div>
+                <div class="code-name">&amp;#xe61c;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe666;</span>
                 <div class="name">转换</div>
@@ -470,6 +536,105 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont icon-share_link"></span>
+            <div class="name">
+              链接
+            </div>
+            <div class="code-name">.icon-share_link
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-link2"></span>
+            <div class="name">
+              链接
+            </div>
+            <div class="code-name">.icon-link2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-jpg"></span>
+            <div class="name">
+              jpg
+            </div>
+            <div class="code-name">.icon-jpg
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-zip"></span>
+            <div class="name">
+              zip
+            </div>
+            <div class="code-name">.icon-zip
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-pdf"></span>
+            <div class="name">
+              PDF
+            </div>
+            <div class="code-name">.icon-pdf
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-ppt"></span>
+            <div class="name">
+              ppt
+            </div>
+            <div class="code-name">.icon-ppt
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-mp3"></span>
+            <div class="name">
+              mp3
+            </div>
+            <div class="code-name">.icon-mp3
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-video1"></span>
+            <div class="name">
+              video
+            </div>
+            <div class="code-name">.icon-video1
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-V"></span>
+            <div class="name">
+              文件
+            </div>
+            <div class="code-name">.icon-V
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-word"></span>
+            <div class="name">
+              word
+            </div>
+            <div class="code-name">.icon-word
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-xlsx"></span>
+            <div class="name">
+              xlsx
+            </div>
+            <div class="code-name">.icon-xlsx
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont icon-convert"></span>
             <div class="name">
@@ -1084,6 +1249,94 @@
       <div class="content symbol">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-share_link"></use>
+                </svg>
+                <div class="name">链接</div>
+                <div class="code-name">#icon-share_link</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-link2"></use>
+                </svg>
+                <div class="name">链接</div>
+                <div class="code-name">#icon-link2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-jpg"></use>
+                </svg>
+                <div class="name">jpg</div>
+                <div class="code-name">#icon-jpg</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-zip"></use>
+                </svg>
+                <div class="name">zip</div>
+                <div class="code-name">#icon-zip</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-pdf"></use>
+                </svg>
+                <div class="name">PDF</div>
+                <div class="code-name">#icon-pdf</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-ppt"></use>
+                </svg>
+                <div class="name">ppt</div>
+                <div class="code-name">#icon-ppt</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-mp3"></use>
+                </svg>
+                <div class="name">mp3</div>
+                <div class="code-name">#icon-mp3</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-video1"></use>
+                </svg>
+                <div class="name">video</div>
+                <div class="code-name">#icon-video1</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-V"></use>
+                </svg>
+                <div class="name">文件</div>
+                <div class="code-name">#icon-V</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-word"></use>
+                </svg>
+                <div class="name">word</div>
+                <div class="code-name">#icon-word</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-xlsx"></use>
+                </svg>
+                <div class="name">xlsx</div>
+                <div class="code-name">#icon-xlsx</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-convert"></use>

Файловите разлики са ограничени, защото са твърде много
+ 51 - 8
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css


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


Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js


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

@@ -5,6 +5,83 @@
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "551219",
+      "name": "链接",
+      "font_class": "share_link",
+      "unicode": "e61a",
+      "unicode_decimal": 58906
+    },
+    {
+      "icon_id": "6537196",
+      "name": "链接",
+      "font_class": "link2",
+      "unicode": "e671",
+      "unicode_decimal": 58993
+    },
+    {
+      "icon_id": "19145271",
+      "name": "jpg",
+      "font_class": "jpg",
+      "unicode": "e61d",
+      "unicode_decimal": 58909
+    },
+    {
+      "icon_id": "1344658",
+      "name": "zip",
+      "font_class": "zip",
+      "unicode": "e8b7",
+      "unicode_decimal": 59575
+    },
+    {
+      "icon_id": "1973778",
+      "name": "PDF",
+      "font_class": "pdf",
+      "unicode": "e64d",
+      "unicode_decimal": 58957
+    },
+    {
+      "icon_id": "2295972",
+      "name": "ppt",
+      "font_class": "ppt",
+      "unicode": "e608",
+      "unicode_decimal": 58888
+    },
+    {
+      "icon_id": "2295990",
+      "name": "mp3",
+      "font_class": "mp3",
+      "unicode": "e617",
+      "unicode_decimal": 58903
+    },
+    {
+      "icon_id": "7684652",
+      "name": "video",
+      "font_class": "video1",
+      "unicode": "e66e",
+      "unicode_decimal": 58990
+    },
+    {
+      "icon_id": "11755918",
+      "name": "文件",
+      "font_class": "V",
+      "unicode": "e6ff",
+      "unicode_decimal": 59135
+    },
+    {
+      "icon_id": "14559710",
+      "name": "word",
+      "font_class": "word",
+      "unicode": "e601",
+      "unicode_decimal": 58881
+    },
+    {
+      "icon_id": "19145279",
+      "name": "xlsx",
+      "font_class": "xlsx",
+      "unicode": "e61c",
+      "unicode_decimal": 58908
+    },
     {
       "icon_id": "2604478",
       "name": "转换",

Файловите разлики са ограничени, защото са твърде много
+ 61 - 28
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.svg


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


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


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


+ 1 - 1
TEAMModelOS/ClientApp/src/boot-app.js

@@ -1,5 +1,5 @@
 import Vue from 'vue'
-import '@babel/polyfill'
+//import '@babel/polyfill'
 import i18n from '@/locale'
 import router from './router/index'
 import store from './store'

+ 88 - 42
TEAMModelOS/ClientApp/src/common/BaseCanvas.vue

@@ -1,9 +1,16 @@
 <template>
-	<canvas :ref="domId" class="app-sign-canvas" :id="domId" @mousedown.prevent.stop="handleMousedown"
-	 @mousemove.prevent.stop="handleMousemove" @mouseup.prevent.stop="handleMouseup" @mouseleave.prevent.stop="handleMouseleave"
-	 @touchstart.prevent.stop="handleTouchstart" @touchmove.prevent.stop="handleTouchmove" @touchend.prevent.stop="handleTouchend">
-		您的浏览器不支持canvas技术,请升级浏览器!
-	</canvas>
+	<div class="canvas-main">
+		<canvas :ref="domId" class="app-sign-canvas" :id="domId" @mousedown.prevent.stop="handleMousedown"
+		 @mousemove.prevent.stop="handleMousemove" @mouseup.prevent.stop="handleMouseup" @mouseleave.prevent.stop="handleMouseleave"
+		 @touchstart.prevent.stop="handleTouchstart" @touchmove.prevent.stop="handleTouchmove" @touchend.prevent.stop="handleTouchend">
+			您的浏览器不支持canvas技术,请升级浏览器!
+		</canvas>
+		<div class="float-text" @mousedown.prevent.stop="move">
+			{{positionX}}
+			        {{positionY}}
+		</div>
+	</div>
+
 </template>
 <script>
 	export default {
@@ -28,6 +35,8 @@
 		},
 		data() {
 			return {
+				positionX:0,
+				positionY:0,
 				value: null, //base64
 				domId: `sign-canvas-${Math.random().toString(36).substr(2)}`, //生成唯一dom标识
 				canvas: null, //canvas dom对象
@@ -54,41 +63,6 @@
 				},
 			};
 		},
-		mounted() {
-			this.init();
-		},
-
-		watch: {
-			'options.writeWidth': {
-				handler(n) {
-					this.context.lineWidth = n
-					this.config.writeWidth = n
-				},
-				deep: true
-			},
-			'options.writeColor': {
-				handler(n) {
-					this.context.strokeStyle = n
-					this.config.writeColor = n
-				},
-				deep: true
-			},
-			'options.canvasWidth': {
-				handler(n) {
-					this.canvas.width = n
-					this.config.canvasWidth = n
-					this.canvasInit();
-				},
-			},
-			'options.canvasHeight': {
-				handler(n) {
-					this.canvas.height = n
-					this.config.canvasHeight = n
-					this.canvasInit();
-				},
-			},
-		},
-
 		methods: {
 			init() {
 				const options = this.options;
@@ -112,7 +86,32 @@
 				})
 
 			},
-			
+
+			move(e) {
+				let odiv = e.target; //获取目标元素
+
+				//算出鼠标相对元素的位置
+				let disX = e.clientX - odiv.offsetLeft;
+				let disY = e.clientY - odiv.offsetTop;
+				document.onmousemove = (e) => { //鼠标按下并移动的事件
+					//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
+					let left = e.clientX - disX;
+					let top = e.clientY - disY;
+
+					//绑定元素位置到positionX和positionY上面
+					this.positionX = top;
+					this.positionY = left;
+
+					//移动当前元素
+					odiv.style.left = left + 'px';
+					odiv.style.top = top + 'px';
+				};
+				document.onmouseup = (e) => {
+					document.onmousemove = null;
+					document.onmouseup = null;
+				};
+			},
+
 			/* 绘制输入的文本 */
 			doAddText(val) {
 				let context = this.context
@@ -383,6 +382,53 @@
 				}
 				return realNum;
 			}
-		}
+		},
+		mounted() {
+			this.init();
+		},
+		
+		watch: {
+			'options.writeWidth': {
+				handler(n) {
+					this.context.lineWidth = n
+					this.config.writeWidth = n
+				},
+				deep: true
+			},
+			'options.writeColor': {
+				handler(n) {
+					this.context.strokeStyle = n
+					this.config.writeColor = n
+				},
+				deep: true
+			},
+			'options.canvasWidth': {
+				handler(n) {
+					this.canvas.width = n
+					this.config.canvasWidth = n
+					this.canvasInit();
+				},
+			},
+			'options.canvasHeight': {
+				handler(n) {
+					this.canvas.height = n
+					this.config.canvasHeight = n
+					this.canvasInit();
+				},
+			},
+		},
 	};
 </script>
+<style lang="less" scoped>
+	.canvas-main {
+		position: relative;
+
+		.float-text {
+			position: absolute;
+			font-size: 20px;
+			color: red;
+			cursor: pointer;
+			user-select: none;
+		}
+	}
+</style>

+ 398 - 0
TEAMModelOS/ClientApp/src/common/BaseKonva.vue

@@ -0,0 +1,398 @@
+<template>
+	<div class="app-container">
+		<div id="container" ref="container" class="container" />
+	</div>
+</template>
+
+<script>
+	import Konva from 'konva'
+
+	export default {
+		name: 'DragAndDrop',
+		props: {
+			image: {
+				required: false,
+				type: [String],
+				default: null
+			},
+
+			options: { //配置项
+				required: false,
+				type: [Object],
+				default: () => null
+			},
+		},
+		data() {
+			return {
+				myStage: null,
+				myLayer: null,
+				isText: false,
+				mode: 'paint',
+				config: {
+					textColor: 'red',
+					textSize: 16,
+					isDpr: false, //是否使用dpr兼容高分屏 [Boolean] 可选
+					lastWriteSpeed: 1, //书写速度 [Number] 可选
+					lastWriteWidth: 2, //下笔的宽度 [Number] 可选
+					lineCap: 'round', //线条的边缘类型 [butt]平直的边缘 [round]圆形线帽 [square]	正方形线帽
+					lineJoin: 'round', //线条交汇时边角的类型  [bevel]创建斜角 [round]创建圆角 [miter]创建尖角。
+					canvasWidth: 1200, //canvas宽高 [Number] 可选
+					canvasHeight: 600, //高度  [Number] 可选
+					isShowBorder: true, //是否显示边框 [可选]
+					bgColor: '#fcc', //背景色 [String] 可选 注:这背景色仅仅只是canvas背景,保存的图片仍然是透明的
+					borderWidth: 1, // 网格线宽度  [Number] 可选
+					borderColor: "#ff787f", //网格颜色  [String] 可选
+					writeWidth: 5, //基础轨迹宽度  [Number] 可选
+					maxWriteWidth: 30, // 写字模式最大线宽  [Number] 可选
+					minWriteWidth: 5, // 写字模式最小线宽  [Number] 可选
+					writeColor: 'red', // 轨迹颜色  [String] 可选
+					isSign: false, //签名模式 [Boolean] 默认为非签名模式,有线框, 当设置为true的时候没有任何线框
+					imgType: 'png' //下载的图片格式  [String] 可选为 jpeg  canvas本是透明背景的
+				},
+			}
+		},
+		created() {
+			this.$nextTick(() => {
+				this.initStage()
+			})
+		},
+		methods: {
+			initStage() {
+				let that = this
+				var stage = new Konva.Stage({
+					container: 'container',
+					width: this.config.canvasWidth,
+					height: this.config.canvasHeight
+				});
+				this.$refs.container.style.width = this.config.canvasWidth + 'px'
+				var layer = new Konva.Layer();
+
+				var isPaint = false;
+				var mode = 'brush';
+				var lastLine;
+
+				stage.on('mousedown.1 touchstart.1', function(e) {
+					if (that.isText || (that.mode !== 'paint' && that.mode !== 'transform')) {
+						that.isText = false
+						that.mode = 'paint'
+						let textNode = new Konva.Text({
+							text: "",
+							x: stage.getPointerPosition().x,
+							y: stage.getPointerPosition().y,
+							fontSize: that.config.textSize,
+							fill: that.config.textColor,
+							draggable: true,
+							width: 200,
+							// height: 50,
+							name: "text",
+						});
+						layer.add(textNode);
+						let tr = new Konva.Transformer({
+							node: textNode,
+							enabledAnchors: ["middle-left", "middle-right"],
+							centeredScaling: true,
+							anchorStroke: "#808080",
+							anchorFill: "#fff",
+							anchorSize: 12,
+							anchorCornerRadius: 5,
+							anchorStrokeWidth: 2,
+							borderStroke: "#000000",
+							borderStrokeWidth: 2,
+							borderDash: [3, 3],
+							padding: 10,
+							// boundBoxFunc: (oldBox, newBox) => {
+							// 	if (newBox.width < 20) {
+							// 		return oldBox
+							// 	}
+							// 	return newBox
+							// }
+
+						});
+						layer.add(tr);
+						textNode.on("transformstart", function() {
+							that.isPaint = false
+							that.mode = 'transform'
+						});
+						textNode.on("transform", function() {
+							that.isPaint = false
+							textNode.setAttrs({
+								width: Math.max(textNode.width() * textNode.scaleX(), 20),
+								height: Math.max(textNode.height() * textNode.scaleY(), 20),
+								scaleX: 1,
+								scaleY: 1
+							})
+						});
+						textNode.on("transformend", function() {
+							that.isPaint = true
+							that.mode = 'paint'
+						});
+						layer.draw();
+						textNode.on("dblclick", (e) => {
+							e.evt.preventDefault();
+							that.$parent.curMode = 'text'
+							that.isPaint = false
+							textNode.hide();
+							tr.show();
+							layer.draw();
+							let textPosition = textNode.absolutePosition();
+							let stageBox = stage.container().getBoundingClientRect();
+							let areaPosition = {
+								x: stageBox.left + textPosition.x,
+								y: stageBox.top + textPosition.y,
+							};
+							let textarea = document.createElement("textarea");
+							document.body.appendChild(textarea);
+							textarea.value = textNode.text();
+							textarea.style.position = "absolute";
+							textarea.style.top = areaPosition.y + "px";
+							textarea.style.left = areaPosition.x + "px";
+							textarea.style.width = textNode.width() - textNode.padding() * 2 + "px";
+							textarea.style.height = textNode.height() - textNode.padding() * 2 + 5 + "px";
+							textarea.style.fontSize = textNode.fontSize() + "px";
+							textarea.style.border = "none";
+							textarea.style.padding = "0px";
+							textarea.style.margin = "0px";
+							textarea.style.overflow = "hidden";
+							textarea.style.background = "none";
+							textarea.style.zIndex = "9999999";
+							textarea.style.outline = "none";
+							textarea.style.resize = "none";
+							textarea.style.lineHeight = textNode.lineHeight();
+							textarea.style.fontFamily = textNode.fontFamily();
+							textarea.style.transformOrigin = "left top";
+							textarea.style.textAlign = textNode.align();
+							textarea.style.color = textNode.fill();
+							let rotation = textNode.rotation();
+							let transform = "";
+							if (rotation) {
+								transform += "rotateZ(" + rotation + "deg)";
+							}
+
+							let px = 0;
+							let isFirefox =
+								navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
+							if (isFirefox) {
+								px += 2 + Math.round(textNode.fontSize() / 20);
+							}
+							transform += "translateY(-" + px + "px)";
+							textarea.style.transform = transform;
+							// reset height
+							textarea.style.height = "auto";
+							// after browsers resized it we can set actual value
+							textarea.style.height = textarea.scrollHeight + 3 + "px";
+							textarea.focus();
+
+							function removeTextarea() {
+								textarea.parentNode.removeChild(textarea);
+								window.removeEventListener("click", handleOutsideClick);
+								textNode.show();
+								tr.hide();
+								tr.forceUpdate();
+								layer.draw();
+							}
+
+							function setTextareaWidth(newWidth) {
+								if (!newWidth) {
+									// set width for placeholder
+									newWidth = textNode.placeholder.length * textNode.fontSize();
+								}
+								// some extra fixes on different browsers
+								let isSafari = /^((?!chrome|android).)*safari/i.test(
+									navigator.userAgent
+								);
+								let isFirefox =
+									navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
+								if (isSafari || isFirefox) {
+									newWidth = Math.ceil(newWidth);
+								}
+
+								let isEdge =
+									document.documentMode || /Edge/.test(navigator.userAgent);
+								if (isEdge) {
+									newWidth += 1;
+								}
+								textarea.style.width = newWidth + "px";
+							}
+
+							console.log(textarea)
+							textarea.addEventListener("keydown", function(e) {
+								if (e.keyCode === 27) {
+									removeTextarea();
+								}
+							});
+
+							textarea.addEventListener("keydown", function(e) {
+								that.$parent.curMode = 'text'
+								that.mode = 'paint'
+								that.isText = false
+								let scale = textNode.getAbsoluteScale().x;
+								setTextareaWidth(textNode.width() * scale);
+								textarea.style.height = "auto";
+								textarea.style.height =
+									textarea.scrollHeight + textNode.fontSize() + "px";
+							});
+
+							function handleOutsideClick(e) {
+								if (e.target !== textarea) {
+									textNode.text(textarea.value);
+									that.$parent.curMode = 'paint'
+									that.mode = 'paint'
+									removeTextarea();
+								}
+							}
+							setTimeout(() => {
+								window.addEventListener("click", handleOutsideClick);
+							});
+						});
+					} else if(that.mode === 'paint') {
+						console.log('进入绘制')
+						isPaint = true;
+						var pos = stage.getPointerPosition();
+						lastLine = new Konva.Line({
+							stroke: that.config.writeColor,
+							strokeWidth: that.config.writeWidth,
+							globalCompositeOperation: mode === 'brush' ? 'source-over' : 'destination-out',
+							points: [pos.x, pos.y],
+						});
+						layer.add(lastLine);
+					}else{
+						// 如果是点击的变形框 则不处理
+					}
+
+				});
+
+				stage.on('mouseup touchend', function() {
+					isPaint = false;
+				});
+
+				stage.on('mousemove touchmove', function(e) {
+					// e.evt.stopPropagation();
+					if (!isPaint || that.mode === 'text') {
+						return;
+					}
+					const pos = stage.getPointerPosition();
+					var newPoints = lastLine.points().concat([pos.x, pos.y]);
+					lastLine.points(newPoints);
+					layer.batchDraw();
+				});
+
+				stage.add(layer);
+				this.myLayer = layer
+				this.myStage = stage
+			},
+			
+			/* 渲染背景图 */
+			doRenderImg(src){
+				console.log(src)
+				const image = new Image()
+				image.src = src
+				image.setAttribute('crossOrigin', 'anonymous')
+				image.onload = () => {
+					const renderWidth = image.width > 1200 ? 1200 : image.width
+					const renderHeight = (image.height / image.width) * renderWidth
+					this.config.canvasWidth = renderWidth
+					this.config.canvasHeight = renderHeight
+					this.initStage()
+					const graph = new Konva.Image({
+						x: (this.myStage.attrs.width - renderWidth) / 2,
+						y: (this.myStage.attrs.height - renderHeight) / 2,
+						image: image,
+						width: renderWidth,
+						height: renderHeight
+					})
+					this.myLayer.add(graph)
+					this.myLayer.batchDraw()
+				}
+			},
+
+			/*白板缩放后 重新获取新的坐标未知*/
+			getRelativePointerPosition(node) {
+				// 函数将返回相对于传递的节点的指针位置
+				var transform = node.getAbsoluteTransform().copy();
+				// 为了检测相对位置,需要进行逆变换
+				transform.invert();
+				// 获取指针(如鼠标或触摸)位置
+				var pos = node.getStage().getPointerPosition();
+				// now we find relative point
+				return transform.point(pos);
+			},
+
+			/* 撤销操作 */
+			doUndo() {
+				this.myLayer.children.pop()
+				this.myLayer.draw()
+			},
+
+			/* 绘制输入的文本 */
+			doAddText(val) {
+				this.isText = true
+			},
+
+			/* 清屏操作 */
+			canvasClear() {
+				this.initStage()
+			},
+
+			/* 截图保存图片 */
+			saveAsImg() {
+				return new Promise(async (r, j) => {
+					console.log(this.myStage)
+					const image = new Image();
+					let compressImg = await this.$editorTools.compressImg(this.myStage.toDataURL(),
+						this.myStage.attrs.width, this.myStage.attrs.height)
+					r({
+						base64: compressImg,
+						width: this.myStage.attrs.width,
+						height: this.myStage.attrs.height
+					})
+				})
+			}
+		},
+
+		watch: {
+			'options.writeWidth': {
+				handler(n) {
+					this.config.writeWidth = n
+				},
+				immediate: true
+			},
+			'options.writeColor': {
+				handler(n) {
+					this.config.writeColor = n
+				},
+				immediate: true
+			},
+			'options.textColor': {
+				handler(n) {
+					this.config.textColor = n
+				},
+				immediate: true
+			},
+			'options.textSize': {
+				handler(n) {
+					this.config.textSize = n
+				},
+				immediate: true
+			},
+			// 'options.canvasWidth': {
+			// 	handler(n) {
+			// 		this.config.canvasWidth = n
+			// 		this.initStage();
+			// 	},
+			// },
+			// 'options.canvasHeight': {
+			// 	handler(n) {
+			// 		this.config.canvasHeight = n
+			// 		this.initStage();
+			// 	},
+			// },
+		},
+	}
+</script>
+<style>
+	.container {
+		max-width: 1200px;
+		margin: 0 auto;
+		background-color: #dadada;
+	}
+</style>

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

@@ -214,32 +214,34 @@
                         icon: 'iconfont icon-activityS',
                         name: this.$t('system.menu.scAc'),
                         router: '',
-                        role: 'admin',
-                        permission: 'schoolAc-read|schoolAc-upd',
+                        // role: 'admin',
+                        // permission: 'schoolAc-read|schoolAc-upd',
+                        role: 'teacher|admin',
+                        permission: '',
                         child: [
                             {
                                 icon: 'iconfont icon-test',
                                 name: this.$t('system.menu.scEv'),
                                 router: '/home/schoolEvaluation',
                                 tag: '*',
-                                role: 'admin',
-                                permission: 'schoolAc-read|schoolAc-upd'
+                                role: 'teacher|admin',
+                                permission: ''
                             },
                             {
                                 icon: 'iconfont icon-vote',
                                 name: this.$t('system.menu.scVote'),
                                 router: '/home/manageVote',
                                 tag: '*',
-                                role: 'admin',
-                                permission: 'schoolAc-read|schoolAc-upd'
+                                role: 'teacher|admin',
+                                permission: ''
                             },
                             {
                                 icon: 'iconfont icon-questionnaire',
                                 name: this.$t('system.menu.scQu'),
                                 router: '/home/manageQuestionnaire',
                                 tag: '*',
-                                role: 'admin',
-                                permission: 'schoolAc-read|schoolAc-upd'
+                                role: 'teacher|admin',
+                                permission: ''
                             },
                         ]
                     },

+ 74 - 56
TEAMModelOS/ClientApp/src/common/BaseMyCanvas.vue

@@ -1,46 +1,44 @@
 <template>
 	<div id="baseCanvas" @mouseover="mouseOver" @mouseleave="mouseLeave">
-		<BaseCanvas class="sign-canvas" ref="SignCanvas" :options="options"/>
+		<!-- <BaseCanvas class="sign-canvas" ref="SignCanvas" :options="options"/> -->
+		<BaseKonva class="sign-canvas" ref="SignCanvas" :options="options"/>
 		<div class="canvas-tools animated fadeIn" ref="canvasTools">
-			<span class="canvas-tools-item" style="border-radius: 15px 0 0 0;">
-				<Poptip trigger="hover">
+			<span :class="['canvas-tools-item',curMode === 'paint' ? 'tools-active' : '']" @click="doPaint()"  style="border-radius: 15px 0 0 0;">
+				<Poptip trigger="click">
 					<div class="flex-center">
-						<Icon type="md-brush" />
-						<span>粗细</span>
+						<Icon type="md-create" />
+						<span>绘制</span>
 					</div>
-					<div slot="content" class="widthDotBox">
-						<span class="widthDot widthDot5" :style="{ backgroundColor: (activeDot === 2 ? 'red' : '#827a7a')}" @click="onSelectWriteWidth(2,$event)"></span>
-						<span class="widthDot widthDot1" :style="{ backgroundColor: (activeDot === 5 ? 'red' : '#827a7a')}" @click="onSelectWriteWidth(5,$event)"></span>
-						<span class="widthDot widthDot2" :style="{ backgroundColor: (activeDot === 10 ? 'red' : '#827a7a')}" @click="onSelectWriteWidth(10,$event)"></span>
-						<span class="widthDot widthDot3" :style="{ backgroundColor: (activeDot === 15 ? 'red' : '#827a7a')}" @click="onSelectWriteWidth(15,$event)"></span>
-						<span class="widthDot widthDot4" :style="{ backgroundColor: (activeDot === 20 ? 'red' : '#827a7a')}" @click="onSelectWriteWidth(20,$event)"></span>
+					<div slot="content" class="textBox">
+						 <p class="text-tools-title">画笔粗细 <span style="font-weight: bold;color: #0086E6;margin-left: 15px;font-size: 18px;">{{ options.textSize }}</span> </p>
+						 <Slider v-model="options.writeWidth"></Slider>
+						 <p class="text-tools-title">画笔颜色</p>
+						 <ColorPicker v-model="options.writeColor" />
 					</div>
 				</Poptip>
 			</span>
-			<span class="canvas-tools-item" v-if="!isStudent">
-				<Poptip trigger="hover">
+			<span :class="['canvas-tools-item',curMode === 'text' ? 'tools-active' : '']" @click="addText()">
+				<Poptip trigger="click">
 					<div class="flex-center">
-						<Icon type="md-color-palette" :color="activeColor" />
-						<span>画笔颜色</span>
+						<Icon type="md-text" />
+						<span>文本输入</span>
 					</div>
-					<div slot="content" class="widthDotBox">
-						<span class="color-item color-item-black" :style="{ borderColor: (activeColor === 'black' ? 'black' : 'transparent')}"
-						 @click="onSelectWriteColor('black',$event)"></span>
-						<span class="color-item color-item-blue" :style="{ borderColor: (activeColor === 'blue' ? 'black' : 'transparent')}"
-						 @click="onSelectWriteColor('blue',$event)"></span>
-						<span class="color-item color-item-red" :style="{ borderColor: (activeColor === 'red' ? 'black' : 'transparent')}"
-						 @click="onSelectWriteColor('red',$event)"></span>
+					<div slot="content" class="textBox">
+						 <p class="text-tools-title">字体大小 <span style="font-weight: bold;color: #0086E6;margin-left: 15px;font-size: 18px;">{{ options.textSize }}</span> </p>
+						 <Slider v-model="options.textSize"></Slider>
+						 <p class="text-tools-title">字体颜色</p>
+						 <ColorPicker v-model="options.textColor" />
 					</div>
 				</Poptip>
 			</span>
-			<span class="canvas-tools-item" @click="addText()">
-				<Icon type="md-text" />
-				<span>文本输入</span>
-			</span>
 			<span class="canvas-tools-item" @click="canvasClear()">
 				<Icon type="md-refresh" />
 				<span>清屏</span>
 			</span>
+			<span class="canvas-tools-item" @click="undo()">
+				<Icon type="md-undo" />
+				<span>撤销</span>
+			</span>
 			<span class="canvas-tools-item" @click="closeModal()">
 				<Icon type="md-close" />
 				<span>关闭</span>
@@ -50,14 +48,6 @@
 				<span>保存</span>
 			</span>
 		</div>
-		
-		<Modal v-model="addTextModal" title="添加文本" ref="pointRef" width="400px" class="related-point-modal" style="z-index:99999">
-			<span>输入文本内容</span>
-			<Input v-model="addTextVal"  style="margin:10px 0" />
-			<div slot="footer">
-				<Button type="primary" style="width: 100px;" @click="doAddText">确认</Button>
-			</div>
-		</Modal>
 	</div>
 </template>
 <script>
@@ -91,7 +81,10 @@
 				addTextVal:'',
 				activeDot: 5,
 				activeColor: 'black',
+				curMode:'paint',
 				options: {
+					textColor:'red',
+					textSize:16,
 					isDpr: false, //是否使用dpr兼容高分屏 [Boolean] 可选
 					lastWriteSpeed: 1, //书写速度 [Number] 可选
 					lastWriteWidth: 2, //下笔的宽度 [Number] 可选
@@ -104,7 +97,7 @@
 					borderWidth: 2, // 网格线宽度  [Number] 可选
 					borderColor: "#7c7c7c", //网格颜色  [String] 可选
 					writeWidth: 5, //基础轨迹宽度  [Number] 可选
-					writeColor: '#000', // 轨迹颜色  [String] 可选
+					writeColor: 'red', // 轨迹颜色  [String] 可选
 					isSign: true, //签名模式 [Boolean] 默认为非签名模式,有线框, 当设置为true的时候没有任何线框
 					imgType: 'png' //下载的图片格式  [String] 可选为 jpeg  canvas本是透明背景的
 				}
@@ -116,18 +109,25 @@
 		methods: {
 			// 移入
 			mouseOver() {
-				this.$refs.canvasTools.style.display = 'flex'
+				// this.$refs.canvasTools.style.display = 'flex'
 			},
 			// 移出
 			mouseLeave() {
-				this.$refs.canvasTools.style.display = 'none'
+				// this.$refs.canvasTools.style.display = 'none'
 			},
 			onChangeWriteColor(val) {
 				this.options.writeColor = val
 			},
 			
 			addText(){
-				this.addTextModal = true
+				this.curMode = 'text'
+				this.$refs.SignCanvas.isText = true
+				this.$refs.SignCanvas.mode = 'text'
+			},
+			
+			doPaint(){
+				this.curMode = 'paint'
+				this.$refs.SignCanvas.mode = 'paint'
 			},
 			
 			doAddText(){
@@ -136,7 +136,6 @@
 			},
 
 			onSelectWriteColor(val) {
-				console.log(val)
 				this.options.writeColor = val
 				this.activeColor = val
 			},
@@ -146,7 +145,6 @@
 			},
 
 			onSelectWriteWidth(val) {
-				console.log(val)
 				this.options.writeWidth = val
 				this.activeDot = val
 			},
@@ -154,6 +152,11 @@
 			closeModal() {
 				this.$emit('onCloseModal')
 			},
+			
+			undo(){
+				this.$refs.SignCanvas.doUndo();
+			},
+			
 			/**
 			 * 清除画板
 			 */
@@ -165,18 +168,19 @@
 			initBgImg() {
 				if (this.bgImg) {
 					this.$nextTick(() => {
-						var img = new Image()
-						img.setAttribute('crossOrigin', 'anonymous');
-						img.src = this.bgImg
-						img.onload = () => {
-							let renderWidth = img.width > 1200 ? 1200 : img.width
-							let renderHeight = renderWidth * (img.height / img.width)
-							this.options.canvasWidth = renderWidth
-							this.options.canvasHeight = renderHeight
-							setTimeout(() => {
-								this.$refs.SignCanvas.context.drawImage(img, 0, 0, img.width, img.height, 0, 0, renderWidth, renderHeight);
-							}, 10)
-						}
+						this.$refs.SignCanvas.doRenderImg(this.bgImg)
+						// var img = new Image()
+						// img.setAttribute('crossOrigin', 'anonymous');
+						// img.src = this.bgImg
+						// img.onload = () => {
+						// 	let renderWidth = img.width > 1200 ? 1200 : img.width
+						// 	let renderHeight = renderWidth * (img.height / img.width)
+						// 	this.options.canvasWidth = renderWidth
+						// 	this.options.canvasHeight = renderHeight
+						// 	setTimeout(() => {
+						// 		// this.$refs.SignCanvas.context.drawImage(img, 0, 0, img.width, img.height, 0, 0, renderWidth, renderHeight);
+						// 	}, 10)
+						// }
 					})
 				}
 			},
@@ -191,7 +195,7 @@
 
 		},
 		mounted() {
-			this.activeColor = this.writeColor || 'black'
+			this.options.writeColor = this.writeColor || 'black'
 			/* 如果有传背景图片 则接收后绘制作为Canvas的背景 */
 
 		},
@@ -199,6 +203,7 @@
 			writeColor: {
 				handler(n, o) {
 					this.onSelectWriteColor(n)
+					this.options.writeColor = n || 'black'
 				},
 				immediate: true
 			},
@@ -234,9 +239,13 @@
 		bottom: 0;
 		left: 50%;
 		transform: translate(-50%, 0);
-		display: none;
+		display: flex;
 		background-color: #696969;
 		border-radius: 15px 15px 0 0;
+		
+		.tools-active{
+			background-color: #2d8cf0;
+		}
 
 		&-item {
 			display: flex;
@@ -249,8 +258,8 @@
 			cursor: pointer;
 
 			&:hover {
-				background-color: rgba(255, 255, 255, .6);
-				color: #000;
+				background-color: #2d8cf0;
+				// color: #000;
 			}
 
 			.ivu-icon {
@@ -321,6 +330,15 @@
 				}
 			}
 		}
+		
+		.textBox{
+			color: #717171;
+			width: 200px;
+			
+			.text-tools-title{
+				margin: 10px 0;
+			}
+		}
 
 		.flex-center {
 			display: flex;

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

@@ -23,7 +23,7 @@
              * 获取blob授权信息
              * */
             getSasStr(list) {
-                this.$api.uploadFile.getContainerSAS().then(
+                this.$api.blob.getContainerSAS().then(
                     (res) => {
                         if (res.error == null) {
                             this.sasString = res.result.data.SAS

+ 2 - 4
TEAMModelOS/ClientApp/src/common/BaseUserPoptip.vue

@@ -121,10 +121,8 @@
 				this.getSize()
 			},
 			//获取Blob空间信息
-			async getSize() {
-				let sasRes = await this.$tools.getPrivateSas()
-                let containerClient = new BlobTool(sasRes.url, sasRes.name, sasRes.sas, 'private')
-                containerClient.getSize().then(
+			getSize() {
+                BlobTool.getSizeByServe(this.$store.state.userInfo.TEAMModelId).then(
                     res => {
                         this.sizeInfo = res
                     },

+ 3 - 2
TEAMModelOS/ClientApp/src/common/UploadModal.vue

@@ -1,7 +1,7 @@
 <template>
     <Modal v-model="uploadStatus" :ok-text="textLoading ? '上传中': isComplete ? '完成':'确认上传'" cancel-text="取消上传" :title="$t('teachContent.btnUpload')" class="upload-modal dark-iview-modal" width="660" :mask-closable="false" :closable="false" @on-ok="modalOk" @on-cancel="modalCancel" :loading="modalLoading">
         <div class="upload-file-box">
-            <Upload type="drag" action="" :show-upload-list="false" multiple :before-upload="customUpload" class="upload-wrap">
+            <Upload type="drag" action="" :show-upload-list="false" multiple :before-upload="customUpload" class="upload-wrap" :disabled="textLoading" >
                 <Icon class="upload-icon" custom="iconfont icon-upload" v-show="!uploadedList.length" />
                 <p class="upload-text" :style="{marginTop: uploadedList.length ? '25px':'0px'}">
                     <Icon size="24" style="font-size: 22px;vertical-align: baseline;margin-right: 5px;" custom="iconfont icon-upload" v-show="uploadedList.length" />
@@ -224,7 +224,7 @@ export default {
 
                 if (prefixFiles.length) {
                     prefixFiles.forEach(item => {
-                        this.$api.uploadFile.deletePrefix({
+                        this.$api.blob.deletePrefix({
                             cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
                             prefix: `res/${item.name.replace('.HTEX', '')}`
                         }).then(
@@ -337,6 +337,7 @@ export default {
                 this.textLoading = false
                 this.isComplete = true
                 this.modalLoading = false
+                
             }
         },
         //处理图片缩略图

+ 15 - 2
TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.vue

@@ -84,7 +84,7 @@
 					<span class="item-tools-info">难度:{{ exersicesDiff[item.level - 1] }}</span>
 					<span class="item-tools-info">认知层次:{{ exersicesField[item.field - 1] }}</span>
 					<span class="item-tools-info">使用次数:{{ item.usageCount || 0 }} 次</span>
-					<span class="item-tools-info">更新时间:{{ $tools.formatTime(item.createTime)  || 0 }} </span>
+					<span class="item-tools-info" v-if="item.createTime">更新时间:{{ $tools.formatTime(item.createTime)  || 0 }} </span>
 					<Button type="info" v-if="!isAnalysis" :style="{backgroundColor:selectList.map(i => i.id).indexOf(item.id) > -1 ? '#bbbbbb' : '#2db7f5'}"
 					 @click.stop="onSelectItem(item,index)">{{ selectList.map(i => i.id).indexOf(item.id) > -1 ? '移除' : '选题'}}</Button>
 				</div>
@@ -117,10 +117,16 @@
 			isAnalysis:{
 				type:Boolean,
 				default:false
+			},
+			
+			examScope:{
+				type:String,
+				default:null
 			}
 		},
 		data() {
 			return {
+				examPropScope:null,
 				dataLoading: false,
 				exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
 				exersicesDiff: this.$GLOBAL.EXERCISE_DIFFS(),
@@ -147,7 +153,8 @@
 			getBlobList(list){
 				return new Promise(async (r,j) => {
 					if(list.length){
-						let blobList = await this.$evTools.getFullItem(list)
+						/* 如果是评测里面的试题 则不需要从getFullItem取 */
+						let blobList = this.examPropScope ? list : await this.$evTools.getFullItem(list)
 						r(blobList)
 					}else{
 						r([])
@@ -315,6 +322,12 @@
 					}
 				}
 			},
+			examScope:{
+				handler(n,o){
+					this.examPropScope = n
+				},
+				immediate:true
+			},
 			selQue:{
 				handler(n,o){
 					if(n){

+ 1 - 1
TEAMModelOS/ClientApp/src/components/homework/BaseHwTable.vue

@@ -569,7 +569,7 @@
              * 获取blob授权信息
              * */
             getSasStr() {
-                this.$api.uploadFile.getContainerSAS().then(
+                this.$api.blob.getContainerSAS().then(
                     (res) => {
                         if (res.error == null) {
                             this.sasString = res.result.data.SAS

+ 11 - 11
TEAMModelOS/ClientApp/src/components/questionnaire/BaseQnForm.vue

@@ -1,34 +1,34 @@
 <template>
 	<div class="component-qn-form">
 		<Form ref="qnForm" :model="qnForm" label-position="top" :rules="ruleValidate" :disabled="!qnFormEdit">
-			<FormItem label="问卷名称" prop="name">
-				<Input :class="!qnFormEdit ? 'qn-form-disabled':''" v-model="qnForm.name" placeholder="请输入问卷名称"></Input>
+			<FormItem :label="$t('survey.form.name')" prop="name">
+				<Input :class="!qnFormEdit ? 'qn-form-disabled':''" v-model="qnForm.name" :placeholder="$t('survey.form.namePlace')"></Input>
 			</FormItem>
 
-			<FormItem label="问卷对象" prop="targetClassIds">
+			<FormItem :label="$t('survey.form.target')" prop="targetClassIds">
 				<RadioGroup v-model="classType" @on-change="onClassTypeChange" v-if="qnFormEdit">
-				        <Radio label="private">个人班级</Radio>
-				        <Radio label="school">校本班级</Radio>
+				        <Radio label="private">{{ $t('survey.form.privateClass') }}</Radio>
+				        <Radio label="school">{{ $t('survey.form.schoolClass') }}</Radio>
 				</RadioGroup>
 				<div v-if="!qnFormEdit && curQnItem" class="vote-class">
 					<span v-for="item in curQnItem.targetClassIds" class="vote-class-item">{{ getTargetName(item) }}</span>
 				</div>
-				<Select multiple v-model="qnForm.targetClassIds" :class="!qnFormEdit ? 'qn-form-disabled':''" placeholder="请选择问卷发布对象" v-else>
+				<Select multiple v-model="qnForm.targetClassIds" :class="!qnFormEdit ? 'qn-form-disabled':''" :placeholder="$t('survey.form.targetPlace')" v-else>
 					<!-- <Option v-for="(item,index) in classRooms" :value="item.id" :key="index">{{ item.name }}</Option> -->
 						<Option v-for="item in classRooms.filter(i=>i.scope === classType)" :value="item.id" :key="item.id">{{ item.name }}</Option>
 						<!-- <Option v-for="item in classRooms" :value="item.id" :key="item.id">{{ item.name }}</Option> -->
 				</Select>
 			</FormItem>
 
-			<FormItem label="起止时间" prop="rangeTime">
+			<FormItem :label="$t('survey.form.time')" prop="rangeTime">
 				<DatePicker type="datetimerange" @on-change="onChangeRange" format="yyyy-MM-dd HH:mm" :class="!qnFormEdit ? 'qn-form-disabled':''"
-				 :editable="isDateEdit" placeholder="请选择问卷起止时间" :value="[qnForm.startTime,qnForm.endTime]">
+				 :editable="isDateEdit" :placeholder="$t('survey.form.endTimePlace')" :value="[qnForm.startTime,qnForm.endTime]">
 
 				</DatePicker>
 			</FormItem>
 
 
-			<FormItem label="问卷描述" prop="description">
+			<FormItem :label="$t('survey.form.description')" prop="description">
 				<div ref="descriptionEditor" style="text-align:left" v-show="qnFormEdit"></div>
 				<div v-html="qnForm.description" v-show="!qnFormEdit" style="margin:10px;font-size:16px;font-weight:bold;color:#fff"></div>
 			</FormItem>
@@ -179,7 +179,7 @@
 							console.log(params)
 							resolve(params)
 						} else {
-							this.$Message.error('请将信息填写完整')
+							this.$Message.error(this.$t('survey.form.noCompleteTip'))
 						}
 					})
 				})
@@ -199,7 +199,7 @@
 							r(res.courses)
 						} else {
 							j(500)
-							this.$Message.error('获取数据失败')
+							this.$Message.error(this.$t('survey.getDataFailTip'))
 						}
 					})
 				})

+ 1 - 1
TEAMModelOS/ClientApp/src/components/selflearn/ContentFileList.vue

@@ -52,7 +52,7 @@
         },
         methods: {
             getSas() {
-                this.$api.uploadFile.getContainerSAS().then(
+                this.$api.blob.getContainerSAS().then(
                     (res) => {
                         if (res.error == null) {
                             this.sasString = res.result.data.SAS

+ 3 - 15
TEAMModelOS/ClientApp/src/components/student-analysis/total/BaseMyTable.vue

@@ -215,18 +215,6 @@
                             className: 'table-rank-value'
                         }
                     }, params.row.classId),
-                    // h('Icon', {
-                    //     props: {
-                    //         type: (row.changesStatus === 1 && row.changesVal !== 0) ? 'md-trending-up' : (row.changesStatus === -1 && row.changesVal !== 0) ? 'md-trending-down' : 'md-git-commit',
-                    //         color: (row.changesStatus === 1 && row.changesVal !== 0) ? '#13ff13' : (row.changesStatus === -1 && row.changesVal !== 0) ? '#fd4e4e' : 'yellow',
-                    //         size: '22'
-                    //     },
-                    //     style: {
-                    //         cursor: 'pointer',
-                    //         marginLeft: '10px'
-                    //     }
-                    // })
-
                 ])
             },
 			
@@ -236,7 +224,7 @@
                     on: {
                         click: function() {
                             that.$parent.$parent.$parent.isShowQuestions = true
-                            that.$router.push({ path: '/total/questionList', query: { QIndex: params.row.id } })
+							that.$router.push({ path: '/total/questionList', query: { QIndex: params.row.id } })
                         }
                     },
                     style: {
@@ -390,7 +378,7 @@
                         on: {
                             'click': function() {
                                 that.$parent.$parent.$parent.isShowQuestions = true
-                                that.$router.push({ path: '/total/questionList', query: { QIndex: item } })
+                                that.$router.push({ path: '/total/questionList', query: { QIndex: isNaN(item) ? 1 : item } })
                             }
                         }
                     }, item + (index === list.length - 1 ? '' : ' , '))
@@ -414,7 +402,7 @@
                         on: {
                             'click': function() {
                                 that.$parent.$parent.$parent.isShowQuestions = true
-                                that.$router.push({ path: '/total/questionList', query: { QIndex: item } })
+                                that.$router.push({ path: '/total/questionList', query: { QIndex: isNaN(item) ? 1 : item } })
                             }
                         }
                     }, item + (index === list.length - 1 ? '' : ' , '))

+ 25 - 26
TEAMModelOS/ClientApp/src/components/student-web/EventBasicInfo.vue

@@ -2,16 +2,15 @@
   <Row :gutter="30">
     <i-col :xs="24" :sm="24" :md="24" :lg="getCurrentLang()=='tw'?12:24" class="title-part">
       <h2 class="event-title">
-        <span class="title-mark" v-if=" eventType=='自主學習' &&from!='hiteach'">
-          {{ getCurrentLang()=='tw'?'评量':transTypetoEn(eventType) }}456
+        <!--<span class="title-mark" v-if=" eventType=='自主學習' &&from!='hiteach'">
+          {{ getCurrentLang()=='tw'?'评量':transTypetoEn(eventType) }}
         </span>
         <span class="title-mark" v-if=" eventType!='自主學習' &&from=='hiteach'">
-          {{ 'T'+this.$store.getters.getItemTitle.eventID.substr(0, 5) }}456
-        </span>
+          {{ 'T'+this.$store.getters.getItemTitle.eventID.substr(0, 5) }}
+        </span>-->
         <span
           class="title-mark"
-          v-if="eventType!='自主學習' &&from!='hiteach'"
-        >评量</span>
+        >{{$t("studentWeb.home.exam")}}</span>
         {{ this.$store.getters.getItemTitle.name }}
       </h2>
     </i-col>
@@ -21,8 +20,8 @@
           v-if="this.$store.getters.getItemTitle.eventType == 'exam' && paper.length !== 0 "
         >
           <svg-icon icon-class="subject" class="base-info-icon" />{{ $t('studentWeb.baseInfo.subject')}}
-          <span class="base-info-text">{{paper.length > 1? '综合科目':paper[0].subject.name }}</span>
-          <span class="base-info-text" v-if="getCurrentLang()=='en'">{{ transSubjecttoEn(this.$store.getters.getItemTitle.eventSubject) }}</span>
+        <span class="base-info-text">{{paper.length > 1? $t('studentWeb.event.allSubject'):paper[0].subject.name }}</span>
+          <!--<span class="base-info-text" v-if="getCurrentLang()=='en'">{{ transSubjecttoEn(this.$store.getters.getItemTitle.eventSubject) }}</span>-->
         </li>
 
         <!--<li>
@@ -55,10 +54,10 @@
 
 
 
-          <span
+          <!--<span
             class="base-info-text"
             v-if="from == '通知'||from == 'hiteach'"
-          >{{ this.$store.getters.getItemTitle.endTime + " 14:20" }}54345</span>
+          >{{ this.$store.getters.getItemTitle.endTime + " 14:20" }}54345</span>-->
         </li>
         <!--<li v-if=" from == 'hiteach'">
            <svg-icon icon-class="time" class="base-info-icon" />{{$t('studentWeb.baseInfo.classTime')}}:
@@ -116,22 +115,22 @@ export default {
     getCurrentLang() {
       return localStorage.getItem('lang');
     },
-     transTypetoEn(type) {
-      if (type == "投票") return "Vote";
-      else if (type == "評量") return "Exam";
-      else if (type == "作業") return "Homework";
-      else if (type == "課前預習") return "Preview";
-      else if (type == "公告") return "Billboard";
-      else if (type == "教師私訊") return "Message";
-      else if(type == "自主學習") return "Self-study";
-      else if (type == "Hiteach電子筆記") return "HiTeach Note";
-    },
-    transSubjecttoEn(type){
-      if (type == "國文") return "Chinese";
-      else if (type == "英文") return "English";
-      else if (type == "數學") return "Math";
-      else if (type == "綜合學科") return "Comprehensive";
-    }
+    // transTypetoEn(type) {
+    //  if (type == "投票") return "Vote";
+    //  else if (type == "評量") return "Exam";
+    //  else if (type == "作業") return "Homework";
+    //  else if (type == "課前預習") return "Preview";
+    //  else if (type == "公告") return "Billboard";
+    //  else if (type == "教師私訊") return "Message";
+    //  else if(type == "自主學習") return "Self-study";
+    //  else if (type == "Hiteach電子筆記") return "HiTeach Note";
+    //},
+    //transSubjecttoEn(type){
+    //  if (type == "國文") return "Chinese";
+    //  else if (type == "英文") return "English";
+    //  else if (type == "數學") return "Math";
+    //  else if (type == "綜合學科") return "Comprehensive";
+    //}
   },
 };
 </script>

+ 124 - 77
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReport.vue

@@ -148,10 +148,9 @@
                                 </div>
                                 <div class="TitleRec1"><span style="margin:5px;color:#1472c7">{{$t("studentWeb.exam.report.ansRes")}}:</span></div>
                                 <br />
-                                <div v-if="ansData[index]" style="margin-left:10px" v-html="ansData[index].length > 0 ? ansData[index][0] : '未作答'"></div>
+                                <div v-if="ansData[index]" style="margin-left:10px" v-html="ansData[index].length > 0 ? ansData[index][0] : $t('studentWeb.exam.report.noAns')"></div>
                             </div>
                         </div>
-                        <!--<br />-->
                         <div class="rightAnalys">
                             <div class="TitleRec2"><span style="margin-left:5px">{{$t("studentWeb.exam.report.testAns")}}:</span></div>
                             <br />
@@ -163,11 +162,12 @@
                             <div style="margin-left:10px;" v-html="question.explain != '' ?  question.explain : $t('studentWeb.exam.report.noAnalyse') "></div>
                             <div v-show="examInfo.stuScore[index] != -1 && examInfo.stuScore[index] != question.score" class="TitleRec2"><span style="margin-left:5px">{{$t("studentWeb.exam.report.repairSource")}}:</span></div>
                             <br />
-                            <div style="margin-left:10px;display:flex" v-show="examInfo.stuScore[index] != -1 && examInfo.stuScore[index] != question.score">
-                                <div v-if="question.repair" class="repair-box">
+                            <div style="margin-left:10px;display:flex" v-show="examInfo.stuScore[index] != -1 && examInfo.stuScore[index] != question.score ">
+                                <span v-if="question.repair.length == 0">{{$t("studentWeb.exam.report.noSource")}}</span>
+                                <div v-if="question.repair && question.repair.length > 0" class="repair-box">
                                     <Collapse style="width:85%" accordion @on-change="getSource(question.repair,question)">
                                         <Panel name="1">
-                                            链接资源
+                                            {{$t("studentWeb.exam.report.linkSource")}}
                                             <p slot="content">
                                                 <List border size="small">
                                                     <ListItem v-for="(item,normalIndex) in repairSource.normal" :key="normalIndex">
@@ -176,13 +176,13 @@
                                                         </span><a :href="item.blobUrl" target="_blank">{{item.blobUrl}}</a>
                                                     </ListItem>
                                                     <ListItem v-show="repairSource.normal.length == 0">
-                                                        <span>暂无资源</span>
+                                                        <span>{{$t("studentWeb.exam.report.noSource")}}</span>
                                                     </ListItem>
                                                 </List>
                                             </p>
                                         </Panel>
                                         <Panel name="2">
-                                            文件资源
+                                            {{$t("studentWeb.exam.report.fileSource")}}
                                             <p slot="content">
                                                 <List border size="small">
                                                     <ListItem v-for="(item,fileIndex) in repairSource.file" :key="fileIndex">
@@ -204,11 +204,9 @@
                                                                 <Icon type="md-download" size="18" @click="downloadFile(item)" />
                                                             </span>
                                                         </div>
-                                                       
-                                                    
                                                     </ListItem>
                                                     <ListItem v-show="repairSource.file.length == 0">
-                                                        <span>暂无资源</span>
+                                                        <span>{{$t("studentWeb.exam.report.noSource")}}</span>
                                                     </ListItem>
                                                 </List>
                                             </p>
@@ -221,31 +219,48 @@
                 </div>
             </div>
         </div>
-        <!--<div v-if="previewStatus" class="image-viewer">
-            <div style="width:fit-content;position:relative;margin:auto;">
-                <Icon type="md-close" class="close-icon" @click="closePreview" />
-                <video v-if="previewFile.type == 'video'" id="previewVideo" :src="previewFile.url" width="870" controls="controls" style="max-height: 800px;">
+        <Modal v-model="previewStatus" footer-hide width="65%" @on-cancel="closePreview">
+            <p slot="header" 
+               style="color:#f60;text-align:center">
+                <Icon type="ios-information-circle"></Icon>
+                <span>{{$t("studentWeb.exam.report.fileView")}}</span>
+            </p>
+            <div class="file-box" v-if="previewStatus">
+                <video v-if="previewFile.file == 'video'"
+                       id="previewVideo"
+                       :src="previewFile.blobUrl"
+                       width="870"
+                       controls="controls"
+                       style="max-height: 800px;">
                     {{$t('teachContent.tips8')}}
                 </video>
-                <audio v-else-if="previewFile.type == 'audio'" controls>
-                    <source :src="previewFile.url">
-                    您的浏览器不支持 audio 元素。
+                <audio v-else-if="previewFile.file == 'audio'" controls>
+                    <source :src="previewFile.blobUrl">
                 </audio>
-                <img v-else-if="previewFile.type == 'image'" :src="previewFile.url" style="border-radius: 5px;max-height: 800px;max-width:870px;" />
-                <embed v-else-if="previewFile.extension == 'PDF'" :src="previewFile.url" width="870" height="720" />
-                <iframe v-else :src="'https://view.officeapps.live.com/op/view.aspx?src=' + escapeBlobUrl" width='870' height='700' frameborder='1'></iframe>
+                <img v-else-if="previewFile.fileType == 'jpg'||previewFile.fileType =='png'||previewFile.fileType =='jpeg' "
+                     :src="previewFile.blobUrl"
+                     style="border-radius: 5px;max-height: 800px;max-width:870px;" />
+                <pdf ref="pdf"
+                     v-else-if="previewFile.fileType == 'pdf'"
+                     v-for="(page,index) in numPages"
+                     :key="index"
+                     :src="previewFile.blobUrl"
+                     :page="page"></pdf>
+                <!--<span v-else-if="previewFile.fileType == 'doc'||previewFile.fileType == 'csv'||previewFile.fileType == 'pptx' ">{{'https://view.officeapps.live.com/op/view.aspx?src=' + previewFile.blobUrl}}</span>-->
+                <iframe v-else-if="previewFile.fileType == 'doc'||previewFile.fileType == 'csv'||previewFile.fileType == 'pptx'||previewFile.fileType == 'xls' " :src="'https://view.officeapps.live.com/op/view.aspx?src=' + previewFile.blobUrl" width='870' height='700' frameborder='1'></iframe>
+                <span v-else>{{$t("studentWeb.exam.report.noReview")}}</span>
             </div>
-        </div>-->
+        </Modal>
     </div>
 </template>
 
 <script>
-    import BlobTool from '@/utils/blobTool.js';
-    import FileSaver from "file-saver";
+    //import BlobTool from '@/utils/blobTool.js';
+    //import FileSaver from "file-saver";
     //import JSZip from "jszip";
-    import elementResizeDetectorMaker from "element-resize-detector"
+    //import elementResizeDetectorMaker from "element-resize-detector"
+    import pdf from 'vue-pdf'
     import LessonTestReportCharts from "./LessonTestReportCharts/LessonTestReportCharts";
-    import { Random } from "mockjs";
     import Loading from "vue-loading-overlay";
     import "vue-loading-overlay/dist/vue-loading.css";
     export default {
@@ -253,6 +268,7 @@
         components: {
             LessonTestReportCharts,
             Loading,
+            pdf
         },
         props: {
             examInfo: {
@@ -277,40 +293,6 @@
                 closeAnsDetail: false,
                 checkedAns: ["right", "wrong", "noAns"],
                 testState: 0,
-                testType: [
-                    {
-                        label: "单选",
-                        value: "single"
-                    },
-                    {
-                        label: "多选",
-                        value: "multiple"
-                    },
-                    {
-                        label: "判断",
-                        value: "judge"
-                    },
-                    {
-                        label: "填空",
-                        value: "complete"
-                    },
-                    {
-                        label: "问答",
-                        value: "subjective"
-                    },
-                    {
-                        label: "综合",
-                        value: "compose"
-                    },
-                    {
-                        label: "改错",
-                        value: "correct"
-                    },
-                    {
-                        label: "连线",
-                        value: "connector"
-                    },
-                ],
                 paperData: [],
                 ansData: [],
                 repairSource: {
@@ -318,7 +300,9 @@
                     file: []
                 },
                 repairData: [],
-                previewStatus: false
+                previewStatus: false,
+                previewFile: {},
+                numPages:null
             };
         },
         mounted() {
@@ -326,19 +310,51 @@
             this.testJudge()
         },
         methods: {
-            getItemData(data) {
+            closePreview() {
+                this.previewStatus = !this.previewStatus
+                this.previewFile = {}
             },
-           async downloadFile(data) {
-                let code = data.blobUrl.split('/')
-                let req = {
-                    url: data.blobUrl
+           async getItemData(data) {
+               this.previewStatus = false
+                if (data.file) {
+                    this.previewFile = this._.cloneDeep(data)
+                    this.previewFile.blobUrl = await this.getFileSas(data)
+                    if (this.previewFile.fileType == 'doc' || this.previewFile.fileType == 'csv'
+                        || this.previewFile.fileType == 'xls' || this.previewFile.fileType == 'xlsx' ||
+                        this.previewFile.fileType == 'pptx' || this.previewFile.fileType == 'ppt' || this.previewFile.fileType == 'docx') {
+                        let url = escape(this.previewFile.blobUrl)
+                        this.previewFile.blobUrl = url
+                    }
+                    if (this.previewFile.fileType == 'pdf') {
+                        await this.getPdf(this.previewFile.blobUrl)
+                    }
+                    this.previewStatus = true
                 }
-                let curFile = ""
-                await this.$api.studentWeb.getFileSas(req).then(res => {
-                    curFile = res.url
+            },
+            getPdf(data) {
+                let loadingTask = pdf.createLoadingTask(data)
+                loadingTask.promise.then(pdf => {
+                    this.numPages = pdf.numPages
+                }).catch(err => {
+                    this.Message.warning(this.$t('studentWeb.exam.report.pdfErr'))
                 })
+            },
+            async getFileSas(data) {
+                if (data) {
+                    let req = {
+                        url: data.blobUrl
+                    }
+                    let curFile = ""
+                    await this.$api.studentWeb.getFileSas(req).then(res => {
+                         curFile = res.url
+                     })
+                    return curFile
+                }
+            },
+            async downloadFile(data) {
+                let blobData = await this.getFileSas(data)
                 const downloadRes = async () => {
-                    let response = await fetch(curFile); // 内容转变成blob地址
+                    let response = await fetch(blobData); // 内容转变成blob地址
                     let blob = await response.blob();  // 创建隐藏的可下载链接
                     let objectUrl = window.URL.createObjectURL(blob);
                     let a = document.createElement('a');
@@ -371,8 +387,6 @@
                     }
                 }
                 this.repairSource = source
-                //let code = data.blobUrl.split('/')
-                //console.log(code)
             },
             showTest() {
                 if (this.examInfo.subject !== undefined) {
@@ -403,13 +417,13 @@
                 }
             },
             getTestType(data) {
-                for (let item of this.testType) {
+                for (let item of this.$t('global.testType')) {
                     if (item.value == data) {
                         return item.label
                     }
                 }
             },
-            async getItem(data) {
+            async getItem(data) { //获取学生作答数据
                 let datas = []
                 if (data !== undefined) {
                     let key = this.$store.getters.getExamInfo.code.split('-')
@@ -426,7 +440,7 @@
                     return []
                 }
             },
-            formUrl(data) {
+            formUrl(data) {  //处理学生作答数据blob地址
                 let container = data.code
                 let a = ""
                 if (data.blob.indexOf('https://teammodelstorage') > -1) {
@@ -438,16 +452,22 @@
             },
            async formPaper() {
                 let paper = []
-                this.paperData.length = []
-                this.ansData.length = []
+                this.paperData.length = 0
+                this.ansData.length = 0
                 let data = this.$store.getters.getPaperInfo.item
                 let exam = [...data]
                 if (this.$store.getters.getPaperInfo.item.length) {
                     for (let i = 0; i < exam.length; i++) {
+                        if (exam[i].repair == undefined) {
+                            exam[i].repair = []
+                        }
                         if (exam[i].type == 'compose') {
                             let k = 1
                             if (exam[i].children.length > 0) {
                                 for (let items of exam[i].children) {
+                                    if (items.repair == undefined) {
+                                        items.repair = []
+                                    }
                                     items.parentInfo = exam[i]
                                     items.parent = i
                                     items.paperIndex = (i + 1) + '-' + k
@@ -460,7 +480,7 @@
                             paper.push(exam[i])
                         }
                     }
-                }
+               }
                this.paperData = [...paper]
                if (this.paperData.length) {
                    this.ansData = await this.getItem(this.examInfo.stuAns[0])
@@ -874,4 +894,31 @@
             max-height:300px;
             overflow:scroll;
         }
+        .file-box{
+            text-align:center;
+            height:100%;
+            overflow:scroll;
+        }
+    .lesson-test-report /deep/ .ivu-modal {
+        top: 50px !important;
+    }
+
+    .lesson-test-report .file-box /deep/ .ivu-modal-body {
+        padding: 16px;
+        font-size: 14px;
+        line-height: 1.5;
+        height: 800px;
+        overflow: scroll;
+    }
+    /*.image-viewer .close-icon {
+        position: absolute;
+        right: -16px;
+        top: -16px;
+        font-size: 24px;
+        color: black;
+        cursor: pointer;
+        border-radius: 50px;
+        background: white;
+        padding: 2px;
+    }*/
 </style>

+ 51 - 96
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReportCharts/StudentScore.vue

@@ -4,59 +4,59 @@
             <div class="stu-info">
                 <ul>
                     <li>
-                        <p><Icon type="ios-home" size="24" />本次考试名称:<span>--</span></p>
+                        <p><Icon type="ios-home" size="24" />{{$t("studentWeb.exam.studentScore.examName")}}:<span>{{$store.getters.getItemTitle.name}}</span></p>
                     </li>
                     <li>
-                        <p><Icon type="ios-paper" size="24" />考试类型:<span>--</span></p>
+                        <p><Icon type="ios-paper" size="24" />{{$t("studentWeb.exam.studentScore.examType")}}:<span>--</span></p>
                     </li>
                     <li>
-                        <p><Icon type="ios-list-box" size="24" />学习稳定系数:<span>--</span></p>
+                        <p><Icon type="ios-list-box" size="24" />{{$t("studentWeb.exam.studentScore.stableIndex")}}:<span>--</span></p>
                     </li>
                     <li>
-                        <p><Icon type="ios-person" size="24" />姓名:<span>{{$store.state.userInfo.name}}</span></p>
+                        <p><Icon type="ios-person" size="24" />{{$t("studentWeb.exam.studentScore.name")}}:<span>{{$store.state.userInfo.name}}</span></p>
                     </li>
                     <li>
-                        <p style="margin-top:5px">学号:<span>{{$store.state.userInfo.sub}}</span></p>
+                        <p style="margin-top:5px">{{$t("studentWeb.exam.studentScore.stuNo")}}:<span>{{$store.state.userInfo.sub}}</span></p>
                     </li>
                     <li>
-                        <p style="margin-top:5px">班级:<span>{{$store.state.user.studentProfile.classinfo.name}}</span></p>
+                        <p style="margin-top:5px">{{$t("studentWeb.exam.studentScore.class")}}:<span>{{$store.state.user.studentProfile.classinfo.name}}</span></p>
                     </li>
                 </ul>
             </div>
             <div class="stu-score">
-                <p class="all-sub">全学科成绩</p>
+                <p class="all-sub">{{$t("studentWeb.exam.studentScore.allSubScore")}}</p>
                 <ul>
                     <li style="margin-right:5%;margin-top:15px;margin-left:3%">
-                        <h2 style="color:cornflowerblue;font-size:18px">个人总分</h2>
+                        <h2 style="color:cornflowerblue;font-size:18px">{{$t("studentWeb.exam.studentScore.total")}}</h2>
                         <p style="color: cornflowerblue;font-size: 30px;font-weight: 800">{{scoreInfo.total}}</p>
                     </li>
                     <li class="sub-ave">
-                        <h2>班级平均分</h2>
+                        <h2>{{$t("studentWeb.exam.studentScore.cAverages")}}</h2>
                         <p>{{scoreInfo.classAverage}}</p>
                     </li>
                     <li class="sub-ave">
-                        <h2>年级平均分</h2>
+                        <h2>{{$t("studentWeb.exam.studentScore.gAverages")}}</h2>
                         <p>{{scoreInfo.gradeAverage}}</p>
                     </li>
                     <li class="sub-ave">
-                        <h2>区级平均分</h2>
+                        <h2>{{$t("studentWeb.exam.studentScore.aAverages")}}</h2>
                         <p>--</p>
                     </li>
                     <li class="sub-ave">
-                        <h2>班级排名</h2>
+                        <h2>{{$t("studentWeb.exam.studentScore.cIndex")}}</h2>
                         <p>{{scoreInfo.rankc}}<span>/{{scoreInfo.classCount}}</span></p>
                     </li>
                     <li class="sub-ave">
-                        <h2>年级排名</h2>
+                        <h2>{{$t("studentWeb.exam.studentScore.gIndex")}}</h2>
                         <p>{{scoreInfo.rankg}}<span>/{{scoreInfo.gradeCount}}</span></p>
                     </li>
                     <li class="sub-ave">
-                        <h2>区级排名</h2>
+                        <h2>{{$t("studentWeb.exam.studentScore.aIndex")}}</h2>
                         <!--<p>-<span>/1456</span></p>-->
                         <p>--</p>
                     </li>
                     <li>
-                        <span style="font-size:16px;margin-top:20px;display:block">是否进线</span>
+                        <span style="font-size:16px;margin-top:20px;display:block">{{$t("studentWeb.exam.studentScore.onLine")}}</span>
                         <p style="font-size:24px;font-weight:600;width:60px;text-align:center">--</p>
                     </li>
                 </ul>
@@ -64,34 +64,34 @@
             <div class="sub-title">
                 <ul>
                     <li class="sub-item">
-                        <span>学科</span>
+                        <span>{{$t("studentWeb.exam.studentScore.subject")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>个人得分</span>
+                        <span>{{$t("studentWeb.exam.studentScore.score")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>班级平均</span>
+                        <span>{{$t("studentWeb.exam.studentScore.cAverage")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>年级平均</span>
+                        <span>{{$t("studentWeb.exam.studentScore.gAverage")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>区级平均</span>
+                        <span>{{$t("studentWeb.exam.studentScore.aAverage")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>班级排名</span>
+                        <span>{{$t("studentWeb.exam.studentScore.cIndex")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>年级排名</span>
+                        <span>{{$t("studentWeb.exam.studentScore.gIndex")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>区级排名</span>
+                        <span>{{$t("studentWeb.exam.studentScore.aIndex")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>答对题数</span>
+                        <span>{{$t("studentWeb.exam.studentScore.rAns")}}</span>
                     </li>
                     <li class="sub-item">
-                        <span>综合难度</span>
+                        <span>{{$t("studentWeb.exam.studentScore.comIndex")}}</span>
                     </li>
                 </ul>
                 <ul v-for="(item,index) in scoreInfo.averageMap">
@@ -122,11 +122,11 @@
                     <li class="sub-item">
                         <div class="ans">
                             <div class="obj-ans">
-                                <h5>客观</h5>
+                                <h5>{{$t("studentWeb.exam.studentScore.objItem")}}</h5>
                                 <p>{{item.sub.objIndex}}<span>/{{item.sub.objItem.length}}</span></p>
                             </div>
                             <div class="sub-ans">
-                                <h5>主观</h5>
+                                <h5>{{$t("studentWeb.exam.studentScore.subItem")}}</h5>
                                 <p>{{item.sub.subIndex}}<span>/{{item.sub.subItem.length}}</span></p>
                             </div>
                         </div>
@@ -141,17 +141,17 @@
                 <div class="table-list">
                     <table class="gridtable" v-if="stuScore.length" style="width: 100%; max-width: 100%;margin-bottom: 20px;">
                         <tr>
-                            <th>科目</th>
-                            <th>题目</th>
-                            <th>客观题</th>
-                            <th>主观题</th>
+                            <th>{{$t("studentWeb.exam.studentScore.subjects")}}</th>
+                            <th>{{$t("studentWeb.exam.studentScore.item")}}</th>
+                            <th>{{$t("studentWeb.exam.studentScore.objItems")}}</th>
+                            <th>{{$t("studentWeb.exam.studentScore.subItems")}}</th>
                         </tr>
                         <tbody v-for="(que,index) in stuScore" :key="index">
                             <tr>
                                 <td width="4%" rowspan="4">{{que.name}}</td>
                             </tr>
                             <tr>
-                                <td width="8%">题号</td>
+                                <td width="8%">{{$t("studentWeb.exam.studentScore.itemIndex")}}</td>
                                 <td>
                                     <div class="score-box">
                                         <span v-for="(objQue,obj) in que.item.objItem" :key="obj">{{objQue.paperIndex}}</span>
@@ -164,7 +164,7 @@
                                 </td>
                             </tr>
                             <tr>
-                                <td width="8%">标准答案/配分</td>
+                                <td width="8%">{{$t("studentWeb.exam.studentScore.standardIndex")}}</td>
                                 <td>
                                     <div class="score-box">
                                         <span v-for="(objAns,obja) in que.item.objItem" :key="obja">{{objAns.answer.length > 1 ? '[?]': objAns.answer[0]}}</span>
@@ -177,7 +177,7 @@
                                 </td>
                             </tr>
                             <tr>
-                                <td width="8%">学生作答/得分</td>
+                                <td width="8%">{{$t("studentWeb.exam.studentScore.stuAnsIndex")}}</td>
                                 <td>
                                     <div class="score-box">
                                         <span v-for="(objScore,objs) in que.item.objItem" :key="objs">{{objScore.stuScore == 0 ? objScore.stuAns[0] : '-'}}</span>
@@ -195,7 +195,7 @@
 
             </div>
             <div class="score-table">
-                <p style="color:#24b880">【-】--答对,【#】--未作答,【?】--多选题</p>
+                <p style="color:#24b880">{{$t("studentWeb.exam.studentScore.notice")}}</p>
             </div>
         </div>
     </div>
@@ -219,64 +219,6 @@
         },
         data() {
             return {
-                data: {
-                    "paperInfo": {
-                        "personScore": 483,
-                        "classScore": 472,
-                        "gradeScore": 468,
-                        "areaScore": 457,
-                        "classNum": 56,
-                        "gradeNum": 456,
-                        "areaNum": 1456,
-                        "classIndex": 5,
-                        "gradeIndex": 65,
-                        "areaIndex": 296,
-                        "pr": 0.8
-                    },
-                    "examInfo": [
-                        {
-                            "subject": "语文",
-                            "subCode": "5465456-hjghj-5464564-23123",
-                            "score": 150,
-                            "stuScore": 120,
-                            "classAve": 104,
-                            "gradeAve": 102,
-                            "areaAve": 105,
-                            "classIndex": 8,
-                            "gradeIndex": 45,
-                            "areaIndex": 465,
-                            "type": "normal"
-                        },
-                        {
-                            "subject": "数学",
-                            "subCode": "5465456-hjghj-5464564-23123",
-                            "score": 150,
-                            "stuScore": 116,
-                            "classAve": 110,
-                            "gradeAve": 102,
-                            "areaAve": 108,
-                            "classIndex": 10,
-                            "gradeIndex": 87,
-                            "areaIndex": 494,
-                            "type": "normal"
-                        },
-                        {
-                            "subject": "英语",
-                            "subCode": "5465456-hjghj-5464564-23123",
-                            "score": 150,
-                            "stuScore": 138,
-                            "classAve": 114,
-                            "gradeAve": 112,
-                            "areaAve": 115,
-                            "classIndex": 3,
-                            "gradeIndex": 35,
-                            "areaIndex": 365,
-                            "type": "normal"
-                        }
-                    ]
-                },
-                data10: [],
-
                 testData: [],
                 stuScore: [],
                 scoreInfo: {}
@@ -303,7 +245,7 @@
                         "code": this.$store.state.user.schoolCode,
                         "id": this.examData[0].id,
                         "sId": this.$store.state.userInfo.sub,
-                        "cId": this.$store.state.user.studentProfile.classinfo.id,
+                        "cId": this.stuData.claId[0],
                         "gId": this.$store.state.user.studentProfile.classinfo.gradeId
                     }
                     this.$api.studentWeb.getStudentAnalysis(req).then(res => {
@@ -326,11 +268,21 @@
                             res.classCount = classCount
                             this.scoreInfo = res
                         } else {
-                            this.$Message.warning('成绩信息错误!')
+                            this.$Message.warning(this.$t('studentWeb.exam.report.gradeErr'))
                         }
                     })
                 }
             },
+            formUrl(data) {  //处理学生作答数据blob地址
+                let container = data.code
+                let a = ""
+                if (data.blob.indexOf('https://teammodelstorage') > -1) {
+                    a = data.blob
+                } else {
+                    a = `${this.$GLOBAL.BLOB_URL}/${container}/exam/${data.blob}`
+                }
+                return a
+            },
             async getStudentData(data,score,ans) {
                 let paper = []
                 let item = {
@@ -346,6 +298,7 @@
                     }
                     let papers = await this.$evTools.getStuPaper(code)
                     let answer = await this.getItem(ans)
+
                     paper = await this.formPaper(papers)
                     this.testData = paper
                     let objIndex = 0
@@ -378,8 +331,10 @@
                     let code = {
                         scope: this.stuData.papers[0].scope,
                         code: key[(key.length - 1)],
-                        blob: data
+                        blob: data[0]
                     }
+                    let blob = this.formUrl(code)
+                    code.blob = blob
                     datas = await this.$evTools.getComposeItem(code)
                     return datas
                 } else {

+ 4 - 4
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperTest.vue

@@ -380,7 +380,7 @@
             nextQ() {
                 if (this.queNo < (this.examInfo.length - 1)) this.queNo++
             },
-            openWarmMessage: function (showMessageNum) {
+            openWarmMessage(showMessageNum) {
                 this.WarmMessageisOpen = true
                 if (showMessageNum == 1) {
                     this.showMessageNum = showMessageNum
@@ -396,10 +396,10 @@
                     }
                 } else return;
             },
-            closeWarmMessage: function () {
+            closeWarmMessage() {
                 this.WarmMessageisOpen = false
             },
-            closetest: function () {
+            closetest() {
                 this.WarmMessageisOpen = false
                 if (this.checkers.length) {
                     this.isLoading = true;
@@ -408,7 +408,7 @@
                         id: this.$store.getters.getItemTitle.id,
                         answer: this.checkers,
                         studentId: this.$store.state.userInfo.sub,
-                        classId: this.$store.state.user.studentProfile.classinfo.id,
+                        classId: this.$store.getters.getExamInfo.allClass,
                         subjectId: this.$store.getters.getExamInfo.subject.id,
                         multipleRule: this.$store.getters.getExamInfo.multipleRule,
                         paperId: this.$store.getters.getPaperInfo.id,

+ 3 - 1
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperView.vue

@@ -116,6 +116,7 @@
                     }
                     let isTest = 0
                     this.$api.studentWeb.FindStudentPaper(req).then(res => {
+                        console.log(res)
                         this.stuData = res
                         let resData = res
                         for (let item of resData.papers) {
@@ -124,8 +125,8 @@
                             } 
                         }
                         for (let i = 0; i < this.paperData.length; i++) {
-                            console.log()
                             this.paperData[i].subject = resData.subjects[i]
+                            this.paperData[i].allClass = resData.claId
                             if (resData.stuAns[i] == undefined) {
                                 this.paperData[i].stuAns = []
                                 this.paperData[i].stuScore = []
@@ -171,6 +172,7 @@
                 }
             },
             async getPaper(data) {
+                console.log(data)
                 this.isExamDown = false
                 this.isLoad = true
                 this.selectData = {}

+ 2 - 0
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventList.vue

@@ -223,6 +223,7 @@
                     }
                     this.eventList = []
                     this.$api.studentWeb.FindExamPaper(params).then(res => {
+                        console.log(res)
                         let data = []
                         for (let item of res.props) {
                             let code = item.code.split('-')
@@ -451,6 +452,7 @@
             },
 
             sentSelectedEventTitle(item) {
+                console.log(item)
                 this.$router.push({
                     path: "/studentWeb/eventView",
                     query: {

+ 22 - 22
TEAMModelOS/ClientApp/src/components/vote/BaseVoteForm.vue

@@ -1,33 +1,33 @@
 <template>
     <div class="component-vote-form">
         <Form ref="voteForm" :model="voteForm" label-position="top" :rules="ruleValidate" :disabled="!voteFormEdit">
-            <FormItem label="投票名称" prop="name">
+            <FormItem :label="$t('vote.form.name')" prop="name">
                 <Input :class="!voteFormEdit ? 'vote-form-disabled':''" v-model="voteForm.name" placeholder="请输入投票名称"></Input>
             </FormItem>
-            <FormItem label="投票对象" prop="targetClassIds">
+            <FormItem :label="$t('vote.form.target')" prop="targetClassIds">
 				<RadioGroup v-model="classType" @on-change="onClassTypeChange" v-if="voteFormEdit">
-				        <Radio label="private">个人班级</Radio>
-				        <Radio label="school">校本班级</Radio>
+				        <Radio label="private">{{ $t('vote.form.privateClass') }}</Radio>
+				        <Radio label="school">{{ $t('vote.form.schoolClass') }}</Radio>
 				</RadioGroup>
 				<div v-if="!voteFormEdit && curVoteItem" class="vote-class">
 					<span v-for="item in curVoteItem.targetClassIds" class="vote-class-item">{{ getTargetName(item) }}</span>
 				</div>
-				<Select multiple v-model="voteForm.targetClassIds" :class="!voteFormEdit ? 'vote-form-disabled':''" placeholder="请选择投票发布对象" not-found-text="暂未创建班级" v-else>
+				<Select multiple v-model="voteForm.targetClassIds" :class="!voteFormEdit ? 'vote-form-disabled':''" :placeholder="$t('vote.form.targetPlace')" :not-found-text="$t('vote.form.noFoundText')" v-else>
 				   	<Option v-for="item in classRooms.filter(i=>i.scope === classType)" :value="item.id" :key="item.id">{{ item.name }}</Option>
 				</Select>
             </FormItem>
 			
-			<FormItem label="起止时间" prop="rangeTime">
+			<FormItem :label="$t('vote.form.time')" prop="rangeTime">
 			    <!-- <DatePicker type="datetime" :class="!voteFormEdit ? 'vote-form-disabled':''" :editable="isDateEdit" placeholder="请选择投票结束时间" v-model="voteForm.endTime"  :options="endTimeOptions"></DatePicker> -->
-				<DatePicker type="datetimerange" transfer @on-change="onChangeRange" format="yyyy-MM-dd HH:mm" :class="!voteFormEdit ? 'vote-form-disabled':''" :editable="isDateEdit" :value="[voteForm.startTime,voteForm.endTime]" placeholder="请选择投票结束时间"></DatePicker>
+				<DatePicker type="datetimerange" transfer @on-change="onChangeRange" format="yyyy-MM-dd HH:mm" :class="!voteFormEdit ? 'vote-form-disabled':''" :editable="isDateEdit" :value="[voteForm.startTime,voteForm.endTime]" :placeholder="$t('vote.form.endTimePlace')"></DatePicker>
 			</FormItem>
 
-            <FormItem label="投票描述" prop="description">
+            <FormItem :label="$t('vote.form.description')" prop="description">
                 <div ref="descriptionEditor" style="text-align:left" v-show="voteFormEdit"></div>
                 <div v-html="voteForm.description" v-show="!voteFormEdit" style="margin:10px;font-size:16px;font-weight:bold;color:#fff"></div>
             </FormItem>
 
-            <FormItem label="选项设置" prop="attachment" ref="optionsBox">
+            <FormItem :label="$t('vote.form.optionSetting')" prop="attachment" ref="optionsBox">
 				<div v-if="voteOptions.length">
 					<div v-for="(item,index) in voteOptions" :key="index" class="option-editor-wrap">
 						<div v-show="voteFormEdit" style="display: flex;">
@@ -41,7 +41,7 @@
 					</div>
 				</div>
                 
-                <p style="float:right;color:#BDBDBD;cursor:pointer" @click="onAddOption" v-show="voteFormEdit"><Icon type="md-add" />添加选项</p>
+                <p style="float:right;color:#BDBDBD;cursor:pointer" @click="onAddOption" v-show="voteFormEdit"><Icon type="md-add" />{{ $t('vote.form.addOption') }}</p>
             </FormItem>
 
             <!--<FormItem label="投票记录" prop="isReset" v-show="editable && isEdit">
@@ -50,20 +50,20 @@
                 </CheckboxGroup>
             </FormItem>-->
 
-            <FormItem label="可投票数" prop="selectMax">
+            <FormItem :label="$t('vote.form.selectNum')" prop="selectMax">
                 <!--<Input :class="!voteFormEdit ? 'vote-form-disabled':''" v-model="voteForm.count" placeholder="请输入投票名称"></Input>-->
                 <InputNumber :max="10" :min="1" v-model="voteForm.selectMax"></InputNumber>
             </FormItem>
 
-            <FormItem label="其他" prop="secret">
+            <FormItem :label="$t('vote.form.other')" prop="secret">
                 <CheckboxGroup v-model="voteForm.secret">
-                    <Checkbox label="secret">开启匿名投票</Checkbox>
+                    <Checkbox label="secret">{{ $t('vote.form.openSecret') }}</Checkbox>
                 </CheckboxGroup>
             </FormItem>
 
             <FormItem v-show="voteFormEdit">
-                <Button type="primary" class="btn-save" @click="handleSubmit('voteForm')" :loading="isBtnLoading">保存</Button>
-                <Button @click="handleCancel('voteForm')" class="btn-reset" style="margin-left: 8px">取消</Button>
+                <Button type="primary" class="btn-save" @click="handleSubmit('voteForm')" :loading="isBtnLoading">{{ $t('vote.form.save') }}</Button>
+                <Button @click="handleCancel('voteForm')" class="btn-reset" style="margin-left: 8px">{{ $t('vote.form.cancel') }}</Button>
             </FormItem>
         </Form>
     </div>
@@ -218,7 +218,7 @@
 						console.log(params)
 						/* 保存BLOB以及COSMOS */
 						this.saveorUpdateVote({ vote: params, reset: isReset }).then(res => {
-						    this.$Message.success((this.isEdit && this.editInfo.id) ? '修改成功!' : '添加成功!')
+						    this.$Message.success((this.isEdit && this.editInfo.id) ? this.$t('vote.form.editSuc') : this.$t('vote.form.addSuc'))
 						    this.$emit('onAddSuccess')
 						    this.isBtnLoading = false
 						}).catch(error => {
@@ -229,9 +229,9 @@
                     } else {
 						this.isBtnLoading = false
 						if(this.voteOptionsContent.length){
-							this.$Message.error('请将信息填写完整')
+							this.$Message.error(this.$t('vote.form.noCompleteTip'))
 						}else{
-							this.$Message.error('投票选项个数不能为空!')
+							this.$Message.error(this.$t('vote.form.noOptionTip'))
 						}
                     }
                 })
@@ -340,7 +340,7 @@
                         editor.create()
                     })
                 } else {
-                    this.$Message.warning('最多只有10个选项!')
+                    this.$Message.warning(this.$t('vote.form.optionNumsTip'))
                 }
             },
 
@@ -355,7 +355,7 @@
             				r(res.courses)
             			} else {
             				j(500)
-            				this.$Message.error('获取数据失败')
+            				this.$Message.error(this.$t('vote.form.getDataFailTip'))
             			}
             		})
             	})
@@ -397,7 +397,7 @@
                 const check = this.uploadList.length < 5;
                 if (!check) {
                     this.$Notice.warning({
-                        title: '最多只能上传5个附件'
+                        title: this.$t('vote.form.attachmentMaxTip')
                     });
                 }
                 return check;
@@ -458,7 +458,7 @@
             getTargetName(classId){
 				if(this.classRooms.length){
 					let targetIndex = this.classRooms.map(i => i.id).indexOf(classId)
-					return targetIndex > -1 ?  this.classRooms[targetIndex].name : '未匹配数据'
+					return targetIndex > -1 ?  this.classRooms[targetIndex].name : this.$t('vote.form.noMatchDataTip')
 				}else{
 					this.getClassrooms(this.userInfo.TEAMModelId).then(res => {
 						return res.filter(i => i.id === classId)[0].name

+ 105 - 0
TEAMModelOS/ClientApp/src/locale/lang/en-US/cusMgt.js

@@ -0,0 +1,105 @@
+export default {
+    // ManageCourse.vue
+    searchHolder:'搜索课程...',
+    delBatch:'批量删除',
+    delCus:'删除课程',
+    addCus:'添加课程',
+    editCus:'编辑课程',
+    noTeacher:'暂无',
+    cusName:'课程名称',
+    nameHolder:'请输入课程名称',
+    cusCode:'课程编码',
+    codeHolder:'请输入课程编码',
+    cusPd:'课程学段',
+    pdHolder:'请选择学段',
+    cusSubject:'课程学科',
+    sjHolder:'请选择学科',
+    cusTeachers:'授课教师',
+    teacherHolder:'请选择授课教师',
+    delContent:'是否确认删除',
+    codeErr1:'课程编码不能为空',
+    codeErr2:'课程编码只能由字母和数字组成',
+    tableCol1:'序号',
+    tableCol2:'课程名称',
+    tableCol3:'课程编码',
+    tableCol4:'学段',
+    tableCol5:'学科',
+    tableCol6:'教师人数',
+    tableCol7:'授课教师',
+    delOk:'删除成功!',
+    delErr:'删除失败!',
+    addOk:'添加成功!',
+    editOk:'修改成功!',
+    formTips:'请先完善信息,再保存!',
+    noSchool:'尚未加入学校,没有学校数据',
+    sltCusTips:'请选择课程',
+
+     //NewCoursePlan.vue
+     spClass:'专科教室',
+     cusPlan:'课程安排',
+     importPlan:'导入安排',
+     course:'课程',
+     addCusPlan:'添加课程安排',
+     saveTips:'保存提醒',
+     tipsContent:'当前班级排课数据尚未保存。如果离开,修改的数据将不会保存!',
+     leaveText:'离开',
+     fullTips:'请先完成已添加的课程设置!',
+     saveOk:'保存成功',
+     saveErr:'保存失败',
+ 
+     //MyCourse.vue
+     scCus:'标准课程',
+     cusInfo:'课程信息',
+     noScCus:'暂无校本课程',
+     privCus:'个人课程',
+     noPrivCus:'暂未创建课程',
+     noCusClass:'暂无上课班级',
+     delStu:'删除学生',
+     addStu:'添加学生',
+     noGroup:'未分组',
+     noNotice:'暂无课程公告!',
+     edit:'编辑',
+     save:'保存',
+     className:'班级名称',
+     editClassInfo:'修改班级信息',
+     dyClass:'动态名单',
+     scClass:'常规教室',
+     delStuContent:'是否确认删除',
+     delStuTips:'请先选择需要删除的学生!',
+     delClassTitle:'删除班级',
+     stuClassCol1:'姓名',
+     stuClassCol2:'账号',
+     stuClassCol3:'座号',
+     stuClassCol4:'组别',
+     stuClassCol5:'组名',
+ 
+     //ManageClass.vue
+     classLabel:'班级:',
+     stuCount:'学生人数:',
+     autoGroup:'自动分组',
+     exportStu:'导出名单',
+     viewport1:'分组视图',
+     viewport2:'列表视图',
+     noStu:'暂无学生',
+     resetPw:'重置密码',
+     noMgtClass:'暂无您管理的班级',
+     groupNameHolder:'请设置组名',
+     edtiGroupName:'修改组名',
+     addGroup:'新增组别',
+     peopleUnit:'人',
+     studentCountLabel: '学生人数',
+     groupCountLabel: '组别数量',
+     groupTypeLabel: '分组方式',
+     groupType1: '随机分组',
+     groupType2: '依座号分组',
+     groupType3: '依座顺序S形分组',
+     action:'操作',
+     atLeast:'至少保留一组',
+     checkName:'请先输入组名再创建组别',
+     noStuTips:'暂无学生可以进行分组',
+     groupCount:'分组数量最少为1',
+     groupTypeTips:'请设置分组方式',
+     groupUnit:'组',
+     nameList:'学生名单'
+
+}

+ 46 - 4
TEAMModelOS/ClientApp/src/locale/lang/en-US/global.js

@@ -1,5 +1,47 @@
-export default{
-    examType1:'期中考试',
-    examType2:'期末考试',
-    examType3:'模拟考试'
+export default {
+    examType1: '期中考试',
+    examType2: '期末考试',
+    examType3: '模拟考试',
+    evType1: '正规考',
+    evType2: '模拟考',
+    evType3: '普通考',
+    evMode1: '线上评量',
+    evMode2: '课中评量',
+    evMode3: '阅卷评量',
+    publishType1: '立即发布',
+    publishType2: '定时发布',
+    testType: [
+        {
+            label: "Single",
+            value: "single"
+        },
+        {
+            label: "Multiple",
+            value: "multiple"
+        },
+        {
+            label: "Judge",
+            value: "judge"
+        },
+        {
+            label: "Complete",
+            value: "complete"
+        },
+        {
+            label: "Q&A",
+            value: "subjective"
+        },
+        {
+            label: "Integrated",
+            value: "compose"
+        },
+        {
+            label: "Correction",
+            value: "correct"
+        },
+        {
+            label: "Connect",
+            value: "connector"
+        },
+    ],
 }

+ 22 - 0
TEAMModelOS/ClientApp/src/locale/lang/en-US/home.js

@@ -0,0 +1,22 @@
+export default{
+    previewStudy:'课前预习',
+    classData:'课堂数据',
+    recentRecord:'近期课堂记录',
+    acCount:'活动概览',
+    goingList:'进行中的活动列表',
+    scNotice:'学校公告',
+    msg:'消息通知',
+    sysMsg:'小豆助手',
+    avgScore:'平均互动指数',
+    col1:'小组学习',
+    col2:'多元评价',
+    col3:'个人学习',
+    col4:'生本决策',
+    col5:'全班互动',
+    col6:'全班测验',
+    ac1:'评量测验',
+    ac2:'自主学习',
+    ac3:'作业活动',
+    ac4:'投票活动',
+    ac5:'问卷调查',
+}

+ 7 - 0
TEAMModelOS/ClientApp/src/locale/lang/en-US/index.js

@@ -19,8 +19,12 @@ import studentWeb from './studentWeb'
 import settings from './settings'
 import serviceDriveAuth from './serviceDriveAuth'
 import elui from './elui'
+import learnActivity from './learnActivity'
 import global from './global'
 import system from './system'
+import cusMgt from './cusMgt'
+import home from './home'
+
 export default {
   schoolBaseInfo,
   schoolMgmt,
@@ -46,6 +50,9 @@ export default {
   elui,
   global,
   system,
+  cusMgt,
+  home,
+  learnActivity,
   formConfigP: {
     input: 'Please Enter ',
     select: 'Please Sele',

+ 188 - 170
TEAMModelOS/ClientApp/src/locale/lang/en-US/learnActivity.js

@@ -1,182 +1,200 @@
-export default{
+export default {
     //MgtSchoolEva.vue
-    mgtScEv:{
-        listLabel:'评测列表',
-        period:'学段:',
-        create:'新建',
-        delete:'删除',
-        edit:'编辑',
-        createTime:'施测时间:',
-        pending:'待发布',
-        going:'进行中',
-        finish:'已结束',
-        endTime:'结束时间:',
-        stop:'立即结束',
-        evType:'测试类型:',
-        stuCount:'施测人数:',
-        nodata:'暂无评测',
-        tab1:'评测数据',
-        tab2:'评测试卷',
-        autoTips1:'此功能仅用于展示情景快速模拟学生作答数据,且学生作答为随机生成,仅供参考!',
-        autoTips2:'此功能仅用于展示情景快速模拟教师评分数据,且分数为随机生成,仅供参考!',
-        autoScore:'一键评分',
-        autoAnswer:'一键作答',
-        evSubject:'测试科目:',
-        returnTop:'返回顶部',
-        mockOk:'模拟成功',
-        mockErr:'模拟失败',
-        stopTitle:'结束评测',
-        stopContent:'结束后学生将不能继续作答,是否立即结束',
-        stopOk:'评测已结束!',
-        deleteTitle:'删除评测',
-        deleteContent:'是否确认删除',
-        deleteOk:'删除成功',
-        deleteErr:'删除失败',
-        noJoin:'此账号暂未加入任何学校!'
+    mgtScEv: {
+        listLabel: '评测列表',
+        period: '学段:',
+        create: '新建',
+        delete: '删除',
+        edit: '编辑',
+        createTime: '施测时间:',
+        pending: '待发布',
+        going: '进行中',
+        finish: '已结束',
+        endTime: '结束时间:',
+        stop: '立即结束',
+        evType: '测试类型:',
+        stuCount: '施测人数:',
+        nodata: '暂无评测',
+        tab1: '评测数据',
+        tab2: '评测试卷',
+        autoTips1: '此功能仅用于展示情景快速模拟学生作答数据,且学生作答为随机生成,仅供参考!',
+        autoTips2: '此功能仅用于展示情景快速模拟教师评分数据,且分数为随机生成,仅供参考!',
+        autoScore: '一键评分',
+        autoAnswer: '一键作答',
+        evSubject: '测试科目:',
+        returnTop: '返回顶部',
+        mockOk: '模拟成功',
+        mockErr: '模拟失败',
+        stopTitle: '结束评测',
+        stopContent: '结束后学生将不能继续作答,是否立即结束',
+        stopOk: '评测已结束!',
+        deleteTitle: '删除评测',
+        deleteContent: '是否确认删除',
+        deleteOk: '删除成功',
+        deleteErr: '删除失败',
+        noJoin: '此账号暂未加入任何学校!'
     },
 
     //创建评测校本/个人
-    createEv:{
-        createLabel:'创建评测',
-        publishEv:'发布评测',
-        return:'返回上级',
-        baseInfo:'基础信息',
-        evName:'评测名称',
-        evPeriod:'测试学段',
-        evMode:'评测模式',
-        evType:'评测类型',
-        examType:'考试类别',
-        courseType:'课程类别',
-        cusLabel1:'校本课程',
-        cusLabel2:'个人课程',
-        evTarget:'施测对象',
-        publishType:'发布方式',
-        startTime:'开始时间',
-        sTimeHolder:'请选择开始时间',
-        endTime:'结束时间',
-        eTimeHolder:'请选择结束时间',
-        addSubject:'添加学科',
-        noSubject:'暂无科目,请添加科目',
-        noSubject1:'* 请先选择测试学段',
-        papersLabel:'试卷库',
-        perviewLabel:'试卷预览',
-        importLabel:'导入说明',
-        answerPreview:'作答预览',
-        errTips1:'评测名称不能为空!',
-        errTips2:'测试类型不能为空!',
-        errTips3:'评量模式不能为空!',
-        errTips4:'请设置类别!',
-        errTips5:'请设置测试类型!',
-        errTips6:'请设置测试对象!',
-        errTips7:'请设置发布方式!',
-        errTips8:'请设置发布时间!',
-        errTips9:'请设置结束时间!',
-        errTips10:'请设置测试学段!',
-        stPaperTitle:'挑选试卷',
-        stPaperContent:'是否确认挑选',
-        inDev:'功能正在开发中,敬请期待!',
-        formWarning:'请先完善评测基础信息!',
-        paperWarning:'请挑选或导入试卷!',
-        paperWarning1:'还没有试卷,请挑选或导入试卷!',
-        pdWarning:'请添加测试科目!',
-        publishOk:'评测发布成功!',
-        togglePeriod:'切换学段',
-        togglePdTip1:'您已添加',
-        togglePdTip2:'的测试科目,如果现在切换学段会清空已选科目,确定切换学段吗?',
-        toggleOkText:'切换',
-        delPdTitle:'删除科目',
-        delPdContent:'是否确认删除',
-        delOk:'删除成功',
-        pdTips:'请先选择测试学段!',
-        defaultPaper:'(请先挑选或导入试卷)',
+    createEv: {
+        createLabel: '创建评测',
+        publishEv: '发布评测',
+        return: '返回上级',
+        baseInfo: '基础信息',
+        evName: '评测名称',
+        evPeriod: '测试学段',
+        evMode: '评测模式',
+        evType: '评测类型',
+        examType: '考试类别',
+        courseType: '课程类别',
+        cusLabel1: '校本课程',
+        cusLabel2: '个人课程',
+        evTarget: '施测对象',
+        publishType: '发布方式',
+        startTime: '开始时间',
+        sTimeHolder: '请选择开始时间',
+        endTime: '结束时间',
+        eTimeHolder: '请选择结束时间',
+        addSubject: '添加学科',
+        noSubject: '暂无科目,请添加科目',
+        noSubject1: '* 请先选择测试学段',
+        papersLabel: '试卷库',
+        perviewLabel: '试卷预览',
+        importLabel: '导入说明',
+        answerPreview: '作答预览',
+        errTips1: '评测名称不能为空!',
+        errTips2: '测试类型不能为空!',
+        errTips3: '评量模式不能为空!',
+        errTips4: '请设置类别!',
+        errTips5: '请设置测试类型!',
+        errTips6: '请设置测试对象!',
+        errTips7: '请设置发布方式!',
+        errTips8: '请设置发布时间!',
+        errTips9: '请设置结束时间!',
+        errTips10: '请设置测试学段!',
+        stPaperTitle: '挑选试卷',
+        stPaperContent: '是否确认挑选',
+        inDev: '功能正在开发中,敬请期待!',
+        formWarning: '请先完善评测基础信息!',
+        paperWarning: '请挑选或导入试卷!',
+        paperWarning1: '还没有试卷,请挑选或导入试卷!',
+        pdWarning: '请添加测试科目!',
+        publishOk: '评测发布成功!',
+        togglePeriod: '切换学段',
+        togglePdTip1: '您已添加',
+        togglePdTip2: '的测试科目,如果现在切换学段会清空已选科目,确定切换学段吗?',
+        toggleOkText: '切换',
+        delPdTitle: '删除科目',
+        delPdContent: '是否确认删除',
+        delOk: '删除成功',
+        pdTips: '请先选择测试学段!',
+        defaultPaper: '(请先挑选或导入试卷)',
     },
     // ManualPaper.vue
-    manual:{
-        source:'来源:',
-        sourceP:'个人试卷库',
-        sourceS:'校本试卷库',
-        pdLabel:'学段:',
-        subjectLabel:'学科:',
-        gradeLabel:'年级:',
-        fitPd:'适用学段:',
-        fitSubject:'适用科目:',
-        quCount:'题量:',
-        useCount:'使用次数:',
-        stdPaper:'已选试卷',
-        stPaper:'选择试卷',
-        previewPaper:'预览试卷',
-        noPaper:'暂无对应的试卷',
-        noPaper1:'暂无试卷'
+    manual: {
+        source: '来源:',
+        sourceP: '个人试卷库',
+        sourceS: '校本试卷库',
+        pdLabel: '学段:',
+        subjectLabel: '学科:',
+        gradeLabel: '年级:',
+        fitPd: '适用学段:',
+        fitSubject: '适用科目:',
+        quCount: '题量:',
+        useCount: '使用次数:',
+        stdPaper: '已选试卷',
+        stPaper: '选择试卷',
+        previewPaper: '预览试卷',
+        noPaper: '暂无对应的试卷',
+        noPaper1: '暂无试卷'
     },
     // PaperScore.vue、Scoring.vue
-    score:{
-        stuName:'学生姓名:',
-        score:'总分:',
-        scoreUnit:'分',
-        saveScore:'保存打分',
-        showAns:'显示答案',
-        hideAns:'隐藏答案',
-        showQu:'显示题干',
-        hideQu:'隐藏题干',
-        quIndex:'题号:',
-        quIndex1:'题号',
-        fullScore:'满分',
-        zeroScore:'零分',
-        zeroScore1:'0分',
-        stuAns:'【学 生 作 答】',
-        mark:'批注',
-        quAns:'【答ㅤ案】',
-        noAnswer:'未设置答案',
-        anaLabel:'【解ㅤ析】',
-        noAna:'暂无解析',
-        kdLabel:'【知识点】',
-        noKd:'暂未绑定知识点',
-        sQuLabel1:'【小题',
-        sQuLabel2:'】',
-        quIndexLabel:'【题号',
-        nextStu:'下一位学生>>>',
-        markOk:'批注成功!',
-        markErr:'批注失败!',
-        isFullTips:'已经是小题满分了',
-        isZeroTips:'已经是零分了',
-        saveScoreOk:'成绩保存成功!',
-        saveSocreErr:'成绩保存失败!',
-        zero:'零',
-        one:'一',
-        two:'二',
-        three:'三',
-        four:'四',
-        five:'五',
-        six:'六',
-        seven:'七',
-        eight:'八',
-        nine:'九',
-        ten:'十',
-        hundred:'百',
-        thousand:'千',
-        tenThd:'万',
-        hMillion:'亿',
-        noStuAns:'未作答',
-        noStuAns1:'暂未作答',
-        trueAns:'正确',
-        falseAns:'错误',
+    score: {
+        stuName: '学生姓名:',
+        score: '总分:',
+        scoreUnit: '分',
+        saveScore: '保存打分',
+        showAns: '显示答案',
+        hideAns: '隐藏答案',
+        showQu: '显示题干',
+        hideQu: '隐藏题干',
+        quIndex: '题号:',
+        quIndex1: '题号',
+        fullScore: '满分',
+        zeroScore: '零分',
+        zeroScore1: '0分',
+        stuAns: '【学 生 作 答】',
+        mark: '批注',
+        quAns: '【答ㅤ案】',
+        noAnswer: '未设置答案',
+        anaLabel: '【解ㅤ析】',
+        noAna: '暂无解析',
+        kdLabel: '【知识点】',
+        noKd: '暂未绑定知识点',
+        sQuLabel1: '【小题',
+        sQuLabel2: '】',
+        quIndexLabel: '【题号',
+        nextStu: '下一位学生>>>',
+        markOk: '批注成功!',
+        markErr: '批注失败!',
+        isFullTips: '已经是小题满分了',
+        isZeroTips: '已经是零分了',
+        saveScoreOk: '成绩保存成功!',
+        saveSocreErr: '成绩保存失败!',
+        zero: '零',
+        one: '一',
+        two: '二',
+        three: '三',
+        four: '四',
+        five: '五',
+        six: '六',
+        seven: '七',
+        eight: '八',
+        nine: '九',
+        ten: '十',
+        hundred: '百',
+        thousand: '千',
+        tenThd: '万',
+        hMillion: '亿',
+        noStuAns: '未作答',
+        noStuAns1: '暂未作答',
+        trueAns: '正确',
+        falseAns: '错误',
         //Scoring.vue
-        subjectLabel:'学科:',
-        gradeLabel:'年级:',
-        classLabel:'班级:',
-        stuLabel:'学生:',
-        scoreView:'分数概览',
-        scoring:'试卷评分',
-        classNoStu:'班级暂无学生',
-        status1:'暂未作答',
-        status2:'前往评分',
-        status3:'查看分数',
-        column1:'姓名',
-        column2:'总分',
-        column3:'状态',
-        finishScore:'已完成所有学生作答的评分!',
-        unableScore:'学生尚未作答,无法评分',
-        stStuWarning:'请先选择学生!'
+        subjectLabel: '学科:',
+        gradeLabel: '年级:',
+        classLabel: '班级:',
+        stuLabel: '学生:',
+        scoreView: '分数概览',
+        scoring: '试卷评分',
+        classNoStu: '班级暂无学生',
+        status1: '暂未作答',
+        status2: '前往评分',
+        status3: '查看分数',
+        column1: '姓名',
+        column2: '总分',
+        column3: '状态',
+        finishScore: '已完成所有学生作答的评分!',
+        unableScore: '学生尚未作答,无法评分',
+        stStuWarning: '请先选择学生!'
+    },
+
+    //SimpleAnalysis.vue
+    simple: {
+        total: '总人数',
+        missExam: '缺考数',
+        classLabel: '班级',
+        sjLabel: '学科',
+        avgScore: '平均分',
+        classStuCount: '班级人数',
+        answered: '已作答',
+        unanswer: '未作答',
+        scored: '已评分',
+        unscore: '未评分',
+        noPublish: '评测尚未发布,暂无统计数据',
+        calcing: '成绩数据结算中,',
+        clickFresh: '点此刷新',
+        inCalc: '数据结算中,请稍后查看'
     }
 
 }

+ 47 - 3
TEAMModelOS/ClientApp/src/locale/lang/en-US/studentWeb.js

@@ -21,7 +21,8 @@ export default {
         "Timeout": "Timeout",
         "makeupExam": "Makeup Exam",
         "makeupHw": "Makeup available",
-        "selectActivity": "Please select an activity from the list"
+        "selectActivity": "Please select an activity from the list",
+        "allSubject": "All Subjects"
     },
     "settingView-title": "Personal Settings",
     "teammodel-account-management": {
@@ -225,7 +226,7 @@ export default {
     "exam": {
         "examLink": "Exam Link",
         "isSubject": "",
-        "subjectNow": "Current Subject",
+        "subjectNow": "Subject",
         "examData": "Evaluation Content",
         "gradeReport": "Grade Report",
         "gradeAnalyse": "Grade Analysis",
@@ -275,7 +276,14 @@ export default {
             "testAns": "Reference Answer",
             "testAnalyse": "Analyze",
             "repairSource": "Repair Source",
-            "noAnalyse": "No analysis"
+            "noAns": "No Answer",
+            "linkSource": "Network Resources",
+            "noSource": "No Resources",
+            "fileSource": "File Source",
+            "fileView": "File Preview",
+            "noReview": "This file does not support preview, please download and view!",
+            "pdfErr": "'pdf failed to load'",
+            "noAnalyse": "No analysis yet"
         },
         "timeoutHint": "The evaluation activity time has ended. Overtime will be calculated as 0 points, or wait for the teacher to open the make-up exam.",
         "contentPage": "Review Content",
@@ -301,6 +309,42 @@ export default {
             "participantAverage": "The whole school",
             "recognizePerformance": "Recognition Level Radar Chart"
         },
+        "studentScore": {
+            "examName": "Exam Name",
+            "examType": "Exam Type",
+            "stableIndex": "Learning stability coefficient",
+            "name": "Name",
+            "stuNo": "Student Number",
+            "class": "class",
+            "allSubScore": "All Subject Scores",
+            "total": "Personal Total Score",
+            "cAverages": "Class Averages",
+            "gAverages": "Grade Averages",
+            "aAverages": "District Averages",
+            "cIndex": "Class Ranking",
+            "gIndex": "Grade Ranking",
+            "aIndex": "District Rank",
+            "onLine": "Whether to enter the line",
+            "subject": "Subject",
+            "score": "Personal Score",
+            "cAverage": "Class Average",
+            "gAverage": "Grade Average",
+            "aAverage": "District-level average",
+            "rAns": "The number of correct answers",
+            "wAns": "Number of wrong answers",
+            "comIndex": "Comprehensive Difficulty",
+            "objItem": "Objective",
+            "subItem": "Subjective",
+            "subjects": "Subjects",
+            "item": "Title",
+            "objItems": "Objective Questions",
+            "subItems": "Subjective questions",
+            "itemIndex": "item number",
+            "standardIndex": "standard answer/partition points",
+            "stuAnsIndex": "Student answer/score",
+            "notice": "【-】--correct answer, 【#】--not answered, 【?】--multiple choice question",
+            "gradeErr": "The grade information is wrong!"
+        },
         "correctAnswer": "correctAnswer",
         "wrongAnswer": "Wrong Answer",
         "unAnswered": "Unanswered",

+ 53 - 32
TEAMModelOS/ClientApp/src/locale/lang/en-US/teachContent.js

@@ -1,34 +1,55 @@
 export default {
-  personalResource: 'Private resource',
-  schoolResource: 'School based resource',
-  filterAll: 'All',
-  filterPicture: 'Picture',
-  filterVideo: 'Video',
-  filterDoc: 'Document',
-  filterOther: 'Other',
-  space: 'Space:',
-  tableC1: 'File name',
-  tableC2: 'Operation',
-  tableC3: 'Knowledge points',
-  tableC4: 'File size',
-  tableC5: 'Relation number',
-  tableC6: 'Upload date',
-  searchText: 'please enter keyword search',
-  btnUpload: 'upload resources',
-  tips1: 'Card',
-  tips2: 'List',
-  tips3: 'download file',
-  tips4: 'preview file',
-  tips5: 'bind knowledge point',
-  tips6: 'rename',
-  tips7: 'delete file',
-  tips8: 'your browser does not support video tags. ',
-  trops1: 'confirm deletion',
-  props2: 'file deleted successfully! ',
-  props3: 'file deletion failed! ',
-  props4: 'Cancel deletion! ',
-  props5: 'file name modified successfully! ',
-  props6: 'file upload succeeded! ',
-  props7: 'not enough storage space! ',
-  uploadText: 'Click or drag to upload'
+  filterRes: '教材',
+  filterPicture: '圖片',
+  filterVideo: '視頻',
+  filterAudio: '音訊',
+  filterDoc: '檔案',
+  filterOther: '其他',
+  space: '空間:',
+  tableC1: '檔案名稱',
+  tableC2: '操作',
+  tableC3: '知識點',
+  tableC4: '文件大小',
+  tableC5: '關聯數量',
+  tableC6: '上傳日期',
+  searchText: '請輸入關鍵字蒐索',
+  btnUpload: '上傳資源',
+  tips1: '大圖',
+  tips2: '清單',
+  tips3: '下載檔案',
+  tips4: '預覽檔案',
+  tips5: '綁定知識點',
+  tips6: '重命名',
+  tips7: '刪除檔',
+  tips8: '您的瀏覽器不支持video標籤。',
+  props1: '確認删除',
+  props2: '檔案删除成功!',
+  props3: '檔案删除失敗!',
+  props4: '取消删除!',
+  props5: '檔名修改成功!',
+  props6: '文件上傳成功!',
+  props7: '存儲空間不足!',
+  uploadText: '點擊或者拖拽上傳',
+
+  resTips: 'HiTeach生成的課件,不包含PPT等其他檔案',
+  space: '空間:',
+  calcing: '計算中…',
+  blobFull: '(已滿)',
+  otherType: '其他類型',
+  appData: '應用數據',
+  delBatch: '批量删除',
+  viewport1: '清單模式',
+  viewport2: '圖片模式',
+  bottomTips: '--------我也是有底線的--------',
+  notAudio: '您的瀏覽器不支持audio元素。',
+  nameOk: '名稱修改成功',
+  nameErr: '名稱修改失敗',
+  fullTips: '存儲空間已滿,無法上傳',
+  loadAll: '已經加載全部',
+  authErr: '獲取Blob授權失敗',
+  sizeErr: '空間計算异常',
+  delBatchTips1: '請在清單模式使用批量删除!',
+  delBatchTips2: '請先選擇需要删除的檔案!',
+  specialChart: '不能包含特殊字元',
+  noData: '暫無數據'
 }

+ 106 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/cusMgt.js

@@ -0,0 +1,106 @@
+export default {
+    // ManageCourse.vue
+    searchHolder:'搜索...',
+    delBatch:'批量删除',
+    delCus:'删除课程',
+    addCus:'添加课程',
+    editCus:'编辑课程',
+    noTeacher:'暂无',
+    cusName:'课程名称',
+    nameHolder:'请输入课程名称',
+    cusCode:'课程编码',
+    codeHolder:'请输入课程编码',
+    cusPd:'课程学段',
+    pdHolder:'请选择学段',
+    cusSubject:'课程学科',
+    sjHolder:'请选择学科',
+    cusTeachers:'授课教师',
+    teacherHolder:'请选择授课教师',
+    delContent:'是否确认删除',
+    codeErr1:'课程编码不能为空',
+    codeErr2:'课程编码只能由字母和数字组成',
+    tableCol1:'序号',
+    tableCol2:'课程名称',
+    tableCol3:'课程编码',
+    tableCol4:'学段',
+    tableCol5:'学科',
+    tableCol6:'教师人数',
+    tableCol7:'授课教师',
+    delOk:'删除成功!',
+    delErr:'删除失败!',
+    addOk:'添加成功!',
+    addErr:'添加成功!',
+    editOk:'修改成功!',
+    editErr:'修改失败!',
+    formTips:'请先完善信息,再保存!',
+    noSchool:'尚未加入学校,没有学校数据',
+    sltCusTips:'请选择课程',
+
+    //NewCoursePlan.vue
+    spClass:'专科教室',
+    cusPlan:'课程安排',
+    importPlan:'导入安排',
+    course:'课程',
+    addCusPlan:'添加课程安排',
+    saveTips:'保存提醒',
+    tipsContent:'当前班级排课数据尚未保存。如果离开,修改的数据将不会保存!',
+    leaveText:'离开',
+    fullTips:'请先完成已添加的课程设置!',
+    saveOk:'保存成功',
+    saveErr:'保存失败',
+
+    //MyCourse.vue
+    scCus:'标准课程',
+    cusInfo:'课程信息',
+    noScCus:'暂无校本课程',
+    privCus:'个人课程',
+    noPrivCus:'暂未创建课程',
+    noCusClass:'暂无上课班级',
+    delStu:'删除学生',
+    addStu:'添加学生',
+    noGroup:'未分组',
+    noNotice:'暂无课程公告!',
+    edit:'编辑',
+    save:'保存',
+    className:'班级名称',
+    editClassInfo:'修改班级信息',
+    dyClass:'动态名单',
+    scClass:'常规教室',
+    delStuContent:'是否确认删除',
+    delStuTips:'请先选择需要删除的学生!',
+    delClassTitle:'删除班级',
+    stuClassCol1:'姓名',
+    stuClassCol2:'账号',
+    stuClassCol3:'座号',
+    stuClassCol4:'组别',
+    stuClassCol5:'组名',
+
+    //ManageClass.vue
+    classLabel:'班级:',
+    stuCount:'学生人数:',
+    autoGroup:'自动分组',
+    exportStu:'导出名单',
+    viewport1:'分组视图',
+    viewport2:'列表视图',
+    noStu:'暂无学生',
+    resetPw:'重置密码',
+    noMgtClass:'暂无您管理的班级',
+    groupNameHolder:'请设置组名',
+    edtiGroupName:'修改组名',
+    addGroup:'新增组别',
+    peopleUnit:'人',
+    studentCountLabel: '学生人数',
+    groupCountLabel: '组别数量',
+    groupTypeLabel: '分组方式',
+    groupType1: '随机分组',
+    groupType2: '依座号分组',
+    groupType3: '依座顺序S形分组',
+    action:'操作',
+    atLeast:'至少保留一组',
+    checkName:'请先输入组名再创建组别',
+    noStuTips:'暂无学生可以进行分组',
+    groupCount:'分组数量最少为1',
+    groupTypeTips:'请设置分组方式',
+    groupUnit:'组',
+    nameList:'学生名单'
+}

+ 5 - 5
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/evaluation.js

@@ -38,12 +38,12 @@ export default {
 	diff3:'一般',
 	diff4:'较难',
 	diff5:'困难',
-	level1:'知识',
+	level1:'记忆',
 	level2:'理解',
 	level3:'应用',
 	level4:'分析',
-	level5:'综合',
-	level6:'评鉴',
+	level5:'评价',
+	level6:'创造',
 	noData:'暂无数据',
 	answer:'答案',
 	noAnswer:'未设置答案',
@@ -181,11 +181,11 @@ export default {
 		downloadDetails:'下载模板制作详情说明',
 		importTips:'导入注意事项',
 		tips1:'点击上方上传图标选择文件',
-		tips2:'只支持".docx"格式的文件导入,请按照模板格式导入',
+		tips2:'只支持".docx,.xlsx,.xls"格式的文件导入,请按照模板格式导入',
 		tips3:'导入题型暂时只支持单选、多选、判断、填空、问答以及综合题型',
 		tips4:'请保持模板语言与当前浏览器语言一致',
 		tips5:'更多注意事项请查看模板制作详情说明',
-		warningTips1:'上传格式仅支持 docx,请重新上传!',
+		warningTips1:'上传格式仅支持 docx/xlsx/xls ,请重新上传!',
 		warningTips2:'最大上传大小为10M,请重新上传!',
 		warningTips3:'试题数据读取完成!',
 		warningTips4:'未解析出符合条件的试题,请检查导入格式是否按照模板文件修改!',

+ 35 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/global.js

@@ -9,5 +9,39 @@ export default{
     evMode2:'课中评量',
     evMode3:'阅卷评量',
     publishType1:'立即发布',
-    publishType2:'定时发布',
+    publishType2: '定时发布',
+    testType: [
+        {
+            label: "单选",
+            value: "single"
+        },
+        {
+            label: "多选",
+            value: "multiple"
+        },
+        {
+            label: "判断",
+            value: "judge"
+        },
+        {
+            label: "填空",
+            value: "complete"
+        },
+        {
+            label: "问答",
+            value: "subjective"
+        },
+        {
+            label: "综合",
+            value: "compose"
+        },
+        {
+            label: "改错",
+            value: "correct"
+        },
+        {
+            label: "连线",
+            value: "connector"
+        }
+        ]
 }

+ 22 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/home.js

@@ -0,0 +1,22 @@
+export default{
+    previewStudy:'课前预习',
+    classData:'课堂数据',
+    recentRecord:'近期课堂记录',
+    acCount:'活动概览',
+    goingList:'进行中的活动列表',
+    scNotice:'学校公告',
+    msg:'消息通知',
+    sysMsg:'小豆助手',
+    avgScore:'平均互动指数',
+    col1:'小组学习',
+    col2:'多元评价',
+    col3:'个人学习',
+    col4:'生本决策',
+    col5:'全班互动',
+    col6:'全班测验',
+    ac1:'评量测验',
+    ac2:'自主学习',
+    ac3:'作业活动',
+    ac4:'投票活动',
+    ac5:'问卷调查',
+}

+ 8 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/index.js

@@ -23,6 +23,10 @@ import evaluation from './evaluation'
 import learnActivity from './learnActivity'
 import global from './global'
 import system from './system'
+import cusMgt from './cusMgt'
+import home from './home'
+import vote from './vote'
+import survey from './survey'
 export default {
   schoolBaseInfo,
   classMgmt,
@@ -49,6 +53,10 @@ export default {
   learnActivity,
   global,
   system,
+  cusMgt,
+  home,
+  vote,
+  survey,
   test: '测试',
   formConfigP: {
     input: '请输入',

+ 18 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/learnActivity.js

@@ -177,6 +177,24 @@ export default{
         finishScore:'已完成所有学生作答的评分!',
         unableScore:'学生尚未作答,无法评分',
         stStuWarning:'请先选择学生!'
+    },
+
+    //SimpleAnalysis.vue
+    simple:{
+        total:'总人数',
+        missExam:'缺考数',
+        classLabel:'班级',
+        sjLabel:'学科',
+        avgScore:'平均分',
+        classStuCount:'班级人数',
+        answered:'已作答',
+        unanswer:'未作答',
+        scored:'已评分',
+        unscore:'未评分',
+        noPublish:'评测尚未发布,暂无统计数据',
+        calcing:'成绩数据结算中,',
+        clickFresh:'点此刷新',
+        inCalc:'数据结算中,请稍后查看'
     }
 
 }

+ 46 - 7
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/studentWeb.js

@@ -21,7 +21,8 @@ export default {
         "Timeout": "已逾时",
         "makeupExam": "可补考",
         "makeupHw": "可补交",
-        "selectActivity":"请从列表挑选一个活动"
+        "selectActivity": "请从列表挑选一个活动",
+        "allSubject":"综合科目"
     },
     "settingView-title": "个人设定",
     "teammodel-account-management": {
@@ -137,8 +138,6 @@ export default {
     },
     "latestNotification": "最新通知",
     "postAt": "发布于",
-
-
     "______________": "______________",
     "eventView-title": "活动任务",
     "nextTask": "下个活动",
@@ -225,7 +224,7 @@ export default {
         "submitBtn": "送出"
     },
     "exam": {
-        "examLink": "试卷连结",
+        "examLink": "试卷链接",
         "isSubject": "科试卷",
         "subjectNow": "当前科目",
         "examData": "评测内容",
@@ -277,6 +276,13 @@ export default {
             "testAns": "参考答案",
             "testAnalyse": "解析",
             "repairSource": "补救资源",
+            "noAns":"未作答",
+            "linkSource": "网络资源",
+            "noSource": "暂无资源",
+            "fileSource": "文件资源",
+            "fileView": "文件预览",
+            "noReview": "该文件暂不支持预览,请下载查看!",
+            "pdfErr":"'pdf 加载失败'",
             "noAnalyse":"暂无解析"
         },
         "timeoutHint": "评量活动时间已结束,逾时将以0分计算,或等待教师开放补考。",
@@ -294,8 +300,6 @@ export default {
         "rightNum": "我的答对题数",
         "smartComment": "智能点评",
         "keypoint": "建议复习知识点:",
-
-
         "chart": {
             "scoreDistribution": "评量成绩分布图",
             "participant": "应考人数:",
@@ -305,6 +309,42 @@ export default {
             "participantAverage": "全校",
             "recognizePerformance": "认知层次雷达图"
         },
+        "studentScore": {
+            "examName": "考试名称",
+            "examType":"考试类型",
+            "stableIndex":"学习稳定系数",
+            "name":"姓名",
+            "stuNo":"学号",
+            "class": "班级",
+            "allSubScore": "全学科成绩",
+            "total":"个人总分",
+            "cAverages":"班级平均分",
+            "gAverages":"年级平均分",
+            "aAverages":"区级平均分",
+            "cIndex":"班级排名",
+            "gIndex":"年级排名",
+            "aIndex":"区级排名",
+            "onLine": "是否进线",
+            "subject":"学科",
+            "score": "个人得分",
+            "cAverage": "班级平均",
+            "gAverage": "年级平均",
+            "aAverage": "区级平均",
+            "rAns":"答对题数",
+            "wAns":"答错题数",
+            "comIndex":"综合难度",
+            "objItem":"客观",
+            "subItem":"主观",
+            "subjects":"科目",
+            "item":"题目",
+            "objItems": "客观题",
+            "subItems": "主观题",
+            "itemIndex":"题号",
+            "standardIndex":"标准答案/配分",
+            "stuAnsIndex":"学生作答/得分",
+            "notice": "【-】--答对,【#】--未作答,【?】--多选题",
+            "gradeErr":"成绩信息错误!"
+        },
         "correctAnswer": "答对",
         "wrongAnswer": "答错",
         "unAnswered": "未答",
@@ -312,7 +352,6 @@ export default {
         "analysis": "解析",
         "correctRate": "答对率",
         "relatedAQues": "关联题目",
-       
     },
     "informview-title": "通知总览",
     "view": "前往检视",

+ 54 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/survey.js

@@ -0,0 +1,54 @@
+export default {
+    list:'问卷活动列表',
+	pending:'待发布',
+	going:'进行中',
+	finish:'已结束',
+	surveyDetails:'问卷详情',
+	undo:'撤销发布',
+	edit:'编辑问卷',
+	save:'保存问卷',
+	cancelEdit:'取消编辑',
+	surveyResult:'问卷数据',
+	addItem:'新增题目',
+	single:'单选题',
+	multiple:'多选题',
+	judge:'判断题',
+	defaultName:'预设问卷名称',
+	isExistTip:'已存在未保存的问卷活动!',
+	getDataFailTip:'获取数据失败!',
+	deletesurvey:'删除问卷',
+	deleteConfirmTip:'确认删除该问卷活动?',
+	cancelSurvey:'取消发布',
+	cancelConfirmTip:'确认取消发布该问卷活动?',
+	deleteSuc:'删除成功',
+	deleteFail:'删除失败',
+	doSuc:'操作成功',
+	doFail:'操作失败',
+	noData:'暂无数据',
+	form:{
+		name:'问卷名称',
+		namePlace:'请输入问卷名称',
+		target:'问卷对象',
+		privateClass:'个人班级',
+		schoolClass:'校本班级',
+		targetPlace:'请选择问卷发布对象',
+		noFoundText:'暂未创建班级',
+		time:'起止时间',
+		endTimePlace:'请选择问卷结束时间',
+		description:'问卷描述',
+		optionSetting:'选项设置',
+		addOption:'添加选项',
+		selectNum:'可问卷数',
+		other:'其他',
+		openSecret:'开启匿名问卷',
+		save:'保存',
+		cancel:'取消',
+		editSuc:'修改成功',
+		addSuc:'添加成功',
+		noCompleteTip:'请将信息填写完整',
+		noOptionTip:'问卷选项个数不能为空!',
+		attachmentMaxTip:'最多只能上传5个附件',
+		optionNumsTip:'最多只能有10个选项',
+		noMatchDataTip:'未匹配数据'
+	}
+}

+ 25 - 4
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/teachContent.js

@@ -1,9 +1,8 @@
 export default {
-  personalResource: '个人资源',
-  schoolResource: '校本资源',
-  filterAll: '全部',
+  filterRes: '教材',
   filterPicture: '图片',
   filterVideo: '视频',
+  filterAudio: '音频',
   filterDoc: '文档',
   filterOther: '其他',
   space: '空间:',
@@ -30,5 +29,27 @@ export default {
   props5: '文件名修改成功!',
   props6: '文件上传成功!',
   props7: '存储空间不足!',
-  uploadText: '点击或者拖拽上传'
+  uploadText: '点击或者拖拽上传',
+
+  resTips:'HiTeach生成的课件,不包含PPT等其他文件',
+  space:'空间:',
+  calcing:'计算中...',
+  blobFull:'(已满)',
+  otherType:'其他类型',
+  appData:'应用数据',
+  delBatch:'批量删除',
+  viewport1:'列表模式',
+  viewport2:'图片模式',
+  bottomTips:'--------我也是有底线的--------',
+  notAudio:'您的浏览器不支持 audio 元素。',
+  nameOk:'名称修改成功',
+  nameErr:'名称修改失败',
+  fullTips:'存储空间已满,无法上传',
+  loadAll:'已经加载全部',
+  authErr:'获取Blob授权失败',
+  sizeErr:'空间计算异常',
+  delBatchTips1:'请在列表模式使用批量删除!',
+  delBatchTips2:'请先选择需要删除的文件!',
+  specialChart:'不能包含特殊字符',
+  noData:'暂无数据'
 }

+ 49 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/vote.js

@@ -0,0 +1,49 @@
+export default {
+    list:'投票活动列表',
+	pending:'待发布',
+	going:'进行中',
+	finish:'已结束',
+	voteDetails:'投票详情',
+	edit:'编辑',
+	voteResult:'投票数据',
+	optionView:'选项清单试图',
+	stuListView:'学生清单视图',
+	defaultName:'预设投票名称',
+	isExistTip:'已存在未保存的投票活动!',
+	getDataFailTip:'获取数据失败!',
+	serverErrorTip:'投票活动接口调整中,请稍后再试!',
+	deleteVote:'删除投票',
+	deleteConfirmTip:'确认删除该投票活动?',
+	deleteSuc:'删除成功',
+	deleteFail:'删除失败',
+	option:'选项',
+	noVote:'未投票',
+	noData:'暂无数据',
+	getClassDataFailTip:'获取班级学生数据异常',
+	form:{
+		name:'投票名称',
+		namePlace:'请输入投票名称',
+		target:'投票对象',
+		privateClass:'个人班级',
+		schoolClass:'校本班级',
+		targetPlace:'请选择投票发布对象',
+		noFoundText:'暂未创建班级',
+		time:'起止时间',
+		endTimePlace:'请选择投票结束时间',
+		description:'投票描述',
+		optionSetting:'选项设置',
+		addOption:'添加选项',
+		selectNum:'可投票数',
+		other:'其他',
+		openSecret:'开启匿名投票',
+		save:'保存',
+		cancel:'取消',
+		editSuc:'修改成功',
+		addSuc:'添加成功',
+		noCompleteTip:'请将信息填写完整',
+		noOptionTip:'投票选项个数不能为空!',
+		attachmentMaxTip:'最多只能上传5个附件',
+		optionNumsTip:'最多只能有10个选项',
+		noMatchDataTip:'未匹配数据'
+	}
+}

+ 107 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/cusMgt.js

@@ -0,0 +1,107 @@
+export default {
+    // ManageCourse.vue
+    searchHolder: '蒐索課程…',
+    delBatch: '批量删除',
+    delCus: '删除課程',
+    addCus: '添加課程',
+    editCus: '編輯課程',
+    noTeacher: '暫無',
+    cusName: '課程名稱',
+    nameHolder: '請輸入課程名稱',
+    cusCode: '課程編碼',
+    codeHolder: '請輸入課程編碼',
+    cusPd: '課程學段',
+    pdHolder: '請選擇學段',
+    cusSubject: '課程學科',
+    sjHolder: '請選擇學科',
+    cusTeachers: '授課教師',
+    teacherHolder: '請選擇授課教師',
+    delContent: '是否確認删除',
+    codeErr1: '課程編碼不能為空',
+    codeErr2: '課程編碼只能由字母和數位組成',
+    tableCol1: '序號',
+    tableCol2: '課程名稱',
+    tableCol3: '課程編碼',
+    tableCol4: '學段',
+    tableCol5: '學科',
+    tableCol6: '教師人數',
+    tableCol7: '授課教師',
+    delOk: '删除成功!',
+    delErr: '删除失敗!',
+    addOk: '添加成功!',
+    editOk: '修改成功!',
+    formTips: '請先完善資訊,再保存!',
+    noSchool: '尚未加入學校,沒有學校數據',
+    sltCusTips: '請選擇課程',
+
+    //NewCoursePlan.vue
+    spClass: '專科教室',
+    cusPlan: '課程安排',
+    importPlan: '導入安排',
+    course: '課程',
+    addCusPlan: '添加課程安排',
+    saveTips: '保存提醒',
+    tipsContent: '當前班級排課數據尚未保存。如果離開,修改的數據將不會保存!',
+    leaveText: '離開',
+    fullTips: '請先完成已添加的課程設置!',
+    saveOk: '保存成功',
+    saveErr: '保存失敗',
+
+
+    //MyCourse.vue
+    scCus: '標準課程',
+    cusInfo: '課程資訊',
+    noScCus: '暫無校本課程',
+    privCus: '個人課程',
+    noPrivCus: '暫未創建課程',
+    noCusClass: '暫無上課班級',
+    delStu: '删除學生',
+    addStu: '添加學生',
+    noGroup: '未分組',
+    noNotice: '暫無課程公告!',
+    edit: '編輯',
+    save: '保存',
+    className: '班級名稱',
+    editClassInfo: '修改班級資訊',
+    dyClass: '動態名單',
+    scClass: '常規教室',
+    delStuContent: '是否確認删除',
+    delStuTips: '請先選擇需要删除的學生!',
+    delClassTitle: '删除班級',
+    stuClassCol1: '姓名',
+    stuClassCol2: '帳號',
+    stuClassCol3: '座號',
+    stuClassCol4: '組別',
+    stuClassCol5: '組名',
+
+
+    //ManageClass.vue
+    classLabel: '班級:',
+    stuCount: '學生人數:',
+    autoGroup: '自動分組',
+    exportStu: '匯出名單',
+    viewport1: '分組視圖',
+    viewport2: '清單視圖',
+    noStu: '暫無學生',
+    resetPw: '重置密碼',
+    noMgtClass: '暫無您管理的班級',
+    groupNameHolder: '請設定組名',
+    edtiGroupName: '修改組名',
+    addGroup: '新增組別',
+    peopleUnit: '人',
+    studentCountLabel: '學生人數',
+    groupCountLabel: '組別數量',
+    groupTypeLabel: '分組管道',
+    groupType1: '隨機分組',
+    groupType2: '依座號分組',
+    groupType3: '依座順序S形分組',
+    action: '操作',
+    atLeast: '至少保留一組',
+    checkName: '請先輸入組名再創建組別',
+    noStuTips: '暫無學生可以進行分組',
+    groupCount: '分組數量最少為1',
+    groupTypeTips: '請設定分組管道',
+    groupUnit: '組',
+    nameList: '學生名單'
+
+}

+ 5 - 5
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/evaluation.js

@@ -38,12 +38,12 @@ export default {
 	diff3: '一般',
 	diff4: '較難',
 	diff5: '困難',
-	level1: '知識',
+	level1: '記憶',
 	level2: '理解',
 	level3: '應用',
 	level4: '分析',
-	level5: '綜合',
-	level6: '評鑒',
+	level5: '評鑒',
+	level6: '創造',
 	noData: '暫無數據',
 	answer: '答案',
 	noAnswer: '未設定答案',
@@ -181,11 +181,11 @@ export default {
 		downloadDetails: '下載範本製作詳情說明',
 		importTips: '導入注意事項',
 		tips1: '點擊上方上傳圖標選擇檔案',
-		tips2: '只支持“.docx”格式的檔案導入,請按照範本格式導入',
+		tips2: '只支持“.docx,.xlsx,.xls”格式的檔案導入,請按照範本格式導入',
 		tips3: '導入題型暫時只支持單選、多選、判斷、填空、問答以及綜合題型',
 		tips4: '請保持範本語言與當前瀏覽器語言一致',
 		tips5: '更多注意事項請查看範本製作詳情說明',
-		warningTips1: '上傳格式僅支持docx,請重新上傳!',
+		warningTips1: '上傳格式僅支持 docx/xlsx/xls ,請重新上傳!',
 		warningTips2: '最大上傳大小為10M,請重新上傳!',
 		warningTips3: '試題數據讀取完成!',
 		warningTips4: '未解析出符合條件的試題,請檢查導入格式是否按照範本檔案修改!',

+ 46 - 4
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/global.js

@@ -1,5 +1,47 @@
-export default{
-    examType1:'期中考試',
-    examType2:'期末考試',
-    examType3:'模擬考試'
+export default {
+    xamType1: '期中考試',
+    examType2: '期末考試',
+    examType3: '模擬考試',
+    evType1: '正規考',
+    evType2: '模擬考',
+    evType3: '普通考',
+    evMode1: '線上評量',
+    evMode2: '課中評量',
+    evMode3: '閱卷評量',
+    publishType1: '立即發佈',
+    publishType2: '定時發佈',
+    testType: [
+        {
+            label: "單選",
+            value: "single"
+        },
+        {
+            label: "多選",
+            value: "multiple"
+        },
+        {
+            label: "判斷",
+            value: "judge"
+        },
+        {
+            label: "填空",
+            value: "complete"
+        },
+        {
+            label: "問答",
+            value: "subjective"
+        },
+        {
+            label: "綜合",
+            value: "compose"
+        },
+        {
+            label: "改錯",
+            value: "correct"
+        },
+        {
+            label: "連線",
+            value: "connector"
+        }
+    ]
 }

+ 22 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/home.js

@@ -0,0 +1,22 @@
+export default {
+    previewStudy: '課前預習',
+    classData: '課堂數據',
+    recentRecord: '近期課堂記錄',
+    acCount: '活動概覽',
+    goingList: '進行中的活動清單',
+    scNotice: '學校公告',
+    msg: '消息通知',
+    sysMsg: '小豆助手',
+    avgScore: '平均互動指數',
+    col1: '小組學習',
+    col2: '多元評估',
+    col3: '個人學習',
+    col4: '生本決策',
+    col5: '全班互動',
+    col6: '全班測驗',
+    ac1: '評量測驗',
+    ac2: '自主學習',
+    ac3: '工作活動',
+    ac4: '投票活動',
+    ac5: '問卷調查',
+}

+ 10 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/index.js

@@ -20,9 +20,13 @@ import settings from './settings'
 import serviceDriveAuth from './serviceDriveAuth'
 import elui from './elui'
 import evaluation from './evaluation'
+import learnActivity from './learnActivity'
 import global from './global'
 import system from './system'
-
+import cusMgt from './cusMgt'
+import home from './home'
+import vote from './vote'
+import survey from './survey'
 export default {
   
   schoolBaseInfo,
@@ -47,8 +51,13 @@ export default {
   serviceDriveAuth, //授權管理頁面
   elui,
   evaluation,
+  learnActivity,
   global,
   system,
+  cusMgt,
+  home,
+  vote,
+  survey,
   test: '測試',
   formConfigP: {
     input: '請輸入',

+ 19 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/learnActivity.js

@@ -182,6 +182,25 @@ export default {
         finishScore: '已完成所有學生作答的評分!',
         unableScore: '學生尚未作答,無法評分',
         stStuWarning: '請先選擇學生!'
+    },
+
+    //SimpleAnalysis.vue
+    simple: {
+        total: '總人數',
+        missExam: '缺考數',
+        classLabel: '班級',
+        sjLabel: '學科',
+        avgScore: '平均分',
+        classStuCount: '班級人數',
+        answered: '已作答',
+        unanswer: '未作答',
+        scored: '已評分',
+        unscore: '未評分',
+        noPublish: '評測尚未發佈,暫無統計資料',
+        calcing: '成績數據結算中,',
+        clickFresh: '點此重繪',
+        inCalc: '數據結算中,請稍後查看'
+
     }
 
 }

+ 46 - 2
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/studentWeb.js

@@ -21,7 +21,8 @@ export default {
         "Timeout": "已逾時",
         "makeupExam": "可補考",
         "makeupHw": "可補交",
-        "selectActivity": "請從列表挑選一個活動"
+        "selectActivity": "請從列表挑選一個活動",
+        "allSubject": "綜合科目"
     },
     "settingView-title": "個人設定",
     "teammodel-account-management": {
@@ -223,7 +224,7 @@ export default {
         "submitBtn": "送出"
     },
     "exam": {
-        "examLink": "試卷連結",
+        "examLink": "試卷鏈接",
         "isSubject": "科試卷",
         "subjectNow": "當前科目",
         "examData": "評測內容",
@@ -275,6 +276,13 @@ export default {
             "testAns": "參考答案",
             "testAnalyse": "解析",
             "repairSource": "補救資源",
+            "noAns": "未作答",
+            "linkSource": "網絡資源",
+            "noSource": "暫無資源",
+            "fileSource": "文件資源",
+            "fileView": "文件預覽",
+            "noReview": "該文件暫不支持預覽,請下載查看!",
+            "pdfErr": "'pdf 加載失敗'",
             "noAnalyse": "暫無解析"
         },
         "timeoutHint": "評量活動時間已結束,逾時將以0分計算,或等待教師開放補考。",
@@ -303,6 +311,42 @@ export default {
             "participantAverage": "全校",
             "recognizePerformance": "認知層次雷達圖"
         },
+        "studentScore": {
+            "examName": "考試名稱",
+            "examType": "考試類型",
+            "stableIndex": "學習穩定係數",
+            "name": "姓名",
+            "stuNo": "學號",
+            "class": "班級",
+            "allSubScore": "全學科成績",
+            "total": "個人總分",
+            "cAverages": "班級平均分",
+            "gAverages": "年級平均分",
+            "aAverages": "區級平均分",
+            "cIndex": "班級排名",
+            "gIndex": "年級排名",
+            "aIndex": "區級排名",
+            "onLine": "是否進線",
+            "subject": "學科",
+            "score": "個人得分",
+            "cAverage": "班級平均",
+            "gAverage": "年級平均",
+            "aAverage": "區級平均",
+            "rAns": "答對題數",
+            "wAns": "答錯題數",
+            "comIndex": "綜合難度",
+            "objItem": "客觀",
+            "subItem": "主觀",
+            "subjects": "科目",
+            "item": "題目",
+            "objItems": "客觀題",
+            "subItems": "主觀題",
+            "itemIndex": "題號",
+            "standardIndex": "標準答案/配分",
+            "stuAnsIndex": "學生作答/得分",
+            "notice": "【-】--答對,【#】--未作答,【?】--多選題",
+            "gradeErr": "成績信息錯誤!"
+        },
         "correctAnswer": "答對",
         "wrongAnswer": "答錯",
         "unAnswered": "未答",

+ 54 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/survey.js

@@ -0,0 +1,54 @@
+export default {
+	list: '問卷活動清單',
+	pending: '待發佈',
+	going: '進行中',
+	finish: '已結束',
+	surveyDetails: '問卷詳情',
+	undo: '撤銷發佈',
+	edit: '編輯問卷',
+	save: '保存問卷',
+	cancelEdit: '取消編輯',
+	surveyResult: '問卷數據',
+	addItem: '新增題目',
+	single: '單選題',
+	multiple: '复选题',
+	judge: '判斷題',
+	defaultName: '預設問卷名稱',
+	isExistTip: '已存在未保存的問卷活動!',
+	getDataFailTip: '獲取數據失敗!',
+	deletesurvey: '删除問卷',
+	deleteConfirmTip: '確認删除該問卷活動?',
+	cancelSurvey: '取消發佈',
+	cancelConfirmTip: '確認取消發佈該問卷活動?',
+	deleteSuc: '删除成功',
+	deleteFail: '删除失敗',
+	doSuc: '操作成功',
+	doFail: '操作失敗',
+	noData: '暫無數據',
+	form: {
+		name: '問卷名稱',
+		namePlace: '請輸入問卷名稱',
+		target: '問卷對象',
+		privateClass: '個人班級',
+		schoolClass: '校本班級',
+		targetPlace: '請選擇問卷發佈對象',
+		noFoundText: '暫未創建班級',
+		time: '起止時間',
+		endTimePlace: '請選擇問卷結束時間',
+		description: '問卷描述',
+		optionSetting: '選項設定',
+		addOption: '添加選項',
+		selectNum: '可問卷數',
+		other: '其他',
+		openSecret: '開啟匿名問卷',
+		save: '保存',
+		cancel: '取消',
+		editSuc: '修改成功',
+		addSuc: '添加成功',
+		noCompleteTip: '請將資訊填寫完整',
+		noOptionTip: '問卷選項個數不能為空!',
+		attachmentMaxTip: '最多只能上傳5個附件',
+		optionNumsTip: '最多只能有10個選項',
+		noMatchDataTip: '未匹配數據'
+	}
+}

+ 26 - 5
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/teachContent.js

@@ -1,10 +1,9 @@
 export default {
-  personalResource: '个人資源',
-  schoolResource: '校本資源',
-  filterAll: '全部',
+  filterRes: '教材',
   filterPicture: '圖片',
   filterVideo: '視頻',
-  filterDoc: '文件',
+  filterAudio: '音訊',
+  filterDoc: '檔案',
   filterOther: '其他',
   space: '空間:',
   tableC1: '檔案名稱',
@@ -30,5 +29,27 @@ export default {
   props5: '檔名修改成功!',
   props6: '文件上傳成功!',
   props7: '存儲空間不足!',
-  uploadText: '點擊或者拖拽上傳'
+  uploadText: '點擊或者拖拽上傳',
+
+  resTips: 'HiTeach生成的課件,不包含PPT等其他檔案',
+  space: '空間:',
+  calcing: '計算中…',
+  blobFull: '(已滿)',
+  otherType: '其他類型',
+  appData: '應用數據',
+  delBatch: '批量删除',
+  viewport1: '清單模式',
+  viewport2: '圖片模式',
+  bottomTips: '--------我也是有底線的--------',
+  notAudio: '您的瀏覽器不支持audio元素。',
+  nameOk: '名稱修改成功',
+  nameErr: '名稱修改失敗',
+  fullTips: '存儲空間已滿,無法上傳',
+  loadAll: '已經加載全部',
+  authErr: '獲取Blob授權失敗',
+  sizeErr: '空間計算异常',
+  delBatchTips1: '請在清單模式使用批量删除!',
+  delBatchTips2: '請先選擇需要删除的檔案!',
+  specialChart: '不能包含特殊字元',
+  noData: '暫無數據'
 }

+ 49 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/vote.js

@@ -0,0 +1,49 @@
+export default {
+	list: '投票活動清單',
+	pending: '待發佈',
+	going: '進行中',
+	finish: '已結束',
+	voteDetails: '投票詳情',
+	edit: '編輯',
+	voteResult: '投票數據',
+	optionView: '選項清單試圖',
+	stuListView: '學生清單視圖',
+	defaultName: '預設投票名稱',
+	isExistTip: '已存在未保存的投票活動!',
+	getDataFailTip: '獲取數據失敗!',
+	serverErrorTip: '投票活動介面調整中,請稍後再試!',
+	deleteVote: '删除投票',
+	deleteConfirmTip: '確認删除該投票活動?',
+	deleteSuc: '删除成功',
+	deleteFail: '删除失敗',
+	option: '選項',
+	noVote: '未投票',
+	noData: '暫無數據',
+	getClassDataFailTip: '獲取班級學生數據异常',
+	form: {
+		name: '投票名稱',
+		namePlace: '請輸入投票名稱',
+		target: '投票對象',
+		privateClass: '個人班級',
+		schoolClass: '校本班級',
+		targetPlace: '請選擇投票發佈對象',
+		noFoundText: '暫未創建班級',
+		time: '起止時間',
+		endTimePlace: '請選擇投票結束時間',
+		description: '投票描述',
+		optionSetting: '選項設定',
+		addOption: '添加選項',
+		selectNum: '可投票數',
+		other: '其他',
+		openSecret: '開啟匿名投票',
+		save: '保存',
+		cancel: '取消',
+		editSuc: '修改成功',
+		addSuc: '添加成功',
+		noCompleteTip: '請將資訊填寫完整',
+		noOptionTip: '投票選項個數不能為空!',
+		attachmentMaxTip: '最多只能上傳5個附件',
+		optionNumsTip: '最多只能有10個選項',
+		noMatchDataTip: '未匹配數據'
+	}
+}

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

@@ -265,7 +265,7 @@ export const routes = [
 			{
 				path: 'myCourse',
 				name: 'myCourse',
-				component: resolve => require(['@/view/newcourse/NewMyCourse.vue'], resolve)
+				component: resolve => require(['@/view/newcourse/MyCourse.vue'], resolve)
 			},
 			{
 				path: 'CourseTable',

+ 34 - 9
TEAMModelOS/ClientApp/src/store/index.js

@@ -12,7 +12,7 @@ import teachers from './module/teachers'
 import studentWeb from './module/studentWeb'
 import scboard from './module/scboard'
 import serviceDriveAuth from './module/serviceDriveAuth'
-import GLOBAL from '@/static/Global.js'
+import { GLOBAL } from '@/static/Global.js'
 import spaceAuth from './module/spaceAuth'
 import studentAclassOneAuth from './module/studentAclassOneAuth'
 
@@ -33,15 +33,40 @@ const mutations = {
     setSchoolSas(state, obj) {
         state.schoolSas = obj
     },
-    setUserInfo(state, obj) {
+    setUserInfo(state, obj) { //obj还是原来的逻辑传参,没有变化。只是这里内部逻辑添加了判断是否为班主任,班主任对应的班级id,以及授课班级id三个字段的处理逻辑
         obj.schoolCode = obj.schoolCode || GLOBAL.DEFAULT_SCHOOL_CODE
         obj.hasSchool = obj.schoolCode !== GLOBAL.DEFAULT_SCHOOL_CODE
-		state.userInfo = obj
-	},
-	setSchoolCode(state,obj){
-		state.userInfo.schoolCode = obj || GLOBAL.DEFAULT_SCHOOL_CODE
-		state.userInfo.hasSchool = obj !== GLOBAL.DEFAULT_SCHOOL_CODE
-	}
+        obj.isHeadmaster = false      //默认不是班主任
+        obj.mgtClasses = []           //班主任管理的班级id
+        obj.teachClasses = []         //授课班级id
+        let schoolStr = localStorage.getItem('school_profile')
+        if (schoolStr) {
+            schoolStr = decodeURIComponent(schoolStr, "utf-8")
+            let schoolJson = JSON.parse(schoolStr)
+            let mgtClasses = schoolJson.school_classes.filter(item=>{
+                return item.teacher.id == obj.TEAMModelId
+            })
+            if(mgtClasses.length){
+                obj.isHeadmaster = true //是班主任
+                obj.mgtClasses = mgtClasses.map(item=>{
+                    return item.id
+                })
+            }
+            if(schoolJson.school_courses){
+                schoolJson.school_courses.forEach(item => {
+                    let classIds = item.classes.map(classItem =>{
+                        return classItem.id
+                    })
+                    obj.teachClasses.push(...classIds)
+                })
+            }
+        }
+        state.userInfo = obj
+    },
+    setSchoolCode(state, obj) {
+        state.userInfo.schoolCode = obj || GLOBAL.DEFAULT_SCHOOL_CODE
+        state.userInfo.hasSchool = obj !== GLOBAL.DEFAULT_SCHOOL_CODE
+    }
 }
 
 // ACTIONS
@@ -64,7 +89,7 @@ export default new Vuex.Store({
             TEAMModelId: '',
             name: '',
             schoolCode: '',
-			hasSchool:false
+            hasSchool: false
         }
     },
     modules: {

+ 12 - 6
TEAMModelOS/ClientApp/src/store/module/totalAnalysis.js

@@ -77,6 +77,9 @@ export default {
 		
 		clearAnalysis(state,val){
 			state.analysisJson = val
+			state.subjectList = val
+			state.currentSubject = val
+			state.classExerciseData = val
 		},
 
         // 更新当前评测CODE
@@ -237,18 +240,21 @@ export default {
 			        r(context.state.analysisJson)
 			    } else {
 			        apiTools.totalAnalysis.getAnalysis(params).then(res => { // api请求
-			            context.commit('updateAnalysisJson', res) // 提交到vuex保存数据
-			            context.commit('updateSubjectList', res.subjects.map(i => i.name)) // 提交到vuex保存数据
-			            context.commit('updateCurSubject', res.subjects.map(i => i.name)[0]) // 提交到vuex保存数据
-			            console.log('API-analysisJson', res)
-			            r(res)
+						if(res.classes.length){
+							context.commit('updateAnalysisJson', res) // 提交到vuex保存数据
+							context.commit('updateSubjectList', res.subjects.map(i => i.name)) // 提交到vuex保存数据
+							context.commit('updateCurSubject', res.subjects.map(i => i.name)[0]) // 提交到vuex保存数据
+							console.log('API-analysisJson', res)
+							r(res)
+						}else{
+							j(500)
+						}
 			        }).catch(err => {
 			            j(err)
 			        })
 			    }
 			})
 		},
-		
 
         // 异步获取 评测列表数据
         getExamList(context) {

+ 235 - 32
TEAMModelOS/ClientApp/src/utils/blobTool.js

@@ -1,7 +1,7 @@
 import { GLOBAL } from '@/static/Global.js';
 import JsFn from '@/utils/js-fn.js';
+import API from '@/api/index.js';
 const { BlobServiceClient, BlobClient } = require("@azure/storage-blob")
-//const s = "?sv=2019-12-12&ss=b&srt=sco&st=2020-12-22T10%3A09%3A08Z&se=2020-12-23T10%3A09%3A08Z&sp=rwdxftlacup&sig=EHyNiA5uoBPHQAHfISLei%2BWdWnldHzOYYR7ZYdtFtRo%3D"
 //获取文件后缀和类型
 function getExAndType(fileName) {
     let ex = fileName.substring(fileName.lastIndexOf('.') + 1)
@@ -77,21 +77,128 @@ export default class BlobTool {
             )
         })
     }
+    /**
+     * 上传文件之前检查文件是否存在,如果存在返回文件大小
+     * @params {urls} 需要上传文件地址 相对地址
+     * @params {containerName} 容器名称,如果直接对象调用则必传;如果已经初始化BlobTool对象,则优先使用参数,没有则使用对象容器
+     */
+    static checkBlobs(urls, containerName) {
+        return new Promise((r, j) => {
+            if ((containerName || this.container) && urls.length) {
+                API.blob.checkBlobs({
+                    urls,
+                    containerName: containerName || this.container
+                }).then(
+                    res => {
+                        r(res.urlsSize)
+                    },
+                    err => {
+                        console.log(err)
+                    }
+                )
+            } else {
+                throw new Error("参数错误")
+            }
+        })
+    }
+
+    /**
+     * 上传文件之前检查文件夹的大小
+     * @params {prefix} 需要上传文件地址 相对地址
+     * @params {containerName} 容器名称,如果直接对象调用则必传;如果已经初始化BlobTool对象,则优先使用参数,没有则使用对象容器
+     * 
+     * @return size
+     */
+    static checkPrefixSize(prefix, containerName) {
+        return new Promise((r, j) => {
+            if (containerName && prefix) {
+                API.blob.checkBlobs({
+                    prefix,
+                    containerName,
+                    urls: []
+                }).then(
+                    res => {
+                        r(res.prefixSize)
+                    },
+                    err => {
+                        console.log(err)
+                    }
+                )
+            } else {
+                throw new Error("参数错误")
+            }
+        })
+    }
+
+    /**
+     * 更新Blob空间数据 
+     * @params {urls} checkBlobs方法返回的数据处理size后
+     * @params {containerName} 容器名称,如果直接对象调用则必传;如果已经初始化BlobTool对象,则优先使用参数,没有则使用初始化对象的容器
+     */
+    static updateSize(optUrls, containerName) {
+        return new Promise((r, j) => {
+            if ((containerName || this.container) && optUrls.length) {
+                API.blob.updateSize({
+                    optUrls,
+                    containerName: containerName || this.container
+                }).then(
+                    res => {
+                        r(res.urlsSize)
+                    },
+                    err => {
+                        console.log(err)
+                    }
+                )
+            } else {
+                throw new Error("参数错误")
+            }
+        })
+    }
+
+    /**
+     * 计算上传前后size
+     * @param {any}  before 上传之前的文件大小
+     * @param {any}  after 上传之后的文件大小
+     */
+    calcSize(before, after) {
+        if (before.length == after.length) {
+            let res = []
+            before.forEach((item, index) => {
+                let afterBlob = after.find((afterItem, afterIndex) => {
+                    return afterItem.url == item.url
+                })
+                if (after) {
+                    res.push({
+                        url: item.url,
+                        size: afterBlob.size - item.size
+                    })
+                } else {
+                    throw new Error(`未找到${item.url}上传之后的大小`)
+                }
+            })
+            return res
+        } else {
+            throw new Error('beforeSize/afterSize上传前后数据不对应')
+        }
+    }
+
     /**
      * 上传文件方法,带回调上传进度
      * @param {any} file 文件对象
      * @param {any} path 文件夹路径
      * @param {any} option 官方可配置项
-     * @param {any} checkSize 上传时是否检查容器空间 官方可配置项。默认需要检查大小
+     * @param {any} checkSize 上传时是否检查容器空间 默认需要检查大小
+     * @param {any} handleSize 是否需要更新size,建议多文件上传时在外部批量处理
      * @returns {object} {url, name,size,createTime,extension,type}
      */
-    upload(file, path, option, checkSize = true) {
+    upload(file, path, option, checkSize = true, handleSize = true) {
         return new Promise(async (r, j) => {
             //检查容器空间大小
             if (checkSize) {
                 if (!this.totalSize) {
                     try {
-                        this.totalSize = await this.getContainerSize()
+                        let sizeRes = await BlobTool.getSizeByServe(this.container)
+                        this.totalSize = sizeRes.size
                     } catch (e) {
                         this.totalSize = -1
                         j({
@@ -109,14 +216,35 @@ export default class BlobTool {
                     })
                 }
             }
+            let blobSize = 0
+            // 上传之前检查size
+            if (handleSize) {
+                let u = path + "/" + file.name
+                try {
+                    let checkRes = await BlobTool.checkBlobs([u], this.container)
+                    blobSize = checkRes && checkRes.urlsSize ? checkRes.urlsSize[0].size : 0
+                } catch (e) {
+                    throw new Error(e)
+                }
+            }
             const blockBlobClient = this.containerClient.getBlockBlobClient(path + "/" + file.name)
             blockBlobClient.uploadBrowserData(file, option).then(
                 res => {
                     let url = decodeURIComponent(res._response.request.url)
                     url = url.substring(0, url.lastIndexOf('?'))
                     let info = getExAndType(file.name)
+
+                    let updateSize = res._response.request.body.size - blobSize
+
                     if (this.totalSize) {
-                        this.totalSize += res._response.request.body.size
+                        this.totalSize += updateSize
+                    }
+                    // 上传成功后更新size
+                    if (handleSize) {
+                        BlobTool.updateSize([{
+                            url: path + "/" + file.name,
+                            size: updateSize
+                        }], this.container)
                     }
                     r({
                         url: url,
@@ -125,7 +253,8 @@ export default class BlobTool {
                         size: res._response.request.body.size,
                         createTime: res.lastModified.getTime(),
                         extension: info.ex,
-                        type: info.type
+                        type: info.type,
+                        updateSize: updateSize
                     })
                 },
                 err => {
@@ -149,7 +278,7 @@ export default class BlobTool {
                 let name = item.blob.substring(startIndex + 1, endIndex)
                 let nameIndex = names.indexOf(name)
                 if (nameIndex == -1) {
-                    fileItem.url = this.blobUrl+ '/' + this.container +'/res/' + name
+                    fileItem.url = this.blobUrl + '/' + this.container + '/res/' + name
                     fileItem.blob = `/res/${name}/index.json`
                     fileItem.name = name + '.HTEX'
                     fileItem.size = item.size
@@ -260,21 +389,30 @@ export default class BlobTool {
      * 删除Blob
      * @param {string} filePath 文件url + 容器 之后的路径
      */
-    deleteBlob(filePath) {
-        if (filePath) {
+    deleteBlob(filePath, size) {
+        console.log(typeof size)
+        console.log(typeof size == 'number')
+        if (filePath && typeof size == 'number') {
             filePath = filePath.substring(1)
+            return new Promise((r, j) => {
+                this.containerClient.deleteBlob(filePath).then(
+                    async res => {
+                        let sizeInfo = await BlobTool.updateSize([{
+                            url: filePath,
+                            size: -size
+                        }], this.container)
+                        r(sizeInfo)
+                    },
+                    err => {
+                        j(err)
+                    }
+                )
+
+            })
+        } else {
+            throw new Error("filePath或size参数不完整")
         }
-        return new Promise((r, j) => {
-            this.containerClient.deleteBlob(filePath).then(
-                res => {
-                    r(res)
-                },
-                err => {
-                    j(err)
-                }
-            )
 
-        })
     }
 
     /**
@@ -330,9 +468,9 @@ export default class BlobTool {
         return new Promise((r, j) => {
             let newBlob = this.containerClient.getBlobClient(targetUrl)
             let encodeUrl = encodeURI(sourceUrl)
-            // sas = "?sv=2020-02-10&st=2021-01-18T11%3A29%3A36Z&se=2021-01-19T11%3A59%3A36Z&sr=c&sp=rcwdl&sig=BXbsiVZ6ZogeWfyr3u2PgPVdeLlMMPOqvsh3%2BDEwnq4%3D"
             newBlob.beginCopyFromURL(encodeUrl + sas).then(
                 res => {
+                    console.log('复制成功返回数据', res)
                     r(200)
                 },
                 err => {
@@ -347,8 +485,9 @@ export default class BlobTool {
      * @param {string} targetFolder eg:'exam/评测id/paper/8b94c6b6-2572-41e5-89b9-a82fcf13891e/' 注意开头不加‘/’, 结尾需要加‘/’
      * @param {string} sourceFolder eg:'paper/JEFF組卷測試01'
      * @param {BlobTool} blobTool 非必传参数, 当目标文件和源文件不在同一个容器的时候,需要传源文件容器初始化的BlobTool
+     * @param {boolean} handleSize 是否内部处理blobsize
      */
-    copyFolder(targetFolder, sourceFolder, blobTool) {
+    copyFolder(targetFolder, sourceFolder, blobTool, handleSize = true) {
         return new Promise(async (r, j) => {
             let blobs = undefined
             let sasString = ''
@@ -363,15 +502,33 @@ export default class BlobTool {
                 }, false)
                 sasString = this.sasString
             }
+            //上传之前检查文件夹大小
+            let beforeSize = 0
+            let cont = ''
+            if (handleSize) {
+                cont = this.container
+                beforeSize = await BlobTool.checkPrefixSize(targetFolder.substring(0, targetFolder.length - 1), cont)
+            }
+
             if (blobs && blobs.blobList) {
                 let count = 0
                 blobs.blobList.forEach(blobItem => {
                     let newUrl = targetFolder + blobItem.name
                     let newBlob = this.containerClient.getBlobClient(newUrl)
                     let resourceUrl = encodeURI(blobItem.url)
-                    newBlob.beginCopyFromURL(resourceUrl + sasString).then().finally(() => {
-                        count++
-                        if (count == (blobs.blobList.length)) {
+                    newBlob.beginCopyFromURL(resourceUrl + sasString).then().finally(async () => {
+                        if (++count == (blobs.blobList.length)) {
+                            if (handleSize) {
+                                //复制之后更新大小
+                                let afterSize = await BlobTool.checkPrefixSize(targetFolder, cont)
+                                let updSize = afterSize - beforeSize
+                                if (updSize !== 0) {
+                                    BlobTool.updateSize([{
+                                        url: targetFolder,
+                                        size: updSize
+                                    }], cont)
+                                }
+                            }
                             r('复制结束后')
                         }
                     })
@@ -404,7 +561,7 @@ export default class BlobTool {
      */
     async getContainerSize() {
         return new Promise((r, j) => {
-            this.listBlob({},false).then(
+            this.listBlob({}, false).then(
                 res => {
                     let totalSize = res.blobList.reduce((total, item) => {
                         return total + parseInt(item.size)
@@ -427,12 +584,14 @@ export default class BlobTool {
     async isContainerFull() {
         return new Promise(async (r, j) => {
             try {
-                let size = await this.getContainerSize()
-                if (size > this.blobSpace) {
-                    r(true)
-                } else {
-                    r(false)
-                }
+                // let size = await this.getContainerSize()
+                let sizeRes = await BlobTool.getSizeByServe(this.container)
+                if (sizeRes)
+                    if (sizeRes.size > this.blobSpace) {
+                        r(true)
+                    } else {
+                        r(false)
+                    }
             } catch (e) {
                 j('容器空间判断失败!')
             }
@@ -447,7 +606,7 @@ export default class BlobTool {
      */
     getSize(option) {
         return new Promise((r, j) => {
-            this.listBlob(option,false).then(
+            this.listBlob(option, false).then(
                 res => {
                     let sizeInfo = {
                         total: 0,   //所有
@@ -497,4 +656,48 @@ export default class BlobTool {
             )
         })
     }
+    /**
+     * 从后端获取blob空间大小
+     * @params {containerName} 容器名称
+     * @params {cache} 是否从缓存读取
+     */
+    static getSizeByServe(containerName, cache = true) {
+        return new Promise((r, j) => {
+            if (containerName) {
+                API.blob.getContainerSize({
+                    cache,
+                    containerName
+                }).then(
+                    res => {
+                        let sizeInfo = {
+                            total: 0,   //所有
+                            image: 0,   //内容模块图片
+                            res: 0,     //内容模块教材
+                            video: 0,   //内容模块视频
+                            audio: 0,   //内容模块音频
+                            doc: 0,     //内容模块文档
+                            other: 0,   //内容模块其他文件
+                            data: 0      //除了内容模块之外的文件
+                        }
+                        sizeInfo.total = res.size
+                        let defined = ['image', 'res', 'video', 'audio', 'doc', 'other']
+                        for (let key in res.catalog) {
+                            if (defined.indexOf(key) > -1) {
+                                sizeInfo[key] = res.catalog[key]
+                            } else {
+                                sizeInfo.data += res.catalog[key]
+                            }
+                        }
+                        r(sizeInfo)
+                    },
+                    err => {
+                        console.log(err)
+                    }
+                )
+            } else {
+                throw new Error("containerName 不正确")
+            }
+        })
+    }
+
 }

+ 2 - 1
TEAMModelOS/ClientApp/src/utils/editorTools.js

@@ -442,7 +442,7 @@ export default {
 	
 	/* 移除上传的音视频富文本src中的HOST */
 	doRemoveMideaHost(exerciseItem) {
-		console.log(exerciseItem)
+		console.log('removeHost',exerciseItem.question)
 		let isSubjective = exerciseItem.type === 'complete' || exerciseItem.type === 'subjective' || exerciseItem.type ===
 			'compose'
 		let richTextObj = {
@@ -477,6 +477,7 @@ export default {
 				}))
 			}
 			Promise.all(promiseArr).then(result => {
+				console.log('removeHost2',exerciseItem.question)
 				resolve(exerciseItem)
 			})
 		})

+ 31 - 25
TEAMModelOS/ClientApp/src/utils/evTools.js

@@ -36,7 +36,7 @@ export default {
 					subjectId:item.subjectId,
 					children:item.children || [],
 					scope:item.scope,
-					score: 0,
+					score: item.score||0,
 					repair:item.repair,
 				},
 				render:2,
@@ -57,7 +57,7 @@ export default {
 				pid:item.pid || null,
 				code:item.code,
 				scope:item.scope,
-				score:0,
+				score:item.score||0,
 				type:item.type,
 				question:this.getSimpleText(item.question),
 				knowledge:item.knowledge,
@@ -117,7 +117,8 @@ export default {
 	},
 	
 	/* 获取完整的试题数据 */
-	getFullItem(list){
+	getFullItem(list,examScope){
+		console.log('接受到的examScope',examScope)
 		return new Promise(async (resolve,reject) => {
 			if(list.length === 0) return
 			let promiseArr = []
@@ -126,9 +127,10 @@ export default {
 			for(let i=0; i<list.length; i++){
 				promiseArr.push(new Promise(async (r,j) => {
 					if(list[i].blob){
-						const blobHost = list[i].scope === 'school' ?  JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri :  JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+						let curScope = list[i].scope
+						const blobHost = curScope === 'school' ?  JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri :  JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
 						// 根据试题的Blob地址 去读取JSON文件
-						let sasString = list[i].scope === 'school' ?  await $tools.getSchoolSas() : await $tools.getPrivateSas()
+						let sasString = curScope === 'school' ?  await $tools.getSchoolSas() : await $tools.getPrivateSas()
 						let jsonInfo = list[i].blob.includes('https://') ? await $tools.getFile(list[i].blob + sasString.sas) : await $tools.getFile(blobHost + list[i].blob + sasString.sas)
 						let jsonData = JSON.parse(jsonInfo)
 						// 如果是综合题 那就拿到children里面的小题id集合 去换取小题的blobJSON文件 然后替换children的内容
@@ -163,9 +165,10 @@ export default {
 	},
 	
 	/* 给富文本添加 */
-	doAddHost(exerciseItem,paperName,examItem){
-		//console.log('addHost接收的examItem',examItem)
-		let container = exerciseItem.scope === 'school' ? store.state.userInfo.schoolCode : store.state.userInfo.TEAMModelId
+	doAddHost(exerciseItem,paperItem){
+		/* 如果操作的是试卷内的试题 则需要拿试卷的code来作为containerName */
+		let curScope = paperItem ? paperItem.scope : exerciseItem.scope
+		let container = curScope === 'school' ? store.state.userInfo.schoolCode : store.state.userInfo.TEAMModelId
 		let isSubjective = exerciseItem.type === 'complete' || exerciseItem.type === 'subjective' || exerciseItem.type === 'compose'
 		let richTextObj = {
 			question: exerciseItem.question,
@@ -186,17 +189,17 @@ export default {
 						for(let i = 0;i<srcList.length;i++){
 							let src = srcList[i]
 							/* 如果是来自试卷的题目 则需要匹配试卷HOST */
-							if(examItem){
-								let examContainer = examItem.examId ?  (examItem.scope === 'school' ? store.state.userInfo.schoolCode : store.state.userInfo.TEAMModelId) : examItem.code
-								let blobUrl = this.getBlobHost() +  '/' + examContainer  + examItem.blob +  '/' + src
+							if(paperItem && paperItem.examId){
+								let examContainer = paperItem.examId
+								let blobUrl = this.getBlobHost() +  '/' + examContainer  + paperItem.blob +  '/' + src
 								try{
 									let addSasUrl = await $tools.getFileSas(blobUrl)
 									richTextObj[key] = richTextObj[key].replace(`src="${ src }"`, `src="${ addSasUrl.url }"`);
 								}catch(e){
 									j(500)
 								}
-							}else if(paperName){
-								let blobUrl = this.getBlobHost() +  '/'  + container + '/paper/' + paperName + '/' + src
+							}else if(paperItem){
+								let blobUrl = this.getBlobHost() +  '/'  + container + '/paper/' + paperItem.name + '/' + src
 								try{
 									let addSasUrl = await $tools.getFileSas(blobUrl)
 									richTextObj[key] = richTextObj[key].replace(`src="${ src }"`, `src="${ addSasUrl.url }"`);
@@ -307,13 +310,14 @@ export default {
 	},
 	
 	/* 获取完整的试卷数据 */
-	getFullPaper(paper){
+	getFullPaper(paper,examScope){
 		console.log('evTools换取试卷' , paper)
+		let curScope = examScope || paper.examScope || paper.scope
+		console.log(curScope)
 		return new Promise(async (r,j) => {
-			let blobHost = paper.scope === 'school' ?  JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri :  JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+			let blobHost = curScope === 'school' ?  JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri :  JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
 			// 根据试卷的Blob地址 去读取JSON文件
-			let sasString = paper.scope === 'school' ?  await $tools.getSchoolSas() : await $tools.getPrivateSas()
-			
+			let sasString = curScope === 'school' ?  await $tools.getSchoolSas() : await $tools.getPrivateSas()
 			try{
 				let jsonInfo = await $tools.getFile(blobHost + paper.blob + '/index.json' + sasString.sas)
 				let jsonData = JSON.parse(jsonInfo)
@@ -335,11 +339,12 @@ export default {
 								itemJson.exercise.blob = path + '/' + item.url // 添加blob是方便在保存试卷是 refresh 与导入的试题区分开
 								itemJson.exercise.score = item.scoring ? item.scoring.score : 0
 								try{
-									if(paper.examId){
-										itemJson.exercise = await this.doAddHost(itemJson.exercise,paper.name,paper) // 检测试题中的富文本 为有src为相对路径的音视频文件添加blob的HOST地址
-									}else{
-										itemJson.exercise = await this.doAddHost(itemJson.exercise,paper.name) // 检测试题中的富文本 为有src为相对路径的音视频文件添加blob的HOST地址
-									}
+									// if(paper.examId){
+									// 	itemJson.exercise = await this.doAddHost(itemJson.exercise,paper) // 检测试题中的富文本 为有src为相对路径的音视频文件添加blob的HOST地址
+									// }else{
+									// 	itemJson.exercise = await this.doAddHost(itemJson.exercise,paper) // 检测试题中的富文本 为有src为相对路径的音视频文件添加blob的HOST地址
+									// }
+									itemJson.exercise = await this.doAddHost(itemJson.exercise,paper) // 检测试题中的富文本 为有src为相对路径的音视频文件添加blob的HOST地址
 									resolve(itemJson.exercise)
 								}catch(e){
 									reject(e)
@@ -375,13 +380,14 @@ export default {
 	
 	
 	/* 获取完整的试卷数据 */
-	getStuPaper(paper){
+	getStuPaper(paper,examScope){
 		//console.log('evTools换取学生试卷' , paper)
+		let curScope = examScope || paper.scope
 		return new Promise(async (r,j) => {
 			let blobHost = JSON.parse(decodeURIComponent(localStorage.student_profile, "utf-8")).blob_uri
 			let splitHost = blobHost.split('/')
 			// 根据试卷的Blob地址 去读取JSON文件
-			let sasString = paper.scope === 'school' ?  await $tools.getSchoolSas(paper.code) : await $tools.getPrivateSas(paper.code)
+			let sasString = curScope === 'school' ?  await $tools.getSchoolSas(paper.code) : await $tools.getPrivateSas(paper.code)
 			try {
 				let jsonInfo = await $tools.getFile(sasString.url + '/' + paper.code + paper.blob + '/index.json' + sasString.sas)
 				let jsonData = JSON.parse(jsonInfo)
@@ -401,7 +407,7 @@ export default {
 							itemJson.exercise.score = item.scoring ? item.scoring.score : 0
 							// jsonData.item.push(itemJson.exercise)
 							try{
-								itemJson.exercise = await this.doAddHost(itemJson.exercise,paper.name,paper)
+								itemJson.exercise = await this.doAddHost(itemJson.exercise,paper)
 								resolve(itemJson.exercise)
 							}catch(e){
 								reject(e)

+ 6 - 6
TEAMModelOS/ClientApp/src/utils/public.js

@@ -288,7 +288,7 @@ export default {
 			switch (param.type) {
 				case 'file': //单文件授权
 					if (!store.state.fileSas || checkSas(store.state.fileSas.timeout)) {
-						$api.uploadFile.urlSasR({
+						$api.blob.urlSasR({
 							params: param.data
 						}).then(
 							(res) => {
@@ -308,7 +308,7 @@ export default {
 					break
 				case 'blobR': //容器只读权限
 					if (!store.state.blobR || checkSas(store.state.blobR.timeout)) {
-						$api.uploadFile.blobSasR(param.data).then(
+						$api.blob.blobSasR(param.data).then(
 							(res) => {
 								if (res.error == null) {
 									//store.commit('setBlobR')
@@ -327,7 +327,7 @@ export default {
 					break
 				case 'blobRW':
 					if (!store.state.blobRW || checkSas(store.state.blobRW.timeout)) {
-						$api.uploadFile.blobSasRCW(param.data).then(
+						$api.blob.blobSasRCW(param.data).then(
 							(res) => {
 								if (res.error == null) {
 									store.commit('setBlobRW', res.result.data)
@@ -357,7 +357,7 @@ export default {
 	getPrivateSas(code) {
 		return new Promise((r, j) => {
 			if (!store.state.privateSas || checkSas(store.state.privateSas.timeout) || store.state.privateSas.name !== store.state.userInfo.TEAMModelId) {
-				$api.uploadFile.blobSasRCW({
+				$api.blob.blobSasRCW({
 					name: code || store.state.userInfo.TEAMModelId,
 					role: 'teacher'
 				}).then(
@@ -385,7 +385,7 @@ export default {
 	getSchoolSas(code) {
 		return new Promise((r, j) => {
 			if (!store.state.schoolSas || checkSas(store.state.schoolSas.timeout)  || store.state.schoolSas.name !== store.state.userInfo.schoolCode) {
-				$api.uploadFile.blobSasRCW({
+				$api.blob.blobSasRCW({
 					name: code || store.state.userInfo.schoolCode,
 					role: 'school'
 				}).then(
@@ -412,7 +412,7 @@ export default {
 	 */
 	getFileSas(param) {
 		return new Promise((r, j) => {
-			$api.uploadFile.urlSasR({
+			$api.blob.urlSasR({
 				url: param
 			}).then(
 				(res) => {

+ 392 - 398
TEAMModelOS/ClientApp/src/view/classmgt/ManageClass.vue

@@ -1,47 +1,47 @@
 <template>
     <div class="mgt-class-container dark-iview-select dark-iview-checkbox custom-scroll-bar common-save-btn" @click="nameEdStatus = true">
         <div class="mgt-class-header">
-            <span class="text-label">班级:</span>
+            <span class="text-label">{{$t('cusMgt.classLabel')}}</span>
             <Select v-model="curClassCode" style="width:200px">
                 <Option v-for="(item,index) in classList" :value="item.id" :key="index" @click.native="selectClass(index)">{{ item.name }}</Option>
             </Select>
             <span v-if="classList[curClassIndex]" style="margin-left: 10px;color: #aaaaaa;">
-                学生人数:{{classList[curClassIndex].students.length}}
+                {{$t('cusMgt.stuCount')}}{{classList[curClassIndex].students.length}}
             </span>
             <Button :disabled="!updated" size="small" style="float:right;margin-right:30px;margin-top:10px;" @click="saveGroup" icon="ios-albums-outline">保存分组</Button>
             <span :class="classList.length > 0 ? 'action-btn-wrap': 'disable-text-icon action-btn-wrap'" @click="customGroupStatus = true">
                 <Icon type="md-shuffle" size="14" />
-                自动分组
+                {{$t('cusMgt.autoGroup')}}
             </span>
             <span :class="classList.length > 0 ? 'action-btn-wrap': 'disable-text-icon action-btn-wrap'" @click="exportStudents">
                 <Icon type="md-arrow-round-down" size="16" />
-                导出名单
+                {{$t('cusMgt.exportStu')}}
             </span>
             <span :class="classList.length > 0 ? 'action-btn-wrap': 'disable-text-icon action-btn-wrap'" @click="toggleView()">
                 <Icon :type="viewType ? 'md-card':'md-list'" size="16" />
-                {{viewType ? '分组视图':'列表视图' }}
+                {{viewType ? $t('cusMgt.viewport1'):$t('cusMgt.viewport2') }}
             </span>
         </div>
         <div class="mgt-class-body dark-iview-table animated fadeIn" id="table-wrap" v-if="viewType == 1">
             <vuescroll>
-                <Table :columns="columns" v-if="classList[curClassIndex]" :data="classList[curClassIndex].students" :loading="tableLoading" ref="students" :height="tableHeight" no-data-text="暂无学生">
+                <Table :columns="columns" v-if="classList[curClassIndex]" :data="classList[curClassIndex].students" :loading="tableLoading" ref="students" :height="tableHeight" :no-data-text="$t('cusMgt.noStu')">
                     <Loading slot="loading" bgColor="rgba(103, 103, 103, 0.27)"></Loading>
                     <template slot-scope="{ row,index }" slot="header">
                         <span class="name-header" :style="{background:bgColor[index % 12]}">{{getFirstChart(row.name)}}</span>
                     </template>
                     <template slot-scope="{ row,index }" slot="action">
                         <div class="item-tools">
-                            <Icon type="md-refresh" size="18" style="cursor:pointer;" title="重置密码" @click="resetPassword(index)"/>
+                            <Icon type="md-refresh" size="18" style="cursor:pointer;" :title="$t('cusMgt.resetPw')" @click="resetPassword(index)" />
                         </div>
                     </template>
                     <template slot-scope="{ row, index }" slot="groupId">
                         <span>{{row.groupId ? row.groupId : '- -'}}</span>
                     </template>
                     <template slot-scope="{ row, index }" slot="groupName">
-                        <span>{{row.groupName ? row.groupName : '未分组'}}</span>
+                        <span>{{row.groupName ? row.groupName : $t('cusMgt.noGroup')}}</span>
                     </template>
                 </Table>
-                <EmptyData v-else textContent="暂无您管理的班级" :top="150"></EmptyData>
+                <EmptyData v-else :textContent="$t('cusMgt.noMgtClass')" :top="150"></EmptyData>
             </vuescroll>
         </div>
         <div class="mgt-class-body dark-iview-table animated fadeIn dark-iview-input disabled-iview-input" id="table-wrap" v-else>
@@ -49,8 +49,7 @@
                 <div class="group-wrap-item" v-for="(item,index) in groupData" :key="index">
                     <div class="group-title-wrap">
                         <span class="group-num-tag">{{parseInt(item.groupId)}}</span>
-                        <Input v-model="item.groupName" class="group-name-tag" placeholder="组名..." 
-                               :disabled="nameEdStatus" @click.native.stop="nameEdStatus = false" title="修改组名" @on-change="$jsFn.debounce(setGroupName,1000)(index)"/>
+                        <Input v-model="item.groupName" class="group-name-tag" :placeholder="$t('cusMgt.groupNameHolder')" :disabled="nameEdStatus" @click.native.stop="nameEdStatus = false" :title="$t('cusMgt.edtiGroupName')" @on-change="$jsFn.debounce(setGroupName,1000)(index)" />
                         <Icon type="md-close" class="close-group-icon" @click="delGroup(index)" />
                     </div>
                     <Draggable ghost-class="ghost" group="student" class="list-group" :list="item.students" :animation='200' @end="groupToList">
@@ -58,37 +57,34 @@
                             <span>{{stuItem.no+ '-' +stuItem.name}}</span>
                             <span>{{'('+stuItem.id+')'}}</span>
                         </div>
-                        <EmptyData textContent="暂无学生" v-if="item.students.length == 0"></EmptyData>
+                        <EmptyData :textContent="$t('cusMgt.noStu')" v-if="item.students.length == 0"></EmptyData>
                     </Draggable>
                 </div>
                 <div id="add-group-box">
-                    <Input v-model="groupName" placeholder="输入组名......" style="width: 200px;margin:40px 50px 10px 50px;" />
+                    <Input v-model="groupName" :placeholder="$t('cusMgt.groupNameHolder')" style="width: 200px;margin:40px 50px 10px 50px;" />
                     <Icon type="md-add-circle" class="add-group-icon" @click="addGroup" />
-                    <p class="add-group-label" @click="addGroup">新增组别</p>
+                    <p class="add-group-label" @click="addGroup">{{$t('cusMgt.addGroup')}}</p>
                 </div>
             </vuescroll>
         </div>
-        <Modal v-model="customGroupStatus"
-               :title="$t('courseManage.classroom.autoGroupBtn')"
-               @on-ok="comfirmCustomRules" class="custom-group"
-               class-name="dark-iview-modal dark-iview-form">
+        <Modal v-model="customGroupStatus" :title="$t('cusMgt.autoGroup')" @on-ok="comfirmCustomRules" class="custom-group" class-name="dark-iview-modal dark-iview-form">
             <Form :label-width="80" :label-colon="true" style="color:white;">
-                <FormItem :label="$t('courseManage.classroom.studentCountLabel')">
+                <FormItem :label="$t('cusMgt.studentCountLabel')">
                     <span v-if="customGroupStatus">{{classList[curClassIndex].students.length}}人</span>
                 </FormItem>
-                <FormItem :label="$t('courseManage.classroom.groupCountLabel')">
+                <FormItem :label="$t('cusMgt.groupCountLabel')">
                     <InputNumber :max="10" :min="1" v-model="groupNum"></InputNumber>
                 </FormItem>
-                <FormItem :label="$t('courseManage.classroom.groupTypeLabel')">
+                <FormItem :label="$t('cusMgt.groupTypeLabel')">
                     <RadioGroup v-model="groupType">
                         <Radio label="1">
-                            <span>{{$t('courseManage.classroom.groupType1')}}</span>
+                            <span>{{$t('cusMgt.groupType1')}}</span>
                         </Radio>
                         <Radio label="2">
-                            <span>{{$t('courseManage.classroom.groupType2')}}</span>
+                            <span>{{$t('cusMgt.groupType2')}}</span>
                         </Radio>
                         <Radio label="3">
-                            <span>{{$t('courseManage.classroom.groupType3')}}</span>
+                            <span>{{$t('cusMgt.groupType3')}}</span>
                         </Radio>
                     </RadioGroup>
                 </FormItem>
@@ -97,413 +93,411 @@
     </div>
 </template>
 <script>
-    import Draggable from 'vuedraggable'
-    import excel from '@/utils/excel.js'
-    export default {
-        components: {
-            Draggable
-        },
-        data() {
-            return {
-                curClassIndex:0,
-                groupName:'',
-                groupData: [],
-                tableHeight:0,
-                nameEdStatus: true,
-                viewType: 1,
-                tableLoading: false,
-                customGroupStatus: false,
-                updated: false,
-                tableLoading: false,
-                classList: [],
-                groupNum: 2,
-                groupType: '1',
-                bgColor: ['#4ECDC4', '#F99406', '#075CD0', '#F7CA17', '#F2774B', '#67809F', '#BF55ED', '#00B169', '#F72459', '#F15A22', '#03C9A8', '#9A1294'],
-                curClassCode: '',
-                columns: [
-                    {
-                        type: 'selection',
-                        width: 80,
-                        align: 'center'
-                    },
-                    {
-                        slot: 'header',
-                        title: ' ',
-                        align: 'center',
-                        //width:120
-                    },
-                    {
-                        key: 'name',
-                        title: this.$t('stuAccount.stuName'),
-                        align: 'center',
-                        //width:200
-                    },
-                    {
-                        key: 'id',
-                        title: this.$t('stuAccount.account'),
-                        align: 'center',
-                        //width:200
-                    },
-                    {
-                        key: 'no',
-                        title: this.$t('stuAccount.seatNo'),
-                        align: 'center',
-                        sortable: true,
-                        sortMethod: function (a, b, type) {
-                            if (type == 'asc') {
-                                return parseInt(a) > parseInt(b) ? 1 : -1
-                            } else if (type == 'desc') {
-                                return parseInt(a) > parseInt(b) ? -1 : 1
-                            }
+import Draggable from 'vuedraggable'
+import excel from '@/utils/excel.js'
+export default {
+    components: {
+        Draggable
+    },
+    data() {
+        return {
+            curClassIndex: 0,
+            groupName: '',
+            groupData: [],
+            tableHeight: 0,
+            nameEdStatus: true,
+            viewType: 1,
+            tableLoading: false,
+            customGroupStatus: false,
+            updated: false,
+            tableLoading: false,
+            classList: [],
+            groupNum: 2,
+            groupType: '1',
+            bgColor: ['#4ECDC4', '#F99406', '#075CD0', '#F7CA17', '#F2774B', '#67809F', '#BF55ED', '#00B169', '#F72459', '#F15A22', '#03C9A8', '#9A1294'],
+            curClassCode: '',
+            columns: [
+                {
+                    type: 'selection',
+                    width: 80,
+                    align: 'center'
+                },
+                {
+                    slot: 'header',
+                    title: ' ',
+                    align: 'center',
+                    //width:120
+                },
+                {
+                    key: 'name',
+                    title: this.$t('stuAccount.stuName'),
+                    align: 'center',
+                    //width:200
+                },
+                {
+                    key: 'id',
+                    title: this.$t('stuAccount.account'),
+                    align: 'center',
+                    //width:200
+                },
+                {
+                    key: 'no',
+                    title: this.$t('stuAccount.seatNo'),
+                    align: 'center',
+                    sortable: true,
+                    sortMethod: function (a, b, type) {
+                        if (type == 'asc') {
+                            return parseInt(a) > parseInt(b) ? 1 : -1
+                        } else if (type == 'desc') {
+                            return parseInt(a) > parseInt(b) ? -1 : 1
                         }
-                    },
-                    {
-                        slot: 'groupId',
-                        title: '组别',
-                        key:'groupId',
-                        align: 'center',
-                        sortable: true,
-                        sortMethod: function (a, b, type) {
-                            if (type == 'asc') {
-                                return parseInt(a) > parseInt(b) ? 1 : -1
-                            } else if (type == 'desc') {
-                                return parseInt(a) > parseInt(b) ? -1 : 1
-                            }
+                    }
+                },
+                {
+                    slot: 'groupId',
+                    title: this.$t('cusMgt.stuClassCol4'),
+                    key: 'groupId',
+                    align: 'center',
+                    sortable: true,
+                    sortMethod: function (a, b, type) {
+                        if (type == 'asc') {
+                            return parseInt(a) > parseInt(b) ? 1 : -1
+                        } else if (type == 'desc') {
+                            return parseInt(a) > parseInt(b) ? -1 : 1
                         }
-                    },
-                    {
-                        slot: 'groupName',
-                        title: '组名',
-                        key: 'groupName',
-                        align: 'center',
-                        sortable: true,
-                        //width: 80
-                    },
-                    {
-                        slot: 'action',
-                        title: '操作',
-                        align: 'center'
                     }
-                ]
+                },
+                {
+                    slot: 'groupName',
+                    title: this.$t('cusMgt.action'),
+                    key: 'groupName',
+                    align: 'center',
+                    sortable: true,
+                    //width: 80
+                },
+                {
+                    slot: 'action',
+                    title: this.$t('cusMgt.searchHolder'),
+                    align: 'center'
+                }
+            ]
+        }
+    },
+    methods: {
+        resetPassword(index) {
+            this.$Message.warning('暂未对接重置密码API')
+        },
+        //切换班级
+        selectClass(index) {
+            this.curClassIndex = index
+            if (this.viewType == 0) {
+                this.handelGroup()
             }
         },
-        methods: {
-            resetPassword(index) {
-                this.$Message.warning('暂未对接重置密码API')
-            },
-            //切换班级
-            selectClass(index) {
-                this.curClassIndex = index
-                if (this.viewType == 0) {
-                    this.handelGroup()
-                    console.log('123')
-                }
-            },
-            //设置组名
-            setGroupName(index) {
-                console.log('执行函数')
-                for (let i in this.classList[this.curClassIndex].students) {
-                    if (this.classList[this.curClassIndex].students[i].groupId == this.groupData[index].groupId) {
-                        this.classList[this.curClassIndex].students[i].groupName = this.groupData[index].groupName
-                        console.log(this.groupData[index].groupName)
-                        console.log(this.classList[this.curClassIndex].students[i])
-                    }
-                }
-                this.updated = true
-            },
-            //分组视图修改后对应调整表格视图
-            groupToList() {
-                this.classList[this.curClassIndex].students = []
-                for (let i in this.groupData) {
-                    for (let j in this.groupData[i].students) {
-                        this.groupData[i].students[j].groupId = this.groupData[i].groupId
-                        this.groupData[i].students[j].groupName = this.groupData[i].groupName
-                        this.classList[this.curClassIndex].students.push(this.groupData[i].students[j])
-                    }
-                }
-                console.log(this.groupData)
-                this.updated = true
-            },
-            //删除组别
-            delGroup(index) {
-                if (this.groupData.length > 1) {
-                    let students = JSON.parse(JSON.stringify(this.groupData[index].students))
-                    for (let i in students) {
-                        students[i].groupId = this.groupData[0].groupId
-                        students[i].groupName = this.groupData[0].groupName
-                    }
-                    this.groupData.splice(index, 1)
-                    this.groupData[0].students.push(...students)
-                    this.groupToList()
-                } else {
-                    this.$Message.warning('至少保留一组')
+        //设置组名
+        setGroupName(index) {
+            for (let i in this.classList[this.curClassIndex].students) {
+                if (this.classList[this.curClassIndex].students[i].groupId == this.groupData[index].groupId) {
+                    this.classList[this.curClassIndex].students[i].groupName = this.groupData[index].groupName
                 }
-                console.log(this.groupData)
-                this.updated = true
-            },
-            //新增分组
-            addGroup() {
-                if (this.groupName == '') {
-                    this.$Message.warning('请先输入组名再创建组别')
-                } else if (this.classList[this.curClassIndex].students.length == 0) {
-                    this.$Message.warning('暂无学生可以进行分组')
-                } else {
-                    this.groupData.push({
-                        groupId: (this.groupData.length + 1) + '',
-                        groupName: this.groupName,
-                        students: []
-                    })
-                    this.groupName = ''
-                }
-                this.updated = true
-            },
-            toggleView() {
-                this.viewType = 1 - this.viewType
-                if (!this.viewType) {
-                    this.handelGroup()
+            }
+            this.updated = true
+        },
+        //分组视图修改后对应调整表格视图
+        groupToList() {
+            this.classList[this.curClassIndex].students = []
+            for (let i in this.groupData) {
+                for (let j in this.groupData[i].students) {
+                    this.groupData[i].students[j].groupId = this.groupData[i].groupId
+                    this.groupData[i].students[j].groupName = this.groupData[i].groupName
+                    this.classList[this.curClassIndex].students.push(this.groupData[i].students[j])
                 }
-                
-            },
-            //列表视图转分组视图
-            handelGroup() {
-                let groupRes = this.$jsFn.groupBy(this.classList[this.curClassIndex].students, 'groupId')
-                this.groupData.length = 0
-                for (let index in groupRes) {
-                    this.groupData.push({
-                        groupName: groupRes[index][0].groupName,
-                        groupId: groupRes[index][0].groupId,
-                        students: groupRes[index]
-                    })
+            }
+            this.updated = true
+        },
+        //删除组别
+        delGroup(index) {
+            if (this.groupData.length > 1) {
+                let students = JSON.parse(JSON.stringify(this.groupData[index].students))
+                for (let i in students) {
+                    students[i].groupId = this.groupData[0].groupId
+                    students[i].groupName = this.groupData[0].groupName
                 }
-                this.groupData.sort((a, b) => {
-                    return parseInt(a.groupId) - parseInt(b.groupId)
-                })
-                //this.updated = true
-            },
-            saveGroup() {
-                console.log('保存分组。。。')
-                console.log(this.classList[this.curClassIndex])
-                this.tableLoading = true
-                this.$api.schoolSetting.upsertGroup({
-                    classroom: this.classList[this.curClassIndex]
-                }).then(
-                    (res) => {
-                        if (!res.error) {
-                            this.$Message.success('保存成功!')
-                        } else {
-                            this.$Message.error('API error!')
-                        }
-                    },
-                    (err) => {
-                        this.$Message.error('API error!')
-                    }
-                ).finally(() => {
-                    setTimeout(() => {
-                        this.tableLoading = false
-                    }, 500)
+                this.groupData.splice(index, 1)
+                this.groupData[0].students.push(...students)
+                this.groupToList()
+            } else {
+                this.$Message.warning(this.$t('cusMgt.atLeast'))
+            }
+            this.updated = true
+        },
+        //新增分组
+        addGroup() {
+            if (this.groupName == '') {
+                this.$Message.warning(this.$t('cusMgt.checkName'))
+            } else if (this.classList[this.curClassIndex].students.length == 0) {
+                this.$Message.warning(this.$t('cusMgt.noStuTips'))
+            } else {
+                this.groupData.push({
+                    groupId: (this.groupData.length + 1) + '',
+                    groupName: this.groupName,
+                    students: []
                 })
+                this.groupName = ''
+            }
+            this.updated = true
+        },
+        toggleView() {
+            this.viewType = 1 - this.viewType
+            if (!this.viewType) {
+                this.handelGroup()
+            }
 
-            },
-            comfirmCustomRules() {
-                if (this.groupNum === 0) {
-                    this.$Message.warning('分组数量不能为0')
-                } else if (this.groupType == 0) {
-                    this.$Message.warning('请设置分组方式')
-                } else {
-                    switch (this.groupType) {
-                        case '1':
-                            this.randomGroup()
-                            break
-                        case '2':
-                            this.orderGroup()
-                            break
-                        case '3':
-                            this.orderGroupS()
-                            break
-                        default:
-                            break
-                    }
-                    this.tableLoading = true
-                    this.updated = true
-                    this.handelGroup()
-                }
-            },
-            orderGroupS() {
-                let stuLen = this.classList[this.curClassIndex].students.length
-                let surplus = stuLen % this.groupNum// 余数
-                let maxCount = surplus == 0 ? stuLen / this.groupNum : Math.ceil(stuLen / this.groupNum)// 每组最大人数
-                this.classList[this.curClassIndex].students = this.classList[this.curClassIndex].students.sort((a, b) => {
-                    a.seatNo > b.seatNo
-                })
-                for (let i = 0; i < maxCount; i++) {
-                    for (let j = 0; j < this.groupNum; j++) {
-                        let startIndex = this.groupNum * i
-                        if (startIndex + j < stuLen) {
-                            this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupId', i + 1)
-                            this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupName', '第'+ (i + 1)+ '组' )
-                        } else {
-                            break
-                        }
-                    }
-                }
-                setTimeout(() => {
-                    this.tableLoading = false
-                }, 500)
-            },
-            orderGroup() {
-                let stuLen = this.classList[this.curClassIndex].students.length
-                let surplus = stuLen % this.groupNum// 余数
-                let maxCount = surplus == 0 ? stuLen / this.groupNum : Math.ceil(stuLen / this.groupNum)// 每组最大人数
-                this.classList[this.curClassIndex].students = this.classList[this.curClassIndex].students.sort((a, b) => {
-                    a.no > b.no
+        },
+        //列表视图转分组视图
+        handelGroup() {
+            let groupRes = this.$jsFn.groupBy(this.classList[this.curClassIndex].students, 'groupId')
+            this.groupData.length = 0
+            for (let index in groupRes) {
+                this.groupData.push({
+                    groupName: groupRes[index][0].groupName,
+                    groupId: groupRes[index][0].groupId,
+                    students: groupRes[index]
                 })
-                let flag = 0
-                let startIndex = 0
-                for (let i = 0; i < this.groupNum; i++) {
-                    for (let j = 0; j < maxCount; j++) {
-                        if (startIndex + j < stuLen) {
-                            this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupId', i + 1)
-                            this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupName', '第' + (i + 1)+ '组')
-                        } else {
-                            break
-                        }
-                    }
-                    startIndex += maxCount
-                    flag++
-                    if (flag == surplus) {
-                        maxCount--
+            }
+            this.groupData.sort((a, b) => {
+                return parseInt(a.groupId) - parseInt(b.groupId)
+            })
+            //this.updated = true
+        },
+        saveGroup() {
+            this.tableLoading = true
+            this.$api.schoolSetting.upsertGroup({
+                classroom: this.classList[this.curClassIndex]
+            }).then(
+                (res) => {
+                    if (!res.error) {
+                        this.$Message.success(this.$t('cusMgt.saveOk'))
+                    } else {
+                        this.$Message.error('API error!')
                     }
+                },
+                (err) => {
+                    this.$Message.error('API error!')
                 }
+            ).finally(() => {
                 setTimeout(() => {
                     this.tableLoading = false
                 }, 500)
-            },
-            randomGroup() {
-                let stuLen = this.classList[this.curClassIndex].students.length
-                let surplus = stuLen % this.groupNum// 余数
-                let surplusCount = surplus// 余数
-                let maxCount = surplus == 0 ? stuLen / this.groupNum : Math.ceil(stuLen / this.groupNum)// 每组最大人数
-                let record = {}// 记录每个组已经分配的人数
-                for (let i = 1; i <= this.groupNum; i++) {
-                    record[i] = 0
+            })
+
+        },
+        comfirmCustomRules() {
+            if (this.groupNum === 0) {
+                this.$Message.warning(this.$t('cusMgt.groupCount'))
+            } else if (this.groupType == 0) {
+                this.$Message.warning(this.$t('cusMgt.groupTypeTips'))
+            } else {
+                switch (this.groupType) {
+                    case '1':
+                        this.randomGroup()
+                        break
+                    case '2':
+                        this.orderGroup()
+                        break
+                    case '3':
+                        this.orderGroupS()
+                        break
+                    default:
+                        break
                 }
-                let flag = true
-                for (let index in this.classList[this.curClassIndex].students) {
-                    let groupIndex = this.$jsFn.getBtwRandom(1, this.groupNum)
-                    if (record[groupIndex] < maxCount) {
-                        record[groupIndex] = record[groupIndex] + 1
+                this.tableLoading = true
+                this.updated = true
+                this.handelGroup()
+            }
+        },
+        orderGroupS() {
+            let stuLen = this.classList[this.curClassIndex].students.length
+            let surplus = stuLen % this.groupNum// 余数
+            let maxCount = surplus == 0 ? stuLen / this.groupNum : Math.ceil(stuLen / this.groupNum)// 每组最大人数
+            this.classList[this.curClassIndex].students = this.classList[this.curClassIndex].students.sort((a, b) => {
+                a.seatNo > b.seatNo
+            })
+            for (let i = 0; i < maxCount; i++) {
+                for (let j = 0; j < this.groupNum; j++) {
+                    let startIndex = this.groupNum * i
+                    if (startIndex + j < stuLen) {
+                        this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupId', i + 1)
+                        this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupName', (i + 1) + this.$t('cusMgt.groupUnit'))
                     } else {
-                        for (let key in record) {
-                            if (record[key] < maxCount) {
-                                record[key] = record[key] + 1
-                                groupIndex = key
-                                break
-                            }
-                        }
-                    }
-                    this.$set(this.classList[this.curClassIndex].students[index], 'groupId', groupIndex+'')
-                    this.$set(this.classList[this.curClassIndex].students[index], 'groupName', '第' + groupIndex + '组')
-                    if (record[groupIndex] == maxCount) {
-                        surplusCount--
+                        break
                     }
-                    if (surplusCount <= 0 && surplus > 0 && flag) {
-                        maxCount--
-                        flag = false
+                }
+            }
+            setTimeout(() => {
+                this.tableLoading = false
+            }, 500)
+        },
+        orderGroup() {
+            let stuLen = this.classList[this.curClassIndex].students.length
+            let surplus = stuLen % this.groupNum// 余数
+            let maxCount = surplus == 0 ? stuLen / this.groupNum : Math.ceil(stuLen / this.groupNum)// 每组最大人数
+            this.classList[this.curClassIndex].students = this.classList[this.curClassIndex].students.sort((a, b) => {
+                a.no > b.no
+            })
+            let flag = 0
+            let startIndex = 0
+            for (let i = 0; i < this.groupNum; i++) {
+                for (let j = 0; j < maxCount; j++) {
+                    if (startIndex + j < stuLen) {
+                        this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupId', i + 1)
+                        this.$set(this.classList[this.curClassIndex].students[startIndex + j], 'groupName', (i + 1) + this.$t('cusMgt.groupUnit'))
+                    } else {
+                        break
                     }
                 }
-                setTimeout(() => {
-                    this.tableLoading = false
-                }, 500)
-            },
-            getFirstChart(name) {
-                if (name) {
-                    return name.substr(0, 1)
+                startIndex += maxCount
+                flag++
+                if (flag == surplus) {
+                    maxCount--
+                }
+            }
+            setTimeout(() => {
+                this.tableLoading = false
+            }, 500)
+        },
+        randomGroup() {
+            let stuLen = this.classList[this.curClassIndex].students.length
+            let surplus = stuLen % this.groupNum// 余数
+            let surplusCount = surplus// 余数
+            let maxCount = surplus == 0 ? stuLen / this.groupNum : Math.ceil(stuLen / this.groupNum)// 每组最大人数
+            let record = {}// 记录每个组已经分配的人数
+            for (let i = 1; i <= this.groupNum; i++) {
+                record[i] = 0
+            }
+            let flag = true
+            for (let index in this.classList[this.curClassIndex].students) {
+                let groupIndex = this.$jsFn.getBtwRandom(1, this.groupNum)
+                if (record[groupIndex] < maxCount) {
+                    record[groupIndex] = record[groupIndex] + 1
                 } else {
-                    return '--'
+                    for (let key in record) {
+                        if (record[key] < maxCount) {
+                            record[key] = record[key] + 1
+                            groupIndex = key
+                            break
+                        }
+                    }
                 }
-            },
-            //导出名单
-            exportStudents() {
-                /*const params = {
-                    title: this.columns.map(i => i.title),
-                    key: this.columns.map(i => i.key),
-                    data: this.classList[this.curClassIndex].students,
-                    autoWidth: true,
-                    filename: '学生名单'
-                }*/
-                const params = {
-                    title: ['姓名','账号','座号','组别','组名'],
-                    key: ['name','id','no','groupId','groupName'],
-                    data: this.classList[this.curClassIndex].students,
-                    autoWidth: true,
-                    filename: '学生名单'
+                this.$set(this.classList[this.curClassIndex].students[index], 'groupId', groupIndex + '')
+                this.$set(this.classList[this.curClassIndex].students[index], 'groupName', groupIndex + this.$t('cusMgt.groupUnit'))
+                if (record[groupIndex] == maxCount) {
+                    surplusCount--
                 }
-                excel.export_array_to_excel(params)
-            },
-            //查询自己管理的班级
-            findClass() {
-                this.tableLoading = true
-                let params = {
-                    'school_code': this.$store.state.userInfo.schoolCode,
-                    'teacher.id': this.$store.state.userInfo.TEAMModelId,
-                    'scope':'school'
+                if (surplusCount <= 0 && surplus > 0 && flag) {
+                    maxCount--
+                    flag = false
                 }
-                this.$api.schoolSetting.getClassroomStudent(params).then(
-                    (res) => {
-                        if (!res.error) {
-                            this.classList = res.classrooms
-                            if (this.classList.length > 0) {
-                                this.curClassCode = this.classList[0].id
-                            }
-                        } else {
-                            this.$Message.error('API error!')
-                        }
-                    },
-                    (err) => {
-                        this.$Message.error('API error!')
-                    }
-                ).finally(() => {
-                    setTimeout(() => {
-                        this.tableLoading = false
-                    },500)
-                })
             }
+            setTimeout(() => {
+                this.tableLoading = false
+            }, 500)
         },
-        created() {
-            this.findClass()
-            this.$nextTick(() => {
-                let dom = document.getElementById('table-wrap')
-                this.tableHeight = dom.offsetHeight - 30
-            })
+        getFirstChart(name) {
+            if (name) {
+                return name.substr(0, 1)
+            } else {
+                return '--'
+            }
         },
-        mounted() {
-            
+        //导出名单
+        exportStudents() {
+            /*const params = {
+                title: this.columns.map(i => i.title),
+                key: this.columns.map(i => i.key),
+                data: this.classList[this.curClassIndex].students,
+                autoWidth: true,
+                filename: '学生名单'
+            }*/
+            const params = {
+                title: [
+                    this.$t('cusMgt.stuClassCol1'),
+                    this.$t('cusMgt.stuClassCol2'),
+                    this.$t('cusMgt.stuClassCol3'),
+                    this.$t('cusMgt.stuClassCol4'),
+                    this.$t('cusMgt.stuClassCol5')
+                ],
+                key: ['name', 'id', 'no', 'groupId', 'groupName'],
+                data: this.classList[this.curClassIndex].students,
+                autoWidth: true,
+                filename: this.$t('cusMgt.nameList')
+            }
+            excel.export_array_to_excel(params)
         },
-        computed: {
+        //查询自己管理的班级
+        findClass() {
+            this.tableLoading = true
+            let params = {
+                'school_code': this.$store.state.userInfo.schoolCode,
+                'teacher.id': this.$store.state.userInfo.TEAMModelId,
+                'scope': 'school'
+            }
+            this.$api.schoolSetting.getClassroomStudent(params).then(
+                (res) => {
+                    if (!res.error) {
+                        this.classList = res.classrooms
+                        if (this.classList.length > 0) {
+                            this.curClassCode = this.classList[0].id
+                        }
+                    } else {
+                        this.$Message.error('API error!')
+                    }
+                },
+                (err) => {
+                    this.$Message.error('API error!')
+                }
+            ).finally(() => {
+                setTimeout(() => {
+                    this.tableLoading = false
+                }, 500)
+            })
         }
+    },
+    created() {
+        this.findClass()
+        this.$nextTick(() => {
+            let dom = document.getElementById('table-wrap')
+            this.tableHeight = dom.offsetHeight - 30
+        })
+    },
+    mounted() {
+
+    },
+    computed: {
     }
+}
 </script>
 <style scoped lang="less">
-    @import "./ManageClass.less";
+@import "./ManageClass.less";
 </style>
 <style>
-    
-    .mgt-class-container #loadingBox {
-        margin-top: 0px !important;
-    }
-    .group-title-wrap .ivu-input[disabled] {
-        color: white;
-        vertical-align: super;
-        font-weight: 800;
-    }
-    .ghost {
-        opacity: 0;
-        background: #c8ebfb;
-    }
-    #add-group-box .ivu-input {
-        background:#2B2B2E;
-    }
-    .group-title-wrap .ivu-input[disabled], .group-title-wrap fieldset[disabled] .ivu-input {
-        cursor:pointer;
-    }
+.mgt-class-container #loadingBox {
+    margin-top: 0px !important;
+}
+.group-title-wrap .ivu-input[disabled] {
+    color: white;
+    vertical-align: super;
+    font-weight: 800;
+}
+.ghost {
+    opacity: 0;
+    background: #c8ebfb;
+}
+#add-group-box .ivu-input {
+    background: #2b2b2e;
+}
+.group-title-wrap .ivu-input[disabled],
+.group-title-wrap fieldset[disabled] .ivu-input {
+    cursor: pointer;
+}
 </style>

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

@@ -688,7 +688,7 @@
                 //} else {
                 //    this.$Message.error('获取Blob授权失败')
                 //}
-                this.$api.uploadFile.blobSasRCW({
+                this.$api.blob.blobSasRCW({
                     name: "1595321354",
                     role: 'teacher'
                 }).then(
@@ -705,7 +705,7 @@
                     }
                 )
 
-                this.$api.uploadFile.blobSasRCW({
+                this.$api.blob.blobSasRCW({
                     name: 'hbcn',
                     role: 'school'
                 }).then(

+ 33 - 37
TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue

@@ -193,8 +193,6 @@
 	</div>
 </template>
 <script>
-	import blobTool from "@/utils/blobTool.js";
-
 	export default {
 		data() {
 			return {
@@ -277,32 +275,32 @@
 			},
 
 			/* 获取BLOB所有试题LIST */
-			async getBlobOrigin() {
-				// 获取初始化Blob需要的数据
-				let sasData = this.isShowSchoolBank ?
-					await this.$tools.getSchoolSas() :
-					await this.$tools.getPrivateSas();
-				//初始化Blob
-				let containerClient = new blobTool(
-					sasData.url,
-					sasData.name,
-					sasData.sas,
-					this.isShowSchoolBank ? "school" : "private"
-				);
-				// 等待blob的返回结果
-				containerClient
-					.listBlob({
-						prefix: "paper",
-					})
-					.then(
-						(res) => {
-							console.log(res);
-						},
-						(err) => {
-							this.$Message.error("API Error");
-						}
-					);
-			},
+			// async getBlobOrigin() {
+			// 	// 获取初始化Blob需要的数据
+			// 	let sasData = this.isShowSchoolBank ?
+			// 		await this.$tools.getSchoolSas() :
+			// 		await this.$tools.getPrivateSas();
+			// 	//初始化Blob
+			// 	let containerClient = new blobTool(
+			// 		sasData.url,
+			// 		sasData.name,
+			// 		sasData.sas,
+			// 		this.isShowSchoolBank ? "school" : "private"
+			// 	);
+			// 	// 等待blob的返回结果
+			// 	containerClient
+			// 		.listBlob({
+			// 			prefix: "paper",
+			// 		})
+			// 		.then(
+			// 			(res) => {
+			// 				console.log(res);
+			// 			},
+			// 			(err) => {
+			// 				this.$Message.error("API Error");
+			// 			}
+			// 		);
+			// },
 
 			/** 执行筛选条件获取数据 */
 			doFilter() {
@@ -338,16 +336,14 @@
 					if(res.items.length){
 						let list = res.items;
 						/* 获取试题总数 */
-						this.totalNum = res.items.length || 0;
-						/* 查找当前页面所有知识点ID换名称 */
-						// this.getPointsByIds(this.getPointIds(list)).then(res => {
-						// 	this.allPointList = res
-						// })
-						// this.exerciseList = list;
+						this.totalNum = res.items.length;
 						this.originData = list;
 						this.pageChange(1);
 					}else{
-						this.$message.warning('获取试题为空')
+						this.originData = []
+						this.exerciseList = []
+						this.totalNum = 0
+						this.$Message.warning('获取试题为空')
 					}
 					setTimeout(() => {
 						that.dataLoading = false;
@@ -642,7 +638,7 @@
 						let deleteIds = [item.id,...item.children.map(i => i.id)]
 						deleteIds.forEach(id => {
 							promiseArr.push(new Promise((r,j) => {
-								this.$api.uploadFile.deletePrefix({
+								this.$api.blob.deletePrefix({
 									"cntr": item.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
 									"prefix": "item/" + id
 								}).then(
@@ -665,7 +661,7 @@
 							reject(err)
 						})
 					}else{
-						this.$api.uploadFile.deletePrefix({
+						this.$api.blob.deletePrefix({
 							"cntr": item.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
 							"prefix": "item/" + item.id
 						}).then(

+ 1 - 2
TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue

@@ -66,7 +66,6 @@
 	</div>
 </template>
 <script>
-	import blobTool from '@/utils/blobTool.js'
 	import Loading from '@/common/Loading.vue'
 	import BaseFilter from '../components/BaseFilter'
 	import BaseImport from '../components/BaseImport'
@@ -263,7 +262,7 @@
 			/* 删除blob指定试题目录下所有 */
 			deleteBlobPrefix(paper) {
 				return new Promise((resolve,reject) => {
-					this.$api.uploadFile.deletePrefix({
+					this.$api.blob.deletePrefix({
 						"cntr": paper.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
 						"prefix": "paper/" + paper.name
 					}).then(

+ 1 - 1
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseCreateChild.vue

@@ -442,7 +442,7 @@
 					editItem.code === this.$store.state.userInfo.TEAMModelId ? 0 : 1;
 				this.exersicesType = editItem.type;
 				this.exerciseField = editItem.field - 1;
-				this.exercisePoints = editItem.points;
+				this.exercisePoints = editItem.knowledge || editItem.points;
 
 				if (editItem.level) {
 					let ac = document

+ 4 - 3
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseEditExercise.vue

@@ -49,7 +49,7 @@
 				<div v-else style="margin-top: 10px">
 					<span v-for="(item, index) in exercisePoints" :key="index" class="exercise-item-point">
 						{{ item }}
-						<span class="exercise-item-point-close">
+						<span class="exercise-item-point-close"> 
 							<Icon type="md-close" @click="onDeletePoint(index)" /></span>
 					</span>
 					<span class="exercise-item-point-modify" @click="selectPointsModal = true">修改</span>
@@ -413,6 +413,7 @@
 							this.$Message.error(e.spaceError);
 						}
 					} else {
+						exerciseItem = await this.$evTools.doAddHost(exerciseItem)
 						// 如果是试卷内编辑试题 则返回编辑好的数据 再统一进行保存
 						this.saveExercise(exerciseItem);
 					}
@@ -537,7 +538,7 @@
 			/* 删除blob指定试题目录下所有 */
 			deleteBlobPrefix(item) {
 				return new Promise((resolve,reject) => {
-					this.$api.uploadFile.deletePrefix({
+					this.$api.blob.deletePrefix({
 						"cntr": item.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
 						"prefix": "item/" + item.id
 					}).then(
@@ -764,7 +765,7 @@
 				this.exerciseScope = editItem.scope === "private" ? 0 : 1;
 				this.exersicesType = editItem.type;
 				this.exerciseField = editItem.field - 1;
-				this.exercisePoints = editItem.points;
+				this.exercisePoints = editItem.knowledge || editItem.points;
 				if (editItem.level) {
 					let ac = document
 						.getElementById(this.refId)

+ 52 - 31
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue

@@ -112,13 +112,20 @@
 									<span class="explain-title">【{{$t('evaluation.knowledgePoints')}}】</span>
 									<div class="item-explain-details">
 										<span v-if="!item.knowledge || !(_.compact(item.knowledge).length)">{{$t('evaluation.noPoints')}}</span>
-										<div v-else>
+										<div v-else> 
 											<span v-for="(point,index) in item.knowledge" :key="index" class="item-point-tag">
 												{{ point }}
 											</span>
 										</div>
 									</div>
 								</div>
+								<!-- 认知层次部分 -->
+								<div class="item-explain" v-show="isShowAnswer">
+									<span class="explain-title">【{{$t('evaluation.filter.level')}}】</span>
+									<div class="item-explain-details">
+										{{ item.field ? exersicesField[item.field - 1] : exersicesField[0]}}
+									</div>
+								</div>
 							</div>
 							<!-- 如果是综合题 则加载子题渲染组件 -->
 							<div v-else>
@@ -187,7 +194,7 @@
 		</Modal>
 
 		<!-- 编辑试题弹窗 -->
-		<Modal v-model="editExerciseModal" class-name="edit-exercise-modal" width="1200px" footer-hide :title="$t('evaluation.exerciseList.editExercise')">
+		<Modal v-model="editExerciseModal" class-name="edit-exercise-modal" width="1200px" footer-hide :title="$t('evaluation.exerciseList.editExercise')"  @on-visible-change="editModalChange">
 			<BaseEditExercise :exerciseItem="currentExercise" @onEditSuccess="onEditSuccess" refId="paperEdit" ref="paperEdit"></BaseEditExercise>
 			<div slot="footer">
 				<Button type="success">{{$t('evaluation.confirm')}}</Button>
@@ -221,6 +228,7 @@
 				schoolCode: '',
 				dataLoading: false,
 				curEditItemId: null,
+				curOrderIndex:0,
 				exerciseList: [],
 				schoolInfo: {},
 				isShowUploadList: false,
@@ -364,6 +372,12 @@
 			handleMoveDown(arr, index) {
 				this.moveItems(arr, index, index + 1)
 			},
+			
+			editModalChange(val) {
+				this.$nextTick(() => {
+					this.$refs.paperEdit.backToTop();
+				})
+			},
 
 
 			/**
@@ -395,6 +409,7 @@
 			 */
 			handleToolEdit(arr, item, index) {
 				this.currentExerciseIndex = this.exerciseList.indexOf(item) // 清单列表下的index
+				this.curOrderIndex = index
 				if(item.scope === 'school'){
 					this.getSchoolBaseInfo().then(res => {
 						if (!res) return
@@ -545,30 +560,31 @@
 
 			/** 编辑成功 */
 			onEditSuccess(item) {
-				if (item.id) {
-					this.$refs.paperEdit.isLoading = false
-					this.editExerciseModal = false
-					this.$Message.success("修改成功!")
-					this.exerciseList.splice(this.currentExerciseIndex, 1, item)
-					this.curTypeItems.splice(this.curIndex, 1, item)
-					this.$emit('dataUpdate', this.exerciseList)
-
-					let existIndex = this.modifyItems.map(i => i.id).indexOf(item.id)
-					if (existIndex < 0) {
-						this.modifyItems.push(item)
-					} else {
-						this.modifyItems[existIndex] = item
-					}
+				let preOrderList = JSON.parse(JSON.stringify(this.orderList[0].list))
+				
+				this.$refs.paperEdit.isLoading = false
+				this.editExerciseModal = false
+				this.$Message.success("修改成功!")
+				
+				this.exerciseList.splice(this.currentExerciseIndex, 1, item)
+				this.curTypeItems.splice(this.curIndex, 1, item)
+				this.$emit('dataUpdate', this.exerciseList)
+
+				let existIndex = this.modifyItems.map(i => i.id).indexOf(item.id)
+				if (existIndex < 0) {
+					this.modifyItems.push(item)
 				} else {
-					this.editExerciseModal = false
-					this.exerciseList.splice(this.currentExerciseIndex, 1, item)
-					this.curTypeItems.splice(this.curIndex, 1, item)
-					let listIndex = this.collapseList.indexOf(this.currentExerciseIndex);
-					if (listIndex > -1) {
-						this.collapseList.splice(listIndex, 1)
-					}
-					this.$Message.success("修改成功!")
+					this.modifyItems[existIndex] = item
+				}
+				
+				if(this.viewModel === 'list' && this.orderList.length){
+					preOrderList.splice(this.curOrderIndex, 1, item)
+					console.log(preOrderList[this.curOrderIndex])
+					this.paper.item = preOrderList
 				}
+				
+				
+				
 			},
 
 
@@ -661,22 +677,23 @@
 			paper: {
 				handler(newPaper) {
 					if (newPaper) {
-						console.log('获取的题目信息')
+						console.log('获取的试卷信息')
                         console.log(newPaper)
 						let that = this
-						this.groupList = []
+						this.groupList = [] // 题型排序的数据
+						this.orderList = [] //顺序排列的数据
 						this.exerciseList = []
-						this.orderList = []
 						this.paperInfo = newPaper
-						this.multipleRule = newPaper.multipleRule || 1
+						this.multipleRule = newPaper.multipleRule || 1 // 配分规则
 						if (newPaper.item.length) {
 							newPaper.item.forEach(i => {
 								if (!i.score) i.score = 0
 								// 如果有综合题 则将小题的分数进行累加作为综合题的分数
-								if(i.type === 'compose' && i.children.length){
-									i.score = i.children.reduce((a,b) => a + b.score , 0)
-								}
+								// if(i.type === 'compose' && i.children.length){
+								// 	i.score = i.children.reduce((a,b) => a + b.score , 0)
+								// }
 							})
+							// 给顺序题目排序
 							this.orderList.push({
 								list: newPaper.item
 							})
@@ -695,11 +712,15 @@
 							});
 						}
 						
+						// 重新赋值
 						this.originData = this.exerciseList
 						this.groupTypeList = this.groupList
 						this.totalNum = newPaper.item.length
+						
+						// 剩余可分配分数 更新
 						this.surPlusScore = newPaper.score - newPaper.item.reduce((p, e) => parseInt(p) + parseInt(e.score), 0);
 						this.$emit('scoreUpdate', this.surPlusScore)
+						
 						this.pageScrollTo(0)
 						this.$nextTick(() => {
 							this.$MathJax.MathQueue(this.$refs.mathJaxContainer);

+ 158 - 12
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseImport.vue

@@ -4,7 +4,7 @@
                 <Upload multiple
                         :action="uploadUrl"
                         :headers="headers"
-                        :format="['docx']"
+                        :format="['docx','xlsx','xls']"
                         :on-format-error="handleFormatError"
                         :show-upload-list="isShowList"
                         :before-upload="beforeUpload"
@@ -37,18 +37,30 @@
 					<p>3.{{$t('evaluation.importFile.tips3')}}</p>
 					<p>4.{{$t('evaluation.importFile.tips4')}}</p>
 					<p>5.{{$t('evaluation.importFile.tips5')}}</a></p>
-					
 				</div>
             </div>
+			
+			<!-- 添加小题弹窗 -->
+			<Modal v-model="errorModal" width="800" class="bmf-error-modal" ok-text="重新导入" cancel-text="继续预览" @on-ok="onErrorOk" @on-cancel="onErrorCancel">
+				<div class="modal-header" slot="header">图片异常提示</div>
+				<p class="error-title">解析数据存在以下 <mark>{{ errorList.length }}</mark> 处图片异常,请选择<mark> 忽略并继续预览 </mark>还是<mark> 修改后重新导入模板 </mark>?</p>
+				<div class="error-item-list">
+					<p v-for="(item,index) in errorList" :key="index" v-html="item" class="error-item"></p>
+				</div>
+			</Modal>
     </div>
 </template>
 <script>
+	import excel from '@/utils/excel.js'
 	import FileSaver from "file-saver";
     export default {
         props: ['period', 'subject'],
         data() {
             return {
 				curLang:'',
+				errorList:[],
+				importList:[],
+				errorModal:false,
                 isBtnLoading: false,
                 isSelectFinish: false,
                 importLoading: false,
@@ -67,7 +79,8 @@
 					url:'/download/%E9%A2%98%E7%9B%AE%E6%A8%A1%E6%9D%BF%E8%8B%B1%E8%AF%AD.docx',
 					fileName:'TitleTemplate.docx'
 				}],
-				hostName:''
+				hostName:'',
+				fieldArr:['記憶记忆1','理解2','應用应用3','分析4','评价評鑒5','创造創造6']
             }
         },
         created() {
@@ -133,11 +146,94 @@
             },
 
             /** 选择文件上传之前 */
-            beforeUpload() {
-                this.isBtnLoading = true
-                this.isSelectFinish = true
+            beforeUpload(file) {
+				this.isBtnLoading = true
+				this.isSelectFinish = true
+				// 如果是excel格式文档 则需要进行对应解析
+				if(this.isExcel(file)){
+					let excelResult = []
+					this.readExcel(file,data => {
+						if(data.results.length){
+							data.results.forEach(item => {
+								excelResult.push({
+									question:item.Question || '',
+									answer:item.Answer ? [item.Answer] : [],
+									knowledge:item.Concept ? item.Concept.split(',') : [],
+									field:this.getItemField(item),
+									score:item.Point || 0,
+									type:item.Type,
+									children:[],
+									option:this.getItemOptions(item)
+								})
+							})
+							this.$Message.success(this.$t('evaluation.importFile.warningTips3'))
+							this.$emit('importFinish',excelResult)
+							this.isImportFinish = false
+							this.isBtnLoading = false
+							this.exerciseList = []
+						}
+					})
+					return false
+				}
             },
 
+			
+			/* 获取表格解析试题的选项 */
+			getItemOptions(item){
+				let options = []
+				let optionIndex = 0
+				if(item.Type === 'judge'){
+					return [{
+						code:'A',
+						value:'对'
+					},{
+						code:'B',
+						value:'错'
+					}]
+				}else{
+					for(let key in item){
+						if(key.includes('Option')){
+							options.push({
+								code: String.fromCharCode(64 + parseInt(optionIndex + 1)),
+								value:item[key]
+							})
+							optionIndex++
+						}
+					}
+					return options
+				}
+				
+				
+			},
+			
+			/* 获取表格解析试题的认知层次 */
+			getItemField(item){
+				for (let i in this.fieldArr) {
+					if(this.fieldArr[i].includes(item.Edu_Goal)){
+						return + i + 1
+						break
+					}
+				}
+			},
+			
+			
+			/* 判断上传的是否为excel表格文件 */
+			isExcel(file){
+				let  fileType = file.name.split('.')[file.name.split('.').length - 1]
+				return  fileType === 'xlsx' || fileType === 'xls'
+			},
+			
+			/* 解析excel表格 */
+			readExcel(file, callback) {
+			    var reader = new FileReader();
+			    reader.onload = function (e) {
+			        var data = e.target.result;
+			        var workbook = excel.read(data, 'binary');                
+			        if (callback) callback(workbook);
+			    };
+			    reader.readAsBinaryString(file);
+			},
+
             /**
              * 当上传文件格式错误
              * @param
@@ -157,29 +253,54 @@
                 this.isBtnLoading = false
 				this.isSelectFinish = false
             },
+			
+			/* 有异常图片情况下选择重新导入模板 */
+			onErrorOk(){
+				this.$Message.warning('请重新导入正确模板!')
+				this.isImportFinish = false
+				this.isBtnLoading = false
+				this.errorList = []
+				this.importList = []
+				this.exerciseList = []
+			},
+			
+			/* 有异常图片情况下选择继续预览 */
+			onErrorCancel(){
+				this.$Message.success(this.$t('evaluation.importFile.warningTips3'))
+				this.$emit('importFinish',this.importList)
+				this.isImportFinish = false
+				this.isBtnLoading = false
+				this.exerciseList = []
+				this.errorList = []
+				this.importList = []
+			},
 
             /**
              * 上传文件成功回调
              * @param response
              */
             uploadSuccess(response) {
-				if(Array.isArray(response) && response.length){
+				console.log(response)
+				if(response.tests.length && !response.emferror.length){
 					this.$Message.success(this.$t('evaluation.importFile.warningTips3'))
-					this.$emit('importFinish',response)
+					this.$emit('importFinish',response.tests)
 					this.isImportFinish = false
 					this.isBtnLoading = false
 					this.exerciseList = []
+				}else if(response.emferror.length){
+					this.errorModal = true
+					this.importList = response.tests
+					this.errorList = response.emferror
+					this.isImportFinish = false
+					this.isBtnLoading = false
 				}else{
 					this.$Message.error(this.$t('evaluation.importFile.warningTips4'))
 					this.isBtnLoading = false
 				}
+				
 				this.isSelectFinish = false
             },
         },
-
-        mounted() {
-
-        },
         computed: {
             headers() {
                 let hd = {}
@@ -201,6 +322,31 @@
 		height: 100%;
 	}
 	
+	.bmf-error-modal{
+		.error-title{
+			margin: 15px 15px 0 15px;
+			letter-spacing: 1px;
+			font-size: 16px;
+			font-weight: bold;
+		}
+		
+		mark{
+			color: red;
+		}
+		
+		.error-item-list{
+			max-height: 500px;
+			overflow-y:scroll;
+			padding: 15px;
+			
+			.error-item{
+				margin-top: 20px;
+				background: #efefef;
+				padding: 10px;
+			}
+		}
+	}
+	
     .cp-import-container,
     .cp-import-container .ivu-upload {
 		display: flex;

+ 11 - 4
TEAMModelOS/ClientApp/src/view/evaluation/components/BasePointPie.vue

@@ -80,10 +80,17 @@
 			})
 			let typeList = this._.groupBy(tempArr, 'knowledge')
 			for (let key in typeList) {
-				arr.push({
-					value: typeList[key].length,
-					name: key === 'undefined' || !key ?  '未绑定知识点' : key
-				})
+				let newKey = key === 'undefined' || !key ?  '未绑定知识点' : key
+				let isExistIndex = arr.map(i => i.name).indexOf(newKey)
+				if(arr.length && isExistIndex > -1){
+					arr[isExistIndex].value = arr[isExistIndex].value + typeList[key].length
+				}else{
+					arr.push({
+						value: typeList[key].length,
+						name: newKey
+					})
+				}
+				
 			}
 			this.drawLine(arr)
 		},

+ 1 - 1
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseRepair.vue

@@ -68,7 +68,7 @@
 			</Select>
 			<p style="margin: 15px 2px;">资源描述</p>
 		    <Input v-model="curRepair.name" placeholder="请输入资源描述..."/>
-			<p style="margin: 15px 2px;">资源链接地址</p>
+			<p style="margin: 15px 2px;">资源链接地址{{ isSiteLink ?  '' : '(输入后回车即可添加成功)'}}</p>
 			
 			<!-- 选择内容 -->
 			<Button type="info" @click="isRelatedContent = true" v-if="isSiteLink">{{$t('evaluation.newExercise.chooseContent')}}</Button>

+ 16 - 8
TEAMModelOS/ClientApp/src/view/evaluation/index/CreateExercises.vue

@@ -377,8 +377,9 @@
 							child.code = exerciseItem.code
 							child.pid = exerciseItem.id
 						})
+						let containerName = exerciseItem.scope === 'private' ? this.$store.state.userInfo.TEAMModelId : this.$store.state.userInfo.schoolCode
 						// 保存完题目返回子题的ID集合 作为cosmos内综合题的children字段
-						exerciseItem.children = await this.saveChildrens(exerciseItem.children)
+						exerciseItem.children = await this.saveChildrens(exerciseItem.children,containerName)
 					}
 					// 将当前的试题数据转化为BLOB内部的试题JSON格式
 					const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem);
@@ -432,8 +433,8 @@
 			},
 
 			/* 保存综合题的子题 */
-			saveChildrens(childrens) {
-				return new Promise((resolve, reject) => {
+			saveChildrens(childrens,containerName) {
+				return new Promise(async (resolve, reject) => {
 					let promiseArr = []
 					childrens.forEach(exerciseItem => {
 						promiseArr.push(new Promise(async (r, j) => {
@@ -455,7 +456,7 @@
 							);
 							try {
 								// 等待上传blob的返回结果
-								let blobFile = await containerClient.upload(file, "item/" + exerciseItem.id);
+								let blobFile = await containerClient.upload(file, "item/" + exerciseItem.id,{},true,false);
 								if (blobFile.blob) {
 									// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
 									exerciseItem.blob = blobFile.blob;
@@ -463,7 +464,10 @@
 										itemInfo: await this.$evTools.createCosmosItem(exerciseItem),
 										option: "insert",
 									}).then((res) => {
-										r(res.itemInfo)
+										r({
+											cosmosItem:res.itemInfo,
+											blobItem:blobFile
+										})
 									});
 								} else {
 									this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
@@ -473,11 +477,15 @@
 							}
 						}))
 					})
-
+					
 					Promise.all(promiseArr).then(result => {
-						console.log('子题保存后的返回结果promiseArr', result)
 						if (result.length) {
-							resolve(result.map(i => i.id))
+							// 子题保存之后 统一更新blobSize
+							blobTool.updateSize(result.map(i => i.blobItem),containerName).then(res => {
+								resolve(result.map(i => i.cosmosItem.id))
+							}).catch(err => {
+								this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
+							})
 						} else {
 							resolve([])
 						}

+ 116 - 41
TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue

@@ -252,7 +252,7 @@
 							continue;
 						}
 						/* 如果是导入的试题 没有ID 则试题的信息和当前试卷的学段信息保持一致 */
-						i.id = this.$tools.guid()
+						i.id = i.id || this.$tools.guid()
 						i.code = code
 						i.level = i.level || 3
 						i.field = i.field || 1
@@ -262,7 +262,7 @@
 						i.periodId = periodId
 						if (i.children.length) {
 							i.children.forEach(j => {
-								j.id = this.$tools.guid()
+								j.id = j.id || this.$tools.guid()
 								j.pid = i.id
 								j.scope = i.scope
 								j.code = code
@@ -274,7 +274,8 @@
 							})
 						}
 						// 如果导入的是客观题 则需要检测答案与选项是否为空
-						if (objectiveTypes.includes(i.type) && (!i.question.replace(/\s*/g, "") || !i.option.length || !i.answer.length || !i.option || !i.answer)) {
+						if (objectiveTypes.includes(i.type) && (!i.question.replace(/\s*/g, "") || !i.option.length || !i.answer.length ||
+								!i.option || !i.answer)) {
 							this.errorList.push(i)
 						}
 					}
@@ -365,9 +366,17 @@
 				let that = this
 				console.log('保存的试题列表')
 				console.log(list)
+
+				let containerName = this.isSchool ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+				/* 上传前进行试卷blob查询size */
+				// let prefixArr = this.getListPrefixs(list)
+				// let beforeSize = await blobTool.checkPrefixSize(prefixArr, containerName)
+				// console.log('试题上传前的size', beforeSize)
+				// return
 				// 获取初始化Blob需要的数据
 				let sasData = this.isSchool ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
 				let itemJsonFiles = []
+				let blobFiles = []
 				//初始化Blob
 				let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.evaluationInfo.type)
 				return new Promise(async (resolve, j) => {
@@ -388,8 +397,12 @@
 										// 保存试题的blob链接
 										const itemJsonFile = await this.$evTools.createBlobItem(composeItem)
 										let file = new File([JSON.stringify(itemJsonFile)], composeItem.id + ".json");
+										blobFiles.push(file)
 										itemJsonFiles.push(composeItem)
-										r(composeItem)
+										r({
+											cosmosItem: composeItem,
+											blobItem: itemJsonFile
+										})
 										console.log(composeItem)
 									}))
 								})
@@ -399,8 +412,12 @@
 									// 保存试题的blob链接
 									const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem)
 									let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+									blobFiles.push(file)
 									itemJsonFiles.push(exerciseItem)
-									r(exerciseItem)
+									r({
+										cosmosItem: exerciseItem,
+										blobItem: itemJsonFile
+									})
 								}))
 							}
 						} else if (exerciseItem.children.length) { //如果是导入的 综合题
@@ -412,13 +429,15 @@
 										composeItem.children = composeItem.children.map(i => i.id)
 									}
 									// 将当前的试题数据转化为BLOB内部的试题JSON格式
-									const itemJsonFile = await this.$evTools.createBlobItem(composeItem)
+									let removeItem = await this.$editorTools.doRemoveMideaHost(composeItem)
+									const itemJsonFile = await this.$evTools.createBlobItem(removeItem)
 									const cosmosItem = await this.$evTools.createCosmosItem(composeItem)
 									// 首先保存新题目的JSON文件到Blob 然后返回URL链接
 									let file = new File([JSON.stringify(itemJsonFile)], composeItem.id + ".json");
+									blobFiles.push(file)
 									try {
 										// 等待上传blob的返回结果
-										let blobFile = await containerClient.upload(file, 'item/' + composeItem.id, undefined, false)
+										let blobFile = await containerClient.upload(file, 'item/' + composeItem.id, undefined, false, false)
 										if (blobFile.blob) {
 											// 保存试题JSON文件到试卷文件夹需要
 											itemJsonFiles.push(composeItem)
@@ -426,7 +445,10 @@
 											cosmosItem.blob = blobFile.blob
 											// 保存当前试题到数据库
 											that.saveExercise(cosmosItem).then(res => {
-												r(composeItem)
+												r({
+													cosmosItem: cosmosItem,
+													blobItem: blobFile
+												})
 											})
 										} else {
 											j(500)
@@ -441,13 +463,15 @@
 							// 如果没有blob字段 说明试题还没有保存到item目录下 则需要进行保存item文件夹后再保存到paper文件夹(导入试题情况)
 							promiseArr.push(new Promise(async (r, j) => {
 								// 将当前的试题数据转化为BLOB内部的试题JSON格式
-								const itemJsonFile = await this.$evTools.createBlobItem(exerciseItem)
+								let removeItem = await this.$editorTools.doRemoveMideaHost(exerciseItem)
+								const itemJsonFile = await this.$evTools.createBlobItem(removeItem)
 								const cosmosItem = await this.$evTools.createCosmosItem(exerciseItem)
 								// 首先保存新题目的JSON文件到Blob 然后返回URL链接
 								let file = new File([JSON.stringify(itemJsonFile)], exerciseItem.id + ".json");
+								blobFiles.push(file)
 								try {
 									// 等待上传blob的返回结果
-									let blobFile = await containerClient.upload(file, 'item/' + exerciseItem.id, undefined, false)
+									let blobFile = await containerClient.upload(file, 'item/' + exerciseItem.id, undefined, false, false)
 									if (blobFile.blob) {
 										// 保存试题JSON文件到试卷文件夹需要
 										itemJsonFiles.push(exerciseItem)
@@ -455,7 +479,10 @@
 										cosmosItem.blob = blobFile.blob
 										// 保存当前试题到数据库
 										that.saveExercise(cosmosItem).then(res => {
-											r(exerciseItem)
+											r({
+												cosmosItem: exerciseItem,
+												blobItem: blobFile
+											})
 										})
 									} else {
 										j(500)
@@ -468,27 +495,37 @@
 						}
 					}
 					Promise.all(promiseArr).then(result => {
+						let updateSize = this._.sum(blobFiles.map(file => file.size))
 						console.log(result)
-						let slides = []
-						// 主观题的answer为空数组
-						let nullType = ['complete', 'subjective', 'compose', 'correct', 'connector']
-						result.forEach(item => {
-							let o = {
-								url: item.id + '.json',
-								type: item.type,
-								scoring: {
-									score: item.score,
-									knowledge: item.knowledge || [],
-									field: item.field || 1,
-									ans: nullType.includes(item.type) ? [] : item.answer
+						// 子题保存之后 统一更新blobSize
+						blobTool.updateSize([{
+							url: 'item',
+							size: updateSize
+						}], containerName).then(res => {
+							let slides = []
+							// 主观题的answer为空数组
+							let nullType = ['complete', 'subjective', 'compose', 'correct', 'connector']
+							result.map(i => i.cosmosItem).forEach(item => {
+								console.log(item)
+								let o = {
+									url: item.id + '.json',
+									type: item.type,
+									scoring: {
+										score: item.score,
+										knowledge: item.knowledge || [],
+										field: item.field || 1,
+										ans: nullType.includes(item.type) ? [] : item.answer
+									}
 								}
-							}
-							item.type === 'compose' && delete o.scoring
-							slides.push(o)
-						})
-						resolve({
-							slides: slides,
-							files: itemJsonFiles
+								item.type === 'compose' && delete o.scoring
+								slides.push(o)
+							})
+							resolve({
+								slides: slides,
+								files: itemJsonFiles
+							})
+						}).catch(err => {
+							this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
 						})
 					}).catch(err => {
 						console.log(err)
@@ -497,6 +534,21 @@
 				})
 			},
 
+			/* 获取所有试题Blob前缀 */
+			getListPrefixs(list) {
+				let prefixArr = []
+				list.forEach(item => {
+					if (item.type === 'compose' && item.children.length) {
+						prefixArr.push('item/' + item.id)
+						prefixArr = prefixArr.concat(item.children.map(i => 'item/' + i.id))
+					} else {
+						prefixArr.push('item/' + item.id)
+					}
+				})
+				console.log('试题的前缀集合', prefixArr)
+				return prefixArr
+			},
+
 			// 提取富文本内容中的文本
 			getSimpleText(html) {
 				var r = /<(?!img|video|audio).*?>/g;
@@ -754,6 +806,14 @@
 					let sasData = this.evaluationInfo.type === 'private' ? await this.$tools.getPrivateSas() : await this.$tools.getSchoolSas()
 					//初始化Blob
 					let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.evaluationInfo.type)
+
+					let containerName = paperItem.scope === 'private' ? this.$store.state.userInfo.TEAMModelId : this.$store.state
+						.userInfo.schoolCode
+					/* 上传前进行试卷blob查询size */
+					let beforeSizeArr = await blobTool.checkPrefixSize(['paper/' + paperItem.name], containerName)
+					let beforeSize = this._.sum(beforeSizeArr.map(i => i.size))
+					console.log('试卷上传前的size',beforeSize)
+
 					try {
 						let promiseArr = []
 						let blobFile = null
@@ -774,13 +834,17 @@
 
 								let item = res.files[i]
 								if (item.scope == 'school') {
-									containerClient.copyFolder('paper/' + paperItem.name + '/', 'item/' + item.id, schoolBlob).then(res => { r(200) })
+									containerClient.copyFolder('paper/' + paperItem.name + '/', 'item/' + item.id, schoolBlob, null, false)
+										.then(res => {
+											r(200)
+										})
 								} else {
-									containerClient.copyFolder('paper/' + paperItem.name + '/', 'item/' + item.id, privateBlob).then(res => { r(200) })
+									containerClient.copyFolder('paper/' + paperItem.name + '/', 'item/' + item.id, privateBlob, null,
+										false).then(res => {
+										r(200)
+									})
 								}
 							}))
-
-
 						}
 						promiseArr.push(new Promise(async (r, j) => {
 							try {
@@ -792,11 +856,13 @@
 								this.$Message.error(e.spaceError)
 								this.isLoading = false
 							}
-
 						}))
 						// 进行试卷文件上传Blob
 						Promise.all(promiseArr).then(async result => {
-							console.log('试卷上传文件的promise结果', result)
+							/* 上传前进行试卷blob查询size */
+							let afterSizeArr = await blobTool.checkPrefixSize(['paper/' + paperItem.name], containerName)
+							let afterSize = this._.sum(afterSizeArr.map(i => i.size))
+							console.log('试卷上传后的size', afterSize)
 							if (blobFile.blob) {
 								// 保存到COSMOS是不含base64图片编码的数据 避免数据量过大
 								paperItem.blob = blobFile.blob.split('/index.json')[0]
@@ -811,11 +877,20 @@
 											this.$Message.success(this.isEditPaper ? this.$t('evaluation.paperList.editSuc') : this.$t(
 												'evaluation.paperList.saveSuc'))
 											this.isLoading = false
-											this.$router.push({
-												name: this.evaluationInfo.type === 'private' ? 'personalBank' : 'schoolBank',
-												params: {
-													tabName: 'paper'
-												}
+
+											// 子题保存之后 统一更新blobSize
+											blobTool.updateSize([{
+												url: 'paper/' + paperItem.name,
+												size: afterSize - beforeSize
+											}], containerName).then(res => {
+												this.$router.push({
+													name: this.evaluationInfo.type === 'private' ? 'personalBank' : 'schoolBank',
+													params: {
+														tabName: 'paper'
+													}
+												})
+											}).catch(err => {
+												this.$Message.error(this.$t('evaluation.newExercise.uploadErrorTip'));
 											})
 										} else {
 											this.$Message.error(this.$t('evaluation.paperList.saveFail'))

+ 1 - 1
TEAMModelOS/ClientApp/src/view/evaluation/types/BaseConnector.vue

@@ -1,7 +1,7 @@
 <template>
 	<div>
 		<div class="exersices-content">
-			<IconText :text="$t('evaluation.corrector') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+			<IconText :text="$t('evaluation.connector') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
 			<div>
 				<div ref="editor" style="text-align:left"></div>
 			</div>

+ 99 - 79
TEAMModelOS/ClientApp/src/view/homepage/AcCountPie.vue

@@ -2,97 +2,117 @@
     <div id="ac-type-count"></div>
 </template>
 <script>
-    export default {
-        data() {
-            return {
-                index:0,
-                typeCountPie: undefined,
-                option: {
-                    backgroundColor:"#27262b",
-                    legend: {
-                        data: ['评量测验', '自主学习', '作业活动', '投票活动', '问卷调查'],
-                        textStyle: {
-                            color: '#DDDDDD',
-                            padding:[5,0,5,0]
-                        },
-                        icon: 'circle',
-                        bottom: 10,
-                        width: 240
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    data() {
+        return {
+            index: 0,
+            typeCountPie: undefined,
+            option: {
+                backgroundColor: "#27262b",
+                legend: {
+                    data: [
+                        this.$t('home.ac1'),
+                        this.$t('home.ac2'),
+                        this.$t('home.ac3'),
+                        this.$t('home.ac4'),
+                        this.$t('home.ac5')
+                    ],
+                    textStyle: {
+                        color: '#DDDDDD',
+                        padding: [5, 0, 5, 0]
                     },
-                    series: [
-                        {
+                    icon: 'circle',
+                    bottom: 10
+                },
+                grid: {
+                    left: '20px',
+                    right: '20px',
+                    top: '20px',
+                    bottom: '20px',
+                    containLabel: true
+                },
+                series: [
+                    {
 
-                            name: '数量',
-                            type: 'pie',
-                            top: -60,
-                            radius: [95, 120],
-                            avoidLabelOverlap: false,
-                            legendHoverLink: false,
+                        name: '数量',
+                        type: 'pie',
+                        top: -60,
+                        radius: [95, 120],
+                        avoidLabelOverlap: false,
+                        legendHoverLink: false,
+                        label: {
+                            show: false,
+                            position: 'center',
+                            formatter: function (data) { // 设置圆饼图中间文字排版
+                                return data.value + "\n" + data.name
+                            }
+                        },
+                        emphasis: {
                             label: {
-                                show: false,
-                                position: 'center',
-                                formatter: function (data) { // 设置圆饼图中间文字排版
-                                    return data.value + "\n" + data.name
-                                }
-                            },
-                            emphasis: {
-                                label: {
-                                    show: true,
-                                    fontSize: '24',
-                                    fontWeight: 'bold'
-                                },
+                                show: true,
+                                fontSize: '24',
+                                fontWeight: 'bold'
                             },
-                            labelLine: {
-                                show: false
-                            },
-                            data: [
-                                { value: 335, name: '评量测验',itemStyle:{color:"#00f492"} },
-                                { value: 310, name: '自主学习',itemStyle:{color:"#fa8d38"} },
-                                { value: 234, name: '作业活动',itemStyle:{color:"#f862bb"} },
-                                { value: 135, name: '投票活动',itemStyle:{color:"#65dcda"} },
-                                { value: 1548, name: '问卷调查',itemStyle:{color:"#d6b8ff"} }
-                            ],
-                            itemStyle: {
-                                borderWidth: 5,
-                                borderColor:'#2b2a2f'
-                            }
+                        },
+                        labelLine: {
+                            show: false
+                        },
+                        data: [
+                            { value: 335, name: this.$t('home.ac1'), itemStyle: { color: "#00f492" } },
+                            { value: 310, name: this.$t('home.ac2'), itemStyle: { color: "#fa8d38" } },
+                            { value: 234, name: this.$t('home.ac3'), itemStyle: { color: "#f862bb" } },
+                            { value: 135, name: this.$t('home.ac4'), itemStyle: { color: "#65dcda" } },
+                            { value: 1548, name: this.$t('home.ac5'), itemStyle: { color: "#d6b8ff" } }
+                        ],
+                        itemStyle: {
+                            borderWidth: 5,
+                            borderColor: '#2b2a2f'
                         }
-                    ]
-                }
+                    }
+                ]
             }
-        },
-        created() {
-
-        },
-        mounted() {
-            this.typeCountPie = this.$echarts.init(document.getElementById('ac-type-count'))
-            this.typeCountPie.setOption(this.option)
-            this.typeCountPie.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: 0 })
-            this.typeCountPie.on('mouseover', (e)=> {
-                this.typeCountPie.dispatchAction({ type: 'downplay', seriesIndex: 0, dataIndex: 0 })
-                if (e.dataIndex != this.index) {
-                    this.typeCountPie.dispatchAction({ type: 'downplay', seriesIndex: 0, dataIndex: this.index })
-                }
-                if (e.dataIndex == 0) {
-                    this.typeCountPie.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: e.dataIndex })
-                }
-            })
+        }
+    },
+    created() {
 
-            //当鼠标离开时,把当前项置为选中 
-            this.typeCountPie.on('mouseout', (e) => {
-                this.index = e.dataIndex
+    },
+    mounted() {
+        this.typeCountPie = this.$echarts.init(document.getElementById('ac-type-count'))
+        this.typeCountPie.setOption(this.option)
+        this.typeCountPie.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: 0 })
+        this.typeCountPie.on('mouseover', (e) => {
+            this.typeCountPie.dispatchAction({ type: 'downplay', seriesIndex: 0, dataIndex: 0 })
+            if (e.dataIndex != this.index) {
+                this.typeCountPie.dispatchAction({ type: 'downplay', seriesIndex: 0, dataIndex: this.index })
+            }
+            if (e.dataIndex == 0) {
                 this.typeCountPie.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: e.dataIndex })
+            }
+        })
+
+        //当鼠标离开时,把当前项置为选中 
+        this.typeCountPie.on('mouseout', (e) => {
+            this.index = e.dataIndex
+            this.typeCountPie.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: e.dataIndex })
+        })
+        this.erd = elementResizeDetectorMaker()
+        this.erd.listenTo(document.getElementById("ac-type-count"), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.typeCountPie.resize()
             })
-        }
+        })
     }
+}
 </script>
 <style scoped lang="less">
-    #ac-type-count {
-        width: 100%;
-        height: 380px;
-        display: flex;
-        justify-content: center;
-    }
+#ac-type-count {
+    width: 100%;
+    height: 360px;
+    display: flex;
+    justify-content: center;
+}
 </style>
 <style>
 </style>

+ 6 - 6
TEAMModelOS/ClientApp/src/view/homepage/HomePage.less

@@ -1,7 +1,7 @@
-@main-bgColor: rgb(40,40,40); //主背景颜
-@borderColor: #424242; //边框颜
-@primary-textColor: #fff; //文本主颜
-@second-textColor: #a5a5a5; //文本副级颜
+@main-bgColor: rgb(40,40,40); //锟斤拷锟斤拷锟斤拷锟斤拷
+@borderColor: #424242; //锟竭匡拷锟斤拷
+@primary-textColor: #fff; //锟侥憋拷锟斤拷锟斤拷
+@second-textColor: #a5a5a5; //锟侥憋拷锟斤拷锟斤拷锟斤拷
 @nothing-textColor: #5f5f5f;
 @primary-fontSize: 14px;
 @second-fontSize: 16px;
@@ -123,7 +123,7 @@
         }
 
         .ac-info-box {
-            width: ~"calc(100% - 170px)";
+            width: ~"calc(100% - 150px)";
 
             p {
                 text-overflow: ellipsis;
@@ -156,7 +156,7 @@
         }
 
         .ac-pro-box {
-            width: 100px;
+            width: 80px;
             text-align: center;
             color: #1cc1f1;
             font-size: 30px;

+ 0 - 0
TEAMModelOS/ClientApp/src/view/homepage/HomePage.vue


Някои файлове не бяха показани, защото твърде много файлове са промени