zhouj1203@hotmail.com 3 vuotta sitten
vanhempi
commit
8907b6c6c6
61 muutettua tiedostoa jossa 1402 lisäystä ja 952 poistoa
  1. 25 11
      TEAMModelFunction/MonitorServicesBus.cs
  2. 15 3
      TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs
  3. 60 51
      TEAMModelOS.SDK/Models/Service/FixDataService.cs
  4. 4 1
      TEAMModelOS/ClientApp/src/api/blob.js
  5. 5 0
      TEAMModelOS/ClientApp/src/api/studentWeb.js
  6. 12 0
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/home-view.css
  7. 14 0
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/home-view.less
  8. 12 0
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/setting-view.css
  9. 8 4
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/setting-view.less
  10. 0 13
      TEAMModelOS/ClientApp/src/common/BaseUserPoptip.vue
  11. 8 0
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReport.vue
  12. 47 56
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperTest.vue
  13. 16 4
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperView.vue
  14. 65 61
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventList.vue
  15. 2 2
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventView.vue
  16. 3 3
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseList.vue
  17. 1 0
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseListView.vue
  18. 69 17
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/HomeView.vue
  19. 2 2
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/MissionListCard.vue
  20. 14 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/studentWeb.js
  21. 2 1
      TEAMModelOS/ClientApp/src/locale/lang/en-US/teachContent.js
  22. 8 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/studentWeb.js
  23. 30 29
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/teachContent.js
  24. 7 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/studentWeb.js
  25. 2 1
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/teachContent.js
  26. 5 0
      TEAMModelOS/ClientApp/src/router/routes.js
  27. 4 3
      TEAMModelOS/ClientApp/src/store/index.js
  28. 4 5
      TEAMModelOS/ClientApp/src/store/module/answerSheet.js
  29. 59 53
      TEAMModelOS/ClientApp/src/utils/html2pdf.js
  30. 13 7
      TEAMModelOS/ClientApp/src/utils/sheetConfig.js
  31. 38 38
      TEAMModelOS/ClientApp/src/view/Home.vue
  32. 31 31
      TEAMModelOS/ClientApp/src/view/answersheet/BaseEditor.vue
  33. 3 3
      TEAMModelOS/ClientApp/src/view/answersheet/BaseSvgBg.vue
  34. 2 2
      TEAMModelOS/ClientApp/src/view/answersheet/BaseTitleEditor.vue
  35. 11 2
      TEAMModelOS/ClientApp/src/view/answersheet/SheetBaseInfo.vue
  36. 1 1
      TEAMModelOS/ClientApp/src/view/answersheet/SheetComplete.vue
  37. 65 43
      TEAMModelOS/ClientApp/src/view/answersheet/SheetObjective.vue
  38. 1 1
      TEAMModelOS/ClientApp/src/view/answersheet/SheetSubjective.vue
  39. 136 113
      TEAMModelOS/ClientApp/src/view/answersheet/index.vue
  40. 1 1
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue
  41. 0 12
      TEAMModelOS/ClientApp/src/view/evaluation/bank/index.vue
  42. 10 6
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue
  43. 37 24
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue
  44. 8 2
      TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.vue
  45. 1 1
      TEAMModelOS/ClientApp/src/view/learnactivity/MgtPrivEva.vue
  46. 1 1
      TEAMModelOS/ClientApp/src/view/settings/BaseApplyForm.vue
  47. 12 6
      TEAMModelOS/ClientApp/src/view/student-web/App.vue
  48. 2 2
      TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue
  49. 5 0
      TEAMModelOS/ClientApp/src/view/teachcontent/index.less
  50. 278 169
      TEAMModelOS/ClientApp/src/view/teachcontent/index.vue
  51. 1 0
      TEAMModelOS/ClientApp/vue.config.js
  52. 3 18
      TEAMModelOS/Controllers/Common/ExamController.cs
  53. 44 4
      TEAMModelOS/Controllers/Core/BlobController.cs
  54. 1 1
      TEAMModelOS/Controllers/Paper/PaperController.cs
  55. 21 58
      TEAMModelOS/Controllers/Paper/SheetConfigController.cs
  56. 20 16
      TEAMModelOS/Controllers/School/CourseController.cs
  57. 13 8
      TEAMModelOS/Controllers/School/SchoolController.cs
  58. 10 6
      TEAMModelOS/Controllers/Teacher/InitController.cs
  59. 85 52
      TEAMModelOS/Controllers/XTest/TestController.cs
  60. 42 0
      TEAMModelOS/Services/Common/SheetService.cs
  61. 3 3
      TEAMModelOS/TEAMModelOS.csproj

+ 25 - 11
TEAMModelFunction/MonitorServicesBus.cs

@@ -166,22 +166,30 @@ namespace TEAMModelFunction
                     if (!exist)
                     {   ///key不存在则正常进行计算
                         bool condition = false;
-                        while (condition || !exist)
-                        {
-                            TimeSpan timeSpan = new TimeSpan(DateTimeOffset.UtcNow.AddMinutes(5).Ticks);
-                            //准备处理Blob刷新时间
-                            long action = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-                            await _azureRedis.GetRedisClient(8).StringSetAsync(lockKey, action, expiry: timeSpan);
+                        TimeSpan timeSpan = new TimeSpan(DateTimeOffset.UtcNow.AddMinutes(5).Ticks);
+                        timeSpan = timeSpan - new TimeSpan(DateTimeOffset.UtcNow.Ticks);
+                        //准备处理Blob刷新时间
+                        long action = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                        await _azureRedis.GetRedisClient(8).StringSetAsync(lockKey, action, expiry: timeSpan);
+                        await RefreshBlob(name, u);
+                        //将action 与Redis最新的时间进行比较,如果
+                        var rds = await CheckLockKey(lockKey, action);
+                        condition = rds.condition;
+                        exist = rds.exist;
+                        if (condition || !exist) {
                             await RefreshBlob(name, u);
-                            //将action 与Redis最新的时间进行比较,如果
-                            var rds =await CheckLockKey(lockKey, action);
-                            condition = rds.condition;
-                            exist = rds.exist;
                         }
+                        
+                        //使用  CancellationToken 
+                        //while (condition || !exist)
+                        //{
+
+                        //}
                     }
                     else {
                         ///key存在则,则刷新key对应的值
                         TimeSpan timeSpan = new TimeSpan(DateTimeOffset.UtcNow.AddMinutes(5).Ticks);
+                        timeSpan = timeSpan - new TimeSpan(DateTimeOffset.UtcNow.Ticks);
                         long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                         await _azureRedis.GetRedisClient(8).StringSetAsync(lockKey, now, expiry: timeSpan);
                     }
@@ -229,6 +237,7 @@ namespace TEAMModelFunction
             }
         }
         private async Task RefreshBlob(string  name ,string  u) {
+            long statr = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
             var client = _azureStorage.GetBlobContainerClient(name);
             var size = await client.GetBlobsSize(u);
             await _azureRedis.GetRedisClient(8).SortedSetRemoveAsync($"Blob:Catalog:{name}", u);
@@ -240,10 +249,15 @@ namespace TEAMModelFunction
                 foreach (var score in scores)
                 {
                     blobsize = blobsize + score.Score;
-                    //list.Add(new Dictionary<string, double?>() { { score.Element.ToString(), score.Score } });
                 }
             }
             await _azureRedis.GetRedisClient(8).HashSetAsync($"Blob:Record", new RedisValue(name), new RedisValue($"{blobsize}"));
+            long end = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            long dis = (end - statr)/1000;
+            long timeout = 10;
+            if (dis> timeout) {
+                await _dingDing.SendBotMsg($"ServiceBus,RefreshBlob:空间计算已经超过{timeout}秒\n容器名:{name}\n文件夹:{u}\n计算时长:{dis}", GroupNames.醍摩豆服務運維群組);
+            }
         }
         /// <summary>
         /// 完善课程变更,StuListChange,  originCode是学校编码 则表示名单是学校自定义名单,如果是tmdid则表示醍摩豆的私有名单,scope=school,private。

+ 15 - 3
TEAMModelOS.SDK/DI/AzureStorage/AzureStorageBlobExtensions.cs

@@ -30,7 +30,6 @@ namespace TEAMModelOS.SDK.DI
             long? size = 0;
             try
             {
-               
                 await foreach (BlobItem item in client.GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix))
                 {
                     size += item.Properties.ContentLength;
@@ -148,8 +147,21 @@ namespace TEAMModelOS.SDK.DI
                     await foreach (var item in bcc.GetBlobsAsync(BlobTraits.None, BlobStates.None, px))
                     {
                         var urib = new UriBuilder(bcc.Uri);
-                        urib.Path = Path.Combine(urib.Path, item.Name);
-                        blobs.Add(urib.Uri);
+                        if (prefix != item.Name)
+                        {
+                            //避免操作(1111) /1111/1111.json  /1111111/11111.json
+                            if (!prefix.EndsWith("/") && item.Name.StartsWith($"{prefix}/"))
+                            {
+                                urib.Path = Path.Combine(urib.Path, item.Name);
+                                blobs.Add(urib.Uri);
+                            }
+                        }
+                        else {
+                            urib.Path = Path.Combine(urib.Path, item.Name);
+                            blobs.Add(urib.Uri);
+                        }
+                      
+                        
                     };
                 }
                 if (blobs.Count <= 256)

+ 60 - 51
TEAMModelOS.SDK/Models/Service/FixDataService.cs

@@ -68,66 +68,75 @@ namespace TEAMModelOS.SDK.Models.Service
         /// <returns></returns>
         public  static  async   Task FixBlobContent(CosmosClient client, DingDing _dingDing, AzureStorageFactory _azureStorage, JsonElement data)
         {
-           
+            if (data.TryGetProperty("doPrivate", out JsonElement _doPrivate)&& $"{_doPrivate}".Equals("yes",StringComparison.OrdinalIgnoreCase)) {
+                foreach (var cnt in _azureStorage.GetBlobServiceClient().GetBlobContainers())
+                {
+                    if (cnt.Name.Length == 10 && int.TryParse(cnt.Name, out _))
+                    {
+                        await doFixBlob(client, _azureStorage, cnt.Name, "private");
+                    }
+                }
+            }
             if (data.TryGetProperty("name", out JsonElement _name))
             {
                 List<string> names = _name.ToObject<List<string>>();
                 foreach (string name in names)
                 {
-                    List<string> prefixs = new List<string>() { "audio", "doc", "image", "other", "res", "video", "thum" };
-                    var ContainerClient = _azureStorage.GetBlobContainerClient($"{name}");
-                    string scope = "private";
-                    if (data.TryGetProperty("scope", out JsonElement _scope))
-                    {
-                        scope = $"{_scope}";
-                    }
-                    var tb = "Teacher";
-                    if (scope != "private")
-                    {
-                        tb = "School";
-                    }
-                    List<string> ids = new List<string>();
-                    await foreach (var item in client.GetContainer("TEAMModelOS", tb).GetItemQueryIterator<Bloblog>(queryDefinition: new QueryDefinition("select c.id from c "), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
-                    {
-                        ids.Add(item.id);
-                    }
-                    await client.GetContainer("TEAMModelOS", tb).DeleteItemsStreamAsync(ids, $"Bloblog-{name}");
-                    long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-                    foreach (var prefix in prefixs)
+                    await doFixBlob(client,  _azureStorage, name, "school");
+                }
+            }
+        }
+        private static async Task doFixBlob(CosmosClient client, AzureStorageFactory _azureStorage,string name,string scope) {
+            List<string> prefixs = new List<string>() { "audio", "doc", "image", "other", "res", "video", "thum" };
+            var ContainerClient = _azureStorage.GetBlobContainerClient($"{name}");
+            var tb = "Teacher";
+            if (scope != "private")
+            {
+                tb = "School";
+            }
+            List<string> ids = new List<string>();
+            await foreach (var item in client.GetContainer("TEAMModelOS", tb).GetItemQueryIterator<Bloblog>(queryDefinition: new QueryDefinition("select c.id from c "), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+            {
+                ids.Add(item.id);
+            }
+            await client.GetContainer("TEAMModelOS", tb).DeleteItemsStreamAsync(ids, $"Bloblog-{name}");
+            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            foreach (var prefix in prefixs)
+            {
+                if (prefix.Equals("res"))
+                {
+                    List<string> itemres = await ContainerClient.List(prefix);
+                    if (itemres.IsNotEmpty())
                     {
-                        if (prefix.Equals("res"))
+                        HashSet<string> set = new HashSet<string>();
+                        itemres.ForEach(x =>
+                        {
+                            var uri = x.Split("/");
+                            set.Add($"res/{uri[1]}");
+                        });
+                        foreach (var item in set)
                         {
-                            List<string> itemres = await ContainerClient.List(prefix);
-                            if (itemres.IsNotEmpty()) {
-                                HashSet<string> set = new HashSet<string>();
-                                itemres.ForEach(x =>
-                                {
-                                    var uri = x.Split("/");
-                                    set.Add($"res/{uri[1]}");
-                                });
-                                foreach (var item in set)
-                                {
-                                    var urlsSize = await ContainerClient.GetBlobsSize(item);
-                                    var url = item;
-                                    if (!item.EndsWith(".hte", StringComparison.OrdinalIgnoreCase) && !item.EndsWith(".HTEX", StringComparison.OrdinalIgnoreCase))
-                                    {
-                                        url += ".HTEX";
-                                    }
-                                    Bloblog bloblog = new Bloblog { id = Guid.NewGuid().ToString(), code = $"Bloblog-{name}", pk = "Bloblog", time = now, url = url, size = urlsSize != null && urlsSize.HasValue ? urlsSize.Value : 0, type = prefix };
-                                    await client.GetContainer("TEAMModelOS", tb).UpsertItemAsync(bloblog, new Azure.Cosmos.PartitionKey(bloblog.code));
-                                }
+                            var urlsSize = await ContainerClient.GetBlobsSize(item);
+                            var url = item;
+                            if (!item.EndsWith(".hte", StringComparison.OrdinalIgnoreCase) && !item.EndsWith(".HTEX", StringComparison.OrdinalIgnoreCase))
+                            {
+                                url += ".HTEX";
                             }
+                            Bloblog bloblog = new Bloblog { id = Guid.NewGuid().ToString(), code = $"Bloblog-{name}", pk = "Bloblog", time = now, url = url, size = urlsSize != null && urlsSize.HasValue ? urlsSize.Value : 0, type = prefix };
+                            await client.GetContainer("TEAMModelOS", tb).UpsertItemAsync(bloblog, new Azure.Cosmos.PartitionKey(bloblog.code));
                         }
-                        else {
-                            List<string> items = await ContainerClient.List(prefix);
-                            if (items.IsNotEmpty()) {
-                                foreach (var item in items)
-                                {
-                                    var urlsSize = await ContainerClient.GetBlobsSize(item);
-                                    Bloblog bloblog = new Bloblog { id = Guid.NewGuid().ToString(), code = $"Bloblog-{name}", pk = "Bloblog", time = now, url = item, size = urlsSize != null && urlsSize.HasValue ? urlsSize.Value : 0, type = prefix };
-                                    await client.GetContainer("TEAMModelOS", tb).UpsertItemAsync(bloblog, new Azure.Cosmos.PartitionKey(bloblog.code));
-                                }
-                            }
+                    }
+                }
+                else
+                {
+                    List<string> items = await ContainerClient.List(prefix);
+                    if (items.IsNotEmpty())
+                    {
+                        foreach (var item in items)
+                        {
+                            var urlsSize = await ContainerClient.GetBlobsSize(item);
+                            Bloblog bloblog = new Bloblog { id = Guid.NewGuid().ToString(), code = $"Bloblog-{name}", pk = "Bloblog", time = now, url = item, size = urlsSize != null && urlsSize.HasValue ? urlsSize.Value : 0, type = prefix };
+                            await client.GetContainer("TEAMModelOS", tb).UpsertItemAsync(bloblog, new Azure.Cosmos.PartitionKey(bloblog.code));
                         }
                     }
                 }

+ 4 - 1
TEAMModelOS/ClientApp/src/api/blob.js

@@ -115,5 +115,8 @@ export default {
         })
 
         // return post('/blob/bloblog-list', data)
-    }
+    },
+    blobRename: function (data) {
+        return post('/blob/bloblog-rename', data)
+    },
 }

+ 5 - 0
TEAMModelOS/ClientApp/src/api/studentWeb.js

@@ -260,4 +260,9 @@ export default {
     getSchoolTeacher: function (data) {
         return post('/school/teacher/get-teacher-all', data)
     },
+
+    // 加入课程
+    getAddClass: function (data) {
+        return post('/school/course/get-list-by-no', data)
+    },
 }

+ 12 - 0
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/home-view.css

@@ -286,6 +286,18 @@
   margin: 0px -28px;
   margin-bottom: -9px;
 }
+.home-view .calenderCard .addClass {
+  margin-bottom: 30px;
+}
+.home-view .calenderCard .addClass .ivu-input-icon {
+  margin-right: 19%;
+}
+.home-view .calenderCard .addClass button {
+  color: #ffffff;
+  background-color: #64AE16;
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
 @media screen and (max-width: 1280px) and (min-width: 992px) {
   .calenderCard .list-block {
     height: auto;

+ 14 - 0
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/home-view.less

@@ -301,6 +301,20 @@
       margin: 0px -28px;
       margin-bottom: -9px;
     }
+    .addClass {
+      margin-bottom: 30px;
+
+      .ivu-input-icon {
+        margin-right: 19%;
+      }
+
+      button {
+        color: #ffffff;
+        background-color: #64AE16;
+        border-top-left-radius: 0;
+        border-bottom-left-radius: 0;
+      }
+    }
   }
 }
 @media screen and (max-width: 1280px) and (min-width: 992px) {

+ 12 - 0
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/setting-view.css

@@ -1,7 +1,19 @@
+/*常用色票*/
+/*回傳的狀態文字,標題段落的方框,分頁*/
+/*標題類型的標記,編序教材附件按鈕*/
+/*各類項目的懸停與選中狀態*/
 .setting-view {
   overflow: hidden;
   position: relative;
 }
+.setting-view h1,
+.setting-view h2,
+.setting-view h3,
+.setting-view h4,
+.setting-view h5,
+.setting-view h6 {
+  display: block;
+}
 .setting-view input[type="checkbox"] {
   cursor: pointer;
   outline: none;

+ 8 - 4
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/setting-view.less

@@ -4,6 +4,10 @@
   overflow: hidden;
   position: relative;
 
+  h1, h2, h3, h4, h5, h6{
+    display: block;
+  }
+
   //設定勾選框
   input[type="checkbox"] {
     cursor: pointer;
@@ -12,7 +16,7 @@
     background-color: #fafafa;
     border: 1px solid #cacece;
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05),
-      inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05);
+    inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05);
     padding: 9px;
     border-radius: 3px;
     display: inline-block;
@@ -23,14 +27,14 @@
   input[type="checkbox"]:active,
   input[type="checkbox"]:checked:active {
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05),
-      inset 0px 1px 3px rgba(0, 0, 0, 0.1);
+    inset 0px 1px 3px rgba(0, 0, 0, 0.1);
   }
   input[type="checkbox"]:checked {
     background-color: @primary;
 
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05),
-      inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05),
-      inset 15px 10px -12px rgba(255, 255, 255, 0.1);
+    inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05),
+    inset 15px 10px -12px rgba(255, 255, 255, 0.1);
     color: #ffffff;
   }
 

+ 0 - 13
TEAMModelOS/ClientApp/src/common/BaseUserPoptip.vue

@@ -106,19 +106,6 @@ export default {
             this.userInfo.username = this.user.name
             this.userInfo.nameColor = this.randomColor()
             this.userInfo.courseNum = user_profile.courses.length
-            this.getSize()
-        },
-        //获取Blob空间信息
-        getSize() {
-            BlobTool.getContainerSize(this.$store.state.userInfo.TEAMModelId, 'private').then(
-                res => {
-                    this.sizeInfo = res
-                },
-                err => {
-                    console.log('err',err)
-                    this.$Message.error(this.$t('utils.caclErrorL'))
-                }
-            )
         },
         //计算空间占比
         getPercent(size) {

+ 8 - 0
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReport.vue

@@ -456,13 +456,21 @@
                 this.repairSource = source
             },
             showTest() {
+                this.$router.push({
+                    name: "eventView/evaluation",
+                    /* params: {
+                        papers: this.paperInfo
+                    } */
+                })
                 if (this.examInfo.subject !== undefined) {
                     if(this.testState != 1) {
                         if(this.rightAns.wrong > 0) {
                             this.$store.commit("ToggleLessonTestPopWithSubject", this.examInfo)
+                            localStorage.setItem("subjectNow", JSON.stringify(this.examInfo))
                         }
                     } else {
                         this.$store.commit("ToggleLessonTestPopWithSubject", this.examInfo)
+                        localStorage.setItem("subjectNow", JSON.stringify(this.examInfo))
                     }
                 }
             },

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 47 - 56
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperTest.vue


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

@@ -1,7 +1,7 @@
 <template>
     <div class="lesson-test">
         <br />
-        <PaperTest :papers="selectData" v-if="getisOpenLessonTestPopNow" /> 
+        <!-- <PaperTest :papers="selectData" v-if="getisOpenLessonTestPopNow" />  -->
         <div>
             <div class="load-box">
                 <load :active.sync="isLoad"
@@ -64,13 +64,13 @@
     import EventBasicInfo from "../../EventBasicInfo";
     import LessonTestReport from "./LessonTestReport";
     import StudentScore from "./LessonTestReportCharts/StudentScore";
-    import PaperTest from "./PaperTest";
+    // import PaperTest from "./PaperTest";
     export default {
         name: "PaperView",
         components: {
             EventBasicInfo,
             LessonTestReport,
-            PaperTest,
+            // PaperTest,
             Load,
             StudentScore
         },
@@ -247,7 +247,9 @@
                             this.isExamDown = true
                         }
                         this.$store.commit("SetPaperInfo", this.selectData)
+                        localStorage.setItem("paperInfo", JSON.stringify(this.selectData))
                         this.$store.commit("SetExamInfo", data)
+                        localStorage.setItem("examInfo", JSON.stringify(data))
                     }
                 }
             },
@@ -272,12 +274,15 @@
                 "getItemTitle",
                 "getCurrentSubject",
             ]),
+            nowActive() {
+                return this.getItemTitle
+            },
             listData() {
                 return this.getCurrentSubject;
             }
         },
         watch: {
-            $route: {
+            /* $route: {
                 handler() {
                     this.isExamDown = false
                     this.examData = []
@@ -285,6 +290,13 @@
                 },
                 // 深度观察监听
                 deep: true
+            }, */
+            nowActive: {
+                handler() {
+                    this.isExamDown = false
+                    this.examData = []
+                    this.getPaperData()
+                }
             },
             listData: {
                 handler() {

+ 65 - 61
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventList.vue

@@ -74,74 +74,77 @@
             </div>
             <!--活動清單分頁-->
             <div class="list-block"
-                :style="{ height: hideIconbtn == false ? '84vh' : '90vh' }">
+                :style="{ height: !hideIconbtn ? '84vh' : '90vh' }">
                 <!-- 空 -->
-                <div v-show="isListNoItem == true" class="emptycondition">
+                <div v-show="isListNoItem" class="emptycondition">
                     <svg-icon icon-class="empty-white-box" class="empty-Icon" />
                     <br />
                     {{ $t("studentWeb.empty") }}
                 </div>
-                <div v-if="isListNoItem == false">
-                    <div :id="`event${item.id}`"
-                         class="list-new"
-                         @click="sentSelectedEventTitle(item)"
-                         :class="{ 'list-item-selected': selectedCondition(item) }"
-                         v-for="(item, index) in eventShow"
-                         :key="index"
-                    >
-                        <div class="list-new-icon">
-                            <svg-icon v-if="item.eventType == 'HomeWork'" icon-class="doc" />
-                            <svg-icon v-if="item.eventType == 'Preview'"  icon-class="selflearninginTime" />
-                            <svg-icon v-if="item.eventType == 'Exam'" icon-class="multiTest" />
-                            <svg-icon v-if="item.eventType == 'Vote'" icon-class="vote" />
-                            <svg-icon v-if="item.eventType == 'Survey'" icon-class="quesnaire" />
-                        </div>
-                        <div v-if="item.eventType == 'Exam'" class="list-new-test">
-                            <p class="list-item-title">
-                                <span class="list-item-typeMark">{{item.owner == 'school' ? $t('studentWeb.public.schoolExam'):$t('studentWeb.public.privateExam')}}</span>
-                                <span>{{ item.name }}</span>
-                            </p>
-                            <!-- 暂时不改 -->
-                            <!-- <p class="list-item-time isScore" v-show="timeStatus(item) == 'finish'">
-                                得分率:20%
-                            </p> -->
-                            <p class="list-item-time">
-                                {{ dateFormat(item.startTime) }} ~ {{ dateFormat(item.endTime) }}
-                            </p>
-                        </div>
-                        <div v-if="item.eventType == 'Vote'" class="list-new-test">
-                            <p class="list-item-title">
-                                <span class="list-item-typeMark">{{item.owner == 'school' ? $t('studentWeb.public.schoolVote'):$t('studentWeb.public.privateVote')}}</span>
-                                <span>{{ item.name }}</span>
-                            </p>
-                            <p class="list-item-time">
-                                {{ dateFormat(item.startTime) }} ~ {{ dateFormat(item.endTime) }}
-                            </p>
-                        </div>
-                        <div v-if="item.eventType == 'Survey'" class="list-new-test">
-                            <p class="list-item-title">
-                                <span class="list-item-typeMark">{{item.owner == 'school' ? $t('studentWeb.public.schoolSurvey'):$t('studentWeb.public.privateSurvey')}}</span>
-                                <span>{{ item.name }}</span>
-                            </p>
-                            <p class="list-item-time">
-                                {{ dateFormat(item.startTime) }} ~ {{ dateFormat(item.endTime) }}
-                            </p>
-                        </div>
-                        <div class="list-new-type">
-                            <div class="list-new-unDone isAllowRetry" v-show="timeStatus(item) == 'going'">
-                                <span>{{$t("studentWeb.public.going")}}</span>
+                <vuescroll>
+                    <div v-if="!isListNoItem" style="margin-bottom: 50px">
+                        <div :id="`event${item.id}`"
+                            class="list-new"
+                            @click="sentSelectedEventTitle(item)"
+                            :class="{ 'list-item-selected': selectedCondition(item) }"
+                            v-for="(item, index) in eventShow"
+                            :key="index"
+                        >
+                            <div class="list-new-icon">
+                                <svg-icon v-if="item.eventType == 'HomeWork'" icon-class="doc" />
+                                <svg-icon v-if="item.eventType == 'Preview'"  icon-class="selflearninginTime" />
+                                <svg-icon v-if="item.eventType == 'Exam'" icon-class="multiTest" />
+                                <svg-icon v-if="item.eventType == 'Vote'" icon-class="vote" />
+                                <svg-icon v-if="item.eventType == 'Survey'" icon-class="quesnaire" />
                             </div>
-                            <div class="list-new-unDone" v-show="timeStatus(item) == 'finish'">
-                                <span class="isOvertime">{{$t("studentWeb.public.finish")}}</span>
+                            <div v-if="item.eventType == 'Exam'" class="list-new-test">
+                                <p class="list-item-title">
+                                    <span class="list-item-typeMark">{{item.owner == 'school' ? $t('studentWeb.public.schoolExam'):$t('studentWeb.public.privateExam')}}</span>
+                                    <span>{{ item.name }}</span>
+                                </p>
+                                <!-- 暂时不改 -->
+                                <!-- <p class="list-item-time isScore" v-show="timeStatus(item) == 'finish'">
+                                    得分率:20%
+                                </p> -->
+                                <p class="list-item-time">
+                                    {{ dateFormat(item.startTime) }} ~ {{ dateFormat(item.endTime) }}
+                                </p>
                             </div>
-                            <!-- 暂时不改 -->
-                            <!-- <div class="list-new-unDone isWrongPra" v-show="timeStatus(item) == 'finish' && item.eventType == 'Exam'">
-                                <span class="">{{$t("studentWeb.exam.report.wrongPractice")}}</span>
-                            </div> -->
+                            <div v-if="item.eventType == 'Vote'" class="list-new-test">
+                                <p class="list-item-title">
+                                    <span class="list-item-typeMark">{{item.owner == 'school' ? $t('studentWeb.public.schoolVote'):$t('studentWeb.public.privateVote')}}</span>
+                                    <span>{{ item.name }}</span>
+                                </p>
+                                <p class="list-item-time">
+                                    {{ dateFormat(item.startTime) }} ~ {{ dateFormat(item.endTime) }}
+                                </p>
+                            </div>
+                            <div v-if="item.eventType == 'Survey'" class="list-new-test">
+                                <p class="list-item-title">
+                                    <span class="list-item-typeMark">{{item.owner == 'school' ? $t('studentWeb.public.schoolSurvey'):$t('studentWeb.public.privateSurvey')}}</span>
+                                    <span>{{ item.name }}</span>
+                                </p>
+                                <p class="list-item-time">
+                                    {{ dateFormat(item.startTime) }} ~ {{ dateFormat(item.endTime) }}
+                                </p>
+                            </div>
+                            <div class="list-new-type">
+                                <div class="list-new-unDone isAllowRetry" v-show="timeStatus(item) == 'going'">
+                                    <span>{{$t("studentWeb.public.going")}}</span>
+                                </div>
+                                <div class="list-new-unDone" v-show="timeStatus(item) == 'finish'">
+                                    <span class="isOvertime">{{$t("studentWeb.public.finish")}}</span>
+                                </div>
+                                <!-- 暂时不改 -->
+                                <!-- <div class="list-new-unDone isWrongPra" v-show="timeStatus(item) == 'finish' && item.eventType == 'Exam'">
+                                    <span class="">{{$t("studentWeb.exam.report.wrongPractice")}}</span>
+                                </div> -->
+                            </div>
+                                
                         </div>
-                            
                     </div>
-                </div>
+                </vuescroll>
+                
 
 
                 <!-- <div :id="`event${item.id}`"
@@ -473,13 +476,13 @@ import { mapGetters, mapState } from 'vuex';
             },
             // 展示活动
             sentSelectedEventTitle(item) {
-                this.$router.push({
+                /* this.$router.push({
                     path: "/studentWeb/eventView",
                     query: {
                         id: item.id,
                         type: item.eventType
                     }
-                })
+                }) */
                 // 活动信息
                 this.$store.commit("SetPaperInfo", item)
                 ////螢幕寬度<767px時,直接關掉sidebar
@@ -488,6 +491,7 @@ import { mapGetters, mapState } from 'vuex';
                 }
                 ////改變ItemName的狀態 vuex mutations
                 this.$store.commit("ChangeItemName", item);
+                localStorage.setItem("Item", JSON.stringify(item))
                 ////重置問卷
                 this.$store.commit('resetSurvey', true)
                 ////重置預習活動

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

@@ -107,10 +107,10 @@
                 "getItemTitle"
             ])
         },
-        beforeRouteLeave: function(to, from, next) {
+        /* beforeRouteLeave: function(to, from, next) {
             this.$store.commit("ChangeItemName", {})
             next()
-        }
+        } */
     };
 </script>
 <style scoped>

+ 3 - 3
TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseList.vue

@@ -339,8 +339,8 @@ export default {
                                             delete data.schedule
                                             data.school = list[i].stuCourse.school
                                             data.scope = list[i].stuCourse.scope
-                                            // 拼接得到一个唯一的标识
-                                            data.unique = 'class' + list[i].course.no + list[i].course.schedule[j].time[m].week + list[i].course.schedule[j].time[m].id
+                                            // 拼接得到一个唯一的标识(目前有同一天的同一节数的课程,所以加入j 和m 来区分)
+                                            data.unique = 'class' + list[i].course.no + list[i].course.schedule[j].time[m].week + list[i].course.schedule[j].time[m].id + j + m
                                             fixList.push(this.getNewClass(data, list[i].course.schedule[j], m))
                                         }
                                     }
@@ -353,7 +353,7 @@ export default {
                                             delete data.schedule
                                             data.school = list[i].stuCourse.school
                                             data.scope = list[i].stuCourse.scope
-                                            data.unique = 'person' + list[i].course.no + list[i].course.schedule[j].time[m].week + list[i].course.schedule[j].time[m].id
+                                            data.unique = 'person' + list[i].course.no + list[i].course.schedule[j].time[m].week + list[i].course.schedule[j].time[m].id + j + m
                                             personList.push(this.getNewClass(data, list[i].course.schedule[j], m))
                                         }
                                     }

+ 1 - 0
TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseListView.vue

@@ -60,6 +60,7 @@ export default {
     this.$emit("onNavNo", this.MyNo);
     this.$emit("onNavName", this.MyName);
     this.$store.commit("ToggleSidebar", true);
+    this.$store.commit("ChangeItemName", {})
     this.findSchoolInfo()
     this.getSchoolTea()
   },

+ 69 - 17
TEAMModelOS/ClientApp/src/components/student-web/HomeView/HomeView.vue

@@ -3,6 +3,35 @@
         <Row :gutter="30" v-if="spanCharts == false">
             <i-col :xs="0" :sm="0" :md="0" :lg="1"></i-col>
             <i-col :xs="24" :sm="24" :md="12" :lg="7">
+                <!-- 加入课程 -->
+                <Card :bordered="true" class="calenderCard">
+                    <p slot="title">
+                        <svg-icon class="titleIcon" icon-class="calander" />
+                        <span class="title">{{ $t("studentWeb.addCourse") }}</span>
+                    </p>
+                    <div class="addClass">
+                        <Input clearable
+                            v-model="addClassNo"
+                            :placeholder="$t('studentWeb.home.classPla')"
+                        >
+                            <Button slot="append" @click="addClass">{{ $t('studentWeb.home.joinClass') }}</Button>
+                        </Input>
+                    </div>
+                </Card>
+                <!-- 课程清单 -->
+                <router-link to="/studentWeb/courseList">
+                    <Card style="overflow: hidden; margin-bottom: 30px">
+                        <h3 style="color: #575757; font-weight: 700; margin: 10px 0 15px">
+                            <svg-icon class="titleIcon" icon-class="course" />
+                            {{ $t("studentWeb.coursesCardTitle") }}
+                            <Icon type="ios-arrow-forward" />
+                        </h3>
+                        <!-- <p class="course-new">{{ $t("evaluation.noData") }}</p> -->
+                        <!-- <p class="course-new">{{ $t("evaluation.noData") }}</p> -->
+                        <!-- <p class="course-new">今日课程:英语</p>
+                        <p class="course-new">{{ $t("studentWeb.newAddCourse") }}</p> -->
+                    </Card>
+                </router-link>
                 <!-- 行事历 -->
                 <Card :bordered="true" class="calenderCard" @click.native="noData">
                     <p slot="title">
@@ -67,20 +96,7 @@
                         </div>
                     </div>
                 </Card>
-                <!-- 课程清单 -->
-                <router-link to="/studentWeb/courseList">
-                    <Card style="overflow: hidden">
-                        <h3 style="color: #575757; font-weight: 700; margin: 10px 0 15px">
-                            <svg-icon class="titleIcon" icon-class="course" />
-                            {{ $t("studentWeb.coursesCardTitle") }}
-                            <Icon type="ios-arrow-forward" />
-                        </h3>
-                        <!-- <p class="course-new">{{ $t("evaluation.noData") }}</p> -->
-                        <!-- <p class="course-new">{{ $t("evaluation.noData") }}</p> -->
-                        <!-- <p class="course-new">今日课程:英语</p>
-                        <p class="course-new">{{ $t("studentWeb.newAddCourse") }}</p> -->
-                    </Card>
-                </router-link>
+                
             </i-col>
             <i-col :xs="24" :sm="24" :md="12" :lg="8">
                 <!-- 活动任务清单 -->
@@ -211,6 +227,7 @@ import TwoLineChart from "../HomeView/ChartCarousel/TwoLineChart";
 import StackBarChart from "../HomeView/ChartCarousel/StackBarChart";
 import SplineAreaChart from "../HomeView/ChartCarousel/SplineAreaChart";
 import StudyHeatMap from "../HomeView/ChartCarousel/StudyHeatMap";
+import studentWeb from '../../../api/studentWeb';
 
 export default {
     name: "HomeView",
@@ -276,6 +293,7 @@ export default {
             MyEventData: "--", // 没有数据用--表示,后面再从vuex里拿
             spanCharts: false,
             todaydaylineListHeight: 0,
+            addClassNo: "", //加入课程的代码
         };
     },
 
@@ -286,13 +304,47 @@ export default {
         getCurrentLang() {
             return localStorage.getItem("lang")
         },
+        setSpanCharts() {
+            this.spanCharts = !this.spanCharts
+        },
+        // 加入课程
+        addClass() {
+            if(this.addClassNo) {
+                let isStu = true
+                this.$store.state.userInfo.roles.map(item => {
+                    if(item == "teacher") {
+                        isStu = false
+                    }
+                })
+                let req = {
+                    stuListNo: this.addClassNo,
+                    studentId: "",
+                    tmdId: ""
+                }
+                if(isStu) {
+                    req.studentId = this.$store.state.userInfo.sub
+                } else {
+                    req.tmdId = this.$store.state.userInfo.sub
+                }
+                this.$api.studentWeb.getAddClass(req).then(res => {
+                    if(res.status == 0) {
+                        // -1 加入失败,0加入成功,1参数异常,2重复加入
+                        this.$Message.success(this.$t('studentWeb.courseType.success'))
+                        this.addClassNo = ""
+                    } else if(res.status == 2) {
+                        this.$Message.warning(this.$t('studentWeb.courseType.warning'))
+                    } else {
+                        this.$Message.error(this.$t('studentWeb.courseType.error'))
+                    }
+                })
+            }
+        },
+
+        /* ============未调用======== */
         sentSelectedEventTitle: function (item) {
             this.$router.push("/studentWeb/eventView#" + item.eventID)
             this.$store.commit("ChangeItemName", item)
         },
-        setSpanCharts() {
-            this.spanCharts = !this.spanCharts
-        },
     },
 
     created() {

+ 2 - 2
TEAMModelOS/ClientApp/src/components/student-web/HomeView/MissionListCard.vue

@@ -196,10 +196,10 @@
             },
             sentSelectedEventTitle: function (item) {
                 this.$router.push({path: "/studentWeb/eventView",
-                  query: {
+                  /* query: {
                     id: item.id,
                     type: item.eventType
-                }
+                } */
                 })
                 //螢幕寬度<767px時,直接關掉sidebar
                 if (window.innerWidth <= 768) {

+ 14 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/studentWeb.js

@@ -5,6 +5,7 @@ export default {
         setting: 'Personal Settings',
         logout: 'Log out',
         joinClass: 'Join Course',
+        classPla: "请输入课程代码",
         exam: 'Assessment',
         vote: 'Poll',
         survey: 'Survey'
@@ -90,6 +91,12 @@ export default {
     defaultClassPlace: 'F504 classroom in the fifth teaching building',
     todaydeadlineList: 'Today deadline event reminder',
     endsTodayTime: 'ends today at 23:59',
+    addCourse: "输入课程代码,加入课程",
+    courseType: {
+        success: "课程加入成功",
+        warning: "重复加入课程!",
+        error: "API错误"
+    },
     coursesCardTitle: 'My Course List',
     newAddCourse: 'The latest addition: Elective Mathematics (2)',
     missionListCardTitle: 'Activity Task List',
@@ -306,6 +313,7 @@ export default {
             next: 'Next Question',
             myAnswerSheet: 'My Answer Card',
             exitQuizhint: 'Leave Assessment Reminder',
+            exitQuizhintD: "离开页面,您已作答的数据不会保存,是否确认退出?",
             exitQuizhintDe: 'The system has detected that you have not yet "submit your answers", if you choose "OK",',
             exitQuizhintDes: 'the current answer will not be saved, and the assessment will need to be retaken next time.',
             cancel: 'Cancel',
@@ -428,7 +436,11 @@ export default {
         submitSuccess: '提交成功',
         overTime: '不在作答时间范围内!',
         answerErr: '作答数据错误!',
-        fileErr: '问卷获取失败!'
+        fileErr: '问卷获取失败!',
+        noAns1: "你有",
+        noAns2: "题",
+        noAns3: "未答,请先完成再提交",
+        noAns4: "确定",
     },
     'informview-title': 'Notification Overview',
     view: 'Go to view',
@@ -456,6 +468,7 @@ export default {
         classID: 'Course Code',
         classTime: 'Class Time',
         classroom: 'Instructor',
+        teacher: 'Teaching teacher',
         'co-teacher': 'Co-teacher',
         addedTime: 'Join Course Date',
         syllabus: 'Syllabus Overview',

+ 2 - 1
TEAMModelOS/ClientApp/src/locale/lang/en-US/teachContent.js

@@ -59,5 +59,6 @@ export default {
   applyGrade: 'Applicable Grade',
   public: 'Public Resources',
   startDown: 'Start Downloading',
-  videoTips:'Friendly reminder: Only MP4 format is supported for online video playing! '
+  videoTips:'Friendly reminder: Only MP4 format is supported for online video playing! ',
+  spaceTips: '學校可用空間 = 學校總空間 - 分配給教師的空間'
 }

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

@@ -91,6 +91,13 @@ export default {
     defaultClassPlace: '第五教学楼 F504 教室',
     todaydeadlineList: '今日截止活动提醒 ',
     endsTodayTime: '今日23:59截止',
+    addCourse: "输入课程代码,加入课程",
+    courseType: {
+        success: "课程加入成功",
+        warning: "重复加入课程!",
+        error: "API错误"
+    },
+
     coursesCardTitle: '我的课程清单',
     newAddCourse: '最新加入:选修数学(二)',
     missionListCardTitle: '活动任务清单',
@@ -307,6 +314,7 @@ export default {
             next: '下一题',
             myAnswerSheet: '我的答题卡',
             exitQuizhint: '离开测验提示',
+            exitQuizhintD: "离开页面,您已作答的数据不会保存,是否确认退出?",
             exitQuizhintDe: '系统检测您尚未「交卷」,如您选择「确定」,',
             exitQuizhintDes: '则目前作答将不保存,下次需重新测验',
             cancel: '取消',

+ 30 - 29
TEAMModelOS/ClientApp/src/locale/lang/zh-CN/teachContent.js

@@ -1,6 +1,6 @@
 export default {
-  recent:'最近',
-  recentTips:'临时缓存最近上传的资源,最多保存20条。退出账号或更换电脑缓存记录将会被清空。',
+  recent: '最近',
+  recentTips: '临时缓存最近上传的资源,最多保存20条。退出账号或更换电脑缓存记录将会被清空。',
   filterRes: '教材',
   filterPicture: '图片',
   filterVideo: '视频',
@@ -33,31 +33,32 @@ export default {
   props7: '存储空间不足!',
   uploadText: '点击或者拖拽上传',
 
-  resTips:'HiTeach生成的课件,只支持HTEX格式的教材预览',
-  space:'空间:',
-  calcing:'计算中...',
-  blobFull:'(已满)',
-  otherType:'其他类型',
-  appData:'应用数据',
-  delBatch:'批量删除',
-  viewport1:'列表模式',
-  viewport2:'图片模式',
-  bottomTips:'--------我也是有底线的--------',
-  notAudio:'您的浏览器不支持 audio 元素。',
-  nameOk:'名称修改成功',
-  nameErr:'名称修改失败',
-  fullTips:'存储空间已满,无法上传',
-  loadAll:'已经加载全部',
-  authErr:'获取Blob授权失败',
-  sizeErr:'空间计算异常',
-  delBatchTips1:'请在列表模式使用批量删除!',
-  delBatchTips2:'请先选择需要删除的文件!',
-  specialChart:'不能包含特殊字符',
-  noData:'暂无数据',
-  applyPd:'适用学段',
-  applySub:'适用学科',
-  applyGrade:'适用年级',
-  public:'公共资源',
-  startDown:'开始下载',
-  videoTips:'温馨提示:视频只支持MP4格式在线播放!'
+  resTips: 'HiTeach生成的课件,只支持HTEX格式的教材预览',
+  space: '空间:',
+  calcing: '计算中...',
+  blobFull: '(已满)',
+  otherType: '其他类型',
+  appData: '应用数据',
+  delBatch: '批量删除',
+  viewport1: '列表模式',
+  viewport2: '图片模式',
+  bottomTips: '--------我也是有底线的--------',
+  notAudio: '您的浏览器不支持 audio 元素。',
+  nameOk: '名称修改成功',
+  nameErr: '名称修改失败',
+  fullTips: '存储空间已满,无法上传',
+  loadAll: '已经加载全部',
+  authErr: '获取Blob授权失败',
+  sizeErr: '空间计算异常',
+  delBatchTips1: '请在列表模式使用批量删除!',
+  delBatchTips2: '请先选择需要删除的文件!',
+  specialChart: '不能包含特殊字符',
+  noData: '暂无数据',
+  applyPd: '适用学段',
+  applySub: '适用学科',
+  applyGrade: '适用年级',
+  public: '公共资源',
+  startDown: '开始下载',
+  videoTips: '温馨提示:视频只支持MP4格式在线播放!',
+  spaceTips: '学校可用空间 = 学校总空间 - 分配给教师的空间'
 }

+ 7 - 0
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/studentWeb.js

@@ -91,6 +91,12 @@ export default {
     defaultClassPlace: '第五教學樓 F504 教室',
     todaydeadlineList: '今日截止活動提醒 ',
     endsTodayTime: '今日23:59截止',
+    addCourse: "輸入課程代碼,加入課程",
+    courseType: {
+        success: "課程加入成功",
+        warning: "重複加入課程!",
+        error: "API錯誤"
+    },
     coursesCardTitle: '我的課程清單',
     newAddCourse: '最新加入:選修數學(二)',
     missionListCardTitle: '活動任務清單',
@@ -307,6 +313,7 @@ export default {
             next: '下一題',
             myAnswerSheet: '我的答題卡',
             exitQuizhint: '離開測驗提示',
+            exitQuizhintD: "離開頁面,您已作答的數據不會保存,是否確認退出?",
             exitQuizhintDe: '系統檢測您尚未「交卷」,如您選擇「確定」,',
             exitQuizhintDes: '則目前作答將不保留,下次需重新測驗',
             cancel: '取消',

+ 2 - 1
TEAMModelOS/ClientApp/src/locale/lang/zh-TW/teachContent.js

@@ -59,5 +59,6 @@ export default {
   applyGrade:'適用年級',
   public:'公共資源',
   startDown:'開始下載',
-  videoTips:'溫馨提示:視頻只支持MP4格式在線播放! '
+  videoTips:'溫馨提示:視頻只支持MP4格式在線播放! ',
+  spaceTips: '學校可用空間 = 學校總空間 - 分配給教師的空間'
 }

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

@@ -772,6 +772,11 @@ export const routes = [
 			path: "eventView",
 			component: resolve => require(['@/components/student-web/EventView/EventView'], resolve),
 		},
+		{
+			name: "eventView/evaluation",
+			path: "eventView/evaluation",
+			component: resolve => require(['@/components/student-web/EventView/EventContentTypeTemplate/PaperTest'], resolve),
+		},
 		{
 			name: "eventView/:name",
 			path: "eventView/:name",

+ 4 - 3
TEAMModelOS/ClientApp/src/store/index.js

@@ -34,6 +34,7 @@ const mutations = {
         state.schoolSas = obj
     },
     setUserInfo(state, obj) { //obj还是原来的逻辑传参,没有变化。只是这里内部逻辑添加了判断是否为班主任,班主任对应的班级id,以及授课班级id三个字段的处理逻辑
+        console.log('刷新用户信息', obj)
         obj.schoolCode = obj.schoolCode || GLOBAL.DEFAULT_SCHOOL_CODE
         obj.hasSchool = obj.schoolCode !== GLOBAL.DEFAULT_SCHOOL_CODE
         obj.isHeadmaster = false      //默认不是班主任
@@ -69,11 +70,9 @@ const mutations = {
     },
     //登录成功后设置个人空间大小
     setPrivateSpace(state, size) {
-        console.log('设置个人空间:', size)
         state.PRIVATE_SPACE = size
     },
     setSchoolSpace(state, size) {
-        console.log('设置学校空间:', size)
         state.SCHOOL_SPACE = size
     }
 }
@@ -86,7 +85,9 @@ const actions = ({
 })
 
 const getters = {
-    radarData: state => state.totalAnalysis.radarData
+    radarData: state => state.totalAnalysis.radarData,
+    PRIVATE_SPACE: state => state.PRIVATE_SPACE,
+    SCHOOL_SPACE: state => state.SCHOOL_SPACE
 }
 
 export default new Vuex.Store({

+ 4 - 5
TEAMModelOS/ClientApp/src/store/module/answerSheet.js

@@ -80,7 +80,7 @@ export default {
 			"threshValuePhone": [190, 220, 10], //二值化区间(手机)
 
 			// "huiSize": ANCHORPROP.width, //单个整体回字大小
-			"huiSize": 25, //绘制有误差,先暂时按照实际大小写固定值(待确认)
+			"huiSize": 24, //绘制有误差,先暂时按照实际大小写固定值(待确认)
 			"minHuiSize": 0.005, //最小回字的大小比列, 以回字最中间的方块为最小
 			"maxHuiSize": 0.1, //最大回字的大小比列, 以回字最外层的方块为最大
 			"notHuisRect": [0.3, 0.3, 0.4, 0.4], //不检测回字区域(pc版辅助效果20%, 手机版50%)
@@ -152,7 +152,6 @@ export default {
 			val.height = Number(val.height.toFixed())
 			val.id = val.pageNum + '-' + val.index
 			val.count = 0
-			console.log(val.id,val.height)
 			// 如果是补充框 ID要保持和上一个ID一致
 			if(val.index === 1 && val.isFix){
 				let prePageItems = state.config.contents.filter(i => i.pageNum === val.pageNum - 1)
@@ -160,7 +159,7 @@ export default {
 				val.id = preItem.id
 			}
 			if(state.config.contents.length){
-				let infoItem = state.config.contents.find(i => i.id === val.id)
+				let infoItem = state.config.contents.find(i => i.id === val.id && i.pageNum === val.pageNum)
 				if(infoItem){
 					infoItem.height= val.height
 					infoItem.y= val.y
@@ -180,8 +179,8 @@ export default {
 			state.pages++
 		},
 		clearPage(state) {
-			state.pages = 1
-			 state.config.contents.filter(i => i.type !== 2)
+			 state.pages = 1
+			 state.config.contents = state.config.contents.filter(i => i.type !== 2)
 		},
 		clearAllConfig(state){
 			state.config.contents = []

+ 59 - 53
TEAMModelOS/ClientApp/src/utils/html2pdf.js

@@ -3,59 +3,65 @@ import JsPDF from 'jspdf'
 import store from '@/store'
 import domtoimage from './dom_to_image';
 export default {
-    install(Vue, options) {
-        Vue.prototype.getPdf = () => {
-			return new Promise((r,j) => {
-            var title = store.state.answerSheet.paperItem.name
-            setTimeout(() => {
-                domtoimage.toJpeg(document.querySelector('#pdfDom'), { bgcolor: '#fff'})
-                    .then((pageData) => {
-                        let img = new Image();
-                        img.src = pageData;
-                        img.onload = function () {
-                            console.log('height', img.height);
-                            console.log('width', img.width);
-                            const a4Height = 841.89
-                            const a4Width = 595.28
-                            let contentWidth = img.width
-                            let contentHeight = img.height
-                            let pageHeight = contentWidth / a4Width * a4Height
-                            let leftHeight = contentHeight
-                            let position = 0
-							//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
-                            let imgWidth = a4Width
-                            let imgHeight = a4Width / contentWidth * contentHeight
-                            console.log(pageData)
-                            let PDF = new JsPDF({
-                                unit: 'pt',
-                                format: 'a4',
-                                putOnlyUsedFonts: true,
-                                floatPrecision: 16
-                            })
-                            // 如果是一页的情况
-                            if (leftHeight <= pageHeight) {
-                                PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
-                            } else {
-                                // 如果超出则多页
-                                while (leftHeight > 0) {
-                                    PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
-                                    leftHeight -= pageHeight
-                                    position -= a4Height
-                                    if (leftHeight > 0) {
-                                        PDF.addPage()
-                                    }
-                                }
-                            }
-                            PDF.save(title + '.pdf')
-							r(200)
-                        }
-                    })
-                    .catch(function (error) {
-                        console.error('oops, something went wrong!', error);
-						j(error)
-                    });
-            }, 1000)
+	install(Vue, options) {
+		Vue.prototype.getPdf = () => {
+			return new Promise((r, j) => {
+				var title = store.state.answerSheet.paperItem.name
+				setTimeout(() => {
+					domtoimage.toJpeg(document.querySelector('#pdfDom'), {
+							bgcolor: '#fff'
+						})
+						.then((pageData) => {
+							let img = new Image();
+							img.src = pageData;
+							img.onload = function() {
+								console.log('height', img.height);
+								console.log('width', img.width);
+								// const a4Height = 841.89
+								// const a4Width = 595.28
+								const a4Height = 297
+								const a4Width = 210
+								let contentWidth = img.width
+								let contentHeight = img.height
+								let pageHeight = contentWidth / a4Width * a4Height
+								let leftHeight = contentHeight
+								let position = 0
+								//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
+								let imgWidth = a4Width
+								let imgHeight = a4Width / contentWidth * contentHeight
+								console.log(pageData)
+								let PDF = new JsPDF({
+									orientation: 'p',
+									unit: 'mm',
+									format: 'a4',
+									putOnlyUsedFonts: true
+								})
+								// 如果是一页的情况
+								if (leftHeight <= pageHeight) {
+									PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth,
+										imgHeight)
+								} else {
+									// 如果超出则多页
+									while (leftHeight > 0) {
+										PDF.addImage(pageData, 'JPEG', 0, position,
+											imgWidth, imgHeight)
+										leftHeight -= pageHeight
+										position -= a4Height
+										if (leftHeight > 0) {
+											PDF.addPage()
+										}
+									}
+								}
+								PDF.save(title + '.pdf')
+								r(200)
+							}
+						})
+						.catch(function(error) {
+							console.error('oops, something went wrong!', error);
+							j(error)
+						});
+				}, 1000)
 			})
 		}
-    }
+	}
 }

+ 13 - 7
TEAMModelOS/ClientApp/src/utils/sheetConfig.js

@@ -1,7 +1,7 @@
 // const PAPER_W = 794; // 答题卡宽度
 // const PAPER_H = 1124; // 答题卡高度
-const PAPER_W = 750; // 答题卡宽度
-const PAPER_H = 1063.05; // 答题卡高度
+const PAPER_W = 826; // 答题卡宽度
+const PAPER_H = 1165; // 答题卡高度
 const ANCHORPROP = {
     width: 10, // 锚点宽度
     height: 10, // 锚点高度
@@ -13,7 +13,7 @@ const SVG_BORDER_PROP = {
     x:55,
     y:65,
     width:PAPER_W - 55 * 2,
-    height:910
+    height:PAPER_H - 175
 }
 const SVG_BORDER_MB = PAPER_H - SVG_BORDER_PROP.y - SVG_BORDER_PROP.height
 const CONTENT_MT = 70; // 内容与定位框间隔
@@ -31,14 +31,17 @@ const INFO_LEFT_X = 400; // 信息左边X值
 const INFO_LEFT_W = INFO_LEFT_X - CONTENT_ML - ANCHORPROP.width - ANCHORPROP.gapX; // 信息左边框宽度
 const ID_TITLE_H = 40; // 准考证号框高度
 const ID_TITLE_Y = ID_TITLE_H + CONTENT_MT + ANCHORPROP.height + ANCHORPROP.gapY; // 准考证号框Y值
-const NUMBER_CELL_H = 30; // 准考证号框单元格高度
+const NUMBER_CELL_H = 25; // 准考证号框单元格高度
 const NUMBER_ITEM_W = 15.5; // 准考证号填涂宽度
 const NUMBER_ITEM_H = 9.5; // 准考证号填涂高度
-const NUMBER_ITEM_MT = (INFO_H - ID_TITLE_H - NUMBER_CELL_H) / 10.5; // 准考证号填涂上间距
+const NUMBER_ITEM_MT = Math.ceil((INFO_H - ID_TITLE_H - NUMBER_CELL_H) / 10.5); // 准考证号填涂上间距
 const NUMBER_ITEM_ML = 5; // 准考证号填涂左间距
 
 const BLOCK_H = 120
 
+const OBJ_ANCHOR_GAP = 25 //客观题锚点间距
+const OBJ_ANCHOR_H_COUNT = 28 // 客观题横向锚点数量
+
 export {
     PAPER_W,
     PAPER_H,
@@ -63,5 +66,8 @@ export {
     NUMBER_ITEM_H,
     NUMBER_ITEM_MT,
     NUMBER_ITEM_ML,
-    SVG_BORDER_PROP
-}
+    SVG_BORDER_PROP,
+	OBJ_ANCHOR_GAP,
+	OBJ_ANCHOR_H_COUNT
+}
+

+ 38 - 38
TEAMModelOS/ClientApp/src/view/Home.vue

@@ -39,7 +39,7 @@ export default {
     created() {
         let user = JSON.parse(decodeURIComponent(localStorage.userInfo, "utf-8"))
         let user_profile = JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8"))
-		console.log(user_profile)
+        console.log(user_profile)
         if (user_profile.schools) {
             user_profile.schools = user_profile.schools.filter((item) => {
                 return item.status == 'join'
@@ -60,7 +60,7 @@ export default {
                 schoolCode: this.$GLOBAL.DEFAULT_SCHOOL_CODE
             })
         }
-
+        this.$store.commit('setPrivateSpace', user_profile.total || 0)
         // this.$api.service.getNotification({
         //     "from": "ies5:" + (user_profile.defaultschool || user_profile.schools[0].schoolId),
         //     "receiver": user.id
@@ -68,41 +68,41 @@ export default {
         //     console.log('端外通知', res)
         //     this.msgs = res.msgs
         // })
-		this.getAllNotice(user_profile,user.id)
+        this.getAllNotice(user_profile, user.id)
 
 
     },
     methods: {
-		async getAllNotice(user_profile,userId){
-			let schoolNotice = await this.getSchoolNotice(user_profile,userId)
-			let privateNotice = await this.getPrivateNotice(userId)
-			this.msgs = schoolNotice.concat(privateNotice)
-			console.log('端外通知', this.msgs)
-		},
-		getSchoolNotice(user_profile,userId){
-			return new Promise((r,j) => {
-				if(this.hasSchool){
-					this.$api.service.getNotification({
-					    "from": "ies5:" + (user_profile.defaultschool || user_profile.schools[0].schoolId),
-					    "receiver": userId
-					}).then(res => {
-					    r(res.msgs)
-					})
-				}else{
-					r([])
-				}
-			})
-		},
-		getPrivateNotice(userId){
-			return new Promise((r,j) => {
-				this.$api.service.getNotification({
-				    "from": "ies5:private",
-				    "receiver": userId
-				}).then(res => {
-				    r(res.msgs)
-				})
-			})
-		},
+        async getAllNotice(user_profile, userId) {
+            let schoolNotice = await this.getSchoolNotice(user_profile, userId)
+            let privateNotice = await this.getPrivateNotice(userId)
+            this.msgs = schoolNotice.concat(privateNotice)
+            console.log('端外通知', this.msgs)
+        },
+        getSchoolNotice(user_profile, userId) {
+            return new Promise((r, j) => {
+                if (this.hasSchool) {
+                    this.$api.service.getNotification({
+                        "from": "ies5:" + (user_profile.defaultschool || user_profile.schools[0].schoolId),
+                        "receiver": userId
+                    }).then(res => {
+                        r(res.msgs)
+                    })
+                } else {
+                    r([])
+                }
+            })
+        },
+        getPrivateNotice(userId) {
+            return new Promise((r, j) => {
+                this.$api.service.getNotification({
+                    "from": "ies5:private",
+                    "receiver": userId
+                }).then(res => {
+                    r(res.msgs)
+                })
+            })
+        },
         toHome() {
             this.$router.push({ path: '/home/homePage' })
         },
@@ -146,11 +146,11 @@ export default {
         })
 
     },
-	computed:{
-		hasSchool() {
-			return this.$store.state.userInfo.hasSchool;
-		},
-	},
+    computed: {
+        hasSchool() {
+            return this.$store.state.userInfo.hasSchool;
+        },
+    },
     watch: {
         $route: {
             handler(val, oldval) {

+ 31 - 31
TEAMModelOS/ClientApp/src/view/answersheet/BaseEditor.vue

@@ -126,21 +126,22 @@
 			
 			/* 设置填空题和简答题的配置数据 */
 			setSubjectiveConfig(y,height,pageNum,isFix){
+				if(height === 0) return
 				let itemConfig = {
 					"type": 2,
 					"x": CONTENT_START_X - ANCHORPROP.gapX + 5,
 					"y": y - ANCHORPROP.gapY + 5,
 					"width": INFO_W,
 					"height": height,
-					"pageNum": pageNum,
+					"pageNum": this.$store.state.answerSheet.pages,
 					"isFix":isFix || false
-				} 
+				}
+				console.log(JSON.stringify(itemConfig))
 				this.$store.commit('setSubjectiveConfig', itemConfig)
 			},
 
 			/* 切换富文本显示模式(简答题、语文作文、英语作文)*/
 			doSelectModel() {
-				console.log(this.curModel)
 				let editor = this.myEditor
 				editor.txt.clear()
 				let allItemIds = this.$store.state.answerSheet.paperItem.item.map(i => i.id)
@@ -252,10 +253,10 @@
 						if ( curEditorY + curEditorH + lastBottomGap + SVG_BORDER_MB > PAPER_H) {
 							// 如果剩余高度不满足渲染 并且需要补充的区域 则需要进行跨页处理
 							if (leftHeight > 100) {
+								this.setSubjectiveConfig(curEditorY,leftHeight + 20,curItemWhichPage,true)
 								this.$store.commit("addPage");
 								let heightArr = []
 								this.myEditor.config.height = leftHeight + 20;
-								this.setSubjectiveConfig(curEditorY,leftHeight + 20,curItemWhichPage,true)
 								heightArr.push(leftHeight)
 								// 如果渲染的客观题高度在这个区间 才需要在下一页添加补充作答区域 其余全部按照正常 跨页处理不需要补充作答区域
 								let fixCount = Math.ceil(fixHeight / SVG_BORDER_PROP.height)
@@ -290,10 +291,19 @@
 								})
 							} else {
 								// 跨页处理不需要补充作答区域
-								this.$store.commit("addPage");
-								this.setSubjectiveConfig(curEditorY,curEditorH,curItemWhichPage)
-								document.getElementById(this.ids).style.marginTop = (PAPER_H - curEditorY + lastBottomGap + SVG_BORDER_PROP.y) + "px";
-								document.getElementById(this.ids + 'btn').style.top = (PAPER_H - curEditorY + lastBottomGap + SVG_BORDER_PROP.y + 20) + "px";
+									this.$store.commit("addPage");
+								// if(this.$parent.completeItems.map(i => i.id).indexOf(this.curItemId) === 0){
+									this.setSubjectiveConfig(100,curEditorH,curItemWhichPage + 1)
+									this.$EventBus.$emit('titleMovePage',{
+										type:'complete',
+										height:leftHeight
+									})
+								// }else{
+								// 	this.setSubjectiveConfig(curEditorY,curEditorH,curItemWhichPage)
+								// 	document.getElementById(this.ids).style.marginTop = (PAPER_H - curEditorY + lastBottomGap + SVG_BORDER_PROP.y) + "px";
+								// 	document.getElementById(this.ids + 'btn').style.top = (PAPER_H - curEditorY + lastBottomGap + SVG_BORDER_PROP.y + 20) + "px";
+								// }
+								
 							}
 						}else {
 							this.setSubjectiveConfig(curEditorY,curEditorH,curItemWhichPage)
@@ -377,31 +387,17 @@
 						let fixHeight = curEditorH - leftHeight + 20
 						let curItemWhichPage = this.$store.state.answerSheet.pages
 						if(rectTop === 0) return
-						// console.log(itemOrder, '在第几页', curItemWhichPage,rectTop);
-						// if(itemOrder === 6){
-							// console.log(itemOrder, 'rectTop', rectTop);
-							// console.log(itemOrder, 'scrollDis', scrollDis);
-							// console.log(itemOrder, '距离第一页顶点的Y', Y);
-							// console.log(itemOrder, '距离当前页面顶点的Y', curEditorY);
-							// console.log(itemOrder, '高度', curEditorH)
-							// console.log(itemOrder, 'LEFT高度', leftHeight)
-							// console.log(itemOrder, '需要fix的高度', fixHeight)
-						// }
-						
-						
 						// 如果 渲染当前富文本的时候 需要渲染的高度超过当前页的剩余高度 则需要进行加页处理
 						if (curEditorY + curEditorH + lastBottomGap + SVG_BORDER_MB > PAPER_H) {
 							startPageIndex++
-							// console.log(itemOrder, Y , curEditorY , curEditorH , '超出了')
-							console.log('++++++++++++',itemOrder, '需要增加页码')
+							console.log('xxxx',itemOrder,leftHeight)
 							// 如果剩余高度满足渲染一部分区域 并且需要补充的区域 则需要进行跨页处理
 							if (leftHeight > 100) {
+								this.setSubjectiveConfig(curEditorY,leftHeight + 20,curItemWhichPage,true)
 								this.$store.commit("addPage");
 								let heightArr = []
 								this.myEditor.config.height = leftHeight + 20;
-								this.setSubjectiveConfig(curEditorY,leftHeight + 20,curItemWhichPage,true)
 								heightArr.push(leftHeight)
-								console.log(fixHeight)
 								// 如果渲染的客观题高度在这个区间 才需要在下一页添加补充作答区域 其余全部按照正常 跨页处理不需要补充作答区域
 								let fixCount = Math.ceil(fixHeight / SVG_BORDER_PROP.height)
 								for(let i = 0;i < fixCount;i++){
@@ -434,8 +430,11 @@
 								// 跨页处理不需要补充作答区域
 								this.$store.commit("addPage");
 								if(this.$parent.subjectiveItems.map(i => i.id).indexOf(this.curItemId) === 0){
-									this.setSubjectiveConfig(100,curEditorH,curItemWhichPage)
-									this.$EventBus.$emit('titleMovePage','subjective')
+									this.setSubjectiveConfig(100,curEditorH,curItemWhichPage + 1)
+									this.$EventBus.$emit('titleMovePage',{
+										type:'subjective',
+										height:leftHeight
+									})
 								}else{
 									this.setSubjectiveConfig(curEditorY,curEditorH,curItemWhichPage)
 									document.getElementById(this.ids).style.marginTop = (PAPER_H - curEditorY + lastBottomGap + SVG_BORDER_PROP.y) + "px";
@@ -577,6 +576,7 @@
 						localStorage.setItem('scrollDis',scrollDom.getPosition().scrollTop)
 						
 						that.$EventBus.$emit('doRefresh')
+						that.$EventBus.$emit('hasDrag')
 					}
 					// 将鼠标事件绑定到 document 上
 					document.addEventListener("mousemove", mousemove);
@@ -866,7 +866,7 @@
 
 	.sheet-Editor {
 		position: relative;
-		width: 640px;
+		width: 716px;
 		margin: 0 auto;
 		border: 2px solid #000 !important;
 
@@ -886,10 +886,10 @@
 		display: none;
 		position: absolute;
 		top: -32px;
-		left: -1px;
-		width: 590px;
-		border: 1px solid #000 !important;
-		border-bottom: none !important;
+		left: -2px;
+		width: 716px;
+		border: 2px solid #000 !important;
+		border-bottom: 1px solid #dddddd !important;
 	}
 
 	.sheet-Editor .w-e-text-container {

+ 3 - 3
TEAMModelOS/ClientApp/src/view/answersheet/BaseSvgBg.vue

@@ -99,7 +99,7 @@
 				if(this.sheetId){
 					var page = snap
 						.text(
-							200,
+							550,
 							PAPER_H - 40,
 							`ID:${ this.sheetId }`
 							)
@@ -230,8 +230,8 @@
 <style lang="less">
 	.sheet-paper-title {
 		position: absolute;
-		top: 70px;
-		left: 80px;
+		top: 60px;
+		left: 58px;
 
 		.w-e-text {
 			font-size: 18px !important;

+ 2 - 2
TEAMModelOS/ClientApp/src/view/answersheet/BaseTitleEditor.vue

@@ -204,7 +204,7 @@ export default {
 <style>
 .sheet-title-Editor {
   position: relative;
-  width: 640px;
+  width: 710px;
   margin: 10px auto 0 auto;
   border: 0 solid #000 !important;
 }
@@ -214,7 +214,7 @@ export default {
   position: absolute;
   top: -32px;
   left: -1px;
-  width: 640px;
+  width: 710px;
   border: 1px solid #000 !important;
   border-bottom: none !important;
 }

+ 11 - 2
TEAMModelOS/ClientApp/src/view/answersheet/SheetBaseInfo.vue

@@ -178,9 +178,14 @@ export default {
 		      });
 		  
 		    this.idNumberBox.add(c1, c2);
+			const context = require.context('@/icons/answersheet', false, /.svg/)
+			const frames = []
+			context.keys().forEach(k => {
+				frames.push(context(k))
+			})
 		    // 画填涂svg图片
 		    for (let i = 0; i < 10; i++) {
-		      let img = require("@/icons/answersheet/" + i + ".svg");
+		      let img = frames[i];
 		      let c3 = snap.image(
 		        img,
 		        INFO_LEFT_X + NUMBER_ITEM_MLR + cellWidth * index,
@@ -260,6 +265,10 @@ export default {
       // 移入
       this.isShowInfoEdit = false;
     });
+	this.$EventBus.$off('onCreateSheet')
+	this.$EventBus.$on('onCreateSheet',() => {
+		this.isShowInfoEdit = false
+	})
   },
 };
 </script>
@@ -268,7 +277,7 @@ export default {
   position: absolute;
   left: 0;
   top: 0;
-  width: 750px;
+  width: 826px;
   height: 342px;
 }
 

+ 1 - 1
TEAMModelOS/ClientApp/src/view/answersheet/SheetComplete.vue

@@ -105,7 +105,7 @@ export default {
 <style scoped>
 .sheet-complete-container {
   position: relative;
-  width: 750px;
+  width: 826px;
 }
 
 

+ 65 - 43
TEAMModelOS/ClientApp/src/view/answersheet/SheetObjective.vue

@@ -5,7 +5,7 @@
 		<!-- <BaseTitleEditor ids="objectiveTitleEditor" :content="'一、选择题(共' + objectiveItems.length + '题,总计' + totalScore + '分)'"></BaseTitleEditor> -->
 		<svg id="sheetObjectiveSvg" width="100%" :style="'height:' + svgHeight + 'px'"></svg>
 		<div class="info-edit" v-show="isShowInfoEdit">
-			<span @click="editModal = true" style="background-color: #00a43a;">{{ $t('answerSheet.edit') }}</span>
+			<span @click="editModal = true" style="background-color: #00a43a;" v-if="!isAutoCreate">{{ $t('answerSheet.edit') }}</span>
 			<span @click="doDelete" style="background-color: red;" v-if="!isAutoCreate">{{ $t('answerSheet.delete') }}</span>
 		</div>
 		<Modal v-model="editModal" :title="$t('answerSheet.baseInfo')" @on-ok="onReRender">
@@ -40,7 +40,9 @@
 		NUMBER_ITEM_H,
 		NUMBER_ITEM_MT,
 		NUMBER_ITEM_ML,
-		BLOCK_H
+		BLOCK_H,
+		OBJ_ANCHOR_GAP,
+		OBJ_ANCHOR_H_COUNT
 	} from "@/utils/sheetConfig.js";
 	export default {
 		props: {
@@ -67,11 +69,13 @@
 				leftItems: [],
 				blockList: [],
 				anchorRectY: 0,
-				everyOptionH:22,
+				everyOptionH:19,
 				curBlockIndex:-2,
 				preBlockCellCount:0,
 				curColumn:0,
-				curCellCount:0
+				curCellCount:0,
+				times:-1,
+				lastItem:null
 			};
 		},
 		created() {
@@ -93,15 +97,15 @@
 					"height": this.anchorRectY + 20,
 					"pageNum": 1,
 					"vblockCount": this.objectiveItems.length > this.number ? this.number * this.blockList.length : this.objectiveItems.length,
-					"hblockCount": 28,
+					"hblockCount": OBJ_ANCHOR_H_COUNT,
 					"count": this.objectiveItems.length
 				}
-				console.log(objectiveConfig);
 				this.$store.commit('setObjectiveConfig', objectiveConfig)
 			},
 			
 			// 修改每列展示题数重新渲染
 			onReRender() {
+				
 				// this.$store.commit('setColumnCount',this.number)
 				this.leftItems = JSON.parse(JSON.stringify(this.objectiveItems))
 				this.doRenderItems(this.objectiveItems, this.number);
@@ -116,7 +120,7 @@
 				this.setObjectiveConfig()
 				// 信息框
 				let infoBox = snap
-					.rect(CONTENT_START_X, 2, INFO_W, this.anchorRectY + 20)
+					.rect(CONTENT_START_X, 2, INFO_W, this.anchorRectY + 30)
 					.attr({
 						shapeRendering:"crispEdges",
 						fill: "none",
@@ -129,6 +133,11 @@
 
 			// 渲染客观题目
 			doRenderItems(items, number) {
+				this.curBlockIndex = -2,
+				this.preBlockCellCount = 0,
+				this.curColumn = 0,
+				this.curCellCount = 0,
+				this.lastItem = null
 				this.blockHeight = this.everyOptionH * number
 				this.objectiveGroup && this.objectiveGroup.remove(); // 先清除之前的绘制内容
 				if (this.snap) {
@@ -136,10 +145,9 @@
 				}
 				let snap = this.snap;
 				let MARGIN_LEFT = 25;
-				let GAP = 22; //纵向间距
-				let optionGap = 22; //横向间距
+				let GAP = this.everyOptionH; //纵向间距
+				let optionGap = 30; //横向间距
 				let startY = GAP + 15;
-
 				let needNewLineArr = items.map((item, index) =>
 					this.needNewLine(index, number, items)
 				);
@@ -153,27 +161,45 @@
 						});
 					}
 				}
-				console.log(needNewLineArr);
 				let itemsPositionArr = [];
 				// 提前计算 每个题目排列时 是否要进行换行 以及计算需要换到第几行 (行可以理解为选项块)
+				let times = -1
+				let xArrIndex = 0
 				for (let index = 0; index < items.length; index++) {
-					itemsPositionArr.push({
-						index: index,
-						needNewLine: needNewLineArr[index].need,
-						lineIndex: needNewLineArr[index].blockIndex,
-						xArrIndex:needNewLineArr[index].xArrIndex
-					});
+					let isChangeLine = needNewLineArr[index].need && !needNewLineArr[index - 1].need
+					if(isChangeLine){
+						itemsPositionArr.push({
+							index: index,
+							needNewLine: needNewLineArr[index].need,
+							lineIndex: ++times,
+							xArrIndex:0
+						});
+					}else{
+						itemsPositionArr.push({
+							index: index,
+							needNewLine: needNewLineArr[index].need,
+							lineIndex: needNewLineArr[index].need && !needNewLineArr[index - 1].need ? ++times : times,
+							xArrIndex:needNewLineArr[index].xArrIndex
+						});
+					}
+					
 				}
 				let blockList = [...new Set(itemsPositionArr.map(i => i.lineIndex))]
 				this.blockList = blockList
-				console.log(itemsPositionArr)
 				
 				let xArr = []
-				for (let index = 0; index < 28; index++) {
-					xArr.push(68 + index * 22)
+				let curXArrIndex = 0
+				for (let index = 0; index < OBJ_ANCHOR_H_COUNT; index++) {
+					xArr.push(68 + index * OBJ_ANCHOR_GAP)
 				}
-				console.log(xArr)
-				
+				let trueSvg = require('@/icons/answersheet/t.svg');
+				let falseSvg = require('@/icons/answersheet/f.svg');
+				const context = require.context('@/icons/answersheet', false, /.svg/)
+				const frames = []
+				context.keys().forEach(k => {
+					frames.push(context(k))
+				})
+				let codeSvgArr = []
 				items.forEach((item, index) => {
 					if (index > 0 && (itemsPositionArr[index].lineIndex !== itemsPositionArr[index - 1]
 							.lineIndex)) {
@@ -181,7 +207,7 @@
 					}
 					let optionX = CONTENT_START_X + MARGIN_LEFT + this.getStartX(number, this.leftItems, item.id,index);
 					let optionY = startY + GAP * (index + 1 <= number ? index : index % number) + ((itemsPositionArr[index].lineIndex + 1) * (this.blockHeight + this.everyOptionH))
-					// console.log(index,startY,optionY)
+					optionX = xArr[itemsPositionArr[index].xArrIndex] + 15
 					// 渲染选项下标
 					let itemOrder = snap
 						.text(
@@ -200,10 +226,7 @@
 						let code = String.fromCharCode(
 							64 + parseInt(optionIndex + 1)
 						).toLowerCase();
-						// let img = require("../../icons/answersheet/" + code + ".svg");
-						let img = require('@/icons/answersheet/' + code + '.svg');
-						let trueSvg = require('@/icons/answersheet/t.svg');
-						let falseSvg = require('@/icons/answersheet/f.svg');
+						let img = frames.slice(10,frames.length - 1)[optionIndex]
 						let c3 = snap.image(
 							item.type !== 'judge' ? img : optionIndex === 0 ? trueSvg : falseSvg,
 							// optionX + NUMBER_ITEM_ML + optionGap * optionIndex,
@@ -213,6 +236,7 @@
 							NUMBER_ITEM_H
 						);
 						this.objectiveGroup.add(c3);
+						curXArrIndex = itemsPositionArr[index].xArrIndex + optionIndex + 1
 					});
 				});
 				console.log(this.leftItems)
@@ -223,11 +247,11 @@
 				let objectiveHeight = this.leftItems.length >= 0 ? refHeight - this.everyOptionH * diff : refHeight
 				this.anchorRectY = startY +  objectiveHeight
 				// 渲染选项框横向定位锚点
-				for (let index = 0; index < 28; index++) {
+				for (let index = 0; index < OBJ_ANCHOR_H_COUNT; index++) {
 					this.objectiveGroup.add(
 						snap.rect(
-							CONTENT_START_X + MARGIN_LEFT + NUMBER_ITEM_ML + optionGap * (index - 1),
-							startY +  objectiveHeight,
+							xArr[index],
+							startY + objectiveHeight,
 							NUMBER_ITEM_W,
 							NUMBER_ITEM_H
 						).attr({ shapeRendering:"crispEdges"})
@@ -283,7 +307,7 @@
 				let preColumnCells = [];
 				let columnCells = [];
 				let cellCount = 0
-				let cellCountMax = 20; // 每行最多能放的选项数
+				let cellCountMax = OBJ_ANCHOR_H_COUNT; // 每行最多能放的选项数
 				for (var i = 0; i < columnCount; i++) {
 					if (i > 0) {
 						// 后面每一列的取值为前一列最多选项宽度
@@ -307,27 +331,25 @@
 					0; // 目前已经有的选项数
 				// 如果前面所有选项数
 				let preAllCount = cellCount + columnCells[curColumn]
-				// 计算当前题目在第几个block
-				let times = Math.ceil(preAllCount / cellCountMax) - 2
-				// 计算当前block的总选项数 = 前面所有列的最大选项数之和 加上 当前列的最大选项数
-				let totalCell = cellCount + columnCells[curColumn]
 				// 触发换列
 				if(this.curCellCount !== cellCount){
 					this.curCellCount = cellCount
 					this.curColumn++
 				}
-				// 触发换行
-				if(times !== this.curBlockIndex){
-					this.curBlockIndex = times
+				let totalCell = cellCount - this.preBlockCellCount + columnCells[curColumn] + (this.curColumn + 1)
+				// 触发换行条件:如果当前需要换行并且上一个block不需要换行
+				if(totalCell > cellCountMax && !this.lastItem.need){
 					this.preBlockCellCount = cellCount
 					this.curColumn = 0
 				}
+				// 计算当前block的总选项数 = 前面所有列的最大选项数之和 加上 当前列的最大选项数
 				let curBlockPreCell = cellCount - this.preBlockCellCount + this.curColumn
-				return {
+				// console.log(index,cellCount,this.preBlockCellCount,curBlockPreCell)
+				this.lastItem = {
 					need: totalCell > cellCountMax, // 是否需要换行
-					blockIndex: times ,// 当前需要换到第几行
 					xArrIndex: curBlockPreCell
-				};
+				}
+				return this.lastItem;
 			},
 		},
 		mounted: function() {
@@ -353,7 +375,7 @@
 							objectiveArr.includes(item.type)
 						);
 
-						this.blockHeight = 24 * this.number
+						this.blockHeight = 16 * this.number
 						this.leftItems = JSON.parse(JSON.stringify(this.objectiveItems))
 						this.$nextTick(() => {
 							this.doRenderItems(this.objectiveItems, this.number);
@@ -379,7 +401,7 @@
 		/* position: absolute; */
 		/* left: 0; */
 		/* top: 350px; */
-		width: 750px;
+		width: 826px;
 	}
 
 	/* .sheet-info-container:hover .info-edit{

+ 1 - 1
TEAMModelOS/ClientApp/src/view/answersheet/SheetSubjective.vue

@@ -106,7 +106,7 @@ export default {
 <style scoped>
 .sheet-subjective-container {
   position: relative;
-  width: 750px;
+  width: 826px;
 }
 
 .edit-title {

+ 136 - 113
TEAMModelOS/ClientApp/src/view/answersheet/index.vue

@@ -14,19 +14,19 @@
 				<div class="sheet-groups">
 					<div v-for="(group,groupIndex) in groups" :key="groupIndex" class="sheet-group-item">
 						<div v-if="group.type === 'objective'">
-							<BaseTitleEditor :ids="'titleEditor' + groupIndex"
-								:content="titleContent(groupIndex,group)" ref="objectiveTitleRef"></BaseTitleEditor>
+							<BaseTitleEditor :ids="'titleEditor' + groupIndex" :content="titleContent(groupIndex,group)"
+								ref="objectiveTitleRef"></BaseTitleEditor>
 							<SheetObjective :items="items" v-show="groupItems.objectiveItems.length"
 								@onRendered="onRendered"></SheetObjective>
 						</div>
 						<div v-else-if="group.type === 'complete'">
-							<BaseTitleEditor :ids="'titleEditor' + groupIndex"
-								:content="titleContent(groupIndex,group)" ref="completeTitleRef"></BaseTitleEditor>
+							<BaseTitleEditor :ids="'titleEditor' + groupIndex" :content="titleContent(groupIndex,group)"
+								ref="completeTitleRef"></BaseTitleEditor>
 							<SheetComplete :items="items" v-if="groupItems.completeItems.length"></SheetComplete>
 						</div>
 						<div v-else>
-							<BaseTitleEditor :ids="'titleEditor' + groupIndex"
-								:content="titleContent(groupIndex,group)" ref="subjectiveTitleRef"></BaseTitleEditor>
+							<BaseTitleEditor :ids="'titleEditor' + groupIndex" :content="titleContent(groupIndex,group)"
+								ref="subjectiveTitleRef"></BaseTitleEditor>
 							<SheetSubjective :items="groupItems.subjectiveItems"
 								v-show="groupItems.subjectiveItems.length"></SheetSubjective>
 						</div>
@@ -37,7 +37,8 @@
 		</div>
 		<div class="sheet-right">
 			<Button @click="goBack">{{ $t('answerSheet.back') }}</Button>
-			<Button @click="doDownload" class="btn-download" v-if="curPaper.id" :loading="isLoading">{{ !isLoading ? $t('answerSheet.print') : $t('answerSheet.loading') }}</Button>
+			<Button @click="doDownload" class="btn-download" v-if="curPaper.id"
+				:loading="isLoading">{{ !isLoading ? $t('answerSheet.print') : $t('answerSheet.loading') }}</Button>
 			<div>
 				<!-- <p class="sheet-right-title">基础设置</p>
 				<div>
@@ -78,23 +79,26 @@
 						<InputNumber :max="100" :min="0" v-model="addType.everyScore" @on-change="renderAddItem">
 						</InputNumber>
 						<span style="margin: 0 10px;">{{ $t('answerSheet.tip5') }}</span>
-						<span v-if="activeTab === '0' || activeTab === '1'" >
+						<span v-if="activeTab === '0' || activeTab === '1'">
 							<span>{{ $t('answerSheet.tip6') }} </span>
 							<InputNumber :max="8" :min="0" v-model="addType.everyOpts" @on-change="renderAddItem">
 							</InputNumber>
-							<span style="margin: 0 10px;">{{ $t('answerSheet.tip19') }}{{ activeTab === '0' ? $t('answerSheet.tip7') : $t('answerSheet.tip8') }}</span>
+							<span
+								style="margin: 0 10px;">{{ $t('answerSheet.tip19') }}{{ activeTab === '0' ? $t('answerSheet.tip7') : $t('answerSheet.tip8') }}</span>
 						</span>
 						<span v-if="activeTab === '3'">
 							<span>,{{ $t('answerSheet.tip9') }} </span>
-							<InputNumber :max="2000" :min="100" v-model="addType.maxWords" :active-change="false"></InputNumber>
+							<InputNumber :max="2000" :min="100" v-model="addType.maxWords" :active-change="false">
+							</InputNumber>
 							<span> {{ $t('answerSheet.tip10') }} </span>
 						</span>
 						<span v-if="activeTab === '4'">
 							<span> {{ $t('answerSheet.tip9') }} </span>
-							<InputNumber :max="30" :min="1" v-model="addType.maxLines" :active-change="false"></InputNumber>
+							<InputNumber :max="30" :min="1" v-model="addType.maxLines" :active-change="false">
+							</InputNumber>
 							<span> {{ $t('answerSheet.tip11') }}</span>
 						</span>
-						
+
 					</div>
 				</div>
 				<p v-show="indexErr" style="color: red;">* {{ $t('answerSheet.tip12') }}</p>
@@ -108,7 +112,8 @@
 								<span> {{ $t('answerSheet.tip5') }} </span>
 								<span v-if="activeTab === '0' || activeTab === '1'" style="margin-left: 10px;">
 									<InputNumber :max="100" :min="0" v-model="item.opts"></InputNumber>
-									<span> 个{{ activeTab === '0' ? $t('answerSheet.tip7') : $t('answerSheet.tip8') }}</span>
+									<span>
+										个{{ activeTab === '0' ? $t('answerSheet.tip7') : $t('answerSheet.tip8') }}</span>
 								</span>
 							</div>
 						</div>
@@ -135,7 +140,8 @@
 		PAPER_W,
 		PAPER_H,
 		ANCHORPROP,
-		INFO_H
+		INFO_H,
+		SVG_BORDER_MB
 	} from "@/utils/sheetConfig.js";
 	export default {
 		props: {
@@ -143,9 +149,9 @@
 				type: String,
 				default: "",
 			},
-			paper:{
-				type:Object,
-				default:() => {}
+			paper: {
+				type: Object,
+				default: () => {}
 			}
 		},
 		components: {
@@ -158,30 +164,30 @@
 		},
 		data(vm) {
 			return {
-				curPaper:{
-					id:null
+				curPaper: {
+					id: null
 				},
 				indexErr: false,
-				sheetId:null,
+				sheetId: null,
 				addItems: [],
 				addType: {
 					startIndex: null,
 					endIndex: null,
 					everyScore: null,
 					everyOpts: null,
-					maxWords:800,
-					maxLines:10
+					maxWords: 800,
+					maxLines: 10
 				},
 				isShowAdd: false,
-				isShowErCode:false,
-				isShowLines:false,
+				isShowErCode: false,
+				isShowLines: false,
 				activeTab: '0',
 				groups: [],
 				isLoading: false,
 				isColumn: false,
 				isRouterAlive: true,
 				isRender: true,
-				isFromExam:false,
+				isFromExam: false,
 				pages: [],
 				items: [],
 				groupItems: {
@@ -195,12 +201,13 @@
 					objective: vm.$t('answerSheet.objective'),
 					complete: vm.$t('answerSheet.complete'),
 					subjective: vm.$t('answerSheet.subjective')
-				}
+				},
+				hasModify:false
 			};
 		},
 		created() {
 			let routerPaper = this.$route.params.paper || this.paper
-			console.log('答题卡路由数据:',routerPaper)
+			console.log('答题卡路由数据:', routerPaper)
 			this.curPaper = routerPaper
 			this.isFromExam = Boolean(routerPaper && routerPaper.examCode)
 			this.$store.commit('clearAllConfig')
@@ -208,6 +215,7 @@
 				this.$store.commit('clearFixArr')
 				this.$store.commit('clearIsNewPage')
 				this.$store.commit('setPaper', routerPaper)
+				routerPaper.sheet && this.setSheetConfig(routerPaper)
 				if (routerPaper.item === null) {
 					this.$store.commit('setCreateModal', 'diy')
 				} else {
@@ -221,7 +229,25 @@
 
 		},
 		methods: {
-			onTabClick(type){
+			
+			async setSheetConfig(routerPaper){
+				let sheetConfig = await this.getSheetConfigById(routerPaper.sheet,routerPaper.code,routerPaper.scope)
+				console.log(sheetConfig)
+			},
+
+			getSheetConfigById(id,code,scope) {
+				return new Promise((r, j) => {
+					this.$api.evaluation.findSheet({
+						"id": id,
+						"code": code.replace('Paper-',''),
+						"scope": scope
+					}).then(res => {
+						r(res.config)
+					})
+				})
+			},
+
+			onTabClick(type) {
 				this.activeTab = type
 			},
 			onAddType(type) {
@@ -232,8 +258,8 @@
 					endIndex: null,
 					everyScore: null,
 					everyOpts: (type === '0' || type === '1') ? null : 1,
-					maxWords:800,
-					maxLines:10
+					maxWords: 800,
+					maxLines: 10
 				}
 			},
 
@@ -246,7 +272,7 @@
 				this.indexErr = val < this.addType.startIndex || (!val && !this.addType.startIndex)
 				this.renderAddItem()
 			},
-			
+
 			/* 点击添加题型 */
 			doAddType() {
 				if (this.isAddInfoComplete) {
@@ -266,74 +292,55 @@
 			},
 			/* 打印答题卡 */
 			doDownload() {
-				this.isLoading = true
-				this.doSaveSheet().then(async res => {
-					let paperInfo = this.$store.state.answerSheet.paperItem
-					this.sheetId = res.config.id
-					if(this.isFromExam){
-						this.$EventBus.$emit('createSheetId',res.config.id)
-					}else{
-						// await this.refreshPaperSheet(paperInfo,res.config.id)
-						// await this.relatePaperSheet(paperInfo,res.config.id)//绑定废弃
-					}
-					this.getPdf().then(r => {
-						this.isLoading = false
+				if(this.curPaper.sheet && this.hasModify){
+					this.$Modal.confirm({
+						title: '更新提示',
+						content: '检测到答题卡有更新,打印答题卡会覆盖原本答题卡数据,是否继续打印?',
+						onOk: () => {
+							this.isLoading = true
+							this.$EventBus.$emit('onCreateSheet')
+							this.doSaveSheet().then(async res => {
+								let paperInfo = this.$store.state.answerSheet.paperItem
+								this.sheetId = res.config.id
+								if (this.isFromExam) {
+									this.$EventBus.$emit('createSheetId', res.config.id)
+								}
+								this.getPdf().then(r => {
+									this.isLoading = false
+								})
+							}).catch(err => {
+								console.log(err)
+								this.isLoading = false
+							})
+						}
 					})
-				}).catch(err => {
-					console.log(err)
-					this.isLoading = false
-				})
-			},
-			
-			/* 更新试卷的sheet字段 */
-			// refreshPaperSheet(paperInfo,sheetId){
-			// 	return new Promise(async (r,j) => {
-			// 		console.log(paperInfo)
-			// 		paperInfo.sheet = sheetId
-			// 		let blobPaper = await this.$evTools.createBlobPaper(paperInfo)
-			// 		console.log(blobPaper)
-			// 		// 首先保存新题目的JSON文件到Blob 然后返回URL链接
-			// 		let paperFile = new File([JSON.stringify(blobPaper)], "index.json");
-			// 		// 获取初始化Blob需要的数据
-			// 		let sasData = paperInfo.scope === 'private' ? await this.$tools
-			// 		.getPrivateSas() : await this.$tools.getSchoolSas()
-			// 		//初始化Blob
-			// 		let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, paperInfo.scope)
-			// 		containerClient.upload(paperFile,'paper/' + paperInfo.name, undefined, false).then(res => {
-			// 			r(200)
-			// 		}).catch(e => j(e))
-			// 	})
-			// },
-			
-			/**
-			 * 关联试卷ID和答题卡ID
-			 *  已废弃liqk
-			 * */ 
-			relatePaperSheet(paperInfo,sheetId){
-				return new Promise((r,j) => {
-					this.$api.evaluation.upsertPaperSheet({
-						"id": paperInfo.id,
-						"code": paperInfo.code.replace('Paper-',''),
-						"scope": paperInfo.scope,
-						"sheet": sheetId
-					}).then(res => {
-						if(!res.error){
-							r(res)
-						}else{
-							j(res.error)
+				}else{
+					this.isLoading = true
+					this.$EventBus.$emit('onCreateSheet')
+					this.doSaveSheet().then(async res => {
+						let paperInfo = this.$store.state.answerSheet.paperItem
+						this.sheetId = res.config.id
+						if (this.isFromExam) {
+							this.$EventBus.$emit('createSheetId', res.config.id)
 						}
-					}).catch(err => j(err))
-				})
+						this.getPdf().then(r => {
+							this.isLoading = false
+						})
+					}).catch(err => {
+						console.log(err)
+						this.isLoading = false
+					})
+				}
+				
 			},
-			
 			/* 保存答题卡模板数据 */
-			doSaveSheet(){
-				return new Promise((r,j) => {
+			doSaveSheet() {
+				return new Promise((r, j) => {
 					let paperInfo = this.$store.state.answerSheet.paperItem
 					let configParams = this.$store.state.answerSheet.config
 					let curCode = paperInfo.examCode || paperInfo.code
 					let curScope = paperInfo.examScope || paperInfo.scope
-					if(paperInfo.sheet){
+					if (paperInfo.sheet) {
 						configParams.id = paperInfo.sheet
 					}
 					configParams.pageHeight = Number(configParams.pageHeight.toFixed())
@@ -341,11 +348,12 @@
 					configParams.pageCount = this.$store.state.answerSheet.pages
 					configParams.itemCount = this.$store.state.answerSheet.paperItem.item.length
 					// configParams.code = curCode.replace('Paper-','')
-					configParams.code = curCode.replace('Paper-','').replace('Exam-','') //如果是评测修改code会有问题
+					configParams.code = curCode.replace('Paper-', '').replace('Exam-', '') //如果是评测修改code会有问题
 					configParams.school = curScope === 'school' ? this.$store.state.userInfo.schoolCode : ''
 					configParams.scope = curScope
-					configParams.creatorId =  this.$store.state.userInfo.TEAMModelId
-					console.log('答题卡数据参数',configParams)
+					configParams.creatorId = this.$store.state.userInfo.TEAMModelId
+					console.log('答题卡数据参数', configParams)
+					// r(configParams)
 					/**
 					 * 这里有参数调整 liqk
 					 */
@@ -355,9 +363,9 @@
 						owner: paperInfo.owner,
 						sheet: configParams
 					}).then(res => {
-						if(!res.error){
+						if (!res.error) {
 							r(res)
-						}else{
+						} else {
 							j(res.error)
 						}
 					}).catch(err => j(err))
@@ -368,13 +376,15 @@
 				let params = {}
 				let curEditPaper = localStorage.getItem('c_edit_paper')
 				// 如果是从新建试卷或者编辑试卷 跳转过来的 则返回过去的时候带上编辑的信息
-				if(this.$route.params.paper && (this.fromRouter === 'newSchoolPaper' || this.fromRouter === 'newPrivatePaper') && curEditPaper){
+				if (this.$route.params.paper && (this.fromRouter === 'newSchoolPaper' || this.fromRouter ===
+						'newPrivatePaper') && curEditPaper) {
 					params = {
-						paper:JSON.parse(curEditPaper)
+						paper: JSON.parse(curEditPaper)
 					}
 				}
+				console.log(params)
 				// 如果是从试卷库预览试卷跳转过来的 就返回试卷库
-				if(this.$route.params.paper && (this.fromRouter === 'schoolBank' || this.fromRouter === 'personalBank')){
+				if (this.$route.params.paper && (this.fromRouter === 'schoolBank' || this.fromRouter === 'personalBank')) {
 					params = {
 						tabName: 'paper'
 					}
@@ -468,9 +478,10 @@
 						score: addInfo.everyScore,
 						opts: addInfo.everyOpts,
 						blankCount: 1,
-						maxWords:this.activeTab === '3' ? addInfo.maxWords : null,
-						maxLines:this.activeTab === '4' ? addInfo.maxLines : null,
-						type: this.activeTab === '0' ? 'single' : this.activeTab === '1' ? 'complete' : 'subjective'
+						maxWords: this.activeTab === '3' ? addInfo.maxWords : null,
+						maxLines: this.activeTab === '4' ? addInfo.maxLines : null,
+						type: this.activeTab === '0' ? 'single' : this.activeTab === '1' ? 'complete' :
+							'subjective'
 					})
 				}
 				this.addItems = addItems
@@ -479,16 +490,16 @@
 		mounted: function() {
 			this.doRender()
 			this.$EventBus.$off('deleteItem')
-			this.$EventBus.$on('deleteItem',val => {
+			this.$EventBus.$on('deleteItem', val => {
 				console.log(val)
-				if(val === '0'){
+				if (val === '0') {
 					this.groups = this.groups.filter(i => i.type !== 'complete')
 					this.items = this.items.filter(i => i.type !== 'complete')
-				}else if (val === 'objective'){
+				} else if (val === 'objective') {
 					let objectiveTypes = ["single", "multiple", "judge"];
 					this.groups = this.groups.filter(i => i.type !== 'objective')
 					this.items = this.items.filter(i => !objectiveTypes.includes(i.type))
-				}else{
+				} else {
 					this.groups = this.groups.filter(i => i.items.length)
 				}
 				this.$EventBus.$emit('doRefresh')
@@ -496,10 +507,20 @@
 			this.$EventBus.$off('titleMovePage')
 			this.$EventBus.$on('titleMovePage', val => {
 				this.$nextTick(() => {
-					console.log(this.$refs.subjectiveTitleRef)
-					this.$refs.subjectiveTitleRef[0].$el.style.marginTop = '220px'
+					console.log('有标题跨页', val)
+					if (val.type === 'subjective') {
+						this.$refs.subjectiveTitleRef[0].$el.style.marginTop = SVG_BORDER_MB * 2 + val
+							.height + 40 + 'px'
+					} else {
+						this.$refs.completeTitleRef[0].$el.style.marginTop = SVG_BORDER_MB * 2 + val
+							.height + 40 + 'px'
+					}
 				})
 			})
+			this.$EventBus.$off("hasDrag")
+			this.$EventBus.$on("hasDrag", () => {
+			  this.hasModify = true
+			});
 		},
 		beforeRouteEnter(to, from, next) {
 			next(vm => {
@@ -507,9 +528,10 @@
 			})
 		},
 		beforeRouteLeave(to, from, next) {
-			if(to.name === 'newSchoolPaper' || to.name === 'newPrivatePaper' || to.name === 'schoolBank' || to.name === 'personalBank'){
+			if (to.name === 'newSchoolPaper' || to.name === 'newPrivatePaper' || to.name === 'schoolBank' || to.name ===
+				'personalBank') {
 				// 设置下一个路由的 meta
-				to.meta.isKeep = true;  // 让 A 缓存,即不刷新
+				to.meta.isKeep = true; // 让 A 缓存,即不刷新
 			}
 			next();
 		},
@@ -536,7 +558,8 @@
 				return (groupIndex, group) => {
 					let totalScore = group.items.length ? group.items.map(i => i.score).reduce((a, b) => a + b) : 0
 					let type = this.typeList[group.type]
-					return this.numberConvertToUppercase(groupIndex + 1) + '、' + type + this.$t('answerSheet.tip16') + group.items.length +
+					return this.numberConvertToUppercase(groupIndex + 1) + '、' + type + this.$t('answerSheet.tip16') +
+						group.items.length +
 						this.$t('answerSheet.tip17') + totalScore + this.$t('answerSheet.tip18')
 				}
 			}
@@ -680,8 +703,8 @@
 
 	.sheet-container {
 		position: relative;
-		width: 750px;
-		height: 1060.71px;
+		width: 826px;
+		height: 1165px;
 		background: #fff;
 		margin: 20px auto;
 	}

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

@@ -5,7 +5,7 @@
 		 :isFilterPaper="isFilterPaper"
 		 @onSearchChange="onSearchChange"
 		 @onCloseSearch="onCloseSearch"
-		   v-if="!isPreview"></BaseFilter>
+		   v-show="!isPreview"></BaseFilter>
 
 		<!-- 空数据展示 -->
 		<div v-if="paperList.length === 0" class="no-data-text">

+ 0 - 12
TEAMModelOS/ClientApp/src/view/evaluation/bank/index.vue

@@ -165,18 +165,6 @@
 			}
 			next();
 		},
-		watch: {
-			paperScrollTop: {
-				handler(n) {
-					// console.log(n)
-				}
-			},
-			// $route( to , from ){   
-			//    if(from.name !== 'answerSheet' && (to.name === 'schoolBank' || to.name === 'personalBank')){
-			// 	  this.onShowPaperList()
-			//    }
-			//  }
-		}
 	}
 </script>
 <style src="./index.less" lang="less" scoped></style>

+ 10 - 6
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue

@@ -10,7 +10,7 @@
 			<div class="list-view" :key="typeIndex" v-for="(typeItem,typeIndex) in listData">
 				<p v-show="viewModel === 'type' && typeItem.list.length" class="type-name">{{ numberConvertToUppercase(getLatestTypeIndex(typeItem.type) + 1) }}
 					: {{ exersicesType[typeItem.type] }}
-					({{ typeItem.score || 0 }} {{$t('evaluation.paperList.score')}}) </p>
+					<span style="font-size: 14px;font-weight: 600;">({{$t('answerSheet.tip9')}} {{ typeItem.list.length }} {{$t('answerSheet.tip17')}} {{ typeItem.score || 0 }} {{$t('evaluation.paperList.score')}})</span> </p>
 
 				<div v-for="(item,index) of typeItem.list" :key="index" :class='["exercise-item",isError(item.id) ? "exercise-item-error":""]'
 				 @mouseenter="exerciseMouseover($event)" @mouseleave="exerciseMouseleave($event)" :data-id="item.id">
@@ -63,7 +63,7 @@
 					</div>
 					<div class="item-btn-toggle" @click.stop v-show="isShowTools && !isPreview">
 						<template v-if="!isExamPaper">
-							<InputNumber v-if="item.type !== 'compose'" :max="item.score + surPlusScore" :min="0" :step="0.5" v-model="item.score" style="display: inline-block ;width: 50px;margin-right: 10px;height: 30px;"
+							<InputNumber v-if="item.type !== 'compose'" :max="item.score + surPlusScore" :min="0" :step="0.5" v-model="item.score" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;"
 							 @click.stop></InputNumber>
 							 <span style="margin-right: 10px;" v-if="item.type === 'compose'">{{ getComposeScore(item) }}</span>
 							<span style="margin-right: 10px;">{{$t('evaluation.paperList.score')}}</span>
@@ -520,8 +520,9 @@
 							} else {
 								// 如果不能整除 则前面所有取整 最后一题加上余数 即可完成配分
 								let integerScore = parseInt(item.score / item.list.length)
-								let lastItem = exerciseIndex === item.list.length - 1
-								exercise.score = lastItem ? integerScore + remainder : integerScore
+								// let lastItem = exerciseIndex === item.list.length - 1
+								// exercise.score = lastItem ? integerScore + remainder : integerScore
+								exercise.score = exerciseIndex + 1 > remainder ? integerScore : integerScore + 1
 							}
 							listItem.score = exercise.score
 							
@@ -529,8 +530,9 @@
 								exercise.children.forEach((child,childIndex) => {
 									let remainder = exercise.score % exercise.children.length
 									let integerScore = parseInt(exercise.score / exercise.children.length)
-									let lastItem = childIndex === exercise.children.length - 1
-									child.score = lastItem ? integerScore + remainder : integerScore
+									// let lastItem = childIndex === exercise.children.length - 1
+									// child.score = lastItem ? integerScore + remainder : integerScore
+									child.score = childIndex + 1 > remainder ? integerScore : integerScore + 1
 								})
 							}
 							
@@ -542,6 +544,8 @@
 					this.$parent.viewModel = 'type'
 					this.$parent.paperInfo.multipleRule = this.multipleRule || 1
 					this.typeScoreModel = false
+					console.log(this.groupList)
+					console.log(this.surPlusScore)
 					this.$emit('scoreUpdate', this.surPlusScore)
 				}
 

+ 37 - 24
TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue

@@ -317,23 +317,28 @@
 			 * @param questions
 			 */
 			async getAutoQuestions(questions) {
-				let autoQuestions = await this.$evTools.getFullItem(questions)
-				let arr = autoQuestions
-				// 拿到题目后 根据试卷总分给所有题目进行平均分配
-				let scoreArr = this.averageTotalScore(this.evaluationInfo.score, arr.length)
-				arr.forEach((i, index) => {
-					i.score = scoreArr[index] || 0
-					// 如果是综合题 则需要进行子题的分数平均分配
-					if(i.type === 'compose' && i.children.length){
-						let childrenScoreArr = this.averageTotalScore(i.score, i.children.length)
-						i.children.forEach((child,childIndex) => {
-							child.score = childrenScoreArr[childIndex]
-						})
-					}
-				})
-				this.evaluationInfo.item = []
-				this.evaluationInfo.item = this.evaluationInfo.item.concat([...autoQuestions])
-				this.activeTab = 'preview'
+				try{
+					let autoQuestions = await this.$evTools.getFullItem(questions)
+					let arr = autoQuestions
+					// 拿到题目后 根据试卷总分给所有题目进行平均分配
+					let scoreArr = this.averageTotalScore(this.evaluationInfo.score, arr.length)
+					arr.forEach((i, index) => {
+						i.score = scoreArr[index] || 0
+						// 如果是综合题 则需要进行子题的分数平均分配
+						if(i.type === 'compose' && i.children.length){
+							let childrenScoreArr = this.averageTotalScore(i.score, i.children.length)
+							i.children.forEach((child,childIndex) => {
+								child.score = childrenScoreArr[childIndex]
+							})
+						}
+					})
+					this.evaluationInfo.item = []
+					this.evaluationInfo.item = this.evaluationInfo.item.concat([...autoQuestions])
+					this.activeTab = 'preview'
+				}catch(e){
+					this.$Message.error(e)
+				}
+				
 			},
 
 			/**
@@ -379,8 +384,9 @@
 				} else {
 					// 如果不能整除 则前面所有取整 最后一题加上余数 即可完成配分
 					let integerScore = parseInt(total / length)
-					result = result.map(i => integerScore)
-					result[result.length - 1] = integerScore + remainder
+					result = result.map((i,index) => {
+						return index + 1 > remainder ? integerScore : integerScore + 1
+					})
 				}
 				return result
 			},
@@ -731,7 +737,7 @@
 							this.isLoading = true
 							let isPaperExist = await this.isPaperExist(this.evaluationInfo.name)
 							let isContainerFull = await this.isContainerFull()
-							if (!isPaperExist) {
+							if (!isPaperExist || (this.isEditPaper && this.evaluationInfo.name === this.oldPaper.name)) {
 								if (list.length) {
 									// 拿到题型顺序的试题数组进行拼接
 									let arr = []
@@ -1198,9 +1204,15 @@
 			},
 			/* 渲染需要编辑的试卷信息 */
 			async doRender(paper) {
-				console.log('传进来的paper')
-				console.log(paper)
-				localStorage.setItem('c_edit_paper',JSON.stringify(paper))
+				if(paper.paperGrade){
+					console.log('传进来缓存的paper')
+					console.log(paper)
+					this.evaluationInfo = paper
+					this.oldPaper = JSON.parse(JSON.stringify(this.evaluationInfo))
+					return 
+				}
+				console.log('渲染的试卷',paper)
+				// localStorage.setItem('c_edit_paper',JSON.stringify(paper))
 				let schoolInfo = null
 				if (paper.scope === 'school') {
 					schoolInfo = await this.getSchoolBaseInfo()
@@ -1248,6 +1260,7 @@
 			this.$EventBus.$on('onBackToTop', () => {
 				this.handleBackToTop()
 			})
+			
 		},
 
 		computed: {
@@ -1280,7 +1293,7 @@
 		beforeRouteLeave(to, from, next) {
 			if(to.name === 'answerSheet'){
 				// 设置下一个路由的 meta
-				from.meta.isKeep = true;  // 让 A 缓存,即不刷新
+				// from.meta.isKeep = true;  // 让 A 缓存,即不刷新
 			}
 			if(to.name === 'schoolBank' || to.name === 'personalBank'){
 				to.meta.isKeep = false

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

@@ -161,13 +161,16 @@
 		},
 		methods: {
 			goAnswerSheet(){
+				if(!this.isExamPaper){
+					localStorage.setItem('c_edit_paper',JSON.stringify(this.paperInfo))
+				}
+				console.log(this.paperInfo)
 				this.$router.push({
 					name: 'answerSheet',
 					params: {
 						paper: this.paperInfo
 					}
 				})
-				// this.isShowAnswerSheet = true
 			},
 			/**
 			 * 标题切换
@@ -313,7 +316,10 @@
 			},
 			ruleType() {
 				return this.paperInfo.markConfig ? this.paperInfo.markConfig.type : 0
-			}
+			},
+			isSchool() {
+				return this.$route.name === 'newSchoolPaper'
+			},
 		},
 		watch: {
 			paper: {

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

@@ -386,7 +386,7 @@ export default {
                             }, 0)
                             if (resData.papers[index].blob) {
                                 let blob = resData.papers[index].blob
-                                resData.papers[index] = await this.$evTools.getFullPaper(resData.papers[index])
+                                resData.papers[index] = await this.$evTools.getFullPaper(resData.papers[index], this.evaListShow[this.curEvaIndex].scope)
                                 resData.papers[index].blob = blob
                                 resData.score += resData.papers[index].score
                                 resData.papers[index].examScope = this.evaListShow[this.curEvaIndex].scope

+ 1 - 1
TEAMModelOS/ClientApp/src/view/settings/BaseApplyForm.vue

@@ -242,7 +242,7 @@
 		},
 		computed:{
 			isChinaSite(){
-				return this.$store.state.config.srvAdr !== 'China'
+				return this.$store.state.config.srvAdr === 'China'
 			}
 		}
 	}

+ 12 - 6
TEAMModelOS/ClientApp/src/view/student-web/App.vue

@@ -65,17 +65,16 @@
                 <!-- 加入课程 -->
                 <!-- <button class="addcoursebtn"
                         @click='sentcourseID()'
-                        v-if="MyName == this.$t('studentWeb.courseList-title')">
+                        v-if="isAddClass">
                     {{$t("studentWeb.home.joinClass")}}
                 </button>
                 <input maxlength="6"
                     class="addcourseinput"
                     v-model="courseID"
-                    placeholder="輸入課程代碼.... ex:T10000"
-                    value="T"
-                    v-if="MyName == this.$t('studentWeb.courseList-title')" />
+                    :placeholder="$t('studentWeb.home.classPla')"
+                    v-if="isAddClass" />
                 <div class="clearinput">
-                    <Icon @click="clearInput()" v-if="isTyping == true" type="md-close" />
+                    <Icon @click="clearInput()" v-if="isTyping" type="md-close" />
                 </div> -->
                 <!-- 加入课程 -->
             </Menu>
@@ -126,7 +125,8 @@
                 curRole: '',
                 users: '',
                 identity: localStorage.getItem('identity'),
-                userInfo: {}
+                userInfo: {},
+                isAddClass: false,
             };
         },
         computed: {
@@ -210,12 +210,18 @@
             },
             getNavName: function (MyName) {
                 this.MyName = MyName;
+                if(MyName == this.$t("studentWeb.courseList-title") || MyName == this.$t("studentWeb.homeView-title")) {
+                    this.isAddClass = true
+                } else {
+                    this.isAddClass = false
+                }
             },
             getUsers() {
                 this.users = JSON.parse(decodeURIComponent(localStorage.userInfo, "utf-8"));
                 let data = jwtDecode(localStorage.getItem('auth_token'))
                 if (data.name !== "") {
                     this.$store.commit("setUserInfo", data)
+                    localStorage.setItem("userInfo", JSON.stringify(data))
                 }
                 if (this.users.roles.length) {
                     this.curRole = localStorage.getItem('identity')

+ 2 - 2
TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue

@@ -108,13 +108,13 @@
 			<div class="syllabus-right">
 				<div class="syllabus-content-header">
 					<span>{{ $t('syllabus.relate') }}</span>
-					<span class="syllabus-content-header-tools">
+					<span class="syllabus-content-header-tools" v-if="curNode.id && (hasSyllabusAuth || hasEditAuth(curNode)) && !inShareView">
 						<!-- <Icon type="md-add" @click="onAddResource" v-if="curNode.id"/> -->
 						<Tooltip :content="$t('tip.addVolumeResource')" class="common-toolTip" placement="bottom" theme="light" max-width="200">
 						    <Icon type="ios-information-circle-outline"/>
 						</Tooltip>
 						<Dropdown @on-click="onAddResource"
-							v-if="curNode.id && (hasSyllabusAuth || hasEditAuth(curNode)) && !inShareView">
+							>
 							<a href="javascript:void(0)" style="color: #ddd;">
 								{{ $t('syllabus.addResource') }}
 								<Icon type="ios-arrow-down"></Icon>

+ 5 - 0
TEAMModelOS/ClientApp/src/view/teachcontent/index.less

@@ -539,4 +539,9 @@
     color: #ff9900 !important;
     font-size: 12px;
     line-height: 24px;
+}
+.space-tips{
+    margin-left:5px;
+    color:#1cc0f3;
+    cursor: pointer;
 }

+ 278 - 169
TEAMModelOS/ClientApp/src/view/teachcontent/index.vue

@@ -36,6 +36,9 @@
                                 {{$jsFn.formatBytes(sizeInfo.total)}}/{{ $jsFn.formatBytes(storageSpace)}}
                             </span>
                             <span v-if="sizeInfo.total > storageSpace">{{$t('teachContent.blobFull')}}</span>
+                            <Tooltip :content="$t('teachContent.spaceTips')" v-show="routerScope == 'school'" transfer max-width="500" theme="light">
+                                <Icon type="ios-information-circle-outline" class="space-tips" />
+                            </Tooltip>
                         </p>
                         <div class="percent-detail-box">
                             <p class="percent-detail-text" v-if="sizeInfo.res != 0">
@@ -230,6 +233,7 @@ export default {
     },
     data() {
         return {
+            teachSpace: 0,
             schoolBase: {
                 period: []
             },
@@ -252,7 +256,7 @@ export default {
             fileColumns: [],
             previewFile: {},
             previewStatus: false,
-            storageSpace: 0,
+            // storageSpace: 0,
             keyWord: '',
             activeType: 'recent',
             fileList: {},
@@ -284,6 +288,22 @@ export default {
         }
     },
     methods: {
+        // 获取容器空间情况 
+        getSize() {
+            this.sizeLoading = true
+            BlobTool.getContainerSize(this.containerName, this.routerScope).then(
+                res => {
+                    this.sizeInfo = res
+                    console.log(res, res.teach)
+                    this.teachSpace = res.teachSpace || 0
+                },
+                err => {
+                    this.$Message.error(this.$t('teachContent.sizeErr'))
+                }
+            ).finally(() => {
+                this.sizeLoading = false
+            })
+        },
         //根据学科id换取名称
         getSubjects(ids) {
             if (this.schoolBase && this.schoolBase.period && this.filterPeriod && ids) {
@@ -350,52 +370,86 @@ export default {
         },
         confirmRename() {
             let editIndex = this.editIndex
-            if (this.fileListShow[this.editIndex].extension == 'HTEX') {
-                let newName = this.fileListShow[editIndex].name
-                this.containerClient.copyFolder(`res/${newName.replace('.HTEX', '/')}`, `res/${this.renameBefore.replace('.HTEX', '')}`).then(
-                    res => {
-                        this.$api.blob.deletePrefix({
-                            cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
-                            prefix: `res/${this.renameBefore.replace('.HTEX', '')}`
-                        }).then(
-                            res => {
-                                this.$Message.success(this.$t('teachContent.nameOk'))
-                                let orginUrl = this.fileListShow[editIndex].url
-                                this.fileListShow[editIndex].blob = `/res/${this.newName}/index.json`
-                                this.fileListShow[editIndex].url = orginUrl.replace(this.renameBefore, this.fileListShow[editIndex].name)
-                            }
-                        )
-                    },
-                    err => {
-                        this.$Message.error(this.$t('teachContent.nameErr'))
-                    }
-                ).finally(() => {
-                    this.editIndex = -1
-                })
-            } else {
-                let sourceUrl = this.fileListShow[this.editIndex].url.substring(0, this.fileListShow[this.editIndex].url.lastIndexOf('?')) //截取授权之前的,授权不能encode
-                let targetUrl = this.fileListShow[this.editIndex].blob
-                targetUrl = targetUrl.replace(this.renameBefore, this.fileListShow[this.editIndex].name)
-                targetUrl = targetUrl.substring(1)
-                this.containerClient.copyBlob(targetUrl, sourceUrl, this.sasString).then(
-                    res => {
-                        //这里虽然是复制,但是是重命名操作,所以空间不会变化,就设置为0
-                        this.containerClient.deleteBlob(this.fileListShow[editIndex].blob, 0).then(
-                            res => {
-                                this.$Message.success(this.$t('teachContent.nameOk'))
-                                let orginUrl = this.fileListShow[editIndex].url
-                                this.fileListShow[editIndex].blob = '/' + targetUrl
-                                this.fileListShow[editIndex].url = orginUrl.replace(this.renameBefore, this.fileListShow[editIndex].name)
-                            }
-                        )
-                    },
-                    err => {
-                        this.$Message.error(this.$t('teachContent.nameErr'))
-                    }
-                ).finally(() => {
-                    this.editIndex = -1
-                })
+            console.log(this.fileListShow[editIndex])
+            let newName = this.fileListShow[editIndex].name
+
+            let params = {
+                scope: this.routerScope,
+                cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                id: this.fileListShow[editIndex].id,
+                newName: this.fileListShow[editIndex].blob.replace(this.renameBefore, newName).substring(1)
             }
+            this.$api.blob.blobRename(params).then(
+                res => {
+                    this.$Message.success(this.$t('teachContent.nameOk'))
+                    let orginUrl = this.fileListShow[editIndex].url
+                    let thum = this.fileListShow[editIndex].url
+                    this.fileListShow[editIndex].blob = '/' + params.newName
+                    this.fileListShow[editIndex].url = orginUrl.replace(this.renameBefore, newName)
+                    this.fileListShow[editIndex].thum = thum.replace(this.renameBefore, newName)
+                    // 更新本地数据
+                    this.renameCacheFiles(this.fileListShow[editIndex])
+                    // 更新源数据
+                    let type = this.fileListShow[editIndex].type
+                    for (let index = 0; index < this.fileList[type].length; index++) {
+                        if(this.fileList[type][index].id == this.fileListShow[editIndex].id){
+                            this.fileList[type].splice(index, 1, this.fileListShow[editIndex])
+                            break
+                        }
+                    }
+                },
+                err => {
+                    this.$Message.error(this.$t('teachContent.nameErr'))
+                }
+            ).finally(() => {
+                this.editIndex = -1
+            })
+            // if (this.fileListShow[this.editIndex].extension == 'HTEX') {
+            //     let newName = this.fileListShow[editIndex].name
+            //     this.containerClient.copyFolder(`res/${newName.replace('.HTEX', '/')}`, `res/${this.renameBefore.replace('.HTEX', '')}`).then(
+            //         res => {
+            //             this.$api.blob.deletePrefix({
+            //                 cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+            //                 prefix: `res/${this.renameBefore.replace('.HTEX', '')}`
+            //             }).then(
+            //                 res => {
+            //                     this.$Message.success(this.$t('teachContent.nameOk'))
+            //                     let orginUrl = this.fileListShow[editIndex].url
+            //                     this.fileListShow[editIndex].blob = `/res/${this.newName}/index.json`
+            //                     this.fileListShow[editIndex].url = orginUrl.replace(this.renameBefore, this.fileListShow[editIndex].name)
+            //                 }
+            //             )
+            //         },
+            //         err => {
+            //             this.$Message.error(this.$t('teachContent.nameErr'))
+            //         }
+            //     ).finally(() => {
+            //         this.editIndex = -1
+            //     })
+            // } else {
+            //     let sourceUrl = this.fileListShow[this.editIndex].url.substring(0, this.fileListShow[this.editIndex].url.lastIndexOf('?')) //截取授权之前的,授权不能encode
+            //     let targetUrl = this.fileListShow[this.editIndex].blob
+            //     targetUrl = targetUrl.replace(this.renameBefore, this.fileListShow[this.editIndex].name)
+            //     targetUrl = targetUrl.substring(1)
+            //     this.containerClient.copyBlob(targetUrl, sourceUrl, this.sasString).then(
+            //         res => {
+            //             //这里虽然是复制,但是是重命名操作,所以空间不会变化,就设置为0
+            //             this.containerClient.deleteBlob(this.fileListShow[editIndex].blob, 0).then(
+            //                 res => {
+            //                     this.$Message.success(this.$t('teachContent.nameOk'))
+            //                     let orginUrl = this.fileListShow[editIndex].url
+            //                     this.fileListShow[editIndex].blob = '/' + targetUrl
+            //                     this.fileListShow[editIndex].url = orginUrl.replace(this.renameBefore, this.fileListShow[editIndex].name)
+            //                 }
+            //             )
+            //         },
+            //         err => {
+            //             this.$Message.error(this.$t('teachContent.nameErr'))
+            //         }
+            //     ).finally(() => {
+            //         this.editIndex = -1
+            //     })
+            // }
         },
         getFileUrl(files) { // 获取文件地址
             this.preUpdFiles.push(...files)
@@ -525,7 +579,26 @@ export default {
             } else {
                 localStorage.setItem('cacheSchoolFiles', JSON.stringify(cacheFiles))
             }
-
+        },
+        //重命名更新本地数据
+        renameCacheFiles(file) {
+            let cacheFiles
+            if (this.routerScope == 'private') {
+                cacheFiles = JSON.parse(localStorage.getItem('cachePrivFiles') || '[]')
+            } else {
+                cacheFiles = JSON.parse(localStorage.getItem('cacheSchoolFiles') || '[]')
+            }
+            for (let i = 0; i < cacheFiles.length; i++) {
+                if (file.id == cacheFiles[i].id) {
+                    cacheFiles.splice(i, 1, file)
+                    break
+                }
+            }
+            if (this.routerScope == 'private') {
+                localStorage.setItem('cachePrivFiles', JSON.stringify(cacheFiles))
+            } else {
+                localStorage.setItem('cacheSchoolFiles', JSON.stringify(cacheFiles))
+            }
         },
         //计算空间占比
         getPercent(size) {
@@ -619,7 +692,6 @@ export default {
                 let gradeFilters = []
 
                 // 获取当前年级做筛选条件
-
                 let arr = [
                     {
                         title: this.$t('teachContent.applySub'),
@@ -734,20 +806,6 @@ export default {
                 this.$Message.error(this.$t('teachContent.authErr'))
             }
         },
-        // 获取容器空间情况 
-        getSize() {
-            this.sizeLoading = true
-            BlobTool.getContainerSize(this.containerName, this.routerScope).then(
-                res => {
-                    this.sizeInfo = res
-                },
-                err => {
-                    this.$Message.error(this.$t('teachContent.sizeErr'))
-                }
-            ).finally(() => {
-                this.sizeLoading = false
-            })
-        },
         //批量删除
         delFileBatch() {
             if (this.selections.length == 0) {
@@ -763,115 +821,155 @@ export default {
                 title: this.$t('teachContent.props1'),
                 content: "<p class='dialog-p'>" + names.join(', ') + '</p>',
                 onOk: () => {
-                    if (this.activeType == 'res') {
-                        //批量删除HTEX需要循环删除每个文件夹下面的文件
-                        this.selections.forEach((item, index) => {
-                            this.$api.blob.deletePrefix({
-                                cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
-                                prefix: `res/${item.name.replace('.HTEX', '')}`,
-                                scope: this.routerScope
-                            }).then(
-                                res => {
-                                    let fileNames = this.selections.map((item) => { return item.name })
-                                    for (let i = 0; i < this.fileList[this.activeType].length; i++) {
-                                        let index = fileNames.indexOf(this.fileList[this.activeType][i].name)
-                                        if (index > -1) {
-                                            this.sizeInfo[this.activeType] -= this.fileList[this.activeType][i].size
-                                            this.sizeInfo.total -= this.fileList[this.activeType][i].size
-                                            this.fileList[this.activeType].splice(i, 1)
-                                            i--
-                                        }
-                                    }
-                                    let fs = this.fileList[this.activeType] ? this.fileList[this.activeType] : []
-                                    this.fileListShow = this._.cloneDeep(fs)
-                                    this.$Message.success(this.$t('teachContent.props2'))
-                                },
-                                err => {
-                                    this.$Message.error(this.$t('teachContent.props3'))
-                                }
-                            ).finally(() => {
-                                this.isLoading = false
-                            })
-                        })
-                        //删除本地最近上传数据
-                        let ids = this.selections.map(item => {
-                            return item.id
-                        })
-                        this.delCacheFiles(ids)
-                    } else {
-                        let blobs = this.selections.map((item) => {
-                            return item.url.substring(0, item.url.lastIndexOf('?'))
-                        })
-                        let ids = this.selections.map((item) => {
-                            return item.id
+                    let params = {
+                        scope: this.routerScope,
+                        cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                        blobs: []
+                    }
+                    this.selections.forEach(item => {
+                        params.blobs.push({
+                            path: item.blob.substring(1),
+                            id: item.id
                         })
-                        this.$api.blob.deleteBlobs({
-                            cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
-                            urls: blobs,
-                            scope: this.routerScope,
-                            ids
-                        }).then(
-                            res => {
-                                let thums = this.selections.map((item) => {
-                                    if (item.type == 'image') {
-                                        return item.url.substring(0, item.url.lastIndexOf('?')).replace('/image/', '/thum/')
-                                    } else if (item.extension == 'MP4') {
-                                        let n = item.url.substring(0, item.url.lastIndexOf('?')).replace('/video/', '/thum/')
-                                        return n.slice(0, n.lastIndexOf('.')) + '.png'
-                                    }
-
-                                })
-                                // if (this.activeType == 'image') {
-                                //     thums = blobs.map((item) => { return item.replace('/image/', '/thum/') })
-                                // } else if (this.activeType == 'video') {
-                                //     thums = blobs.map((item) => {
-                                //         let n = item.replace('/video/', '/thum/')
-                                //         return n.slice(0, n.lastIndexOf('.')) + '.png'
-                                //     })
-                                // }
-                                if (thums.length) {
-                                    this.$api.blob.deleteBlobs({
-                                        cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
-                                        urls: thums,
-                                        scope: this.routerScope,
-                                        ids: []
-                                    })
-                                }
-
-                                let files = this.selections.map((item) => { return item.blob })
-                                for (let i = 0; i < this.fileList[this.activeType].length; i++) {
-                                    let index = files.indexOf(this.fileList[this.activeType][i].blob)
-                                    if (index != -1) {
-                                        this.sizeInfo[this.activeType] -= this.fileList[this.activeType][i].size
-                                        this.sizeInfo.total -= this.fileList[this.activeType][i].size
-                                        this.fileList[this.activeType].splice(i, 1)
-                                        i--
-                                    }
+                    })
+                    this.$api.blob.deleteBlobs(params).then(
+                        res => {
+                            let ids = this.selections.map(item => {
+                                return item.id
+                            })
+                            //删除本地最近上传数据
+                            this.delCacheFiles(ids)
+                            this.fileList[this.activeType] = this.fileList[this.activeType] ? this.fileList[this.activeType] : []
+                            for (let i = 0; i < this.fileList[this.activeType].length; i++) {
+                                if (ids.includes(this.fileList[this.activeType][i].id)) {
+                                    this.sizeInfo[this.activeType] -= this.fileList[this.activeType][i].size
+                                    this.sizeInfo.total -= this.fileList[this.activeType][i].size
+                                    this.fileList[this.activeType].splice(i, 1)
+                                    i--
                                 }
-                                let fs = this.fileList[this.activeType] ? this.fileList[this.activeType] : []
-                                this.fileListShow = this._.cloneDeep(fs)
-                                this.$Message.success(this.$t('teachContent.props2'))
-
-                                if (this.activeType == 'recent') {
-                                    this.selections.forEach(item => {
-                                        let type = item.type
-                                        if (this.fileList[type]) {
-                                            for (let i = 0; i < this.fileList[type].length; i++) {
-                                                if (item.id == this.fileList[type][i].id) {
-                                                    this.fileList[type].splice(i, 1)
-                                                }
+                            }
+                            this.fileListShow = this._.cloneDeep(this.fileList[this.activeType])
+                            this.$Message.success(this.$t('teachContent.props2'))
+                            //如果是从最近删除,需要更新其他类型列表
+                            if (this.activeType == 'recent') {
+                                this.selections.forEach(item => {
+                                    let type = item.type
+                                    if (this.fileList[type]) {
+                                        for (let i = 0; i < this.fileList[type].length; i++) {
+                                            if (item.id == this.fileList[type][i].id) {
+                                                this.fileList[type].splice(i, 1)
+                                                break
                                             }
                                         }
-                                    })
-                                }
-                                //删除本地最近上传数据
-                                this.delCacheFiles(ids)
-                            },
-                            err => {
-                                this.$Message.success(this.$t('teachContent.props3'))
+                                    }
+                                })
                             }
-                        )
-                    }
+                        },
+                        err => {
+                            this.$Message.error(this.$t('teachContent.props3'))
+                        }
+                    )
+                    // if (this.activeType == 'res') {
+                    //     //批量删除HTEX需要循环删除每个文件夹下面的文件
+                    //     this.selections.forEach((item, index) => {
+                    //         this.$api.blob.deletePrefix({
+                    //             cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                    //             prefix: `res/${item.name.replace('.HTEX', '')}`,
+                    //             scope: this.routerScope
+                    //         }).then(
+                    //             res => {
+                    //                 let fileNames = this.selections.map((item) => { return item.name })
+                    //                 for (let i = 0; i < this.fileList[this.activeType].length; i++) {
+                    //                     let index = fileNames.indexOf(this.fileList[this.activeType][i].name)
+                    //                     if (index > -1) {
+                    //                         this.sizeInfo[this.activeType] -= this.fileList[this.activeType][i].size
+                    //                         this.sizeInfo.total -= this.fileList[this.activeType][i].size
+                    //                         this.fileList[this.activeType].splice(i, 1)
+                    //                         i--
+                    //                     }
+                    //                 }
+                    //                 let fs = this.fileList[this.activeType] ? this.fileList[this.activeType] : []
+                    //                 this.fileListShow = this._.cloneDeep(fs)
+                    //                 this.$Message.success(this.$t('teachContent.props2'))
+                    //             },
+                    //             err => {
+                    //                 this.$Message.error(this.$t('teachContent.props3'))
+                    //             }
+                    //         ).finally(() => {
+                    //             this.isLoading = false
+                    //         })
+                    //     })
+                    //     //删除本地最近上传数据
+                    //     let ids = this.selections.map(item => {
+                    //         return item.id
+                    //     })
+                    //     this.delCacheFiles(ids)
+                    // } else {
+                    //     let blobs = this.selections.map((item) => {
+                    //         return item.url.substring(0, item.url.lastIndexOf('?'))
+                    //     })
+                    //     let ids = this.selections.map((item) => {
+                    //         return item.id
+                    //     })
+                    //     this.$api.blob.deleteBlobs({
+                    //         cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                    //         urls: blobs,
+                    //         scope: this.routerScope,
+                    //         ids
+                    //     }).then(
+                    //         res => {
+                    //             let thums = this.selections.map((item) => {
+                    //                 if (item.type == 'image') {
+                    //                     return item.url.substring(0, item.url.lastIndexOf('?')).replace('/image/', '/thum/')
+                    //                 } else if (item.extension == 'MP4') {
+                    //                     let n = item.url.substring(0, item.url.lastIndexOf('?')).replace('/video/', '/thum/')
+                    //                     return n.slice(0, n.lastIndexOf('.')) + '.png'
+                    //                 }
+
+                    //             })
+                    //             if (thums.length) {
+                    //                 this.$api.blob.deleteBlobs({
+                    //                     cntr: this.routerScope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                    //                     urls: thums,
+                    //                     scope: this.routerScope,
+                    //                     ids: []
+                    //                 })
+                    //             }
+
+                    //             let files = this.selections.map((item) => { return item.blob })
+                    //             for (let i = 0; i < this.fileList[this.activeType].length; i++) {
+                    //                 let index = files.indexOf(this.fileList[this.activeType][i].blob)
+                    //                 if (index != -1) {
+                    //                     this.sizeInfo[this.activeType] -= this.fileList[this.activeType][i].size
+                    //                     this.sizeInfo.total -= this.fileList[this.activeType][i].size
+                    //                     this.fileList[this.activeType].splice(i, 1)
+                    //                     i--
+                    //                 }
+                    //             }
+                    //             let fs = this.fileList[this.activeType] ? this.fileList[this.activeType] : []
+                    //             this.fileListShow = this._.cloneDeep(fs)
+                    //             this.$Message.success(this.$t('teachContent.props2'))
+
+                    //             if (this.activeType == 'recent') {
+                    //                 this.selections.forEach(item => {
+                    //                     let type = item.type
+                    //                     if (this.fileList[type]) {
+                    //                         for (let i = 0; i < this.fileList[type].length; i++) {
+                    //                             if (item.id == this.fileList[type][i].id) {
+                    //                                 this.fileList[type].splice(i, 1)
+                    //                             }
+                    //                         }
+                    //                     }
+                    //                 })
+                    //             }
+                    //             //删除本地最近上传数据
+                    //             this.delCacheFiles(ids)
+                    //         },
+                    //         err => {
+                    //             this.$Message.success(this.$t('teachContent.props3'))
+                    //         }
+                    //     )
+                    // }
                 }
             })
         },
@@ -1251,6 +1349,10 @@ export default {
 
     },
     computed: {
+        ...mapGetters({
+            SCHOOL_SPACE: 'SCHOOL_SPACE',
+            PRIVATE_SPACE: 'PRIVATE_SPACE'
+        }),
         filterPeriodName() {
             if (this.schoolBase && this.schoolBase.period) {
                 let data = this.schoolBase.period
@@ -1266,8 +1368,16 @@ export default {
             } else {
                 return ''
             }
-
         },
+        storageSpace() {
+            if (this.routerScope == 'school') {
+                console.log('空间', this.SCHOOL_SPACE, this.teachSpace)
+                return (this.SCHOOL_SPACE - this.teachSpace) * 1024 * 1024 * 1024
+            } else {
+                console.log('空间', this.PRIVATE_SPACE)
+                return this.PRIVATE_SPACE * 1024 * 1024 * 1024
+            }
+        }
     },
     mounted() {
         let box = document.getElementById('card-box')
@@ -1311,11 +1421,10 @@ export default {
                 this.initData()
                 if (this.$route.name == 'schoolcontent') {
                     this.routerScope = 'school'
-                    this.storageSpace = this.$GLOBAL.SCHOOL_SPACE
                 } else {
                     this.routerScope = 'private'
-                    this.storageSpace = this.$GLOBAL.PRIVATE_SPACE
                 }
+
                 this.getSasStr()
             },
             immediate: true

+ 1 - 0
TEAMModelOS/ClientApp/vue.config.js

@@ -9,6 +9,7 @@ module.exports = {
 	outputDir: '../wwwroot',
 	//lintOnSave: process.env.NODE_ENV !== 'production',
 	lintOnSave: false,
+	// filenameHashing:false,
 	pages: {
 		app: {
 			entry: 'src/main.js',

+ 3 - 18
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -235,18 +235,14 @@ namespace TEAMModelOS.Controllers
                     var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
                     await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     int n = 0;
+                    List<string> sheetIds = new List<string>();
                     foreach (PaperSimple simple in request.papers)
                     {
-                        simple.blob = "/exam/" + request.id + "/paper/" + request.subjects[n].id;
+                        simple.blob =$"/exam/{request.id}/paper/{request.subjects[n].id}";
                         n++;
+                        simple.sheet = null;
                     }
-                    //request.papers
-                    /*long SequenceNumber = await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.startTime);
-                    request.sequenceNumber = SequenceNumber;*/
-                    //await _azureStorage.GetBlobContainerClient("hbcn").GetBlobsCatalogSize($"exam/{request.id}");
                     exam = await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
-                    //await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.startTime);
-                    //request.sequenceNumber = SequenceNumber;
                 }
                 else
                 {
@@ -278,19 +274,8 @@ namespace TEAMModelOS.Controllers
                         simple.blob = "/exam/" + request.id + "/paper/" + request.subjects[n].id;
                         n++;
                     }
-                    /*  await _serviceBus.GetServiceBusClient().cancelMessage(Constants.TopicName, info.sequenceNumber);
-                      long SequenceNumber = await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.startTime);
-                      request.sequenceNumber = SequenceNumber;*/
                     exam = await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(request, request.id, new PartitionKey($"{request.code}"));
-
-
-                    //await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.startTime);
                 }
-                //Survey homeWork = await _azureCosmos.SaveOrUpdate<Survey>(request.survey);
-
-                //设定结束时间
-                //string msgEndId = _snowflakeId.NextId() + "";
-                //await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.endTime);
                 return Ok(new { exam });
             }
             catch (Exception ex)

+ 44 - 4
TEAMModelOS/Controllers/Core/BlobController.cs

@@ -422,9 +422,26 @@ namespace TEAMModelOS.Controllers.Core
                 }
                 foreach (var item in blobItems)
                 {
+                    if (item.Name.StartsWith("image") || item.Name.StartsWith("video"))
+                    {
+                        var thum = $"thum{ item.Name.Substring(5)}";
+                        var tnewName = $"thum{ newName.Substring(5)}";
+                        var tpx = $"thum{ px.Substring(5)}";
+                        string tname = thum.Replace(tpx, tnewName);
+                        if (item.Name.StartsWith("video")) {
+                            string fileexturl = thum.Substring(thum.LastIndexOf(".") > 0 ? thum.LastIndexOf(".") : 0);
+                            thum = thum.Replace(fileexturl, ".png");
+                            string fileextname = tname.Substring(tname.LastIndexOf(".") > 0 ? tname.LastIndexOf(".") : 0);
+                            tname = tname.Replace(fileextname, ".png");
+                        }
+                        var turl = _azureStorage.GetBlobSAS($"{_cntr}", thum, BlobSasPermissions.Read | BlobSasPermissions.List);
+                        bcc.GetBlobClient(tname).SyncCopyFromUri(new Uri(turl));
+                        await _azureStorage.GetBlobServiceClient().DelectBlobs($"{_cntr}", new List<string> { thum });
+                    }
                     string targetName = item.Name.Replace(px, newName);
-                    var url = _azureStorage.GetBlobSAS($"{_cntr}", item.Name, BlobSasPermissions.Read  | BlobSasPermissions.List);
+                    var url = _azureStorage.GetBlobSAS($"{_cntr}", item.Name, BlobSasPermissions.Read | BlobSasPermissions.List);
                     bcc.GetBlobClient(targetName).SyncCopyFromUri(new Uri(url));
+
                 };
                 await _azureStorage.GetBlobServiceClient().DelectBlobs($"{_cntr}", new List<string> { px });
                 await client.GetContainer("TEAMModelOS", tbname).ReplaceItemAsync<Bloblog>(bloblog, $"{_id}", new PartitionKey($"Bloblog-{_cntr}"));
@@ -717,12 +734,35 @@ namespace TEAMModelOS.Controllers.Core
                 if (flag)
                 {
                     var urls = blobs.Select(x => x.path).ToList();
-                    var status = await _azureStorage.GetBlobServiceClient().DelectBlobs(blobContainerName, urls);
+                    List<string> deleteUrl = new List<string>();
+                    urls.ForEach(x => {
+                        string delUrl = x;
+                        if (x.StartsWith("res") && x.EndsWith(".HTEX", StringComparison.OrdinalIgnoreCase))
+                        {
+                            delUrl = x.Substring(0, x.Length - 4);
+                        }
+                        //自动删除视频和图片的缩略图
+                        if (x.StartsWith("image")||x.StartsWith("video")) {
+
+                            var thum  = $"thum{ x.Substring(5)}" ;
+                            if (x.StartsWith("video"))
+                            {
+                                string fileexturl = thum.Substring(thum.LastIndexOf(".") > 0 ? thum.LastIndexOf(".") : 0);
+                                thum = thum.Replace(fileexturl, ".png");
+                            }
+                            deleteUrl.Add(thum);
+                        }
+                        deleteUrl.Add(delUrl);
+                    });
+                    var status = await _azureStorage.GetBlobServiceClient().DelectBlobs(blobContainerName, deleteUrl);
                     //释放的空间
                     HashSet<string> root = new HashSet<string>();
-                    foreach (var x in urls)
+                    foreach (var x in deleteUrl)
                     {
-                        string[] uls = System.Web.HttpUtility.UrlDecode(x, Encoding.UTF8).Split("/");
+                        string url = System.Web.HttpUtility.UrlDecode(x, Encoding.UTF8);
+                       
+                        
+                        string[] uls = url.Split("/");
                         if (uls != null)
                         {
                             string u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];

+ 1 - 1
TEAMModelOS/Controllers/Paper/PaperController.cs

@@ -171,7 +171,7 @@ namespace TEAMModelOS.Controllers
             }
             if (scope.ToString().Equals("private"))
             {
-                sql.Append("select c.id,c.code,c.name,c.blob,c.subjectName,c.score,c.useCount,c.scope,c.scoring ,c.createTime from c");
+                sql.Append("select c.id,c.code,c.name,c.blob,c.subjectName,c.score,c.useCount,c.scope,c.scoring ,c.createTime ,c.sheet from c");
                 AzureCosmosQuery cosmosDbQuery = SQLHelper.GetSQL(dict, sql);
                 await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryStreamIterator(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Paper-{code}") }))
                 {

+ 21 - 58
TEAMModelOS/Controllers/Paper/SheetConfigController.cs

@@ -5,25 +5,13 @@ using System;
 using System.Collections.Generic;
 using System.IdentityModel.Tokens.Jwt;
 using System.Linq;
-using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
-using TEAMModelOS.Models.Dto;
 using TEAMModelOS.SDK.Models;
-using TEAMModelOS.SDK;
-using TEAMModelOS.SDK.Context.Constant.Common;
 using TEAMModelOS.SDK.DI;
-using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
 using TEAMModelOS.SDK.Extension;
-using TEAMModelOS.SDK;
-using TEAMModelOS.SDK.Helper.Common.StringHelper;
 using TEAMModelOS.Models;
 using Microsoft.Extensions.Options;
-using TEAMModelOS.SDK.Models.Cosmos;
-using Microsoft.AspNetCore.Authorization;
-using TEAMModelOS.Filter;
-using StackExchange.Redis;
-using TEAMModelOS.SDK.Models.Cosmos.Common.Inner;
 using TEAMModelOS.Services.Common;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using Azure;
@@ -204,6 +192,9 @@ namespace TEAMModelOS.Controllers.Common
                 request.TryGetProperty("owner", out JsonElement _owner);
                 var client = _azureCosmos.GetCosmosClient();
                 SheetConfig sheet = _sheet.ToObject<SheetConfig>();
+                if (sheet == null) { 
+                    return BadRequest("sheet params is null!");
+                }
                 if (!sheet.code.StartsWith("SheetConfig-")) {
                     sheet.code = $"SheetConfig-{sheet.code}";
                 }
@@ -214,7 +205,7 @@ namespace TEAMModelOS.Controllers.Common
                 if (!_paperId.ValueKind.Equals(JsonValueKind.Null) && !_examId.ValueKind.Equals(JsonValueKind.Null) && !string.IsNullOrEmpty($"{ _examId}") && !string.IsNullOrEmpty($"{ _paperId}"))
                 {
                     string code = "";
-                    if (string.IsNullOrEmpty($"{_owner}") && $"{_owner}".Equals("school", StringComparison.OrdinalIgnoreCase)) {
+                    if (!string.IsNullOrEmpty($"{_owner}") && $"{_owner}".Equals("school", StringComparison.OrdinalIgnoreCase)) {
                         code = $"Exam-{sheet.school}";
                         sheet.scope = "school";
                     }
@@ -228,22 +219,19 @@ namespace TEAMModelOS.Controllers.Common
                         var ps = exam.papers.Where(p => p.id == $"{_paperId}").FirstOrDefault();
                         if (ps != null)
                         {
-                            if (string.IsNullOrEmpty(ps.sheet))
+                            if (!string.IsNullOrEmpty(ps.sheet))
                             {
-                                sheet.id = await genSheetId(client, sheet.code, tbname);
-                                exam.papers.ForEach(x =>
-                                {
-                                    if (x.id.Equals($"{_paperId}"))
-                                    {
-                                        x.sheet = sheet.id;
-                                    }
-                                });
-                                exam = await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync<ExamInfo>(exam, $"{_examId}", new PartitionKey($"{code}"));
+                                await client.GetContainer("TEAMModelOS", tbname).DeleteItemStreamAsync($"{ps.sheet}", new PartitionKey(sheet.code));
                             }
-                            else
+                            sheet.id = await SheetService.genSheetId(client, _dingDing, _option, sheet.code, tbname);
+                            exam.papers.ForEach(x =>
                             {
-                                sheet.id = ps.sheet;
-                            }
+                                if (x.id.Equals($"{_paperId}"))
+                                {
+                                    x.sheet = sheet.id;
+                                }
+                            });
+                            exam = await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync<ExamInfo>(exam, $"{_examId}", new PartitionKey($"{code}"));
                         }
                         else
                         {
@@ -265,7 +253,7 @@ namespace TEAMModelOS.Controllers.Common
                         Paper paper = await client.GetContainer("TEAMModelOS", tbname).ReadItemAsync<Paper>($"{_paperId}", new PartitionKey($"{code}"));
                         if (string.IsNullOrEmpty(paper.sheet))
                         {
-                            sheet.id = await genSheetId(client, sheet.code, tbname);
+                            sheet.id =await SheetService.genSheetId(client, _dingDing, _option, sheet.code, tbname);
                             paper.sheet = sheet.id;
                             paper = await client.GetContainer("TEAMModelOS", tbname).ReplaceItemAsync<Paper>(paper, $"{_paperId}", new PartitionKey($"{code}"));
                         }
@@ -283,7 +271,7 @@ namespace TEAMModelOS.Controllers.Common
                 else if (string.IsNullOrEmpty($"{ _examId}") && string.IsNullOrEmpty($"{ _paperId}"))
                 {
                     if (string.IsNullOrEmpty(sheet.id)) {
-                        sheet.id = await genSheetId(client, sheet.code, tbname);
+                        sheet.id = await SheetService.genSheetId(client, _dingDing, _option, sheet.code, tbname);
                     }
                 }
                 else {
@@ -292,8 +280,8 @@ namespace TEAMModelOS.Controllers.Common
                 }
                 if (string.IsNullOrEmpty(sheet.id))
                 {
-                    sheet.id = await genSheetId(client, sheet.code, tbname);
-                    
+                    sheet.id = await SheetService.genSheetId(client, _dingDing, _option, sheet.code, tbname);
+
                 }
                 sheet = await client.GetContainer("TEAMModelOS", tbname).UpsertItemAsync(sheet, new PartitionKey($"{sheet.code}"));
                 return Ok(new { config = sheet, status=200 });
@@ -304,37 +292,12 @@ namespace TEAMModelOS.Controllers.Common
             }
             catch (Exception e)
             {
-                await _dingDing.SendBotMsg($"OS,{_option.Location},common/SheetConfig/upsert()\n{e.Message}", GroupNames.醍摩豆服務運維群組);
-                return BadRequest(e.StackTrace);
+                await _dingDing.SendBotMsg($"OS,{_option.Location},common/SheetConfig/upsert()\n{e.Message}{e.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                return BadRequest(e.Message);
             }
         }
 
-        private async Task<string> genSheetId(CosmosClient client,string  sheetCode,string  tbname) {
-            string _num09 = "0123456789";
-            string id = $"{Utils.CreatSaltString(7, _num09)}";
-            for (int i = 0; i < 10; i++)
-            {
-                Response response = await client.GetContainer("TEAMModelOS", tbname).ReadItemStreamAsync($"{id}", new PartitionKey(sheetCode)); ;
-                if (response.Status == 404)
-                {
-                    break;
-                }
-                else
-                {
-                    if (i == 9)
-                    {  
-                        string  msg= "common/SheetConfig/upsert()\n id生成异常,重复生成次数超过10次";
-                        await _dingDing.SendBotMsg($"OS,{_option.Location},{msg}", GroupNames.醍摩豆服務運維群組);
-                        throw new Exception(msg);
-                    }
-                    else
-                    {
-                        id = $"{Utils.CreatSaltString(7, _num09)}";
-                    }
-                }
-            }
-            return id;
-        }
+        
         /// <summary>
         ///删除答题卡
         /// </summary>

+ 20 - 16
TEAMModelOS/Controllers/School/CourseController.cs

@@ -236,7 +236,7 @@ namespace TEAMModelOS.Controllers
         /// <param name="json"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        [AuthToken(Roles = "admin,teacher")]
+        [AuthToken(Roles = "admin,teacher,student")]
         [HttpPost("get-list-by-no")]
         public async Task<IActionResult> GetListByNo(JsonElement json) {
             try {
@@ -244,7 +244,7 @@ namespace TEAMModelOS.Controllers
                 if (!json.TryGetProperty("stuListNo", out JsonElement _stuListNo)) return BadRequest();
                 var (userid, _, _, school) = HttpContext.GetAuthTokenInfo();
                 var client = _azureCosmos.GetCosmosClient();
-                var queryNo = $"SELECT  c.no  FROM c where  c.no ='{_stuListNo}'";
+                var queryNo = $"SELECT  *  FROM c where  c.no ='{_stuListNo}'";
                 StuList stuList = null;
                 json.TryGetProperty("studentId", out JsonElement _studentId);
                 json.TryGetProperty("tmdId", out JsonElement _tmdId);
@@ -271,25 +271,29 @@ namespace TEAMModelOS.Controllers
                         }
                     }
                 }
-               
-                if (stuList == null) {
-                    await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryStreamIterator(queryText: queryNo,
-                        requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"StuList") }))
+                await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryStreamIterator(queryText: queryNo,
+                    requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"StuList") }))
+                {
+                    using var jsonNo = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (jsonNo.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
                     {
-                        using var jsonNo = await JsonDocument.ParseAsync(item.ContentStream);
-                        if (jsonNo.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                        var stuDoc = jsonNo.RootElement.GetProperty("Documents").EnumerateArray();
+                        while (stuDoc.MoveNext())
                         {
-                            var stuDoc = jsonNo.RootElement.GetProperty("Documents").EnumerateArray();
-                            while (stuDoc.MoveNext())
+                            JsonElement data = stuDoc.Current;
+                            stuList = data.ToObject<StuList>();
+                            if (stuList != null)
                             {
-                                JsonElement data = stuDoc.Current;
-                                stuList = data.ToObject<StuList>();
+                                (int status, StuList stuLis) = JoinList(stuList, $"{_studentId}", $"{_tmdId}", school);
+                                stuLis = await upsertList(stuList, "private");
+                                return Ok(new { status, stuLis });
                             }
                         }
                     }
                 }
-                return Ok();
+                return Ok(new { status= - 1 });
             } catch (Exception ex) {
+                await _dingDing.SendBotMsg($"OS,{_option.Location},course/get-list-by-no()\n{ex.Message}", GroupNames.醍摩豆服務運維群組);
                 return BadRequest();
             }
         }
@@ -300,7 +304,7 @@ namespace TEAMModelOS.Controllers
             if (string.IsNullOrEmpty($"{_studentId}") && string.IsNullOrEmpty($"{_tmdId}"))
             {
                 //加入学生或醍摩豆ID为空
-                status=2;
+                status=1;
             }
             else
             {
@@ -311,7 +315,7 @@ namespace TEAMModelOS.Controllers
                     if (student != null)
                     {
                         //重复加入
-                        status=  3  ;
+                        status= 2  ;
                     }
                     else
                     {
@@ -326,7 +330,7 @@ namespace TEAMModelOS.Controllers
                     if (!string.IsNullOrEmpty(tmd))
                     {
                         //重复加入
-                        status = 3;
+                        status = 2;
                     }
                     else
                     {

+ 13 - 8
TEAMModelOS/Controllers/School/SchoolController.cs

@@ -1128,11 +1128,12 @@ namespace TEAMModelOS.Controllers
                                         var json = await JsonDocument.ParseAsync(response.ContentStream);
 
                                         //軟體
-                                        Teacher teacherHimself = json.ToObject<Teacher>();
-                                        teacherHimself.size = teacherHimself.size - orgTeacherSize + teacher.size;
-
+                                        // Teacher teacherHimself = json.ToObject<Teacher>();
+                                        //teacherHimself.size = teacherHimself.size - orgTeacherSize + teacher.size;
                                         //最後一起修改
-                                        await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacherHimself, teacherHimself.id, new PartitionKey("Base"));
+                                        //huanghb 2021,7.21修改 ,不用再统计到Teacher-Base中 ,Teacher-Base只记录教师自己的空间大小,并包含其他学校赠与的空间,其他学校的赠与空间放在School 表的Teacher-hbcn(SchoolTeacher)中SchoolTeacher
+                                        //,并在教师登陆的时候 一并计算个人空间和总空间大小。其中总空间大小包含(个人空间+多个学校累加的空间大小,并且如果退出或者被移除Teacher-Base 的"schools": [],为空,则已经实现自动回收)
+                                        // await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacherHimself, teacherHimself.id, new PartitionKey("Base"));
                                         await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolTeacher>(teacher, obj.GetProperty("id").ToString(), new PartitionKey($"Teacher-{school_code}"));
                                     }
                                     else
@@ -1188,15 +1189,19 @@ namespace TEAMModelOS.Controllers
                                     var response = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync(teacher.id, new PartitionKey("Base"));
                                     if (response.Status == 200)
                                     {
-                                        var json = await JsonDocument.ParseAsync(response.ContentStream);
+                                       // var json = await JsonDocument.ParseAsync(response.ContentStream);
 
                                         //軟體
-                                        Teacher teacherHimself = json.ToObject<Teacher>();
-                                        teacherHimself.size -= teacherSizeInSchool;
+                                       // Teacher teacherHimself = json.ToObject<Teacher>();
+                                       // teacherHimself.size -= teacherSizeInSchool;
 
                                         // 最後一起修改
+
+
+                                        //huanghb 2021,7.21修改 ,不用再统计到Teacher-Base中 ,Teacher-Base只记录教师自己的空间大小,并包含其他学校赠与的空间,其他学校的赠与空间放在School 表的Teacher-hbcn(SchoolTeacher)中SchoolTeacher
+                                        //,并在教师登陆的时候 一并计算个人空间和总空间大小。其中总空间大小包含(个人空间+多个学校累加的空间大小,并且如果退出或者被移除Teacher-Base 的"schools": [],为空,则已经实现自动回收)
                                         await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolTeacher>(teacher, teacher.id, new PartitionKey($"Teacher-{school_code}"));
-                                        await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacherHimself, teacherHimself.id, new PartitionKey("Base"));
+                                        //await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacherHimself, teacherHimself.id, new PartitionKey("Base"));
                                     }
                                     else
                                     {

+ 10 - 6
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -53,8 +53,6 @@ namespace TEAMModelOS.Controllers
         [HttpPost("get-teacher-info")]
         public async Task<IActionResult> GetTeacherInfo(JsonElement request)
         {
-            //Debug
-            //string json = System.Text.Json.JsonSerializer.Serialize(id_token);
             try
             {
                 if (!request.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
@@ -65,15 +63,18 @@ namespace TEAMModelOS.Controllers
                 jwt.Payload.TryGetValue("name", out object name);
                 jwt.Payload.TryGetValue("picture", out object picture);
                 List<object> schools = new List<object>();
-                // object schools = null;
                 string defaultschool = null;
                 //TODO 取得Teacher 個人相關數據(課程清單、虛擬教室清單、歷史紀錄清單等),學校數據另外API處理,多校切換時不同
                 var client = _azureCosmos.GetCosmosClient();
                 Teacher teacher = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
+                teacher.name = $"{name}";
+                teacher.picture = $"{picture}";
                 int total = 0;
                 int tsize = 0;
                 try {
+                    ///教师的个人空间
                     tsize = teacher.size;
+                    ///教师的总空间 包含 个人空间和学校赠送的空间累加
                     total = teacher.size;
                     //检查是否有加入学校,如果加入学校,则当个人空间size是0G的时候,则免费获得一个G空间,但无论加入多少个学校,只能获取一次 1G的免费空间。没有加入学校则默认0G空间,除非自己购买空间
                     int joinCount = 0;
@@ -89,12 +90,15 @@ namespace TEAMModelOS.Controllers
                                 schoolExtobj.status =sc.status;
                                 schoolExtobj.time = sc.time;
                                 schoolExtobj.picture = school.RootElement.GetProperty("picture");
-                               
                                 joinCount += 1;
                                 var sctch = await client.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(id, new PartitionKey($"Teacher-{sc.schoolId}"));
                                 if (sctch.Status == 200)
                                 {
                                     var jsonDoc = await JsonDocument.ParseAsync(sctch.ContentStream);
+                                    SchoolTeacher schoolTeacher = jsonDoc.RootElement.ToObject<SchoolTeacher>();
+                                    schoolTeacher.name = $"{name}";
+                                    schoolTeacher.picture = $"{picture}";
+                                    await  client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync(schoolTeacher, id, new PartitionKey($"Teacher-{sc.schoolId}"));
                                     if (jsonDoc.RootElement.TryGetProperty("size", out JsonElement _size) && _size.ValueKind.Equals(JsonValueKind.Number))
                                     {
                                         total += _size.GetInt32();
@@ -113,9 +117,9 @@ namespace TEAMModelOS.Controllers
                         if (total <= 0 && joinCount > 0)
                         {
                             teacher.size = 1;
-                            await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, id, new PartitionKey("Base"));
                         }
                     }
+                    await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, id, new PartitionKey("Base"));
                     //預設學校ID
                     defaultschool = teacher.defaultSchool;
                 } catch(CosmosException ex) 
@@ -579,7 +583,7 @@ namespace TEAMModelOS.Controllers
                             id = id,
                             name = name.ToString(),
                             picture = picture?.ToString(), //TODO JJ,原本的判断会出现报错,没检查null却又去ToString
-                            permissions = new List<string>(),
+                            permissions = new List<string>() { "content-read", "exercise-read", "knowledge-read", "syllabus-read" },
                             roles = new List<string>() { "teacher" },
                             status = grant_type.GetString()
                         };

+ 85 - 52
TEAMModelOS/Controllers/XTest/TestController.cs

@@ -13,17 +13,11 @@ using System.Net.Http.Json;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
-using TEAMModelFunction;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
-using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using TEAMModelOS.Services.Common;
-using Microsoft.Extensions.Configuration;
-using TEAMModelOS.SDK.Models.Service;
-using TEAMModelOS.Models;
-using Microsoft.Extensions.Options;
 
 namespace TEAMModelOS.Controllers.XTest
 {
@@ -35,63 +29,50 @@ namespace TEAMModelOS.Controllers.XTest
         private readonly AzureRedisFactory _azureRedis;
         private readonly AzureCosmosFactory _azureCosmos;
         private readonly DingDing _dingDing;
-        private readonly IConfiguration _configuration; 
-        private readonly Option _option;
-        private readonly NotificationService _notificationService;
+       
 
-        public TestController(AzureCosmosFactory azureCosmos, AzureRedisFactory azureRedis, AzureStorageFactory azureStorage, DingDing dingDing, IConfiguration configuration, IOptionsSnapshot<Option> option, NotificationService notificationService) {
+        public TestController(AzureCosmosFactory azureCosmos, AzureRedisFactory azureRedis, AzureStorageFactory azureStorage, DingDing dingDing) {
             _azureCosmos = azureCosmos;
             _azureRedis = azureRedis;
             _azureStorage = azureStorage;
             _dingDing = dingDing;
-            _configuration = configuration;
-            _notificationService = notificationService;
-            _option = option?.Value;
-        }
-        [ProducesDefaultResponseType]
-        [HttpGet("testrds")]
-        public async Task<IActionResult> testrds() {
-            long newestTime = 0;
-            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-            string lockKey = $"Blob:Lock:hbcn:res";
-            RedisValue value = await _azureRedis.GetRedisClient(8).StringGetAsync(lockKey);
-            if (value != default && !value.IsNullOrEmpty)
-            {
-                JsonElement record = value.ToString().ToObject<JsonElement>();
-                if (record.TryGetInt64(out newestTime))
-                {
-                }
-            }
-            return Ok(newestTime);
         }
-
         /// <summary>
         /// 测试blob多线程写入同一个文件
-       
-        //本次处理完成,则继续拉最后进入redis锁的数据
-       
         /// </summary>
         /// <returns></returns>
         [ProducesDefaultResponseType]
         [HttpPost("blobroot")]
         public async Task<IActionResult> MultipleBlob(JsonElement jsonMsg) {
-            var url = _configuration.GetValue<string>("HaBookAuth:CoreService:sendnotification");
-            var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
-            var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
-            var location = _option.Location;
-            var aa =  new { biz = "join_school", tmdid = "1595321354", tmdname = "醍摩豆管理员".ToString(), schoolcode = $"hbcn", schoolname = $"青城山学校", status = 1, time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() };
-            Notification notification = new Notification
+            if (jsonMsg.TryGetProperty("name", out JsonElement name) && name.ValueKind == JsonValueKind.String
+                    && jsonMsg.TryGetProperty("root", out JsonElement root) && root.ValueKind == JsonValueKind.String)
             {
-                hubName = "hita",
-                type = "msg",
-                from = $"ies5:hbcn",
-                to = new List<string> { "1595321354" },
-                label = $"join_school",
-                body = aa.ToJsonString(),
-                expires = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds()
-            };
-            var code = await _notificationService.SendNotification(clientID, clientSecret, location, url, notification);
-            return Ok(new { code });
+                List<Dictionary<string, double?>> list = new List<Dictionary<string, double?>>();
+                string u = System.Web.HttpUtility.UrlDecode($"{root}", Encoding.UTF8).Split("/")[0];
+                var client = _azureStorage.GetBlobContainerClient($"{name}");
+                var size = await client.GetBlobsSize(u);
+                await _azureRedis.GetRedisClient(8).SortedSetRemoveAsync($"Blob:Catalog:{name}", u);
+                await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Blob:Catalog:{name}", u, size.HasValue ? size.Value : 0);
+                var scores = await _azureRedis.GetRedisClient(8).SortedSetRangeByRankWithScoresAsync($"Blob:Catalog:{name}");
+                double blobsize = 0;
+                if (scores != default && scores != null)
+                {
+                    foreach (var score in scores)
+                    {
+                        blobsize = blobsize + score.Score;
+                        list.Add(new Dictionary<string, double?>() { { score.Element.ToString(), score.Score } });
+                    }
+                }
+                await _azureRedis.GetRedisClient(8).HashSetAsync($"Blob:Record", new RedisValue($"{name}"), new RedisValue($"{blobsize}"));
+
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,Blob() 容器:{name}使用:{root},文件分类:{list.ToJsonString()}",
+                    GroupNames.成都开发測試群組);
+                return Ok(list);
+            }
+            else {
+                return Ok();
+            }
+            
         }
 
         private async Task<int> SendNotification( )
@@ -124,9 +105,61 @@ namespace TEAMModelOS.Controllers.XTest
         [ProducesDefaultResponseType]
         [HttpGet("test-delete-read")]
         public async Task<IActionResult> TestDelete() {
-            var status = await _azureStorage.GetBlobServiceClient().DelectBlobs("hbcn", new List<String> { "audio/sound/001.mp3" ,"/audio/醍摩豆原創試題(國中英文)_聽力試題.mp3" , "video/第一段视频.mp4" });
-            //
-            return Ok(new { status });
+
+            foreach (var cnt in _azureStorage.GetBlobServiceClient().GetBlobContainers()) {
+               Console.WriteLine(cnt.Name);
+            }
+
+            await  SendNotification();
+            var client = _azureCosmos.GetCosmosClient();
+            string aaa = "0";
+            try { 
+                ItemResponse<Student> a = await client.GetContainer("TEAMModelOS", "Student").DeleteItemAsync<Student>("1111111", new PartitionKey($"Course-111111"));
+                Ok(a.GetRawResponse().Status);
+            } catch (CosmosException ex) {
+                if (ex.Response.Status == 404) {
+                    aaa = "404";
+                }
+            }
+
+            try
+            {
+                ItemResponse<Student> a = await client.GetContainer("TEAMModelOS", "Student").ReadItemAsync<Student>("1111111", new PartitionKey($"Course-111111"));
+                Ok(a.GetRawResponse().Status);
+            }
+            catch (CosmosException ex)
+            {
+                if (ex.Response.Status == 404)
+                {
+                    aaa = aaa+ " 404";
+                }
+            }
+            try
+            {
+                var a = await client.GetContainer("TEAMModelOS", "Student").DeleteItemStreamAsync("1111111", new PartitionKey($"Course-111111"));
+                Ok(a.Status);
+            }
+            catch (CosmosException ex)
+            {
+                if (ex.Response.Status == 404)
+                {
+                    aaa = "404";
+                }
+            }
+
+            try
+            {
+                var a = await client.GetContainer("TEAMModelOS", "Student").ReadItemStreamAsync("1111111", new PartitionKey($"Course-111111"));
+                Ok(a.Status);
+            }
+            catch (CosmosException ex)
+            {
+                if (ex.Response.Status == 404)
+                {
+                    aaa = aaa + " 404";
+                }
+            }
+            return Ok(new { aaa });
         }
         /// <summary>
         /// 测试redis通配符

+ 42 - 0
TEAMModelOS/Services/Common/SheetService.cs

@@ -0,0 +1,42 @@
+using Azure;
+using Azure.Cosmos;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.Extension;
+
+namespace TEAMModelOS.Services.Common
+{
+    public static class SheetService
+    {
+        public static async Task<string> genSheetId(CosmosClient client , DingDing _dingDing, TEAMModelOS.Models. Option _option, string sheetCode, string tbname)
+        {
+            string _num09 = "0123456789";
+            string id = $"{Utils.CreatSaltString(7, _num09)}";
+            for (int i = 0; i < 10; i++)
+            {
+                Response response = await client.GetContainer("TEAMModelOS", tbname).ReadItemStreamAsync($"{id}", new PartitionKey(sheetCode)); 
+                if (response.Status == 404)
+                {
+                    break;
+                }
+                else
+                {
+                    if (i == 9)
+                    {
+                        string msg = "common/SheetConfig/upsert()\n id生成异常,重复生成次数超过10次";
+                        await _dingDing.SendBotMsg($"OS,{_option.Location},{msg}", GroupNames.醍摩豆服務運維群組);
+                        throw new Exception(msg);
+                    }
+                    else
+                    {
+                        id = $"{Utils.CreatSaltString(7, _num09)}";
+                    }
+                }
+            }
+            return id;
+        }
+    }
+}

+ 3 - 3
TEAMModelOS/TEAMModelOS.csproj

@@ -37,9 +37,9 @@
     <SpaRoot>ClientApp\</SpaRoot>
     <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
     <UserSecretsId>078b5d89-7d90-4f6a-88fc-7d96025990a8</UserSecretsId>
-    <Version>5.2107.16</Version>
-    <AssemblyVersion>5.2107.16.1</AssemblyVersion>
-    <FileVersion>5.2107.16.1</FileVersion>
+    <Version>5.2107.20</Version>
+    <AssemblyVersion>5.2107.20.2</AssemblyVersion>
+    <FileVersion>5.2107.20.2</FileVersion>
     <Description>1.HiTeach API 變更
 (1) 取得老師所在學校相關數據:追加學校blob寫入權限
 (2) 上傳評測結果:追加搬運Blob及Blob路徑資料變更判斷