소스 검색

Merge branch 'develop5.0-tmd' of http://106.12.23.251:10080/TEAMMODEL/TEAMModelOS into develop5.0-tmd

XW 4 년 전
부모
커밋
c2c2350803
48개의 변경된 파일3576개의 추가작업 그리고 345개의 파일을 삭제
  1. 22 0
      TEAMModelFunction/ActivityHttpTrigger.cs
  2. 127 0
      TEAMModelFunction/ItemService.cs
  3. 1 1
      TEAMModelOS.SDK/Models/Cosmos/Common/Bloblog.cs
  4. 24 0
      TEAMModelOS.SDK/Models/Cosmos/Common/ItemCond.cs
  5. 6 0
      TEAMModelOS/ClientApp/src/api/mark.js
  6. BIN
      TEAMModelOS/ClientApp/src/assets/source/audio.png
  7. BIN
      TEAMModelOS/ClientApp/src/assets/source/excel.png
  8. BIN
      TEAMModelOS/ClientApp/src/assets/source/folder.png
  9. BIN
      TEAMModelOS/ClientApp/src/assets/source/image.png
  10. BIN
      TEAMModelOS/ClientApp/src/assets/source/item.png
  11. BIN
      TEAMModelOS/ClientApp/src/assets/source/link.png
  12. BIN
      TEAMModelOS/ClientApp/src/assets/source/pdf.png
  13. BIN
      TEAMModelOS/ClientApp/src/assets/source/ppt.png
  14. BIN
      TEAMModelOS/ClientApp/src/assets/source/unknow.png
  15. BIN
      TEAMModelOS/ClientApp/src/assets/source/video.png
  16. BIN
      TEAMModelOS/ClientApp/src/assets/source/word.png
  17. BIN
      TEAMModelOS/ClientApp/src/assets/source/zip.png
  18. 1 1
      TEAMModelOS/ClientApp/src/common/BaseLayout.less
  19. 24 17
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  20. 21 17
      TEAMModelOS/ClientApp/src/common/UploadModal.vue
  21. 3 2
      TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.less
  22. 7 4
      TEAMModelOS/ClientApp/src/components/selflearn/ExerciseList.vue
  23. 3 2
      TEAMModelOS/ClientApp/src/components/selflearn/NewChooseContent.vue
  24. 10 0
      TEAMModelOS/ClientApp/src/components/syllabus/DragTree.less
  25. 8 3
      TEAMModelOS/ClientApp/src/components/syllabus/DragTree.vue
  26. 19 0
      TEAMModelOS/ClientApp/src/router/routes.js
  27. 5 0
      TEAMModelOS/ClientApp/src/utils/blobTool.js
  28. 18 10
      TEAMModelOS/ClientApp/src/utils/public.js
  29. 3 3
      TEAMModelOS/ClientApp/src/view/evaluation/bank/ExerciseList.vue
  30. 6 4
      TEAMModelOS/ClientApp/src/view/evaluation/bank/TestPaperList.vue
  31. 6 1
      TEAMModelOS/ClientApp/src/view/learnactivity/CreateSchoolEva.vue
  32. 2 2
      TEAMModelOS/ClientApp/src/view/learnactivity/MgtSchoolEva.vue
  33. 370 84
      TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkView.vue
  34. 84 2
      TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.less
  35. 210 19
      TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue
  36. 1 3
      TEAMModelOS/ClientApp/src/view/task/index.less
  37. 174 113
      TEAMModelOS/ClientApp/src/view/task/index.vue
  38. 621 0
      TEAMModelOS/ClientApp/src/view/task/mark/ByQu.vue
  39. 724 0
      TEAMModelOS/ClientApp/src/view/task/mark/ByStu.vue
  40. 696 0
      TEAMModelOS/ClientApp/src/view/task/mark/MarkCanvas.vue
  41. 1 2
      TEAMModelOS/ClientApp/src/view/teachcontent/index.less
  42. 65 21
      TEAMModelOS/Controllers/Common/ExamController.cs
  43. 23 1
      TEAMModelOS/Controllers/Common/SurveyController.cs
  44. 26 1
      TEAMModelOS/Controllers/Common/VoteController.cs
  45. 97 29
      TEAMModelOS/Controllers/Core/BlobController.cs
  46. 23 2
      TEAMModelOS/Controllers/Item/ItemController.cs
  47. 18 1
      TEAMModelOS/Controllers/Pager/PaperController.cs
  48. 127 0
      TEAMModelOS/Services/Common/ItemService.cs

+ 22 - 0
TEAMModelFunction/ActivityHttpTrigger.cs

@@ -15,6 +15,7 @@ using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
 using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
 using TEAMModelOS.SDK.Models.Cosmos;
 using TEAMModelOS.SDK.Models.Cosmos;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
+using TEAMModelOS.TEAMModelFunction;
 
 
 namespace TEAMModelFunction
 namespace TEAMModelFunction
 {
 {
@@ -387,6 +388,27 @@ namespace TEAMModelFunction
             return new OkObjectResult(new { });
             return new OkObjectResult(new { });
         }
         }
         /// <summary>
         /// <summary>
+        //获取题目摘要信息
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        //[AuthToken(Roles = "teacher")]
+        [HttpPost("fix-itemcond")]
+        public async Task<IActionResult> FixItemCond(JsonElement requert)
+        {
+            var client = _azureCosmos.GetCosmosClient();
+            //List<ItemInfo> items = new List<ItemInfo>();
+            var queryslt = $"SELECT  value(c) FROM c ";
+            ItemCond cond = new ItemCond();
+            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<ItemInfo>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Item-habook") }))
+            {
+                ItemService.CountItemCond(item,null,cond);
+                //items.Add(item);
+            }
+            return new OkObjectResult(new { cond });
+        }
+        /// <summary>
         /// 设置问卷调查未初始化学生列表的业务
         /// 设置问卷调查未初始化学生列表的业务
         /// </summary>
         /// </summary>
         /// <param name="req"></param>
         /// <param name="req"></param>

+ 127 - 0
TEAMModelFunction/ItemService.cs

@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Models;
+
+namespace TEAMModelOS.TEAMModelFunction
+{
+    public static class ItemService
+    {
+        /// <summary>
+        /// 计算题目的条件数量变化
+        /// </summary>
+        /// <param name="newItem"></param>
+        /// <param name="odlItem"></param>
+        /// <param name="cond"></param>
+        public static void CountItemCond(ItemInfo newItem, ItemInfo odlItem, ItemCond cond) {
+            //检查两个对象是否是同一条记录
+            if (newItem != null && odlItem == null)
+            {
+                string newKey = $"{newItem.periodId}-{newItem.subjectId}";
+                List<string> grade = newItem.gradeIds;
+                UpdateItemCond(cond, true,newKey, grade, newItem.type, newItem.level, newItem.field.HasValue?newItem.field.Value:0);
+            }
+            else if (newItem != null && odlItem != null)
+            {
+                //更新时 需要保证两个题的 id code 一致
+                if (newItem.id == odlItem.id && newItem.code == odlItem.code)
+                {
+                    //先增加
+                    string newKey = $"{newItem.periodId}-{newItem.subjectId}";
+                    List<string> newGrade = newItem.gradeIds;
+                    UpdateItemCond(cond, true, newKey, newGrade, newItem.type, newItem.level, newItem.field.HasValue ? newItem.field.Value : 0);
+                    //后变更删除
+                    string oldKey = $"{odlItem.periodId}-{odlItem.subjectId}";
+                    List<string> oldGrade = odlItem.gradeIds;
+                    UpdateItemCond(cond, false, oldKey, oldGrade, odlItem.type, odlItem.level, odlItem.field.HasValue ? odlItem.field.Value : 0);
+                }
+            }
+            else if (newItem == null && odlItem != null)
+            {
+                string oldKey = $"{odlItem.periodId}-{odlItem.subjectId}";
+                List<string> oldGrade = odlItem.gradeIds;
+                UpdateItemCond(cond, false, oldKey, oldGrade, odlItem.type, odlItem.level, odlItem.field.HasValue ? odlItem.field.Value : 0);
+            }
+            else {
+               // throw new Exception();
+            }
+        }
+        /// <summary>
+        /// opt=false  减  true 增
+        /// </summary>
+        /// <param name="cond"></param>
+        /// <param name="opt"></param>
+        public static void UpdateItemCond(ItemCond cond, bool opt,string key,List<string> grade,string type,int level,int field)
+        {
+            int count = 0;
+            if (opt)
+            {
+                count = 1;
+            }
+            else
+            {
+                //未计入的则默认0
+                count = -1;
+            }
+            if (cond.conds.ContainsKey(key))
+            {
+                foreach (var x in grade)
+                {
+                    var exCondCount= cond.conds[key].Where(x => x.grade.Key.Equals(x)).FirstOrDefault();
+                    if (exCondCount != null)
+                    {
+                        exCondCount.grade= new KeyValuePair<string, int> (x, exCondCount.grade.Value + count);
+                        if (exCondCount.type.ContainsKey(type))
+                        {
+                            exCondCount.type[type] = exCondCount.type[type] + count;
+                        }
+                        else {
+                            exCondCount.type.Add(type, count);
+                        }
+                        if (exCondCount.level.ContainsKey(level))
+                        {
+                            exCondCount.level[level] = exCondCount.level[level] + count;
+                        }
+                        else
+                        {
+                            exCondCount.level.Add(level, count);
+                        }
+                        if (exCondCount.field.ContainsKey(field))
+                        {
+                            exCondCount.field[field] = exCondCount.field[field] + count;
+                        }
+                        else
+                        {
+                            exCondCount.field.Add(field, count);
+                        }
+                    }
+                    else {
+                        CondCount condCount = new CondCount
+                        {
+                            grade = new KeyValuePair<string, int>(x, count),
+                            type = new Dictionary<string, int> { { type, count } },
+                            field = new Dictionary<int, int> { { field, count } },
+                            level = new Dictionary<int, int> { { level, count } }
+                        };
+                        cond.conds[key].Add(condCount);
+                    }
+                }
+            }
+            else {
+                List<CondCount> conds = new List<CondCount>();
+                foreach (var x in grade) {
+                    CondCount condCount = new CondCount {
+                        grade = new KeyValuePair<string, int>(x, count),
+                        type = new Dictionary<string, int> { { type, count } },
+                        field = new Dictionary<int, int> { { field, count } },
+                        level= new Dictionary<int, int> { { level, count } }
+                    };
+                    conds.Add(condCount);
+                }
+                cond.conds.Add(key, conds);
+            }
+        }
+
+    }
+}

+ 1 - 1
TEAMModelOS.SDK/Models/Cosmos/Common/Bloblog.cs

@@ -15,7 +15,7 @@ namespace TEAMModelOS.SDK.Models
         public long  size { get; set; }
         public long  size { get; set; }
         public string period { get; set; }
         public string period { get; set; }
         /// <summary>
         /// <summary>
-        /// 
+        /// audio 音频,video 视频 ,doc文档,image图片,other 其他,res教材,thum缩略图
         /// </summary>
         /// </summary>
         public string type { get; set; }
         public string type { get; set; }
     }
     }

+ 24 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/ItemCond.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace TEAMModelOS.SDK.Models
+{
+    /// <summary>
+    /// 题目集合
+    /// </summary>
+    public class ItemCond : CosmosEntity
+    {
+        public ItemCond() {
+            pk = "ItemCond";
+        }
+        //key  学段id-科目id
+        public  Dictionary<string, List<CondCount>>  conds = new  Dictionary<string, List<CondCount>> ();
+    }
+    public class CondCount {
+        public KeyValuePair<string, int> grade { get; set; } = new KeyValuePair<string, int>();
+        public Dictionary<string, int> type { get; set; } = new Dictionary<string, int>();
+        public Dictionary<int, int> level { get; set; } = new Dictionary<int, int>();
+        public Dictionary<int, int> field { get; set; } = new Dictionary<int, int>();
+    }
+}

+ 6 - 0
TEAMModelOS/ClientApp/src/api/mark.js

@@ -29,6 +29,12 @@ export default {
      */
      */
      FindNextStu: function (data) {
      FindNextStu: function (data) {
         return post('/common/exam/review', data)
         return post('/common/exam/review', data)
+    },
+    /**
+     * 阅卷打分
+     */
+    saveScore: function(data){
+        return post('/common/exam/sub-result', data)
     }
     }
 
 
 }
 }

BIN
TEAMModelOS/ClientApp/src/assets/source/audio.png


BIN
TEAMModelOS/ClientApp/src/assets/source/excel.png


BIN
TEAMModelOS/ClientApp/src/assets/source/folder.png


BIN
TEAMModelOS/ClientApp/src/assets/source/image.png


BIN
TEAMModelOS/ClientApp/src/assets/source/item.png


BIN
TEAMModelOS/ClientApp/src/assets/source/link.png


BIN
TEAMModelOS/ClientApp/src/assets/source/pdf.png


BIN
TEAMModelOS/ClientApp/src/assets/source/ppt.png


BIN
TEAMModelOS/ClientApp/src/assets/source/unknow.png


BIN
TEAMModelOS/ClientApp/src/assets/source/video.png


BIN
TEAMModelOS/ClientApp/src/assets/source/word.png


BIN
TEAMModelOS/ClientApp/src/assets/source/zip.png


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

@@ -38,7 +38,7 @@
         top: 70px;
         top: 70px;
         bottom: 0px;
         bottom: 0px;
         width: 100%;
         width: 100%;
-        padding-left: 225px;
+        padding-left: 191px;
         overflow-y: auto;
         overflow-y: auto;
         background: var(--body-bg);
         background: var(--body-bg);
         transition: padding-left 0.4s;
         transition: padding-left 0.4s;

+ 24 - 17
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -11,7 +11,7 @@
             <slot name="header-content"></slot>
             <slot name="header-content"></slot>
         </Header>
         </Header>
         <!-- 侧边菜单栏 -->
         <!-- 侧边菜单栏 -->
-        <Sider class="biz-menu" ref="side1" hide-trigger collapsible width="225" :collapsed-width="78" v-model="isCollapsed">
+        <Sider class="biz-menu" ref="side1" hide-trigger collapsible width="190" :collapsed-width="78" v-model="isCollapsed">
             <div class="collapse-icon-box" @click="changeMenuStatus">
             <div class="collapse-icon-box" @click="changeMenuStatus">
                 <Icon :class="rotateIcon" custom="iconfont icon-arrow" style="color:var(--primary-text-color)" @click.stop="changeMenuStatus" size="12" />
                 <Icon :class="rotateIcon" custom="iconfont icon-arrow" style="color:var(--primary-text-color)" @click.stop="changeMenuStatus" size="12" />
             </div>
             </div>
@@ -26,16 +26,16 @@
                                 <Submenu :name="item.subName" v-if="$access.ability(item.role,item.permission).validateAll && item.child.length" v-show="(index == 0 && $store.state.userInfo.hasSchool) || index > 0">
                                 <Submenu :name="item.subName" v-if="$access.ability(item.role,item.permission).validateAll && item.child.length" v-show="(index == 0 && $store.state.userInfo.hasSchool) || index > 0">
                                     <template slot="title">
                                     <template slot="title">
                                         <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
                                         <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
-                                            <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="20" />
+                                            <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                                         </Tooltip>
                                         </Tooltip>
-                                        <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="20" />
+                                        <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                                         <span>{{item.name}}</span>
                                         <span>{{item.name}}</span>
                                     </template>
                                     </template>
                                     <MenuItem class="sub-item-wrap sub-item-wrap-active" :name="menuItem.menuName" :to="menuItem.router" v-for="(menuItem,i) in item.child" :key="i" v-show="$access.ability(menuItem.role,menuItem.permission).validateAll">
                                     <MenuItem class="sub-item-wrap sub-item-wrap-active" :name="menuItem.menuName" :to="menuItem.router" v-for="(menuItem,i) in item.child" :key="i" v-show="$access.ability(menuItem.role,menuItem.permission).validateAll">
                                     <Tooltip :content="menuItem.name" placement="right" transfer v-show="isCollapsed">
                                     <Tooltip :content="menuItem.name" placement="right" transfer v-show="isCollapsed">
-                                        <Icon class="sub-menu-icon" :custom="menuItem.icon" size="18" />
+                                        <Icon class="sub-menu-icon" :custom="menuItem.icon" size="16" />
                                     </Tooltip>
                                     </Tooltip>
-                                    <Icon v-show="!isCollapsed" class="sub-menu-icon" :custom="menuItem.icon" size="18" />
+                                    <Icon v-show="!isCollapsed" class="sub-menu-icon" :custom="menuItem.icon" size="16"/>
                                     <span>
                                     <span>
                                         {{menuItem.name}}
                                         {{menuItem.name}}
                                         <span style="color: aqua;margin-left: 2px;font-size: 12px;vertical-align: text-top;">
                                         <span style="color: aqua;margin-left: 2px;font-size: 12px;vertical-align: text-top;">
@@ -46,9 +46,9 @@
                                 </Submenu>
                                 </Submenu>
                                 <MenuItem :name="item.menuName" v-else-if="$access.ability(item.role,item.permission).validateAll" :to="item.router">
                                 <MenuItem :name="item.menuName" v-else-if="$access.ability(item.role,item.permission).validateAll" :to="item.router">
                                 <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
                                 <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
-                                    <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="22" />
+                                    <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                                 </Tooltip>
                                 </Tooltip>
-                                <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="22" />
+                                <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                                 <span>{{item.name}}</span>
                                 <span>{{item.name}}</span>
                                 </MenuItem>
                                 </MenuItem>
                             </div>
                             </div>
@@ -59,16 +59,16 @@
                             <Submenu :name="item.subName" v-if="$access.ability(item.role,item.permission).validateAll && item.child.length" v-show="(index == 0 && $store.state.userInfo.hasSchool) || index > 0">
                             <Submenu :name="item.subName" v-if="$access.ability(item.role,item.permission).validateAll && item.child.length" v-show="(index == 0 && $store.state.userInfo.hasSchool) || index > 0">
                                 <template slot="title">
                                 <template slot="title">
                                     <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
                                     <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
-                                        <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="20" />
+                                        <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                                     </Tooltip>
                                     </Tooltip>
-                                    <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="20" />
+                                    <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                                     <span>{{item.name}}</span>
                                     <span>{{item.name}}</span>
                                 </template>
                                 </template>
                                 <MenuItem class="sub-item-wrap sub-item-wrap-active" :name="menuItem.menuName" :to="menuItem.router" v-for="(menuItem,i) in item.child" :key="i" v-show="$access.ability(menuItem.role,menuItem.permission).validateAll && menuItem.isShow">
                                 <MenuItem class="sub-item-wrap sub-item-wrap-active" :name="menuItem.menuName" :to="menuItem.router" v-for="(menuItem,i) in item.child" :key="i" v-show="$access.ability(menuItem.role,menuItem.permission).validateAll && menuItem.isShow">
                                 <Tooltip :content="menuItem.name" placement="right" transfer v-show="isCollapsed">
                                 <Tooltip :content="menuItem.name" placement="right" transfer v-show="isCollapsed">
                                     <Icon class="sub-menu-icon" :custom="menuItem.icon" size="18" />
                                     <Icon class="sub-menu-icon" :custom="menuItem.icon" size="18" />
                                 </Tooltip>
                                 </Tooltip>
-                                <Icon v-show="!isCollapsed" class="sub-menu-icon" :custom="menuItem.icon" size="18" />
+                                <Icon v-show="!isCollapsed" class="sub-menu-icon" :custom="menuItem.icon" size="16" />
                                 <span>
                                 <span>
                                     {{menuItem.name}}
                                     {{menuItem.name}}
                                     <span style="color: aqua;margin-left: 2px;font-size: 12px;vertical-align: text-top;">
                                     <span style="color: aqua;margin-left: 2px;font-size: 12px;vertical-align: text-top;">
@@ -79,9 +79,9 @@
                             </Submenu>
                             </Submenu>
                             <MenuItem :name="item.menuName" v-else-if="$access.ability(item.role,item.permission).validateAll && item.isShow" :to="item.router">
                             <MenuItem :name="item.menuName" v-else-if="$access.ability(item.role,item.permission).validateAll && item.isShow" :to="item.router">
                             <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
                             <Tooltip :content="item.name" placement="right" transfer v-show="isCollapsed">
-                                <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="22" />
+                                <Icon :custom="item.icon" style="width:55px;text-align:left;" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                             </Tooltip>
                             </Tooltip>
-                            <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="22" />
+                            <Icon v-show="!isCollapsed" :custom="item.icon" :class="isCollapsed ? 'collapse-icon-size':''" size="16" />
                             <span>{{item.name}}</span>
                             <span>{{item.name}}</span>
                             </MenuItem>
                             </MenuItem>
                         </div>
                         </div>
@@ -598,7 +598,7 @@ export default {
 .menu-item i {
 .menu-item i {
     transform: translateX(0px);
     transform: translateX(0px);
     transition: font-size 0.5s ease, transform 0.5s ease;
     transition: font-size 0.5s ease, transform 0.5s ease;
-    vertical-align: middle;
+    vertical-align: sub;
     font-size: 16px;
     font-size: 16px;
     /*color: var(--primary-text-color);*/
     /*color: var(--primary-text-color);*/
 }
 }
@@ -607,11 +607,11 @@ export default {
     color: #cccccc;
     color: #cccccc;
 }
 }
 .menu-item .ivu-menu-item {
 .menu-item .ivu-menu-item {
-    color: white;
+    color: hsla(0,0%,100%,.7);
 }
 }
 
 
 .menu-item .ivu-menu-submenu-title {
 .menu-item .ivu-menu-submenu-title {
-    color: var(--primary-text-color);
+    color: hsla(0,0%,100%,.7);
 }
 }
 
 
 .collapsed-menu i {
 .collapsed-menu i {
@@ -626,7 +626,7 @@ export default {
     top: -5px;
     top: -5px;
     font-size: 12px;
     font-size: 12px;
     opacity: 0.8;
     opacity: 0.8;
-    color: #1cc0f3;
+    color: hsla(0,0%,100%,.7);
 }
 }
 
 
 .collapsed-menu .ivu-icon-ios-arrow-down:before {
 .collapsed-menu .ivu-icon-ios-arrow-down:before {
@@ -661,7 +661,7 @@ export default {
 
 
 .biz-menu .ivu-menu-vertical .ivu-menu-item,
 .biz-menu .ivu-menu-vertical .ivu-menu-item,
 .biz-menu .ivu-menu-vertical .ivu-menu-submenu-title {
 .biz-menu .ivu-menu-vertical .ivu-menu-submenu-title {
-    padding: 8px 24px;
+    padding: 10px 24px;
 }
 }
 
 
 .biz-menu
 .biz-menu
@@ -675,4 +675,11 @@ export default {
     .ivu-menu-item-active:not(.ivu-menu-submenu):after {
     .ivu-menu-item-active:not(.ivu-menu-submenu):after {
     background: #1cc0f3;
     background: #1cc0f3;
 }
 }
+
+// .ivu-menu-submenu-title:hover .ivu-menu-submenu-title-icon::before,.ivu-menu-submenu-title:hover span, .ivu-menu-submenu-title:hover .ivu-icon{
+//     color: #fff;
+// }
+.ivu-menu-submenu-title:hover .ivu-menu-submenu-title-icon::before{
+    color: rgb(45, 183, 245);
+}
 </style>
 </style>

+ 21 - 17
TEAMModelOS/ClientApp/src/common/UploadModal.vue

@@ -11,9 +11,7 @@
                 </p>
                 </p>
                 <p class="upload-text" style="font-size:12px;">{{$t('updModal.tips1')}}</p>
                 <p class="upload-text" style="font-size:12px;">{{$t('updModal.tips1')}}</p>
                 <p class="upload-text" :style="{fontSize:'12px',marginBottom: uploadedList.length ? '25px':'50px'}">
                 <p class="upload-text" :style="{fontSize:'12px',marginBottom: uploadedList.length ? '25px':'50px'}">
-                    <!-- {{$t('updModal.tips2')}}
-                    <Icon custom="iconfont icon-convert" size="12" class="is-parse-htex" /> -->
-                    * {{$t('updModal.tips3')}}
+                    {{$t('updModal.tips3')}}
                 </p>
                 </p>
             </Upload>
             </Upload>
             <div class="upload-file-box">
             <div class="upload-file-box">
@@ -86,13 +84,6 @@ export default {
         }
         }
     },
     },
     props: {
     props: {
-        //默认文件列表
-        defaultFileList: {
-            default: () => {
-                return []
-            },
-            type: Array
-        },
         //文件路径
         //文件路径
         path: {
         path: {
             default: '',
             default: '',
@@ -133,6 +124,11 @@ export default {
         pdId: {
         pdId: {
             default: '',
             default: '',
             type: String
             type: String
+        },
+        //上传到容器位置 school/private
+        scope: {
+            default: '',
+            type: String
         }
         }
     },
     },
     methods: {
     methods: {
@@ -279,7 +275,8 @@ export default {
                 this.uploadedList.forEach((item, index) => {
                 this.uploadedList.forEach((item, index) => {
                     if (item.loadedBytes < item.size) {
                     if (item.loadedBytes < item.size) {
                         this.loadingCount++
                         this.loadingCount++
-                        this.confirmUpload(item.file, item.type, index)
+                        //默认使用props接受的路径,没有则根据文件类型上传
+                        this.confirmUpload(item.file, this.path || item.type, index)
                     }
                     }
                 })
                 })
 
 
@@ -287,7 +284,6 @@ export default {
         },
         },
         // 确认上传文件
         // 确认上传文件
         confirmUpload(file, fileType, index) {
         confirmUpload(file, fileType, index) {
-            console.log(fileType)
             let extension = this.uploadedList[index].extension
             let extension = this.uploadedList[index].extension
             let _this = this
             let _this = this
             this.containerClient.upload(file, fileType, {
             this.containerClient.upload(file, fileType, {
@@ -404,11 +400,19 @@ export default {
     },
     },
     created() {
     created() {
         let route = this.$route
         let route = this.$route
-        if (route.name == 'personalcontent') {
-            this.routerScope = 'private'
-        } else {
-            this.routerScope = 'school'
+        //没有接收 scope,则通过内容模块逻辑,路由判断
+        if (!this.scope) {
+            if (route.name == 'personalcontent') {
+                this.routerScope = 'private'
+            } else {
+                this.routerScope = 'school'
+            }
         }
         }
+        // 使用组件接收的值
+        else{
+            this.routerScope = this.scope
+        }
+
     },
     },
     watch: {
     watch: {
         urlString: {
         urlString: {
@@ -458,7 +462,7 @@ export default {
 
 
 </script>
 </script>
 <style scoped>
 <style scoped>
-.upd-to-pd{
+.upd-to-pd {
     margin-bottom: 10px;
     margin-bottom: 10px;
 }
 }
 .upload-text {
 .upload-text {

+ 3 - 2
TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.less

@@ -122,7 +122,7 @@
     height: auto;
     height: auto;
     padding: 10px 20px 10px 20px;
     padding: 10px 20px 10px 20px;
     margin-top: 10px;
     margin-top: 10px;
-    font-size: 16px;
+    font-size: 14px;
     background: #fff;
     background: #fff;
     border: 2px solid transparent;
     border: 2px solid transparent;
     cursor: pointer;
     cursor: pointer;
@@ -145,6 +145,7 @@
   border-bottom: 2px solid rgb(128, 128, 128);
   border-bottom: 2px solid rgb(128, 128, 128);
 }
 }
 .cp-exercise-item {
 .cp-exercise-item {
+	color: #565656;
   .item-question {
   .item-question {
     position: relative;
     position: relative;
     cursor: pointer;
     cursor: pointer;
@@ -251,7 +252,7 @@
       border-radius: 5px;
       border-radius: 5px;
     }
     }
     .explain-title {
     .explain-title {
-      width: 10%;
+      width: 12%;
       max-width: 85px;
       max-width: 85px;
       display: inline-block;
       display: inline-block;
       color: rgb(16, 171, 231);
       color: rgb(16, 171, 231);

+ 7 - 4
TEAMModelOS/ClientApp/src/components/selflearn/ExerciseList.vue

@@ -19,7 +19,7 @@
                 <span class="filter-title">年级:</span>
                 <span class="filter-title">年级:</span>
                 <CheckboxGroup v-model="filterGrade" border @on-change="filterGradeChange">
                 <CheckboxGroup v-model="filterGrade" border @on-change="filterGradeChange">
                     <Checkbox lable="all">全部</Checkbox>
                     <Checkbox lable="all">全部</Checkbox>
-                    <Checkbox v-for="(item, index) in gradeList" :key="index" :label="item.id">{{ item.name }}</Checkbox>
+                    <Checkbox v-for="(item, index) in gradeList" :key="index" :label="index">{{ item }}</Checkbox>
                 </CheckboxGroup>
                 </CheckboxGroup>
             </div>
             </div>
             <div class="filter-item" v-show="filterOrigin == schoolCode">
             <div class="filter-item" v-show="filterOrigin == schoolCode">
@@ -76,12 +76,12 @@
         </div>
         </div>
         <!-- 筛选部分结束 -->
         <!-- 筛选部分结束 -->
         <!-- 题目列表部分 -->
         <!-- 题目列表部分 -->
+		<Loading :top="100" v-show="dataLoading" hideMask></Loading>
         <div v-if="exerciseList.length === 0" class="no-data-text">
         <div v-if="exerciseList.length === 0" class="no-data-text">
             <img src="@/assets/icon/no_data_evaluation.png" width="120" />
             <img src="@/assets/icon/no_data_evaluation.png" width="120" />
             <span style="margin-top: 15px; color: #808080">暂无数据</span>
             <span style="margin-top: 15px; color: #808080">暂无数据</span>
         </div>
         </div>
         <div class="content-wrap" ref="mathJaxContainer" v-else>
         <div class="content-wrap" ref="mathJaxContainer" v-else>
-            <Loading :top="100" v-show="dataLoading" hideMask></Loading>
             <div class="exercise-item" v-for="(item, index) of exerciseList" :key="index" @click="onQuestionToggle(index, item.id, $event)">
             <div class="exercise-item" v-for="(item, index) of exerciseList" :key="index" @click="onQuestionToggle(index, item.id, $event)">
                 <!-- 题干部分 -->
                 <!-- 题干部分 -->
                 <div class="item-question">
                 <div class="item-question">
@@ -322,7 +322,7 @@ export default {
                 "@DESC": this.filterSort,
                 "@DESC": this.filterSort,
                 code: this.filterOrigin,
                 code: this.filterOrigin,
                 periodId: this.filterOrigin == this.schoolCode ? [this.periodList[this.filterPeriod].id] : [],
                 periodId: this.filterOrigin == this.schoolCode ? [this.periodList[this.filterPeriod].id] : [],
-                "gradeIds[*]": this.filterOrigin == this.schoolCode ? this.deleteFalse(this.filterGrade) : [],
+                "gradeIds[*]": this.filterOrigin == this.schoolCode ? this.deleteFalse(this.filterGrade).map(i => i + '') : [],
                 subjectId: this.filterOrigin == this.schoolCode ? this.deleteFalse(this.filterSubject) : [],
                 subjectId: this.filterOrigin == this.schoolCode ? this.deleteFalse(this.filterSubject) : [],
                 level: this.deleteFalse(this.filterDiff),
                 level: this.deleteFalse(this.filterDiff),
                 type: this.deleteFalse(this.filterType),
                 type: this.deleteFalse(this.filterType),
@@ -526,7 +526,7 @@ export default {
         deleteFalse(arr) {
         deleteFalse(arr) {
             let list = JSON.parse(JSON.stringify(arr));
             let list = JSON.parse(JSON.stringify(arr));
             list.forEach((item, index) => {
             list.forEach((item, index) => {
-                if (!item || item === "all") list.splice(index, 1);
+                if ((!item || item === "all") && item !== 0 ) list.splice(index, 1);
             });
             });
             return list;
             return list;
         },
         },
@@ -619,4 +619,7 @@ export default {
     transform: translateY(10px);
     transform: translateY(10px);
     opacity: 0;
     opacity: 0;
 }
 }
+.filter-item .ivu-checkbox{
+    display: none;
+}
 </style>
 </style>

+ 3 - 2
TEAMModelOS/ClientApp/src/components/selflearn/NewChooseContent.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
     <div class="choose-content">
     <div class="choose-content">
-        <Tabs type="card" name="chooseContent" @on-click="clickTab">
+        <Tabs :value="tabName" type="card" name="chooseContent" @on-click="clickTab">
             <!-- 选择课纲内容 -->
             <!-- 选择课纲内容 -->
             <TabPane label="课纲" name="syllabus" v-if="showSyllabus" tab="chooseContent">
             <TabPane label="课纲" name="syllabus" v-if="showSyllabus" tab="chooseContent">
                 <div class="tab-wrap">
                 <div class="tab-wrap">
@@ -104,7 +104,6 @@
                     </div>
                     </div>
                 </div>
                 </div>
             </TabPane>
             </TabPane>
-
             <!-- 选择题库 -->
             <!-- 选择题库 -->
             <TabPane label="题目" name="question" v-if="showQuestion" tab="chooseContent">
             <TabPane label="题目" name="question" v-if="showQuestion" tab="chooseContent">
                 <div class="tab-wrap">
                 <div class="tab-wrap">
@@ -160,6 +159,7 @@ export default {
     },
     },
     data() {
     data() {
         return {
         return {
+			tabName:'content',
             questionList: [],
             questionList: [],
             pageNum: 1,
             pageNum: 1,
             pageSize: 20,
             pageSize: 20,
@@ -389,6 +389,7 @@ export default {
         },
         },
         //Tab切换事件
         //Tab切换事件
         clickTab(name) {
         clickTab(name) {
+			this.tabName = name
             switch (name) {
             switch (name) {
                 case 'content':
                 case 'content':
                     this.getFileList()
                     this.getFileList()

+ 10 - 0
TEAMModelOS/ClientApp/src/components/syllabus/DragTree.less

@@ -8,6 +8,10 @@
     padding-top: 10px;
     padding-top: 10px;
     padding-bottom: 100px;
     padding-bottom: 100px;
   }
   }
+  
+  .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{
+	  background-color: #313131 !important;
+  }
   .el-tree-node__content {
   .el-tree-node__content {
     height: 45px;
     height: 45px;
     padding-left: 0 !important;
     padding-left: 0 !important;
@@ -74,6 +78,12 @@
     color: #fff;
     color: #fff;
   }
   }
   
   
+  .content-wrap{
+	  height: 600px !important;
+	  overflow: auto;
+	  padding-bottom: 200px;
+  }
+  
   .ivu-modal-footer{
   .ivu-modal-footer{
 	  border: none;
 	  border: none;
 	  display: flex;
 	  display: flex;

+ 8 - 3
TEAMModelOS/ClientApp/src/components/syllabus/DragTree.vue

@@ -1,12 +1,12 @@
 <template>
 <template>
 	<div class="syllabus-tree-main">
 	<div class="syllabus-tree-main">
 		<vuescroll>
 		<vuescroll>
-			<el-tree :data="treeDatas" :props="defaultProps" class="tree" node-key="id" default-expand-all
+			<el-tree :data="treeDatas" :props="defaultProps" class="tree" node-key="id"  default-expand-all highlight-current
 				@node-drop="handleDrop" @node-click="onNodeClick" :draggable="editable" :expand-on-click-node="false">
 				@node-drop="handleDrop" @node-click="onNodeClick" :draggable="editable" :expand-on-click-node="false">
 				<span class="custom-tree-node" slot-scope="{ node, data }">
 				<span class="custom-tree-node" slot-scope="{ node, data }">
 					<span class="tree-node-lable">
 					<span class="tree-node-lable">
 						{{data.title}}
 						{{data.title}}
-						<!-- <Icon type="md-cube" title="有关联资源" v-if="data.rnodes.length"/> -->
+						<Icon type="md-cube" title="有关联资源" v-if="data.rnodes && data.rnodes.length"/>
 					</span>
 					</span>
 					<span class="custom-tree-tools" v-if="editable">
 					<span class="custom-tree-tools" v-if="editable">
 						<Icon type="md-create" size="16" title="编辑" @click="onEditItem(node,data,$event)" />
 						<Icon type="md-create" size="16" title="编辑" @click="onEditItem(node,data,$event)" />
@@ -71,7 +71,7 @@
 					expand: true,
 					expand: true,
 					editable: true,
 					editable: true,
 					version: '',
 					version: '',
-					type: 1,
+					type: '1',
 					children: [],
 					children: [],
 					remark: '',
 					remark: '',
 					nodeKey: '',
 					nodeKey: '',
@@ -86,6 +86,7 @@
 			onNodeClick(data, node) {
 			onNodeClick(data, node) {
 				console.log(data, node)
 				console.log(data, node)
 				this.curNode = data
 				this.curNode = data
+				this.$emit('onNodeClick',data)
 			},
 			},
 			// 拖拽完成回调
 			// 拖拽完成回调
 			handleDrop(draggingNode, dropNode, dropType) {
 			handleDrop(draggingNode, dropNode, dropType) {
@@ -286,6 +287,10 @@
 					// volumeParent.children = n
 					// volumeParent.children = n
 					// defaultTree.push(volumeParent)
 					// defaultTree.push(volumeParent)
 					this.treeDatas = n
 					this.treeDatas = n
+					this.$nextTick().then(() =>{
+						const firstNode = document.querySelector('.el-tree-node')
+						firstNode.click();
+					  })
 				},
 				},
 				immediate: true
 				immediate: true
 			},
 			},

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

@@ -237,11 +237,30 @@ export const routes = [
 			}
 			}
 			]
 			]
 		},
 		},
+		//废弃 阅卷和批注代码混合,不方便维护
 		{
 		{
 			path: 'MarkView',
 			path: 'MarkView',
 			name:'MarkView',
 			name:'MarkView',
 			component: resolve => require(['@/view/learnactivity/markpaper/MarkView.vue'], resolve)
 			component: resolve => require(['@/view/learnactivity/markpaper/MarkView.vue'], resolve)
 		},
 		},
+		// 按人阅卷
+		{
+			path: 'ByStu',
+			name:'ByStu',
+			component: resolve => require(['@/view/task/mark/ByStu.vue'], resolve),
+			meta: {
+				activeName: 'taskList'
+			}
+		},
+		// 按题阅卷
+		{
+			path: 'ByQu',
+			name:'ByQu',
+			component: resolve => require(['@/view/task/mark/ByQu.vue'], resolve),
+			meta: {
+				activeName: 'taskList'
+			}
+		},
 		//校园基础数据管理
 		//校园基础数据管理
 		{
 		{
 			name: 'system',
 			name: 'system',

+ 5 - 0
TEAMModelOS/ClientApp/src/utils/blobTool.js

@@ -1,6 +1,7 @@
 import { GLOBAL } from '@/static/Global.js';
 import { GLOBAL } from '@/static/Global.js';
 import JsFn from '@/utils/js-fn.js';
 import JsFn from '@/utils/js-fn.js';
 import API from '@/api/index.js';
 import API from '@/api/index.js';
+const blobPath = ['audio', 'doc', 'exam','image', 'item', 'notice', 'other', 'paper', 'res', 'student', 'survey', 'temp', 'thum', 'video', 'vote']
 const { BlobServiceClient, BlobClient } = require("@azure/storage-blob")
 const { BlobServiceClient, BlobClient } = require("@azure/storage-blob")
 //获取文件后缀和类型
 //获取文件后缀和类型
 function getExAndType(fileName) {
 function getExAndType(fileName) {
@@ -193,6 +194,10 @@ export default class BlobTool {
      * @returns {object} {url, name,size,createTime,extension,type}
      * @returns {object} {url, name,size,createTime,extension,type}
      */
      */
     upload(file, path, option, checkSize = true, handleSize = true) {
     upload(file, path, option, checkSize = true, handleSize = true) {
+        console.log(blobPath.includes(path))
+        if (!blobPath.includes(path)) {
+            throw new Error('上传路径不合法,请检查上传路径:' + path)
+        }
         return new Promise(async (r, j) => {
         return new Promise(async (r, j) => {
             //检查容器空间大小
             //检查容器空间大小
             if (checkSize) {
             if (checkSize) {

+ 18 - 10
TEAMModelOS/ClientApp/src/utils/public.js

@@ -351,22 +351,24 @@ export default {
 			}
 			}
 		})
 		})
 	},
 	},
-
+	
+	getBlobHost(){
+		let s = store.state.user.userProfile.blob_uri || store.state.user.studentProfile.blob_uri || 'https://teammodelstorage.blob.core.chinacloudapi.cn/hbcn'
+		return s.split(s.substring(s.lastIndexOf('/')))[0]
+	},
 
 
 	/**
 	/**
 	 * 获取个人容器授权
 	 * 获取个人容器授权
 	 */
 	 */
 	getPrivateSas(code) {
 	getPrivateSas(code) {
 		return new Promise((r, j) => {
 		return new Promise((r, j) => {
-			if (!store.state.privateSas || checkSas(store.state.privateSas.timeout) || store.state.privateSas
-				.name !== store.state.userInfo.TEAMModelId) {
+			if (!store.state.user.schoolProfile.blob_sas) {
 				$api.blob.blobSasRCW({
 				$api.blob.blobSasRCW({
 					name: code || store.state.userInfo.TEAMModelId,
 					name: code || store.state.userInfo.TEAMModelId,
-					role: 'teacher'
+					role: 'school'
 				}).then(
 				}).then(
 					(res) => {
 					(res) => {
 						if (res.error == null) {
 						if (res.error == null) {
-							store.commit('setPrivateSas', res)
 							res.sas = '?' + res.sas
 							res.sas = '?' + res.sas
 							r(res)
 							r(res)
 						} else {
 						} else {
@@ -378,7 +380,11 @@ export default {
 					}
 					}
 				)
 				)
 			} else {
 			} else {
-				r(store.state.privateSas)
+				r({
+					sas:'?' + store.state.user.userProfile.blob_sas,
+					name:code || store.state.userInfo.TEAMModelId,
+					url:this.getBlobHost()
+				})
 			}
 			}
 		})
 		})
 	},
 	},
@@ -387,15 +393,13 @@ export default {
 	 */
 	 */
 	getSchoolSas(code) {
 	getSchoolSas(code) {
 		return new Promise((r, j) => {
 		return new Promise((r, j) => {
-			if (!store.state.schoolSas || checkSas(store.state.schoolSas.timeout) || store.state.schoolSas
-				.name !== store.state.userInfo.schoolCode) {
+			if (!store.state.user.schoolProfile.blob_sas) {
 				$api.blob.blobSasRCW({
 				$api.blob.blobSasRCW({
 					name: code || store.state.userInfo.schoolCode,
 					name: code || store.state.userInfo.schoolCode,
 					role: 'school'
 					role: 'school'
 				}).then(
 				}).then(
 					(res) => {
 					(res) => {
 						if (res.error == null) {
 						if (res.error == null) {
-							store.commit('setSchoolSas', res)
 							res.sas = '?' + res.sas
 							res.sas = '?' + res.sas
 							r(res)
 							r(res)
 						} else {
 						} else {
@@ -407,7 +411,11 @@ export default {
 					}
 					}
 				)
 				)
 			} else {
 			} else {
-				r(store.state.schoolSas)
+				r({
+					sas:'?' + store.state.user.schoolProfile.blob_sas,
+					name:code || store.state.userInfo.schoolCode,
+					url:this.getBlobHost()
+				})
 			}
 			}
 		})
 		})
 	},
 	},

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

@@ -318,7 +318,7 @@
 				this.$api.newEvaluation.FindExerciseList(data).then(async (res) => {
 				this.$api.newEvaluation.FindExerciseList(data).then(async (res) => {
 					if(res.items.length){
 					if(res.items.length){
 						let list = res.items;
 						let list = res.items;
-						if(!this.flag){
+						if((!this.flag && this.isShowSchoolBank) || (!this.flag && !this.isShowSchoolBank && this.filterOrigin === this.schoolCode)){
 							this.periodCountArr = this.getPeriodCount(res.items)
 							this.periodCountArr = this.getPeriodCount(res.items)
 							list = res.items.filter(i => i.periodId === this.periodList[this.filterPeriod].id)
 							list = res.items.filter(i => i.periodId === this.periodList[this.filterPeriod].id)
 							this.flag = true
 							this.flag = true
@@ -354,13 +354,13 @@
 				if(filterKey !== 'grade'){
 				if(filterKey !== 'grade'){
 					let gradeIdArr = this.gradeList.map((i,index) => index)
 					let gradeIdArr = this.gradeList.map((i,index) => index)
 					this.gradeCountArr = gradeIdArr.map(i => {
 					this.gradeCountArr = gradeIdArr.map(i => {
-						return items.filter(j => j.gradeIds.includes(i+'')).length
+						return items.length ? items.filter(j => j.gradeIds && j.gradeIds.includes(i+'')).length : 0
 					})
 					})
 				}
 				}
 				if(filterKey !== 'subject'){
 				if(filterKey !== 'subject'){
 					let subjectIdArr = this.subjectList.map(i => i.id)
 					let subjectIdArr = this.subjectList.map(i => i.id)
 					this.subjectCountArr = subjectIdArr.map(i => {
 					this.subjectCountArr = subjectIdArr.map(i => {
-						return items.filter(j => j.subjectId === i).length
+						return items.length ? items.filter(j => j.subjectId === i).length : 0
 					})
 					})
 				}
 				}
 			},
 			},

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

@@ -142,12 +142,14 @@
 					gradeCountArr:[],
 					gradeCountArr:[],
 					subjectCountArr:[],
 					subjectCountArr:[],
 					periodCountArr:[]
 					periodCountArr:[]
-				}
+				},
+				isShowSchoolBank:false
 			}
 			}
 		},
 		},
 		created() {
 		created() {
 			// this.getPaperList()
 			// this.getPaperList()
 			// this.doFilter()
 			// this.doFilter()
+			this.isShowSchoolBank = this.$route.name === "schoolBank";
 
 
 		},
 		},
 		methods: {
 		methods: {
@@ -236,7 +238,7 @@
 				let that = this
 				let that = this
 				this.$api.learnActivity.FindExamPaper(params).then(async res => {
 				this.$api.learnActivity.FindExamPaper(params).then(async res => {
 					let list = res.papers
 					let list = res.papers
-					if(!this.flag && this.periodList.length){
+					if((!this.flag && this.periodList.length && this.filterParams.code === this.$store.state.userInfo.schoolCode) || (this.flag && !this.isShowSchoolBank && this.filterParams.code === this.$store.state.userInfo.schoolCode)){
 						this.filterCounts.periodCountArr = this.getPeriodCount(res.papers)
 						this.filterCounts.periodCountArr = this.getPeriodCount(res.papers)
 						list = res.papers.filter(i => i.periodId === this.periodList[0].id)
 						list = res.papers.filter(i => i.periodId === this.periodList[0].id)
 						this.flag = true
 						this.flag = true
@@ -272,7 +274,7 @@
 				if(filterKey !== 'grade'){
 				if(filterKey !== 'grade'){
 					let gradeIdArr = this.$refs.baseFilter.gradeList.map((i,index) => index)
 					let gradeIdArr = this.$refs.baseFilter.gradeList.map((i,index) => index)
 					this.filterCounts.gradeCountArr = gradeIdArr.map(i => {
 					this.filterCounts.gradeCountArr = gradeIdArr.map(i => {
-						return papers.filter(j => j.gradeIds.includes(i + '')).length
+						return papers.filter(j => j.gradeIds && j.gradeIds.includes(i + '')).length
 					})
 					})
 				}
 				}
 				if(filterKey !== 'subject'){
 				if(filterKey !== 'subject'){
@@ -433,7 +435,7 @@
 			 * @param code
 			 * @param code
 			 */
 			 */
 			getPeriodName(code) {
 			getPeriodName(code) {
-				return this.$store.state.user.schoolProfile.school_base ? this.$store.state.user.schoolProfile.school_base
+				return code && this.$store.state.user.schoolProfile.school_base ? this.$store.state.user.schoolProfile.school_base
 					.period.filter(i => i.id === code)[0].name : this.$t('evaluation.noData')
 					.period.filter(i => i.id === code)[0].name : this.$t('evaluation.noData')
 			},
 			},
 
 

+ 6 - 1
TEAMModelOS/ClientApp/src/view/learnactivity/CreateSchoolEva.vue

@@ -540,7 +540,12 @@ export default {
 
 
                         //发布成功需要备份试卷数据
                         //发布成功需要备份试卷数据
                         let examId = res.exam.id
                         let examId = res.exam.id
-                        let privateSas = this.$store.state.privateSas
+                        // let privateSas = this.$store.state.privateSas
+                        let privateSas = {
+                            sas: '?' + this.$store.state.user.userProfile.blob_sas,
+                            url: this.$store.state.user.userProfile.blob_uri.slice(0, this.$store.state.user.userProfile.blob_uri.lastIndexOf(this.$store.state.userInfo.TEAMModelId) - 1),
+                            name: this.$store.state.userInfo.TEAMModelId
+                        }
                         let schoolSas = {
                         let schoolSas = {
                             sas: '?' + this.$store.state.user.schoolProfile.blob_sas,
                             sas: '?' + this.$store.state.user.schoolProfile.blob_sas,
                             url: this.$store.state.user.schoolProfile.blob_uri.slice(0, this.$store.state.user.schoolProfile.blob_uri.lastIndexOf(this.$store.state.userInfo.schoolCode) - 1),
                             url: this.$store.state.user.schoolProfile.blob_uri.slice(0, this.$store.state.user.schoolProfile.blob_uri.lastIndexOf(this.$store.state.userInfo.schoolCode) - 1),

+ 2 - 2
TEAMModelOS/ClientApp/src/view/learnactivity/MgtSchoolEva.vue

@@ -109,10 +109,10 @@
                     </span>
                     </span>
 
 
                     <!-- 阅卷功能0531之前完成不了,暂时隐藏 -->
                     <!-- 阅卷功能0531之前完成不了,暂时隐藏 -->
-                    <!-- <span :class="curBarIndex == 2 ? 'evalustion-bar-item line-bottom-active line-bottom':'evalustion-bar-item line-bottom'" @click="selectBar(2)">
+                    <span :class="curBarIndex == 2 ? 'evalustion-bar-item line-bottom-active line-bottom':'evalustion-bar-item line-bottom'" @click="selectBar(2)">
                         {{$t('learnActivity.mgtScEv.markSetting')}}
                         {{$t('learnActivity.mgtScEv.markSetting')}}
                     </span>
                     </span>
-                    <span :class="curBarIndex == 3 ? 'evalustion-bar-item line-bottom-active line-bottom':'evalustion-bar-item line-bottom'" @click="selectBar(3)">
+                    <!-- <span :class="curBarIndex == 3 ? 'evalustion-bar-item line-bottom-active line-bottom':'evalustion-bar-item line-bottom'" @click="selectBar(3)">
                         {{$t('learnActivity.mgtScEv.markData')}}
                         {{$t('learnActivity.mgtScEv.markData')}}
                     </span> -->
                     </span> -->
 
 

+ 370 - 84
TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkView.vue

@@ -4,24 +4,23 @@
         <!-- 头部基础信息 -->
         <!-- 头部基础信息 -->
         <div class="mark-header">
         <div class="mark-header">
             <span class="quit-marking-text">
             <span class="quit-marking-text">
-                <!-- <Icon custom="iconfont icon-quit2" class="quit-marking-icon" title="退出阅卷" @click="test"/> -->
                 <Icon type="ios-arrow-back" class="quit-marking-icon" title="退出阅卷" @click="quit" />
                 <Icon type="ios-arrow-back" class="quit-marking-icon" title="退出阅卷" @click="quit" />
-                <!-- 退出阅卷 -->
             </span>
             </span>
             <span class="info-label">考试名称:</span>
             <span class="info-label">考试名称:</span>
-            <span class="info-value">一年级数学期末考试</span>
+            <span class="info-value">{{taskInfo.name}}</span>
             <span class="info-label">阅卷方式:</span>
             <span class="info-label">阅卷方式:</span>
             <span class="info-value">{{mode == 0 ? '按题阅卷' : '按人阅卷'}}</span>
             <span class="info-value">{{mode == 0 ? '按题阅卷' : '按人阅卷'}}</span>
             <span class="info-label">学生id:</span>
             <span class="info-label">学生id:</span>
-            <span class="info-value">基金会</span>
+            <span class="info-value stu-id-info">{{stuId}}</span>
             <span class="info-label" v-show="mode == 1">分数:</span>
             <span class="info-label" v-show="mode == 1">分数:</span>
-            <span class="info-value" v-show="mode == 1">82</span>
+            <span class="info-value score-info" v-show="mode == 1">{{totalScore}}</span>
             <span class="info-label">当前题号:</span>
             <span class="info-label">当前题号:</span>
-            <span class="info-value">{{quIndex}}</span>
-            <span class="info-label">阅卷进度:</span>
-            <span class="info-value">{{`${12}/${41}`}}</span>
+            <span class="info-value cur-qu-index" v-if="childIndex > -1">{{`${quIndex + 1}-${childIndex + 1}`}}</span>
+            <span class="info-value cur-qu-index" v-else>{{quIndex + 1}}</span>
+            <!-- <span class="info-label">阅卷进度:</span>
+            <span class="info-value">{{`${12}/${41}`}}</span> -->
             <div class="btn-wrap">
             <div class="btn-wrap">
-                <span class="action-btn">
+                <span class="action-btn" @click="toggleStatus = !toggleStatus">
                     <Icon type="md-shuffle" class="action-btn-icon" />
                     <Icon type="md-shuffle" class="action-btn-icon" />
                     切换
                     切换
                 </span>
                 </span>
@@ -65,21 +64,24 @@
                 <div class="qu-index-box" v-show="mode == 1">
                 <div class="qu-index-box" v-show="mode == 1">
                     <div class="qu-tips-box">
                     <div class="qu-tips-box">
                         <span class="qu-tips-tag">
                         <span class="qu-tips-tag">
-                            满分
-                        </span>
-                        <span class="qu-tips-tag">
-                            半对
-                        </span>
-                        <span class="qu-tips-tag">
-                            错误
+                            已阅
                         </span>
                         </span>
                         <span class="qu-tips-tag">
                         <span class="qu-tips-tag">
                             未阅
                             未阅
                         </span>
                         </span>
                     </div>
                     </div>
                     <div>
                     <div>
-                        <span v-for="(item,index) in qus" :key="index" :class="['qu-index',index < 5 ? 'right-qu' : index < 8 ? 'half-qu' : index < 15 ? 'err-qu' : '']" @click="quIndex = index + 1">
-                            {{index + 1}}
+                        <span v-for="(item,index) in paperData.item" :key="index">
+                            <!-- 综合题 -->
+                            <span v-if="item.children.length" :key="index">
+                                <span @click="toQu(index,childIndex)" v-for="(childItem,childIndex) in item.children" :key="childIndex" :class="['qu-index',stuScore[getScoreIndex(index,childIndex)] > -1 ? 'right-qu' : '']">
+                                    {{(index + 1) + '-' + (childIndex + 1)}}
+                                </span>
+                            </span>
+                            <!-- 其他题 -->
+                            <span v-else @click="toQu(index)" :class="['qu-index',stuScore[getScoreIndex(index)] > -1 ? 'right-qu' : '']">
+                                {{index + 1}}
+                            </span>
                         </span>
                         </span>
                     </div>
                     </div>
                 </div>
                 </div>
@@ -88,37 +90,55 @@
             <div class="score-wrap">
             <div class="score-wrap">
                 <div class="quick-score-box score-input-box">
                 <div class="quick-score-box score-input-box">
                     <span>分数:</span>
                     <span>分数:</span>
-                    <InputNumber style="flex:1" :max="10" :min="1" v-model="score"></InputNumber>
+                    <InputNumber style="flex:1" :max="10" :min="1" v-model="stuScore[getScoreIndex(quIndex,childIndex)]"></InputNumber>
                 </div>
                 </div>
                 <div class="quick-score-box">
                 <div class="quick-score-box">
                     <Button size="small" type="info" style="margin-right:8px" ghost @click="score = 10">满分</Button>
                     <Button size="small" type="info" style="margin-right:8px" ghost @click="score = 10">满分</Button>
                     <Button size="small" type="error" ghost @click="score = 0">零分</Button>
                     <Button size="small" type="error" ghost @click="score = 0">零分</Button>
                     <Icon :type="isShowNum ? 'md-eye-off' : 'md-eye'" class="toggle-num-status" @click="isShowNum = !isShowNum" />
                     <Icon :type="isShowNum ? 'md-eye-off' : 'md-eye'" class="toggle-num-status" @click="isShowNum = !isShowNum" />
                     <div :class="['score-key-box', isShowNum ? '':'hind-key-box']">
                     <div :class="['score-key-box', isShowNum ? '':'hind-key-box']">
-                        <span v-for="(item,index) in scores" :key="index" :class="['score-key', score == index ? 'score-key-active':'']" @click="score = index">
+                        <span v-for="(item,index) in quScoreArr" :key="index" :class="['score-key', stuScore[getScoreIndex(quIndex,childIndex)] == index ? 'score-key-active':'']" @click="setScore(index)">
                             {{item}}
                             {{item}}
                         </span>
                         </span>
                     </div>
                     </div>
                 </div>
                 </div>
                 <Button type="success" class="submit-score" @click="submit()">提交分数</Button>
                 <Button type="success" class="submit-score" @click="submit()">提交分数</Button>
+                <div class="score-setting-wrap">
+                    <div class="score-setting-item">
+                        <span>提交分数自动切换题目</span>
+                        <i-switch v-model="autoQu" size="small"/>
+                    </div>
+                    <div class="score-setting-item">
+                        <span>完成阅卷自动获取新学生</span>
+                        <i-switch v-model="autoStu" @on-change="change" size="small"/>
+                    </div>
+                </div>
             </div>
             </div>
         </div>
         </div>
+        <!-- 用来单独渲染学生作答数据,提高tocanvas 的效率 -->
+        <iframe id="markIframe" :srcdoc="curAnswer" v-if="curAnswer"></iframe>
+        <Modal v-model="toggleStatus" title="切换学生">
+            进行中
+        </Modal>
     </div>
     </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import Konva from 'konva'
 import Konva from 'konva'
+import html2canvas from 'html2canvas';
 export default {
 export default {
     name: 'Home',
     name: 'Home',
     data() {
     data() {
         return {
         return {
+            autoQu: true,//自动切换下一题
+            autoStu:true,//自动获取下一学生
+            toggleStatus: false,
             activeIcon: -1,
             activeIcon: -1,
             score: 0,
             score: 0,
             isShowNum: true,
             isShowNum: true,
-            scores: [],
-            qus: [],
             mode: '0',//阅卷模式 1:按人阅卷 0:按题阅卷
             mode: '0',//阅卷模式 1:按人阅卷 0:按题阅卷
-            quIndex: 1,
+            quIndex: 0,
+            childIndex: undefined,
             stage: undefined,
             stage: undefined,
             orgLayer: undefined,
             orgLayer: undefined,
             markLayer: undefined,
             markLayer: undefined,
@@ -147,28 +167,50 @@ export default {
             curImg: undefined,
             curImg: undefined,
             counter: 0,
             counter: 0,
             isFull: false, //是否为全屏模式
             isFull: false, //是否为全屏模式
-            examData:{
-                name:'期末考试',
+            examData: {
+                name: '期末考试',
             },
             },
-            stuData:{
-                name:'',
-                id:'',
-            }
+            stuData: {
+                name: '',
+                id: '',
+            },
+            paperData: {
+                item: []
+            },
+            stuAnswer: [],
+            stuScore: [],
+            taskInfo: {},
+            stuId: ''
         }
         }
     },
     },
     methods: {
     methods: {
-        togglefull() {
-            this.$router.push({
-                name: this.isFull ? 'MarkView' : 'FullMarkView'
-            })
-        },
-        //提交分数
-        submit() {
-            this.counter++
-            this.nextQu()
+        /**将答案绘制到canvas上 */
+        ansToImg() {
+            let answerIframe = document.getElementById('markIframe')
+            answerIframe.onload = () => {
+                answerIframe.style.width = '850px'
+                answerIframe.contentWindow.document.body.style.margin = '0px 20px'
+                answerIframe.contentWindow.document.body.style.padding = '10px'
+                answerIframe.contentWindow.document.body.style.minWidth = '600px'
+                answerIframe.contentWindow.document.body.style.minHeight = '240px'
+                answerIframe.contentWindow.document.body.style.height = 'fit-content'
+                answerIframe.contentWindow.document.body.style.width = 'fit-content'
+                let bodyWidth = answerIframe.contentWindow.document.body.clientWidth
+                answerIframe.style.width = (bodyWidth + 20) + 'px'
+                answerIframe.contentWindow.document.body.style.backgroundColor = '#f5f5f5'
+                console.log('markIframe', answerIframe)
+                html2canvas(answerIframe.contentWindow.document.body, {}).then((canvas) => {
+                    console.log('canvas', canvas)
+                    canvas.id = "canvas" + this.getScoreIndex(this.quIndex, this.childIndex)
+                    let markBg = canvas.toDataURL()
+                    // 将转出来的答案绘制到canvas上
+                    this.imgToCanvas(markBg)
+                    console.log(markBg)
+                })
+            }
         },
         },
-        //加载下一道题目
-        nextQu() {
+        //将图片(答案)绘制到canvas
+        imgToCanvas(img) {
             this.orgLayer.removeChildren()
             this.orgLayer.removeChildren()
             this.markLayer.removeChildren()
             this.markLayer.removeChildren()
             let imageObj = new Image()
             let imageObj = new Image()
@@ -189,9 +231,157 @@ export default {
                 _this.orgLayer.add(img)
                 _this.orgLayer.add(img)
                 _this.orgLayer.batchDraw()
                 _this.orgLayer.batchDraw()
             }
             }
-            imageObj.src = require('@/assets/mark/img' + this.counter % 3 + '.jpg')
+            imageObj.src = img
             this.stage.add(this.orgLayer)
             this.stage.add(this.orgLayer)
         },
         },
+        /** 打分 */
+        setScore(score) {
+            this.$set(this.stuScore, this.getScoreIndex(this.quIndex, this.childIndex), score)
+        },
+        toQu(index, childIndex) {
+            this.quIndex = index
+            this.childIndex = childIndex
+
+        },
+        /**
+         * index 题目index 必传
+         * childIndex 小题index 非必传 
+         */
+        getScoreIndex(index, childIndex) {
+            let realIndex = index
+            this.paperData.item.forEach((item, itemIndex) => {
+                if (itemIndex < index && item.children.length) {
+                    realIndex += item.children.length
+                } else if (itemIndex == index && item.children.length) {
+                    realIndex += childIndex
+                }
+            })
+            return realIndex
+        },
+        togglefull() {
+            this.$router.push({
+                name: this.isFull ? 'MarkView' : 'FullMarkView'
+            })
+        },
+        //提交分数
+        submit() {
+            let requstData = {
+                id: this.taskInfo.id,
+                stuId: this.stuId,
+                subjectId: this.taskInfo.subject,
+                tmdId: this.$store.state.userInfo.TEAMModelId,
+                score: this.stuScore,
+                count: this.taskInfo.count,
+                code: this.taskInfo.ecode.replace('Exam-', ''),
+                mark: ''
+            }
+            this.$api.mark.saveScore(requstData).then(
+                res => {
+                    this.$Message.success('保存成功')
+                    // 按人阅卷自动跳转下一题
+                    if (this.mode == 1 && this.autoQu) {
+                        this.nextQuestion()
+                        this.ansToImg()
+                    }
+                    //按题阅卷自动加载下一人
+                    else {
+
+                    }
+                },
+                err => {
+                    this.$Message.error('保存失败')
+                }
+            )
+
+        },
+        nextQuestion() {
+            // 当前不是最后一题
+            if (this.quIndex < this.paperData.item.length - 1) {
+                //当前题目是综合题
+                if (this.paperData.item[this.quIndex].children.length) {
+                    //当前小题不是最后一个
+                    if (this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                        this.childIndex++
+                    }
+                    //当前小题是最后一个,需要判断下一个题目类型
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+                //当前题目不是综合题
+                else {
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+            }
+            //如果是最后一题
+            else {
+                //当前题目是综合题,并且不是小题最后一题
+                if (this.paperData.item[this.quIndex].children.length && this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                    this.childIndex++
+                }
+                //当前目不是综合题 则代表题号已经到最后一个
+                else {
+                    //检查所有题目是否完成评测
+                    if (this.stuScore.includes(-1)) {
+                        let qu = ''
+                        let quIndex = 0
+                        let childIndex = -1
+                        let realIndex = 0
+                        // 检测具体未评分的题目
+                        for (let i = 0; i < this.paperData.item.length; i++) {
+                            if (this.paperData.item[i].children.length) {
+                                let flag = false
+                                for (let j = 0; j < this.paperData.item[i].children.length; j++) {
+                                    if (this.stuScore[realIndex++] == -1) {
+                                        quIndex = i
+                                        childIndex = j
+                                        flag = true
+                                        break
+                                    }
+                                }
+                                if (flag) {
+                                    break
+                                }
+                            } else {
+                                if (this.stuScore[realIndex++] == -1) {
+                                    quIndex = i
+                                    break
+                                }
+                            }
+                        }
+                        qu = childIndex > -1 ? `${(quIndex + 1)}-${(childIndex + 1)}` : quIndex
+                        this.$Modal.confirm({
+                            title: '未阅题目',
+                            content: `${qu}题目尚未评分,是否跳转到对应题目继续评分?`,
+                            onOk: () => {
+                                this.quIndex = quIndex
+                                this.childIndex = childIndex
+                            }
+                        })
+                    } else {
+                        this.$Message.success('已阅完')
+                    }
+
+                }
+            }
+        },
+
         quit() {
         quit() {
             // 返回页面
             // 返回页面
             this.$router.push({
             this.$router.push({
@@ -726,44 +916,65 @@ export default {
     },
     },
     mounted() {
     mounted() {
         let _this = this
         let _this = this
-        let imageObj = new Image()
-        imageObj.onload = function () {
-            //创建画布
-            _this.imgWidth = imageObj.width
-            _this.imgHeight = imageObj.height
-            _this.stage = new Konva.Stage({
-                container: 'container',
-                id: 'canvas',
-                width: imageObj.width,
-                height: imageObj.height
-            })
-            _this.orgLayer = new Konva.Layer({
-            })
-            _this.markLayer = new Konva.Layer()
-            _this.stage.add(_this.orgLayer)
-            _this.stage.add(_this.markLayer)
-            let img = new Konva.Image({
-                x: 0,
-                y: 0,
-                image: imageObj,
-                width: imageObj.width,
-                height: imageObj.height
-            })
-            _this.orgLayer.add(img)
-            _this.orgLayer.batchDraw()
-            _this.bundleScoll()
-            _this.stage.on('click', (evt) => {
-                _this.tr.nodes([])
-            })
-            _this.stage.on('mouseover', (evt) => {
-                let nodes = _this.tr.nodes()
-                if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+        // let imageObj = new Image()
+        // imageObj.onload = function () {
+        //     //创建画布
+        //     _this.imgWidth = imageObj.width
+        //     _this.imgHeight = imageObj.height
+        //     _this.stage = new Konva.Stage({
+        //         container: 'container',
+        //         id: 'canvas',
+        //         width: imageObj.width,
+        //         height: imageObj.height
+        //     })
+        //     _this.orgLayer = new Konva.Layer({
+        //     })
+        //     _this.markLayer = new Konva.Layer()
+        //     _this.stage.add(_this.orgLayer)
+        //     _this.stage.add(_this.markLayer)
+        //     let img = new Konva.Image({
+        //         x: 0,
+        //         y: 0,
+        //         image: imageObj,
+        //         width: imageObj.width,
+        //         height: imageObj.height
+        //     })
+        //     _this.orgLayer.add(img)
+        //     _this.orgLayer.batchDraw()
+        //     _this.bundleScoll()
+        //     _this.stage.on('click', (evt) => {
+        //         _this.tr.nodes([])
+        //     })
+        //     _this.stage.on('mouseover', (evt) => {
+        //         let nodes = _this.tr.nodes()
+        //         if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
 
 
-            })
+        //     })
 
 
-        }
-        imageObj.src = require('@/assets/mark/img' + this.counter % 3 + '.jpg')
-        
+        // }
+        // imageObj.src = require('@/assets/mark/img' + this.counter % 3 + '.jpg')
+
+        //创建画布
+        _this.stage = new Konva.Stage({
+            container: 'container',
+            id: 'canvas',
+            width: 600,
+            height: 240
+        })
+        _this.orgLayer = new Konva.Layer()
+        _this.markLayer = new Konva.Layer()
+        _this.stage.add(_this.orgLayer)
+        _this.stage.add(_this.markLayer)
+        _this.orgLayer.batchDraw()
+        _this.bundleScoll()
+        _this.stage.on('click', (evt) => {
+            _this.tr.nodes([])
+        })
+        _this.stage.on('mouseover', (evt) => {
+            let nodes = _this.tr.nodes()
+            if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+
+        })
 
 
         this.tr = new Konva.Transformer({
         this.tr = new Konva.Transformer({
             rotateEnabled: false,
             rotateEnabled: false,
@@ -774,15 +985,25 @@ export default {
         })
         })
         this.tr.borderEnabled(true)
         this.tr.borderEnabled(true)
         this.tr.anchorCornerRadius(10)
         this.tr.anchorCornerRadius(10)
+        this.ansToImg()
 
 
     },
     },
     created() {
     created() {
-        this.scores = Array.from(new Array(10).keys())
-        this.qus = Array.from(new Array(40).keys())
-
-        //模拟按题阅卷和整体阅卷
-        let routeData = this.$route
-        this.mode = routeData.params.type
+        //按题阅卷和整体阅卷
+        let routeData = this.$route.params
+        this.paperData = routeData.fullPaper
+        this.stuAnswer = routeData.answer
+        this.stuScore = routeData.score
+        this.mode = routeData.type
+        this.taskInfo = routeData.task
+        this.stuId = routeData.stuId
+        //初始化题目index
+        if (this.paperData && this.paperData.item.length) {
+            this.quIndex = 0
+            if (this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children.length) {
+                this.childIndex = 0
+            }
+        }
         if (routeData.name == 'FullMarkView') this.isFull = true
         if (routeData.name == 'FullMarkView') this.isFull = true
         // this.mode = 1
         // this.mode = 1
 
 
@@ -793,17 +1014,82 @@ export default {
                 if (shape.length) {
                 if (shape.length) {
                     shape[0].remove()
                     shape[0].remove()
                     this.tr.nodes([])
                     this.tr.nodes([])
-                    // this.tr.zIndex(1000)
                 }
                 }
             }
             }
         }
         }
+        //默认表情包
         for (let i = 0; i < 9; i++) {
         for (let i = 0; i < 9; i++) {
             this.imgs.push(require('@/assets/mark/' + i + '.jpg'))
             this.imgs.push(require('@/assets/mark/' + i + '.jpg'))
         }
         }
+    },
+    computed: {
+        /**当期题目分数数组 */
+        quScoreArr() {
+            let score = 0
+            if (this.childIndex > -1 && this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children) {
+                score = this.paperData.item[this.quIndex].children[this.childIndex].score
+            } else {
+                score = this.paperData.item[this.quIndex].score
+            }
+            return Array.from(new Array(score + 1).keys())
+        },
+        /**当前题目作答数据 */
+        curAnswer() {
+            let index = this.getScoreIndex(this.quIndex, this.childIndex)
+            return this.stuAnswer[index]
+        },
+        totalScore() {
+            if (this.mode == 1) {
+                return this.stuScore.reduce((a, b) => {
+                    return a + b
+                }, 0)
+            } else {
+                return 0
+            }
+        }
     }
     }
 }
 }
 </script>
 </script>
 <style scoped lang="less">
 <style scoped lang="less">
+.score-setting-wrap{
+    margin-top: 50px;
+    padding: 0px 5px;
+
+}
+.score-setting-item{
+    margin-top: 15px;
+    display: flex;
+    justify-content: space-between;
+    span{
+        // font-size: 16px;
+    }
+}
+.stu-id-info {
+    color: black;
+    font-size: 16px;
+}
+.score-info {
+    background: #19be6b;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+.cur-qu-index {
+    background: #2db7f5;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+#markIframe {
+    position: fixed;
+    top: 9990px;
+    width: 850px;
+    background: #f5f5f5;
+}
 .quit-marking-icon {
 .quit-marking-icon {
     display: inline-block;
     display: inline-block;
     font-size: 18px;
     font-size: 18px;

+ 84 - 2
TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.less

@@ -123,7 +123,7 @@
 		}
 		}
 		.syllabus-mid{
 		.syllabus-mid{
 			height: 100%;
 			height: 100%;
-			width: 40%;
+			width: 30%;
 			border-right: 1px solid @borderColor;
 			border-right: 1px solid @borderColor;
 			z-index: 1;
 			z-index: 1;
 			
 			
@@ -134,9 +134,62 @@
 		}
 		}
 		.syllabus-right{
 		.syllabus-right{
 			height: 100%;
 			height: 100%;
-			width: 40%;
+			width: 50%;
 			border-right: 1px solid @borderColor;
 			border-right: 1px solid @borderColor;
+			
+			.node-resource-box{
+				display: flex;
+				flex-direction: column;
+				padding: 10px 20px;
+				overflow: auto;
+				
+				.node-resource-item{
+					position: relative;
+					border: 1px solid #5d5d5d;
+					padding: 15px 20px;
+					margin-top: 5px;
+					color: #DDDDDD;
+					display: flex;
+					align-items: center;
+					background: #282828;
+					box-shadow: inset 0px 0px 0px 4px #2d2d2d;
+					
+					&:hover{
+						.node-resource-tools{
+							display: flex;
+						}
+					}
+					
+					img{
+						width: 25px;
+						height: 25;
+						margin-right: 20px;
+					}
+					
+					.node-resource-tools{
+						position: absolute;
+						right: 30px;
+						align-items: center;
+						display: none;
+						
+						.node-resource-tool{
+							display: flex;
+							align-items: center;
+							margin-right: 15px;
+							cursor: pointer;
+						}
+						
+						.ivu-icon{
+							color: #ddd;
+							font-size: 20px;
+							margin-right: 5px;
+						}
+					}
+				}
+			}
 		}
 		}
+		
+		
 	}
 	}
 	
 	
 	.volume-active {
 	.volume-active {
@@ -145,4 +198,33 @@
 	    background-image: -moz-linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
 	    background-image: -moz-linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
 	    background-image: linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
 	    background-image: linear-gradient(90deg, rgba(30,30,30,0) 0%, rgba(110,110,110,.2) 50%, rgba(110,110,110,.4) 100%);
 	}
 	}
+	
+	.image-viewer {
+	    background-color: rgba(0, 0, 0, 0.8);
+	    z-index: 9999;
+	    width: 100%;
+	    height: 100%;
+	    position: fixed;
+	    top: 0;
+	    left: 0;
+	    overflow-y: scroll;
+	    overflow-x: hidden;
+	    text-align: center;
+	    /*display: flex;
+	    justify-content: center;
+	    align-items: center;*/
+	    padding:50px;
+	    padding-top:8%;
+	    .close-icon {
+	        position: absolute;
+	        right: -16px;
+	        top: -16px;
+	        font-size: 24px;
+	        color: black;
+	        cursor: pointer;
+	        border-radius:50px;
+	        background:white;
+	        padding:2px;
+	    }
+	}
 }
 }

+ 210 - 19
TEAMModelOS/ClientApp/src/view/syllabus/Syllabus.vue

@@ -50,22 +50,67 @@
 				</div>
 				</div>
 				<div class="syllabus-tree-box">
 				<div class="syllabus-tree-box">
 					<EmptyData :top="-240" v-if="!treeOrigin.length"></EmptyData>
 					<EmptyData :top="-240" v-if="!treeOrigin.length"></EmptyData>
-					<Tree ref="treeRef" :treeData="treeOrigin" :volume="curVolume" :editable="$access.can('admin.*|Syllabus_Edit')" v-else></Tree>
+					<Tree ref="treeRef" :treeData="treeOrigin" :volume="curVolume"
+					 :editable="$access.can('admin.*|Syllabus_Edit')"
+					  @onNodeClick="onNodeClick"
+					  v-else></Tree>
 				</div>
 				</div>
 			</div>
 			</div>
 			<div class="syllabus-right">
 			<div class="syllabus-right">
 				<div class="syllabus-content-header">
 				<div class="syllabus-content-header">
 					<span>关联资源</span>
 					<span>关联资源</span>
 					<span class="syllabus-content-header-tools">
 					<span class="syllabus-content-header-tools">
-						<Icon type="md-add" @click="isRelateContentModal = true"/>
+						<!-- <Icon type="md-add" @click="onAddResource" v-if="curNode.id"/> -->
+						<Dropdown @on-click="onAddResource">
+							<a href="javascript:void(0)" style="color: #ddd;">
+								添加资源
+								<Icon type="ios-arrow-down"></Icon>
+							</a>
+							<DropdownMenu slot="list">
+								<DropdownItem name="content">+内容资源</DropdownItem>
+								<DropdownItem name="question">+试题资源</DropdownItem>
+								<DropdownItem name="papaer">+试卷资源</DropdownItem>
+								<DropdownItem name="file">+本地文件</DropdownItem>
+								<DropdownItem name="link">+超链接</DropdownItem>
+							</DropdownMenu>
+						</Dropdown>
 					</span>
 					</span>
 				</div>
 				</div>
 				<div class="syllabus-tree-box">
 				<div class="syllabus-tree-box">
-					<EmptyData :top="100"></EmptyData>
+					<EmptyData :top="100" v-if="curNode.rnodes && !curNode.rnodes.length"></EmptyData>
+					<div class="node-resource-box" v-else>
+						<div class="node-resource-item" v-for="(item,index) in curNode.rnodes">
+							<img src="../../assets/source/image.png" v-if="item.type === 'image'"/>
+							<img src="../../assets/source/word.png" v-else-if="item.type === 'doc'"/>
+							<img src="../../assets/source/video.png" v-else-if="item.type === 'video'"/>
+							<img src="../../assets/source/audio.png" v-else-if="item.type === 'audio'"/>
+							<img src="../../assets/source/item.png" v-else-if="item.type === 'item'"/>
+							<img src="../../assets/source/folder.png" v-else-if="item.type === 'paper'"/>
+							<img src="../../assets/source/link.png" v-else-if="item.type === 'link'"/>
+							<img src="../../assets/source/zip.png" v-else-if="item.type === 'res'"/>
+							<img src="../../assets/source/image.png" v-else-if="item.type === 'thum'"/>
+							<img src="../../assets/source/unknow.png" v-else="item.type === 'other'"/>
+							<span v-html="item.title"></span>
+							<div class="node-resource-tools">
+								<div class="node-resource-tool" @click="onPreview(item)">
+									<Icon type="md-eye"/>
+									<span>查看</span>
+								</div>
+								<div class="node-resource-tool" @click="onDeleteResource(item,index)">
+									<Icon type="md-trash"/>
+									<span>删除</span>
+								</div>
+							</div>
+						</div>
+					</div>
 				</div>
 				</div>
 			</div>
 			</div>
 		</div>
 		</div>
 		
 		
+		<!--上传文件-->
+		<!-- <UploadModal ref="uploadModal" :sasString="sasString" :urlString="urlString" :path="folder" :containerName="containerName" :quality="1" @successData="getFileUrl" :pdId="filterPeriod">
+		</UploadModal> -->
+		
 		<!-- 新增册别弹窗 -->
 		<!-- 新增册别弹窗 -->
 		<Modal v-model="isAddVolumeModal" width="500" footer-hide class="add-volume-modal">
 		<Modal v-model="isAddVolumeModal" width="500" footer-hide class="add-volume-modal">
 			<div class="modal-header" slot="header">
 			<div class="modal-header" slot="header">
@@ -110,19 +155,51 @@
 	
 	
 		<!-- 新增课纲节点弹窗 -->
 		<!-- 新增课纲节点弹窗 -->
 		<Modal v-model="isRelateContentModal" width="900" class="tree-modal add-volume-modal">
 		<Modal v-model="isRelateContentModal" width="900" class="tree-modal add-volume-modal">
-		    <ChooseContent @on-file-change="onSelectFile" :showSyllabus="false"></ChooseContent>
+			<ChooseContent v-if="isRelateContentModal" ref="chooseContentRef" @on-file-change="onSelectFile" @quChange="onSelectQuestion" :showSyllabus="false"></ChooseContent>
 			<Button slot="footer" @click="onRelateContent" style="margin-bottom: 20px;" class="modal-btn">确认</Button>
 			<Button slot="footer" @click="onRelateContent" style="margin-bottom: 20px;" class="modal-btn">确认</Button>
 		</Modal>
 		</Modal>
+		
+		<!-- 新增课纲节点弹窗 -->
+		<Modal v-model="isPreviewPaper" width="900" footer-hide class="tree-modal tree-paper-modal">
+			<div class="paper-box" v-if="previewPaper">
+				<p class="paper-title">{{ previewPaper.name }}</p>
+				<p class="paper-info">
+					<span>配分:{{ previewPaper.score }}</span>
+					<span style="margin-left: 50px;">题数:{{ previewPaper.item.length  }}</span>
+				</p>
+			</div>
+			<ExerciseList :propsList="questionList" ref="exList" isAnalysis></ExerciseList>
+		</Modal>
+		
+		<!--文件预览-->
+		<div v-if="previewStatus" class="image-viewer">
+		    <div style="width:fit-content;position:relative;margin:auto;">
+		        <Icon type="md-close" class="close-icon" @click="previewStatus = false" />
+		        <video v-if="previewFile.type == 'video'" id="previewVideo" :src="previewFile.link[0]" width="870" controls="controls" style="max-height: 800px;">
+		            {{$t('teachContent.tips8')}}
+		        </video>
+		        <audio v-else-if="previewFile.type == 'audio'" controls>
+		            <source :src="previewFile.link[0]">
+		            {{$t('teachContent.notAudio')}}
+		        </audio>
+		        <img v-else-if="previewFile.type == 'image'" :src="previewFile.link[0]" style="border-radius: 5px;max-height: 800px;max-width:870px;" />
+		        <embed v-else-if="previewFile.type == 'doc'" :src="previewFile.link[0]" width="870" height="720" />
+		        <iframe v-else :src="'https://view.officeapps.live.com/op/view.aspx?src=' + previewFile.link[0]" width='870' height='700' frameborder='1'></iframe>
+		    </div>
+		</div>
 	</div>
 	</div>
 </template>
 </template>
 
 
 <script>
 <script>
 	import Tree from "@/components/syllabus/DragTree";
 	import Tree from "@/components/syllabus/DragTree";
 	import ChooseContent from '@/components/selflearn/NewChooseContent'
 	import ChooseContent from '@/components/selflearn/NewChooseContent'
+	import ExerciseList from '@/components/evaluation/ExerciseList.vue'
 	export default {
 	export default {
-		components:{ Tree , ChooseContent },
+		components:{ Tree , ChooseContent , ExerciseList },
 		data() {
 		data() {
 			return {
 			return {
+				isPreviewPaper:false,
+				previewStatus:false,
 				hasModify:false,
 				hasModify:false,
 				isLoading:false,
 				isLoading:false,
 				isAddLoading:false,
 				isAddLoading:false,
@@ -139,7 +216,14 @@
 				subjectList: [],
 				subjectList: [],
 				semesterList: [],
 				semesterList: [],
 				volumeList:[],
 				volumeList:[],
+				questionList:[],
 				schoolInfo:null,
 				schoolInfo:null,
+				previewFile:{},
+				previewPaper:null,
+				curNode:{
+					id:'',
+					rnodes:[]
+				},
 				treeOrigin:[],
 				treeOrigin:[],
 				curVolume:{
 				curVolume:{
 					name:''
 					name:''
@@ -154,7 +238,8 @@
 					"pid": "",
 					"pid": "",
 					"title": "",
 					"title": "",
 					"type": 1,
 					"type": 1,
-					"children":[]
+					"children":[],
+					"rnodes":[]
 				},
 				},
 			}
 			}
 		},
 		},
@@ -168,7 +253,7 @@
 				this.$store.dispatch("user/getSchoolProfile").then((res) => {
 				this.$store.dispatch("user/getSchoolProfile").then((res) => {
 					let schoolBaseInfo = res.school_base;
 					let schoolBaseInfo = res.school_base;
 					if (schoolBaseInfo) {
 					if (schoolBaseInfo) {
-						this.schoolInfo = schoolBaseInfo;
+						this.schoolInfo = res;
 						if (schoolBaseInfo.period.length) {
 						if (schoolBaseInfo.period.length) {
 							this.periodList = schoolBaseInfo.period
 							this.periodList = schoolBaseInfo.period
 							this.gradeList = schoolBaseInfo.period[0].grades;
 							this.gradeList = schoolBaseInfo.period[0].grades;
@@ -207,7 +292,7 @@
 				this.$api.syllabus.FindVolumes(findParams).then(res => {
 				this.$api.syllabus.FindVolumes(findParams).then(res => {
 					if(!res.error){
 					if(!res.error){
 						this.isLoading = false
 						this.isLoading = false
-						this.volumeList = res.volumes
+						this.volumeList = res.volumes.reverse()
 						res.volumes.length && this.onVolumeClick(res.volumes[0],0)
 						res.volumes.length && this.onVolumeClick(res.volumes[0],0)
 					}else{
 					}else{
 						this.$Message.warning(res.error);
 						this.$Message.warning(res.error);
@@ -216,7 +301,6 @@
 					this.$Message.error(err);
 					this.$Message.error(err);
 				})
 				})
 			},
 			},
-			
 			/* 点击某个册别 */
 			/* 点击某个册别 */
 			onVolumeClick(volume,volumeIndex){
 			onVolumeClick(volume,volumeIndex){
 				if(volume.id === this.curVolume.id) return
 				if(volume.id === this.curVolume.id) return
@@ -226,7 +310,6 @@
 				this.getTreeByVolumeId(volume)
 				this.getTreeByVolumeId(volume)
 				this.hasModify = false
 				this.hasModify = false
 			},
 			},
-			
 			/* 添加册别 */
 			/* 添加册别 */
 			doAddVolume(){
 			doAddVolume(){
 				this.addVolumeForm = {
 				this.addVolumeForm = {
@@ -237,7 +320,6 @@
 				this.isEditVolume = false
 				this.isEditVolume = false
 				this.isAddVolumeModal = true
 				this.isAddVolumeModal = true
 			},
 			},
-			
 			/* 编辑当前册别 */
 			/* 编辑当前册别 */
 			doEditVolume(){
 			doEditVolume(){
 				this.addVolumeForm = {
 				this.addVolumeForm = {
@@ -248,7 +330,6 @@
 				this.isEditVolume = true
 				this.isEditVolume = true
 				this.isAddVolumeModal = true
 				this.isAddVolumeModal = true
 			},
 			},
-			
 			/* 删除当前册别 */
 			/* 删除当前册别 */
 			doDeleteVolume(){
 			doDeleteVolume(){
 				let curVolume = this.curVolume
 				let curVolume = this.curVolume
@@ -276,9 +357,10 @@
 					},
 					},
 				});
 				});
 			},
 			},
-			
 			/* 根据册别查询对应课纲树形结构 */
 			/* 根据册别查询对应课纲树形结构 */
 			getTreeByVolumeId(volume){
 			getTreeByVolumeId(volume){
+				this.curNode.rnodes = []
+				this.curNode.id = ''
 				this.$api.syllabus.GetTreeByVolume({
 				this.$api.syllabus.GetTreeByVolume({
 					id:volume.id,
 					id:volume.id,
 					code:volume.code.replace('Volume-',''),
 					code:volume.code.replace('Volume-',''),
@@ -296,7 +378,6 @@
 					this.isLoading = false
 					this.isLoading = false
 				})
 				})
 			},
 			},
-			
 			/* 添加课纲的第一层节点 */
 			/* 添加课纲的第一层节点 */
 			onAddTreeNode(){
 			onAddTreeNode(){
 				this.nodeInfo.id = this.$tools.guid()
 				this.nodeInfo.id = this.$tools.guid()
@@ -312,8 +393,7 @@
 					"children":[]
 					"children":[]
 				}
 				}
 			},
 			},
-			
-			// 存储变更保存最新课纲数据
+			/* 存储变更保存最新课纲数据 */
 			onSaveSyllabus() {
 			onSaveSyllabus() {
 				console.log(this.$refs.treeRef)
 				console.log(this.$refs.treeRef)
 				let latestTree = this.$refs.treeRef ? this.$refs.treeRef.treeDatas : [];
 				let latestTree = this.$refs.treeRef ? this.$refs.treeRef.treeDatas : [];
@@ -333,15 +413,92 @@
 					}
 					}
 				});
 				});
 			},
 			},
-			
+			/* 点击某个节点 */
+			onNodeClick(data){
+				this.curNode = data
+			},
+			/* 点击添加关联内容 */
+			onAddResource(type){
+				console.log(type)
+				this.isRelateContentModal = true
+				this.$nextTick(() => {
+					this.$refs.chooseContentRef.clickTab(type)
+				})
+			},
+			/* 关联内容 */
 			onSelectFile(val){
 			onSelectFile(val){
-				this.relateFiles = val
 			},
 			},
 			
 			
+			/* 关联题目 */
+			onSelectQuestion(val){
+				this.relateFiles = val.map(i => {
+					return {
+						type:'item',
+						title:i.question,
+						id:i.id,
+						code:i.code,
+						scope:i.scope,
+						cntr:i.scope === 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+						link:[i.blob]
+					}
+				})
+			},
 			// 拿到当前选择的资源内容 进行节点关联
 			// 拿到当前选择的资源内容 进行节点关联
 			onRelateContent(){
 			onRelateContent(){
 				console.log(this.relateFiles)
 				console.log(this.relateFiles)
 				console.log(this.$refs.treeRef.curNode)
 				console.log(this.$refs.treeRef.curNode)
+				this.$refs.treeRef.curNode.rnodes = this.relateFiles
+				this.isRelateContentModal = false
+				this.hasModify = true
+			},
+			
+			/* 预览关联资源 */
+			async onPreview(item){
+				this.questionList = []
+				switch (item.type){
+					case 'item':
+						this.previewPaper = null
+						this.questionList = [{
+							code:item.code,
+							id:item.id,
+							blob:item.link[0],
+							scope:item.scope
+						}]
+						this.isPreviewPaper = true
+						break;
+					case 'paper':
+						let paper = {
+							code:item.code,
+							id:item.id,
+							blob:item.link[0],
+							scope:item.scope,
+						}
+						let fullPaper = await this.$evTools.getFullPaper(paper)
+						this.previewPaper = fullPaper
+						this.questionList = fullPaper.item
+						this.isPreviewPaper = true
+						break;	
+					case 'link':
+						window.open(item.link[0]);
+						break;		
+					default:
+						this.previewFile = item
+						this.previewStatus = true
+						break;
+				}
+			},
+			
+			/* 删除关联资源 */
+			onDeleteResource(item,index){
+				this.$Modal.confirm({
+					title: this.$t('settings.modalTip4'),
+					content: '确定要移除该资源吗?',
+					onOk: () => {
+						this.$refs.treeRef.curNode.rnodes.splice(index,1)
+						this.hasModify = true
+					}
+				})
+				
 			},
 			},
 			
 			
 			/* 提交新增册别 */
 			/* 提交新增册别 */
@@ -380,7 +537,7 @@
 		},
 		},
 		computed:{
 		computed:{
 			getDefaultVolumeName(){
 			getDefaultVolumeName(){
-				return this.subjectList[this.activeSubjectIndex].name + this.gradeList[this.addVolumeForm.grade].name + this.semesterList[this.addVolumeForm.semester].name
+				return this.subjectList[this.activeSubjectIndex].name + this.gradeList[this.addVolumeForm.grade] + this.semesterList[this.addVolumeForm.semester].name
 			},
 			},
 			getGradeName(){
 			getGradeName(){
 				return index => {
 				return index => {
@@ -430,6 +587,40 @@
 		}
 		}
 	}
 	}
 	
 	
+	.tree-paper-modal{
+		
+		.ivu-modal-content{
+			background-color: #fff;
+		}
+		
+		.ivu-modal-body{
+			overflow: auto;
+			font-size: 14px !important;
+			margin: 40px 10px;
+		}
+		
+		.cp-exercise-item{
+			border: 1px solid #dfdfdf !important;
+		}
+		
+		.paper-box{
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			
+			.paper-title{
+				color: #333;
+				font-size: 20px;
+				font-weight: bold;
+			}
+			
+			.paper-info{
+				margin-top: 20px;
+				color: #939393;
+			}
+		}
+	}
+	
 	/* 修改iview Modal样式 */
 	/* 修改iview Modal样式 */
 	.add-volume-modal{
 	.add-volume-modal{
 		font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;
 		font-family: 'NotoSerif', '微软正黑体', 'Microsoft JhengHei UI', 'Microsoft JhengHei', Sans-serif;

+ 1 - 3
TEAMModelOS/ClientApp/src/view/task/index.less

@@ -128,6 +128,7 @@
         color: @second-textColor;
         color: @second-textColor;
     }
     }
 }
 }
+
 .qu-info-item{
 .qu-info-item{
     display: flex;
     display: flex;
     width: 100%;
     width: 100%;
@@ -135,9 +136,6 @@
     align-items: center;
     align-items: center;
     padding: 20px 0px;
     padding: 20px 0px;
     border-bottom: 1px solid #424242;
     border-bottom: 1px solid #424242;
-    &:last-child{
-        border: none;
-    }
     .qu-index{
     .qu-index{
         display: block;
         display: block;
         // width: 80px;
         // width: 80px;

+ 174 - 113
TEAMModelOS/ClientApp/src/view/task/index.vue

@@ -38,7 +38,6 @@
                             {{$t('task.markMode1')}}
                             {{$t('task.markMode1')}}
                         </span>
                         </span>
                     </div>
                     </div>
-                    <!-- 按题批阅 -->
                     <vuescroll class="mark-info-content">
                     <vuescroll class="mark-info-content">
                         <!-- 数据概览 -->
                         <!-- 数据概览 -->
                         <!-- <div class="setting-block" v-show="curBarIndex == 0">
                         <!-- <div class="setting-block" v-show="curBarIndex == 0">
@@ -88,6 +87,7 @@
                                 {{`${$t('task.mLabel2')} ( ${marked.length} )`}}
                                 {{`${$t('task.mLabel2')} ( ${marked.length} )`}}
                             </span>
                             </span>
                         </div>
                         </div>
+                        <!-- 已阅、未阅、进行中学生 -->
                         <div class="setting-block stu-name-wrap" v-show="curBarIndex == 1">
                         <div class="setting-block stu-name-wrap" v-show="curBarIndex == 1">
                             <div class="setting-content stu-name-content">
                             <div class="setting-content stu-name-content">
                                 <div class="stu-wrap">
                                 <div class="stu-wrap">
@@ -96,13 +96,13 @@
                                             还剩
                                             还剩
                                             <span class="no-mark-count">{{unmarked}}</span>
                                             <span class="no-mark-count">{{unmarked}}</span>
                                             人未阅,
                                             人未阅,
-                                            <span class="continue-mark" @click="toMarkView(1)">继续阅卷</span>
+                                            <span class="continue-mark" @click="toByStuView()">继续阅卷</span>
                                         </p>
                                         </p>
                                         <p class="no-mark-text" v-else-if="unmarked > 0">
                                         <p class="no-mark-text" v-else-if="unmarked > 0">
                                             阅卷总量
                                             阅卷总量
                                             <span class="no-mark-count">{{unmarked}}</span>
                                             <span class="no-mark-count">{{unmarked}}</span>
                                             人,
                                             人,
-                                            <span class="continue-mark" @click="toMarkView(1)">开始阅卷</span>
+                                            <span class="continue-mark" @click="toByStuView()">开始阅卷</span>
                                         </p>
                                         </p>
                                         <p class="no-mark-text" v-else>
                                         <p class="no-mark-text" v-else>
                                             暂无未阅学生
                                             暂无未阅学生
@@ -110,10 +110,10 @@
 
 
                                     </div>
                                     </div>
                                     <div class="stu-list-wrap" v-show="tabIndex == 1">
                                     <div class="stu-list-wrap" v-show="tabIndex == 1">
-                                        <span class="stu-name" v-for="(item,index) in marking" :key="index" @click="toMarkView(1,item.stuId)">{{item.stuId}}</span>
+                                        <span class="stu-name" v-for="(item,index) in marking" :key="index" @click="toByStuView(item.stuId)">{{item.stuId}}</span>
                                     </div>
                                     </div>
                                     <div class="stu-list-wrap" v-show="tabIndex == 2">
                                     <div class="stu-list-wrap" v-show="tabIndex == 2">
-                                        <span class="stu-name" v-for="(item,index) in marked" :key="index" @click="toMarkView(1,item.stuId)">{{item.stuId}}</span>
+                                        <span class="stu-name" v-for="(item,index) in marked" :key="index" @click="toByStuView(item.stuId)">{{item.stuId}}</span>
                                     </div>
                                     </div>
                                 </div>
                                 </div>
                             </div>
                             </div>
@@ -126,68 +126,56 @@
                             </p>
                             </p>
                             <div class="setting-content" :style="{height: fullQuProg ? 'fit-content' : '300px'}">
                             <div class="setting-content" :style="{height: fullQuProg ? 'fit-content' : '300px'}">
                                 <vuescroll>
                                 <vuescroll>
-                                    <div class="qu-info-item" v-for="(item,index) in quCount" :key="index">
-                                        <span class="qu-index" :style="{color:index < 3 ? '#606060' : '#fff'}">
-                                            {{index + 1}}
-                                        </span>
-                                        <div class="data-wrap" style="display:none">
-                                            <p>
-                                                <!-- 标准差 -->
-                                                <span class="data-item">
-                                                    <Icon custom="iconfont icon-formula" class="data-item-icon" size="16" title="标准差" />
-                                                    <span class="data-item-value" title="我的">
-                                                        5
-                                                    </span>
-                                                    <span class="total-value" title="集体">
-                                                        (8)
-                                                    </span>
+                                    <div v-for="(item,index) in fullPaper.item" :key="index">
+                                        <!-- 综合题 -->
+                                        <div v-if="item.children.length">
+                                            <div class="qu-info-item" v-for="(childItem,childIndex) in item.children" :key="childIndex">
+                                                <span class="qu-index" :style="{color:index < 3 ? '#606060' : '#fff'}">
+                                                    {{`${index + 1}-${childIndex + 1}`}}
                                                 </span>
                                                 </span>
-                                                <!-- 平均分 -->
-                                                <span class="data-item">
-                                                    <Icon custom="iconfont icon-avg" class="data-item-icon" size="16" title="平均分" />
-                                                    <span class="data-item-value" title="我的">
-                                                        5
-                                                    </span>
-                                                    <span class="total-value" title="集体">
-                                                        (5.5)
-                                                    </span>
-                                                </span>
-                                                <!-- 最高分 -->
-                                                <span class="data-item">
-                                                    <Icon custom="iconfont icon-highest" class="data-item-icon" size="16" title="最高分" />
-                                                    <span class="data-item-value" title="我的">
-                                                        10
-                                                    </span>
-                                                    <span class="total-value" title="集体">
-                                                        (9)
-                                                    </span>
-                                                </span>
-                                                <!-- 最低分 -->
-                                                <span class="data-item">
-                                                    <Icon custom="iconfont icon-lowest" class="data-item-icon" size="16" title="最低分" />
-                                                    <span class="data-item-value" title="我的">
-                                                        0
-                                                    </span>
-                                                    <span class="total-value" title="集体">
-                                                        (1)
-                                                    </span>
-                                                </span>
-                                            </p>
-                                        </div>
-                                        <div class="progress-wrap">
-                                            <Progress :percent="index < 3 ? 100 : 25" />
-                                            <p v-if="index < 3" class="prog-tips" style="color: #19be6b">
-                                                客观题已由系统自动完成评分。
-                                            </p>
-                                            <p v-else class="prog-tips">
-                                                <span>{{$t('task.mLabel2')}}:4</span>
-                                                <span>{{$t('task.mLabel3')}}:8</span>
-                                            </p>
+                                                <div class="progress-wrap">
+                                                    <Progress :percent="['single','multiple','judge'].includes(childItem.type) ? 100 : byQuPct[getScoreIndex(index,childIndex)]" />
+                                                    <!-- <Progress :percent="byQuPct[getScoreIndex(index,childIndex)]" /> -->
+                                                    <p v-if="['single','multiple','judge'].includes(childItem.type)" class="prog-tips" style="color: #19be6b">
+                                                        客观题已由系统自动完成评分。
+                                                    </p>
+                                                    <p v-else class="prog-tips">
+                                                        <span style="margin-right:10px">
+                                                            {{$t('task.mLabel2')}}:{{byQuCount[getScoreIndex(index,childIndex)]}}
+                                                        </span>
+                                                        <span>
+                                                            {{$t('task.mLabel3')}}:{{markList[curTaskIndex].count - byQuCount[getScoreIndex(index,childIndex)]}}
+                                                        </span>
+                                                    </p>
+                                                </div>
+                                                <div class="to-mark">
+                                                    <Button type="primary" shape="circle" :disabled="['single','multiple','judge'].includes(childItem.type)" size="small" style="width:120px" @click="toByQuView(index, childIndex)">
+                                                        {{$t('task.mark')}}
+                                                    </Button>
+                                                </div>
+                                            </div>
                                         </div>
                                         </div>
-                                        <div class="to-mark">
-                                            <Button type="primary" shape="circle" :disabled="index < 3" size="small" style="width:120px" @click="toMarkView(0)">
-                                                {{$t('task.mark')}}
-                                            </Button>
+                                        <!-- 其他题 -->
+                                        <div v-else class="qu-info-item">
+                                            <span class="qu-index" :style="{color:index < 3 ? '#606060' : '#fff'}">
+                                                {{index + 1}}
+                                            </span>
+                                            <div class="progress-wrap">
+                                                <Progress :percent="['single','multiple','judge'].includes(item.type) ? 100 : byQuPct[getScoreIndex(index)]" />
+                                                <!-- <Progress :percent="byQuPct[getScoreIndex(index)]" /> -->
+                                                <p v-if="['single','multiple','judge'].includes(item.type)" class="prog-tips" style="color: #19be6b">
+                                                    客观题已由系统自动完成评分。
+                                                </p>
+                                                <p v-else class="prog-tips">
+                                                    <span>{{$t('task.mLabel2')}}:4</span>
+                                                    <span>{{$t('task.mLabel3')}}:8</span>
+                                                </p>
+                                            </div>
+                                            <div class="to-mark">
+                                                <Button type="primary" shape="circle" :disabled="['single','multiple','judge'].includes(item.type)" size="small" style="width:120px" @click="toByQuView(index, -1)">
+                                                    {{$t('task.mark')}}
+                                                </Button>
+                                            </div>
                                         </div>
                                         </div>
                                     </div>
                                     </div>
                                 </vuescroll>
                                 </vuescroll>
@@ -263,11 +251,6 @@ export default {
                     quNo: 5,
                     quNo: 5,
                     desc: '模糊,看不清楚',
                     desc: '模糊,看不清楚',
                     status: '未处理'
                     status: '未处理'
-                }, {
-                    id: '001',
-                    quNo: 5,
-                    desc: '模糊,看不清楚',
-                    status: '未处理'
                 }
                 }
             ],
             ],
             columns2: [
             columns2: [
@@ -303,13 +286,6 @@ export default {
                 }
                 }
             ],
             ],
             data2: [
             data2: [
-                {
-                    id: '001',
-                    quNo: 5,
-                    myScore: 5,
-                    otScore: 10,
-                    status: '未处理'
-                },
                 {
                 {
                     id: '001',
                     id: '001',
                     quNo: 5,
                     quNo: 5,
@@ -323,12 +299,29 @@ export default {
             curBarIndex: 1,
             curBarIndex: 1,
             markList: [],
             markList: [],
             curTaskIndex: 0,
             curTaskIndex: 0,
-            quCount: [],
             fullQuProg: false,
             fullQuProg: false,
-            markData: undefined
+            markData: undefined,
+            fullPaper: {
+                item: []
+            }
         }
         }
     },
     },
     methods: {
     methods: {
+        /**
+         * index 题目index 必传
+         * childIndex 小题index 非必传 
+         */
+        getScoreIndex(index, childIndex) {
+            let realIndex = index
+            this.fullPaper.item.forEach((item, itemIndex) => {
+                if (itemIndex < index && item.children.length) {
+                    realIndex += item.children.length
+                } else if (itemIndex == index && item.children.length) {
+                    realIndex += childIndex
+                }
+            })
+            return realIndex
+        },
         rowClassName(row, index) {
         rowClassName(row, index) {
             if (index % 2 == 0) {
             if (index % 2 == 0) {
                 return 'stripe-row'
                 return 'stripe-row'
@@ -337,40 +330,68 @@ export default {
             }
             }
         },
         },
         /**
         /**
-         * mode 0:按题 1:按人
-         * stuId 学生id
+         * 按人阅卷
          */
          */
-        async toMarkView(mode, stuId) {
+        async toByStuView(stuId) {
             sessionStorage.setItem('markFrom', this.$route.name)
             sessionStorage.setItem('markFrom', this.$route.name)
             let sas = this.$store.state.user.schoolProfile.blob_sas //目前只有校本评测安排阅卷任务
             let sas = this.$store.state.user.schoolProfile.blob_sas //目前只有校本评测安排阅卷任务
             let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri //目前只有校本评测安排阅卷任务
             let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri //目前只有校本评测安排阅卷任务
-            let stuInfo = this.markData.attr.find(item => {
-                return item.stuId == stuId
-            })
-            let answer, fullPaper, score
-            if (stuInfo) {
-                answer = await this.$tools.getFile(`${blobUrl}/exam/${stuInfo.info.ans}?${sas}`)
-                score = stuInfo.info.score
-                fullPaper = await this.$evTools.getFullPaper({
-                    blob: this.markData.paper
-                }, 'school')
 
 
-                console.log(answer)
+            let answer, score, sId
+            if (stuId) {
+                let stuInfo = this.markData.attr.find(item => {
+                    return item.stuId == stuId
+                })
+                answer = stuInfo.info.ans ? JSON.parse(await this.$tools.getFile(`${blobUrl}/exam/${stuInfo.info.ans}?${sas}`)) : []
+                score = stuInfo.info.score
+                sId = stuId
+            } else {
+                let resData = await this.getNextStu()
+                console.log('API返回了',resData)
+                answer = resData.ans.ans ? JSON.parse(await this.$tools.getFile(`${blobUrl}/exam/${resData.ans.ans}?${sas}`)) : []
+                score = resData.ans.score
+                sId = resData.stuId
             }
             }
-
-            // this.getNextStu(stuId)
-
+            console.log('跳转了')
             this.$router.push({
             this.$router.push({
-                name: 'MarkView',
+                name: 'ByStu',
                 params: {
                 params: {
-                    type: mode,
                     from: this.$route.name,
                     from: this.$route.name,
+                    task: this.markList[this.curTaskIndex],
+                    stuId: sId,
+                    fullPaper: this.fullPaper,
                     answer,
                     answer,
-                    fullPaper,
                     score
                     score
                 }
                 }
             })
             })
         },
         },
+        /**
+        * 按题阅卷
+        * 
+        * quIndex 必传
+        * childIndex 非必传
+        */
+        async toByQuView(quIndex, childIndex) {
+            sessionStorage.setItem('markFrom', this.$route.name)
+            let sas = this.$store.state.user.schoolProfile.blob_sas //目前只有校本评测安排阅卷任务
+            let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri //目前只有校本评测安排阅卷任务
+            // 获取学生作答数据
+            this.markData.attr.forEach(async item => {
+                item.info.answer = item.info.ans ? JSON.parse(await this.$tools.getFile(`${blobUrl}/exam/${item.info.ans}?${sas}`)) : []
+            })
+            console.log(this.markData)
+            this.$router.push({
+                name: 'ByQu',
+                params: {
+                    from: this.$route.name,
+                    task: this.markList[this.curTaskIndex], //阅卷任务数据
+                    stusInfo: this.markData.attr, //已阅和进行中的学生数据
+                    paperData: this.fullPaper, //试卷数据
+                    quIndex,
+                    childIndex
+                }
+            })
+        },
         /**
         /**
          * 批阅下个学生
          * 批阅下个学生
          * @param stuId 如果传了stuid则会获取对应学生的数据,否则随机获取一个学生
          * @param stuId 如果传了stuid则会获取对应学生的数据,否则随机获取一个学生
@@ -380,18 +401,19 @@ export default {
                 code: this.markList[this.curTaskIndex].ecode.replace('Exam-', ''),
                 code: this.markList[this.curTaskIndex].ecode.replace('Exam-', ''),
                 id: this.markList[this.curTaskIndex].id,
                 id: this.markList[this.curTaskIndex].id,
                 subjectId: this.markList[this.curTaskIndex].subject,
                 subjectId: this.markList[this.curTaskIndex].subject,
+                count: this.markList[this.curTaskIndex].count,
                 tmdId: this.$store.state.userInfo.TEAMModelId,
                 tmdId: this.$store.state.userInfo.TEAMModelId,
-                stuId: 'hbcn070201'
+                stuId
             }
             }
-            this.$api.mark.FindNextStu(requestData).then(
-                res => {
-                    console.log(res)
-                },
-                err => {
-                    console.log(err)
-                }
-            )
-
+            return this.$api.mark.FindNextStu(requestData)
+            // this.$api.mark.FindNextStu(requestData).then(
+            //     res => {
+            //         console.log(res)
+            //     },
+            //     err => {
+            //         console.log(err)
+            //     }
+            // )
         },
         },
         /**获取type对应的label */
         /**获取type对应的label */
         getTypeLabel(code) {
         getTypeLabel(code) {
@@ -441,17 +463,21 @@ export default {
                 tmdId: this.$store.state.userInfo.TEAMModelId
                 tmdId: this.$store.state.userInfo.TEAMModelId
             }
             }
             this.$api.mark.FindTeaData(requestData).then(
             this.$api.mark.FindTeaData(requestData).then(
-                res => {
-                    console.log(res)
+                async res => {
                     if (!res.error) {
                     if (!res.error) {
                         this.markData = res
                         this.markData = res
+                        //获取试卷详细数据
+                        //获取试卷完整信息
+                        this.fullPaper = await this.$evTools.getFullPaper({
+                            blob: this.markData.paper
+                        }, 'school')
+                        console.log('试卷数据', this.fullPaper)
                     }
                     }
                 },
                 },
                 err => {
                 err => {
                     console.log(err)
                     console.log(err)
                 }
                 }
             )
             )
-
         },
         },
         selectTask(index) {
         selectTask(index) {
             this.curTaskIndex = index
             this.curTaskIndex = index
@@ -461,7 +487,6 @@ export default {
     },
     },
     created() {
     created() {
         this.findTask()
         this.findTask()
-        this.quCount = Array.from(new Array(13).keys())
         this.curBarIndex = sessionStorage.getItem('markMode') || 0
         this.curBarIndex = sessionStorage.getItem('markMode') || 0
 
 
     },
     },
@@ -496,7 +521,43 @@ export default {
                 return []
                 return []
             }
             }
         },
         },
+        //试卷题目数量,包括小题
+        quLength() {
+            if (this.fullPaper && this.fullPaper.item) {
+                let count = 0
+                this.fullPaper.item.forEach(item => {
+                    if (item.children.length) {
+                        count += item.children.length
+                    } else {
+                        count++
+                    }
+                })
+                return count
+            }
+            return 0
+        },
+        //题目进度百分比
+        byQuPct() {
+            let total = this.markList[this.curTaskIndex] ? this.markList[this.curTaskIndex].count : 1
+            return this.byQuCount.map(item => {
+                return item * 100 / total
+            })
+        },
+        //题目进度count
+        byQuCount() {
+            let res = new Array(this.quLength).fill(0)
+            if (this.markData && this.markData.attr) {
+                for (let i = 0; i < this.quLength; i++) {
+                    this.markData.attr.forEach(item => {
+                        if (item.info.score[i] > -1) {
+                            res[i]++
+                        }
+                    })
+                }
 
 
+            }
+            return res
+        },
     }
     }
 }
 }
 </script>
 </script>

+ 621 - 0
TEAMModelOS/ClientApp/src/view/task/mark/ByQu.vue

@@ -0,0 +1,621 @@
+<template>
+    <!-- 未做多语言 -->
+    <div class="mark-area">
+        <!-- 头部基础信息 -->
+        <div class="mark-header">
+            <span class="quit-marking-text">
+                <Icon type="ios-arrow-back" class="quit-marking-icon" title="退出阅卷" @click="quit" />
+            </span>
+            <span class="info-label">考试名称123:</span>
+            <span class="info-value">{{taskInfo.name}}</span>
+            <span class="info-label">阅卷方式:</span>
+            <span class="info-value">按题阅卷</span>
+            <span class="info-label">当前题号:</span>
+            <span class="info-value cur-qu-index" v-if="childIndex > -1">{{`${quIndex + 1}-${childIndex + 1}`}}</span>
+            <span class="info-value cur-qu-index" v-else>{{quIndex + 1}}</span>
+            <span class="info-label">学生id:</span>
+            <span class="info-value stu-id-info">{{stusInfo[stuIndex] ? stusInfo[stuIndex].stuId : ''}}</span>
+            <div class="btn-wrap">
+                <span class="action-btn" @click="toggleStatus = !toggleStatus">
+                    <Icon type="md-shuffle" class="action-btn-icon" />
+                    切换
+                </span>
+                <span class="action-btn">
+                    <Icon type="md-refresh" class="action-btn-icon" />
+                    回评
+                </span>
+                <span class="action-btn">
+                    <Icon custom="iconfont icon-exception" class="action-btn-icon" />
+                    异常申报
+                </span>
+            </div>
+        </div>
+        <div class="mark-main">
+            <!-- 工具条 -->
+            <div class="mark-tools-wrap">
+                <Icon custom="iconfont icon-move" class="tool-icon" title="移动" @click="mouseStatus = 'move'" />
+                <Icon custom="iconfont icon-text" class="tool-icon" title="文字批注" @click="mouseStatus = 'text'" />
+                <Icon custom="iconfont icon-mark" class="tool-icon" title="画笔" @click="mouseStatus = 'line'" />
+                <Icon custom="iconfont icon-arrow-mark" class="tool-icon" title="箭头" @click="mouseStatus = 'arrow'" />
+                <Icon custom="iconfont icon-oval" class="tool-icon" title="椭圆" @click="mouseStatus = 'oval'" />
+                <Icon custom="iconfont icon-rect" class="tool-icon" title="方框" @click="mouseStatus = 'rect'" />
+                <Poptip transfer placement="right">
+                    <Icon custom="iconfont icon-smile" class="tool-icon" title="图标" />
+                    <div slot="content" class="expression-box">
+                        <img v-for="(item,index) in imgs" :key="index" :src="item" alt="" class="expression-img" @click="drawImg(index)">
+                    </div>
+                </Poptip>
+                <Icon custom="iconfont icon-fresh" class="tool-icon" title="清除批注" @click="clear" />
+                <!-- <Icon :custom="isFull ? 'iconfont icon-cancel-full' : 'iconfont icon-full-screen'" class="tool-icon" :title="isFull ? '取消全屏' : '全屏'" @click="togglefull" /> -->
+            </div>
+            <div class="mark-stage">
+                <MarkCanvas :mouseStatus="mouseStatus" :bgImg="ansImg" :drawImgData="drawImgData"></MarkCanvas>
+            </div>
+            <!-- 打分部分 -->
+            <div class="score-wrap">
+                <div class="quick-score-box score-input-box">
+                    <span>分数:</span>
+                    <InputNumber style="flex:1" :max="10" :min="1" v-model="score" @on-change="setScore"></InputNumber>
+                </div>
+                <div class="quick-score-box">
+                    <Button size="small" type="info" style="margin-right:8px" ghost @click="score = 10">满分</Button>
+                    <Button size="small" type="error" ghost @click="score = 0">零分</Button>
+                    <Icon :type="isShowNum ? 'md-eye-off' : 'md-eye'" class="toggle-num-status" @click="isShowNum = !isShowNum" />
+                    <div :class="['score-key-box', isShowNum ? '':'hind-key-box']">
+                        <span v-for="(item,index) in quScoreArr" :key="index" class="score-key" @click="setScore(index)">
+                            {{item}}
+                        </span>
+                    </div>
+                </div>
+                <Button type="success" class="submit-score" @click="submit()">提交分数</Button>
+                <div class="score-setting-wrap">
+                    <div class="score-setting-item">
+                        <span>提交分数自动切换题目</span>
+                        <i-switch v-model="autoQu" size="small" />
+                    </div>
+                    <div class="score-setting-item">
+                        <span>完成阅卷自动获取新学生</span>
+                        <i-switch v-model="autoStu" size="small" />
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!-- 用来单独渲染学生作答数据,提高tocanvas 的效率 -->
+        <iframe id="markIframe1" :srcdoc="curAnswer" v-if="curAnswer"></iframe>
+        <Modal v-model="toggleStatus" title="切换学生">
+            进行中
+        </Modal>
+    </div>
+</template>
+<script>
+import html2canvas from 'html2canvas';
+import MarkCanvas from './MarkCanvas';
+export default {
+    name: 'ByStu',
+    components: {
+        MarkCanvas
+    },
+    data() {
+        return {
+            score: null,
+            mouseStatus: 'move',
+            drawImgData: '',
+            ansImg: '',
+            imgs: [],
+            autoQu: true,//自动切换下一题
+            autoStu: true,//自动获取下一学生
+            toggleStatus: false,
+            activeIcon: -1,
+            isShowNum: true,
+            quIndex: 0,
+            childIndex: -1,
+            paperData: {
+                item: []
+            },
+            taskInfo: {},
+            stusInfo: [],
+            stuIndex: 0
+        }
+    },
+    methods: {
+        drawImg(imgIndex) {
+        },
+        //清除所有批注
+        clear() {
+        },
+        /**将答案绘制到canvas上 */
+        ansToImg() {
+            let answerIframe = document.getElementById('markIframe1')
+            answerIframe.onload = () => {
+                answerIframe.style.width = '850px'
+                answerIframe.contentWindow.document.body.style.margin = '0px 20px'
+                answerIframe.contentWindow.document.body.style.padding = '10px'
+                answerIframe.contentWindow.document.body.style.minWidth = '600px'
+                answerIframe.contentWindow.document.body.style.minHeight = '240px'
+                answerIframe.contentWindow.document.body.style.height = 'fit-content'
+                answerIframe.contentWindow.document.body.style.width = 'fit-content'
+                let bodyWidth = answerIframe.contentWindow.document.body.clientWidth
+                answerIframe.style.width = (bodyWidth + 20) + 'px'
+                answerIframe.contentWindow.document.body.style.backgroundColor = '#f5f5f5'
+                html2canvas(answerIframe.contentWindow.document.body, {}).then((canvas) => {
+                    canvas.id = "canvas"
+                    // 将转出来的答案绘制到canvas上c
+                    this.ansImg = canvas.toDataURL()
+
+                })
+            }
+        },
+        //将图片(答案)绘制到canvas
+        imgToCanvas(img) {
+            console.log('将图片(答案)绘制到canvas', img)
+        },
+        /** 打分 */
+        setScore(score) {
+            this.score = score
+            let index = this.getScoreIndex(this.quIndex, this.childIndex)
+            this.stusInfo[this.stuIndex].info.score[index] = score
+        },
+        //提交分数
+        submit() {
+            if (this.score > -1 && this.score != null) {
+                let requstData = {
+                    id: this.taskInfo.id,
+                    stuId: this.stusInfo[this.stuIndex].stuId,
+                    subjectId: this.taskInfo.subject,
+                    tmdId: this.$store.state.userInfo.TEAMModelId,
+                    score: this.stusInfo[this.stuIndex].info.score,
+                    count: this.taskInfo.count,
+                    code: this.taskInfo.ecode.replace('Exam-', ''),
+                    mark: ''
+                }
+                this.$api.mark.saveScore(requstData).then(
+                    res => {
+                        this.$Message.success('保存成功')
+                        //按题阅卷自动加载下一人
+                        this.getDefStu()
+                    },
+                    err => {
+                        this.$Message.error('保存失败')
+                    }
+                )
+            } else {
+                this.$Message.warning('请先打分')
+            }
+        },
+        quit() {
+            // 返回页面
+            this.$router.push({
+                name: sessionStorage.getItem('markFrom')
+            })
+        },
+        /**
+         * index 题目index 必传
+         * childIndex 小题index 非必传 
+         */
+        getScoreIndex(index, childIndex) {
+            let realIndex = index
+            this.paperData.item.forEach((item, itemIndex) => {
+                if (itemIndex < index && item.children.length) {
+                    realIndex += item.children.length
+                } else if (itemIndex == index && item.children.length) {
+                    realIndex += childIndex
+                }
+            })
+            return realIndex
+        },
+        //获取批阅学生数据
+        getDefStu() {
+            this.score = 0
+            let scoreIndex = this.getScoreIndex(this.quIndex, this.childIndex)
+            let has = false //当前数据是否有当前题目没有评分的学生
+            for (let i = 0; i < this.stusInfo.length; i++) {
+                if (this.stusInfo[i].info.score[scoreIndex] == -1) {
+                    this.stuIndex = i
+                    this.score = null
+                    has = true
+                }
+            }
+            //当前数据没有未评,并且人数==阅卷任务量 —> 说明这道题目已阅完
+            if (!has && this.stusInfo.length == this.taskInfo.count) {
+                // 提示当前题目已阅完,切换题目
+                this.$Message.warning('当前题目已阅完,请切换题目')
+            } else if (!has && this.stusInfo.length < this.taskInfo.count) {
+                //当前学生数据已阅,需要继续随机获取学生进行阅卷
+                this.getNextStu()
+            }
+        },
+        /**
+         * 批阅下个学生
+         * @param stuId 如果传了stuid则会获取对应学生的数据,否则随机获取一个学生
+         */
+        getNextStu(stuId) {
+            let requestData = {
+                code: this.taskInfo.ecode.replace('Exam-', ''),
+                id: this.taskInfo.id,
+                subjectId: this.taskInfo.subject,
+                tmdId: this.$store.state.userInfo.TEAMModelId,
+                count: this.taskInfo.count,
+                stuId: stuId
+            }
+            this.$api.mark.FindNextStu(requestData).then(
+                async res => {
+                    console.log(res)
+                    let sas = this.$store.state.user.schoolProfile.blob_sas //目前只有校本评测安排阅卷任务
+                    let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri //目前只有校本评测安排阅卷任务
+                    this.stusInfo.push({
+                        stuId: res.stuId,
+                        info: {
+                            ans: res.ans.ans,
+                            answer: res.ans.ans ? await this.$tools.getFile(`${blobUrl}/exam/${res.ans.ans}?${sas}`) : [],
+                            score: res.ans.score
+                        }
+                    })
+                    this.stuIndex = this.stusInfo.length - 1
+                    this.score = res.ans.score[this.getScoreIndex(this.quIndex,this.childIndex)] == -1 ? null : res.ans.score[this.getScoreIndex(this.quIndex,this.childIndex)]
+                },
+                err => {
+                    console.log(err)
+                }
+            )
+        }
+    },
+    mounted() {
+        this.ansToImg()
+
+    },
+    created() {
+        let routeData = this.$route.params
+        this.paperData = routeData.paperData
+        this.stusInfo = routeData.stusInfo
+        this.taskInfo = routeData.task
+        this.quIndex = routeData.quIndex
+        this.childIndex = routeData.childIndex
+        console.log('路由数据', routeData)
+        //获取一个学生数据
+        this.getDefStu()
+
+        //默认表情包
+        for (let i = 0; i < 9; i++) {
+            this.imgs.push(require('@/assets/mark/' + i + '.jpg'))
+        }
+    },
+    computed: {
+        curAnswer() {
+            if (this.stusInfo && this.stusInfo[this.stuIndex]) {
+                if (this.stusInfo[this.stuIndex].ans) {
+                    return this.stusInfo[this.stuIndex].answer[this.getScoreIndex(this.quIndex, this.childIndex)]
+                } else {
+                    return this.stusInfo[this.stuIndex].stuId + '未作答'
+                }
+            } else {
+                return '学生信息异常'
+            }
+        },
+        /**当期题目分数数组 */
+        quScoreArr() {
+            let score = 0
+            if (this.paperData.item) {
+                if (this.childIndex > -1 && this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children) {
+                    score = this.paperData.item[this.quIndex].children[this.childIndex].score
+                } else {
+                    score = this.paperData.item[this.quIndex].score
+                }
+            }
+            return Array.from(new Array(score + 1).keys())
+        },
+    }
+}
+</script>
+<style scoped lang="less">
+.score-setting-wrap {
+    margin-top: 50px;
+    padding: 0px 5px;
+}
+.score-setting-item {
+    margin-top: 15px;
+    display: flex;
+    justify-content: space-between;
+}
+.stu-id-info {
+    color: black;
+    font-size: 16px;
+}
+.score-info {
+    background: #19be6b;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+.cur-qu-index {
+    background: #2db7f5;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+#markIframe1 {
+    position: fixed;
+    top: 9990px;
+    width: 850px;
+    background: #f5f5f5;
+}
+.quit-marking-icon {
+    display: inline-block;
+    font-size: 18px;
+    width: 40px;
+    text-align: center;
+    cursor: pointer;
+    color: black;
+}
+.quit-marking-text {
+    margin-right: 15px;
+}
+.expression-box {
+    max-width: 300px;
+    display: flex;
+    flex-wrap: wrap;
+    max-height: 600px;
+}
+.expression-img {
+    width: 80px;
+    margin: 10px;
+    height: auto;
+}
+.container-box {
+    display: flex;
+    justify-content: center;
+    width: 100%;
+    padding: 10px;
+}
+#container {
+    max-width: 100%;
+}
+.qu-tips-box {
+    & :nth-child(1)::before {
+        background: #19be6b;
+    }
+    & :nth-child(2)::before {
+        background: #ff9900;
+    }
+    & :nth-child(3)::before {
+        background: #ed4014;
+    }
+    & :nth-child(4)::before {
+        background: #e1e1e1;
+    }
+}
+
+.qu-tips-tag {
+    display: inline-block;
+    margin-right: 15px;
+    color: #909090;
+    font-size: 12px;
+    &::before {
+        content: "";
+        width: 15px;
+        height: 10px;
+        display: inline-block;
+        border-radius: 3px;
+        vertical-align: baseline;
+    }
+}
+.qu-index {
+    width: 30px;
+    height: 22px;
+    border-radius: 5px;
+    display: inline-block;
+    border: 1px solid #e1e1e1;
+    line-height: 22px;
+    text-align: center;
+    margin-bottom: 5px;
+    margin-top: 5px;
+    margin-right: 5px;
+    cursor: pointer;
+    background: #f1f1f1;
+    &:hover {
+        background: white;
+    }
+}
+.right-qu {
+    color: #19be6b;
+    border-color: #19be6b;
+    background: white;
+}
+.err-qu {
+    color: #ed4014;
+    border-color: #ed4014;
+    background: white;
+}
+.half-qu {
+    color: #ff9900;
+    border-color: #ff9900;
+    background: white;
+}
+.submit-score {
+    width: 236px;
+    margin-left: 2px;
+}
+.score-key-box {
+    margin-top: 10px;
+    height: 120px;
+    transition: height 0.5s;
+    -webkit-transition: height 0.5s;
+    overflow: hidden;
+}
+.hind-key-box {
+    height: 0px;
+}
+.score-key {
+    display: inline-block;
+    width: 30px;
+    height: 30px;
+    border-radius: 50%;
+    border: 1px solid #e1e1e1;
+    line-height: 30px;
+    text-align: center;
+    margin-top: 5px;
+    margin-right: 5px;
+    cursor: pointer;
+    background: #f1f1f1;
+}
+.score-key-active {
+    background: white;
+    border-color: #1cc0f3;
+    color: #1cc0f3;
+}
+.score-input-box {
+    display: flex;
+    align-items: center;
+}
+.score-wrap > :nth-child(2) {
+    background: white;
+}
+.quick-score-box {
+    padding: 10px 15px;
+    border-bottom: 1px solid #e1e1e1;
+}
+
+.toggle-num-status {
+    float: right;
+    margin-right: 5px;
+    font-size: 18px;
+    cursor: pointer;
+    margin-top: 2px;
+    user-select: none;
+}
+.btn-wrap {
+    float: right;
+    // padding-right: 15px;
+}
+.action-btn {
+    margin-left: 30px;
+    cursor: pointer;
+}
+.action-btn-icon {
+    display: inline-block;
+    margin-right: 5px;
+}
+.tmd-logo {
+    width: 35px;
+    height: 35px;
+    margin: 5px 18px 5px 13px;
+    vertical-align: bottom;
+}
+.mark-area {
+    width: 100%;
+    height: 100%;
+    user-select: none;
+    // padding: 5px;
+}
+.mark-header {
+    height: 50px;
+    width: 100%;
+    border-bottom: 1px solid #e1e1e1;
+    background: #fafafa;
+    line-height: 50px;
+    text-align: left;
+    padding: 0px 18px 0px 0px;
+    box-shadow: 0px 2px 3px 0px rgba(100, 100, 100, 0.2);
+    position: relative;
+    z-index: 99;
+    .info-label {
+        color: #909090;
+    }
+    .info-value {
+        margin-right: 30px;
+    }
+}
+.mark-main {
+    display: flex;
+    flex-direction: row;
+    height: calc(100% - 50px);
+}
+.mark-tools-wrap {
+    position: relative;
+    box-shadow: 2px 0px 5px 0px rgba(100, 100, 100, 0.2);
+    z-index: 9;
+    flex: 0 0 40px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    border-right: 1px solid #e1e1e1;
+    background: #fafafa;
+}
+.mark-stage {
+    flex: 1;
+    overflow: hidden;
+    background: white;
+    text-align: center;
+    position: relative;
+    height: 100%;
+}
+.qu-index-box {
+    position: absolute;
+    bottom: 0px;
+    background: #fafafa;
+    width: 100%;
+    left: 0px;
+    height: fit-content;
+    text-align: left;
+    max-height: 180px;
+    padding: 10px 10px 20px 10px;
+    box-shadow: 0 -2px 20px -12px #595959;
+    p {
+        width: fit-content;
+    }
+}
+.score-wrap {
+    position: relative;
+    box-shadow: -2px 0px 5px 0px rgba(100, 100, 100, 0.2);
+    z-index: 9;
+    flex: 0 0 241px;
+    height: 100%;
+    border-left: 1px solid #e1e1e1;
+    background: #fafafa;
+}
+.tool-icon {
+    font-size: 20px;
+    cursor: pointer;
+    height: 40px;
+    line-height: 40px;
+    // border-bottom: 1px solid #e1e1e1;
+    width: 39px;
+    &:hover {
+        color: #1cc0f3;
+        box-shadow: 0 5px 10px -8px #1cc0f3;
+        background: white;
+        border-color: transparent;
+    }
+}
+.exception-icon:hover {
+    box-shadow: 0 5px 10px -8px #ed4014;
+}
+.tool-icon-active {
+    color: #1cc0f3;
+    box-shadow: 0 5px 10px -8px #1cc0f3;
+    background: white;
+    border-color: transparent;
+    &:last-child {
+        box-shadow: 0 5px 10px -8px #ed4014;
+    }
+}
+</style>
+<style>
+.mark-tools-wrap .ivu-poptip-title::after {
+    display: none;
+}
+.textarea:focus {
+    box-shadow: none;
+    overflow: hidden;
+    outline: none !important;
+    line-height: 1.5;
+}
+.score-input-box .ivu-input-number-input {
+    font-size: 18px;
+    color: red;
+    font-weight: 800;
+}
+</style>

+ 724 - 0
TEAMModelOS/ClientApp/src/view/task/mark/ByStu.vue

@@ -0,0 +1,724 @@
+<template>
+    <!-- 未做多语言 -->
+    <div class="mark-area">
+        <!-- 头部基础信息 -->
+        <div class="mark-header">
+            <span class="quit-marking-text">
+                <Icon type="ios-arrow-back" class="quit-marking-icon" title="退出阅卷" @click="quit" />
+            </span>
+            <span class="info-label">考试名称:</span>
+            <span class="info-value">{{taskInfo.name}}</span>
+            <span class="info-label">阅卷方式:</span>
+            <span class="info-value">按人阅卷</span>
+            <span class="info-label">学生id:</span>
+            <span class="info-value stu-id-info">{{stuId}}</span>
+            <span class="info-label">分数:</span>
+            <span class="info-value score-info">{{totalScore}}</span>
+            <span class="info-label">当前题号:</span>
+            <span class="info-value cur-qu-index" v-if="childIndex > -1">{{`${quIndex + 1}-${childIndex + 1}`}}</span>
+            <span class="info-value cur-qu-index" v-else>{{quIndex + 1}}</span>
+            <!-- <span class="info-label">阅卷进度:</span>
+            <span class="info-value">{{`${12}/${41}`}}</span> -->
+            <div class="btn-wrap">
+                <span class="action-btn" @click="toggleStatus = !toggleStatus">
+                    <Icon type="md-shuffle" class="action-btn-icon" />
+                    切换
+                </span>
+                <span class="action-btn">
+                    <Icon type="md-refresh" class="action-btn-icon" />
+                    回评
+                </span>
+                <span class="action-btn">
+                    <Icon custom="iconfont icon-exception" class="action-btn-icon" />
+                    异常申报
+                </span>
+            </div>
+        </div>
+        <div class="mark-main">
+            <!-- 工具条 -->
+            <div class="mark-tools-wrap">
+                <Icon custom="iconfont icon-move" class="tool-icon" title="移动" @click="mouseStatus = 'move'" />
+                <Icon custom="iconfont icon-text" class="tool-icon" title="文字批注" @click="mouseStatus = 'text'" />
+                <Icon custom="iconfont icon-mark" class="tool-icon" title="画笔" @click="mouseStatus = 'line'" />
+                <Icon custom="iconfont icon-arrow-mark" class="tool-icon" title="箭头" @click="mouseStatus = 'arrow'" />
+                <Icon custom="iconfont icon-oval" class="tool-icon" title="椭圆" @click="mouseStatus = 'oval'" />
+                <Icon custom="iconfont icon-rect" class="tool-icon" title="方框" @click="mouseStatus = 'rect'" />
+                <Poptip transfer placement="right">
+                    <Icon custom="iconfont icon-smile" class="tool-icon" title="图标" />
+                    <div slot="content" class="expression-box">
+                        <img v-for="(item,index) in imgs" :key="index" :src="item" alt="" class="expression-img" @click="drawImg(index)">
+                    </div>
+                </Poptip>
+                <Icon custom="iconfont icon-fresh" class="tool-icon" title="清除批注" @click="clear" />
+                <!-- <Icon :custom="isFull ? 'iconfont icon-cancel-full' : 'iconfont icon-full-screen'" class="tool-icon" :title="isFull ? '取消全屏' : '全屏'" @click="togglefull" /> -->
+            </div>
+            <div class="mark-stage">
+                <MarkCanvas :mouseStatus="mouseStatus" :bgImg="ansImg" :drawImgData="drawImgData"></MarkCanvas>
+                <!-- 题号显示部分 -->
+                <div class="qu-index-box">
+                    <div class="qu-tips-box">
+                        <span class="qu-tips-tag">
+                            已阅
+                        </span>
+                        <span class="qu-tips-tag">
+                            未阅
+                        </span>
+                    </div>
+                    <div>
+                        <span v-for="(item,index) in paperData.item" :key="index">
+                            <!-- 综合题 -->
+                            <span v-if="item.children.length" :key="index">
+                                <span @click="toQu(index,childIndex)" v-for="(childItem,childIndex) in item.children" :key="childIndex" :class="['qu-index',stuScore[getScoreIndex(index,childIndex)] > -1 ? 'right-qu' : '']">
+                                    {{(index + 1) + '-' + (childIndex + 1)}}
+                                </span>
+                            </span>
+                            <!-- 其他题 -->
+                            <span v-else @click="toQu(index)" :class="['qu-index',stuScore[getScoreIndex(index)] > -1 ? 'right-qu' : '']">
+                                {{index + 1}}
+                            </span>
+                        </span>
+                    </div>
+                </div>
+            </div>
+            <!-- 打分部分 -->
+            <div class="score-wrap">
+                <div class="quick-score-box score-input-box">
+                    <span>分数:</span>
+                    <InputNumber style="flex:1" :max="10" :min="1" v-model="score" @on-change="setScore"></InputNumber>
+                </div>
+                <div class="quick-score-box">
+                    <Button size="small" type="info" style="margin-right:8px" ghost @click="score = 10">满分</Button>
+                    <Button size="small" type="error" ghost @click="score = 0">零分</Button>
+                    <Icon :type="isShowNum ? 'md-eye-off' : 'md-eye'" class="toggle-num-status" @click="isShowNum = !isShowNum" />
+                    <div :class="['score-key-box', isShowNum ? '':'hind-key-box']">
+                        <span v-for="(item,index) in quScoreArr" :key="index" :class="['score-key', stuScore[getScoreIndex(quIndex,childIndex)] == index ? 'score-key-active':'']" @click="setScore(index)">
+                            {{item}}
+                        </span>
+                    </div>
+                </div>
+                <Button type="success" class="submit-score" @click="submit()">提交分数</Button>
+                <div class="score-setting-wrap">
+                    <div class="score-setting-item">
+                        <span>提交分数自动切换题目</span>
+                        <i-switch v-model="autoQu" size="small" />
+                    </div>
+                    <div class="score-setting-item">
+                        <span>完成阅卷自动获取新学生</span>
+                        <i-switch v-model="autoStu" size="small" />
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!-- 用来单独渲染学生作答数据,提高tocanvas 的效率 -->
+        <iframe id="markIframe" :srcdoc="curAnswer"></iframe>
+        <Modal v-model="toggleStatus" title="切换学生">
+            进行中
+        </Modal>
+    </div>
+</template>
+
+<script>
+import html2canvas from 'html2canvas';
+import MarkCanvas from './MarkCanvas';
+export default {
+    name: 'ByStu',
+    components: {
+        MarkCanvas
+    },
+    data() {
+        return {
+            score: null,
+            mouseStatus: 'move',
+            drawImgData: '',
+            ansImg: '',
+            imgs: [],
+            autoQu: true,//自动切换下一题
+            autoStu: true,//自动获取下一学生
+            toggleStatus: false,
+            activeIcon: -1,
+            isShowNum: true,
+            quIndex: 0,
+            childIndex: -1,
+            stuData: {
+                name: '',
+                id: '',
+            },
+            paperData: {
+                item: []
+            },
+            stuAnswer: [],
+            stuScore: [],
+            taskInfo: {},
+            markData: {},
+            stuId: ''
+        }
+    },
+    methods: {
+        drawImg(imgIndex) {
+            // this.activeIcon = imgIndex
+            // this.curImg = new Image()
+            // this.curImg.src = this.imgs[imgIndex]
+            // this.curImg.onload = () => {
+            //     this.mouseStatus = 'img'
+            //     this.startImg({ x: 100, y: 100 })
+            // }
+        },
+        //清除所有批注
+        clear() {
+
+            // this.markLayer.removeChildren()
+            // this.stage.add(this.markLayer)
+        },
+        /**将答案绘制到canvas上 */
+        ansToImg() {
+            let answerIframe = document.getElementById('markIframe')
+            answerIframe.onload = () => {
+                answerIframe.style.width = '850px'
+                answerIframe.contentWindow.document.body.style.margin = '0px 20px'
+                answerIframe.contentWindow.document.body.style.padding = '10px'
+                answerIframe.contentWindow.document.body.style.minWidth = '600px'
+                answerIframe.contentWindow.document.body.style.minHeight = '240px'
+                answerIframe.contentWindow.document.body.style.height = 'fit-content'
+                answerIframe.contentWindow.document.body.style.width = 'fit-content'
+                let bodyWidth = answerIframe.contentWindow.document.body.clientWidth
+                answerIframe.style.width = (bodyWidth + 20) + 'px'
+                answerIframe.contentWindow.document.body.style.backgroundColor = '#f5f5f5'
+                console.log('markIframe', answerIframe)
+                html2canvas(answerIframe.contentWindow.document.body, {}).then((canvas) => {
+                    canvas.id = "canvas" + this.getScoreIndex(this.quIndex, this.childIndex)
+                    this.ansImg = canvas.toDataURL()
+                    // 将转出来的答案绘制到canvas上
+                    console.log(this.ansImg)
+                })
+            }
+        },
+        //将图片(答案)绘制到canvas
+        imgToCanvas(img) {
+            console.log('将图片(答案)绘制到canvas', img)
+        },
+        /** 打分 */
+        setScore(score) {
+            this.score = score
+            this.$set(this.stuScore, this.getScoreIndex(this.quIndex, this.childIndex), score)
+        },
+        toQu(index, childIndex) {
+            this.quIndex = index
+            this.childIndex = childIndex
+        },
+        /**
+         * index 题目index 必传
+         * childIndex 小题index 非必传 
+         */
+        getScoreIndex(index, childIndex) {
+            let realIndex = index
+            this.paperData.item.forEach((item, itemIndex) => {
+                if (itemIndex < index && item.children.length) {
+                    realIndex += item.children.length
+                } else if (itemIndex == index && item.children.length) {
+                    realIndex += childIndex
+                }
+            })
+            return realIndex
+        },
+        //提交分数
+        submit() {
+            if (this.score > -1 && this.score != null) {
+                let requstData = {
+                    id: this.taskInfo.id,
+                    stuId: this.stuId,
+                    subjectId: this.taskInfo.subject,
+                    tmdId: this.$store.state.userInfo.TEAMModelId,
+                    score: this.stuScore,
+                    count: this.taskInfo.count,
+                    code: this.taskInfo.ecode.replace('Exam-', ''),
+                    mark: ''
+                }
+                this.$api.mark.saveScore(requstData).then(
+                    res => {
+                        this.$Message.success('保存成功')
+                        // 按人阅卷自动跳转下一题
+                        this.nextQuestion()
+                        this.ansToImg()
+                    },
+                    err => {
+                        this.$Message.error('保存失败')
+                    }
+                )
+            } else {
+                this.$Message.warning('请先打分')
+            }
+        },
+        nextQuestion() {
+            // 当前不是最后一题
+            if (this.quIndex < this.paperData.item.length - 1) {
+                //当前题目是综合题
+                if (this.paperData.item[this.quIndex].children.length) {
+                    //当前小题不是最后一个
+                    if (this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                        this.childIndex++
+                    }
+                    //当前小题是最后一个,需要判断下一个题目类型
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+                //当前题目不是综合题
+                else {
+                    //下一个题目是综合题
+                    if (this.paperData.item[this.quIndex + 1].children.length) {
+                        this.quIndex++
+                        this.childIndex = 0
+                    }
+                    // 下一个题目不是综合题
+                    else {
+                        this.quIndex++
+                        this.childIndex = -1
+                    }
+                }
+            }
+            //如果是最后一题
+            else {
+                //当前题目是综合题,并且不是小题最后一题
+                if (this.paperData.item[this.quIndex].children.length && this.childIndex < this.paperData.item[this.quIndex].children.length - 1) {
+                    this.childIndex++
+                }
+                //当前目不是综合题 则代表题号已经到最后一个
+                else {
+                    //检查所有题目是否完成评测
+                    if (this.stuScore.includes(-1)) {
+                        let qu = ''
+                        let quIndex = 0
+                        let childIndex = -1
+                        let realIndex = 0
+                        // 检测具体未评分的题目
+                        for (let i = 0; i < this.paperData.item.length; i++) {
+                            if (this.paperData.item[i].children.length) {
+                                let flag = false
+                                for (let j = 0; j < this.paperData.item[i].children.length; j++) {
+                                    if (this.stuScore[realIndex++] == -1) {
+                                        quIndex = i
+                                        childIndex = j
+                                        flag = true
+                                        break
+                                    }
+                                }
+                                if (flag) {
+                                    break
+                                }
+                            } else {
+                                if (this.stuScore[realIndex++] == -1) {
+                                    quIndex = i
+                                    break
+                                }
+                            }
+                        }
+                        qu = childIndex > -1 ? `${(quIndex + 1)}-${(childIndex + 1)}` : quIndex
+                        this.$Modal.confirm({
+                            title: '未阅题目',
+                            content: `${qu}题目尚未评分,是否跳转到对应题目继续评分?`,
+                            onOk: () => {
+                                this.quIndex = quIndex
+                                this.childIndex = childIndex
+                            }
+                        })
+                    } else {
+                        this.$Message.success('已阅完')
+                        if(this.autoStu){
+                            //TODE 随机获取下一位学生
+                        }
+                    }
+
+                }
+            }
+            this.score = this.stuScore[this.getScoreIndex(this.quIndex, this.childIndex)] == -1 ? null : this.stuScore[this.getScoreIndex(this.quIndex, this.childIndex)]
+        },
+
+        quit() {
+            // 返回页面
+            this.$router.push({
+                name: sessionStorage.getItem('markFrom')
+            })
+        },
+        uuid() {
+            function S4() {
+                return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
+            }
+            return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4())
+        },
+    },
+    mounted() {
+        this.ansToImg()
+
+    },
+    created() {
+        let routeData = this.$route.params
+        console.log('路由数据', routeData)
+        this.paperData = routeData.fullPaper
+        this.stuAnswer = routeData.answer
+        this.stuScore = routeData.score
+        this.taskInfo = routeData.task
+        this.stuId = routeData.stuId
+        //初始化题目index
+        if (this.paperData && this.paperData.item.length) {
+            this.quIndex = 0
+            if (this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children.length) {
+                this.childIndex = 0
+            }
+        }
+        //默认表情包
+        for (let i = 0; i < 9; i++) {
+            this.imgs.push(require('@/assets/mark/' + i + '.jpg'))
+        }
+    },
+    computed: {
+        /**当期题目分数数组 */
+        quScoreArr() {
+            let score = 0
+            if (this.childIndex > -1 && this.paperData.item[this.quIndex] && this.paperData.item[this.quIndex].children) {
+                score = this.paperData.item[this.quIndex].children[this.childIndex].score
+            } else {
+                score = this.paperData.item[this.quIndex].score
+            }
+            return Array.from(new Array(score + 1).keys())
+        },
+        /**当前题目作答数据 */
+        curAnswer() {
+            if (this.stuAnswer.length) {
+                let index = this.getScoreIndex(this.quIndex, this.childIndex)
+                return this.stuAnswer[index]
+            } else {
+                return this.stuId + '未作答'
+            }
+
+        },
+        totalScore() {
+            return this.stuScore.reduce((a, b) => {
+                a = a == -1 ? 0 : a
+                b = b == -1 ? 0 : b
+                return a + b
+            }, 0)
+        }
+    }
+}
+</script>
+<style scoped lang="less">
+.score-setting-wrap {
+    margin-top: 50px;
+    padding: 0px 5px;
+}
+.score-setting-item {
+    margin-top: 15px;
+    display: flex;
+    justify-content: space-between;
+}
+.stu-id-info {
+    color: black;
+    font-size: 16px;
+}
+.score-info {
+    background: #19be6b;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+.cur-qu-index {
+    background: #2db7f5;
+    color: white;
+    padding: 1px 5px;
+    border-radius: 4px;
+    text-align: center;
+    font-size: 16px;
+}
+#markIframe {
+    position: fixed;
+    top: 9990px;
+    width: 850px;
+    background: #f5f5f5;
+}
+.quit-marking-icon {
+    display: inline-block;
+    font-size: 18px;
+    width: 40px;
+    text-align: center;
+    cursor: pointer;
+    color: black;
+}
+.quit-marking-text {
+    margin-right: 15px;
+}
+.expression-box {
+    max-width: 300px;
+    display: flex;
+    flex-wrap: wrap;
+    max-height: 600px;
+}
+.expression-img {
+    width: 80px;
+    margin: 10px;
+    height: auto;
+}
+.container-box {
+    display: flex;
+    justify-content: center;
+    width: 100%;
+    padding: 10px;
+}
+#container {
+    max-width: 100%;
+}
+.qu-tips-box {
+    & :nth-child(1)::before {
+        background: #19be6b;
+    }
+    & :nth-child(2)::before {
+        background: #e1e1e1;
+    }
+    & :nth-child(3)::before {
+        background: #ed4014;
+    }
+    & :nth-child(4)::before {
+        background: #e1e1e1;
+    }
+}
+
+.qu-tips-tag {
+    display: inline-block;
+    margin-right: 15px;
+    color: #909090;
+    font-size: 12px;
+    &::before {
+        content: "";
+        width: 15px;
+        height: 10px;
+        display: inline-block;
+        border-radius: 3px;
+        vertical-align: baseline;
+    }
+}
+.qu-index {
+    width: 30px;
+    height: 22px;
+    border-radius: 5px;
+    display: inline-block;
+    border: 1px solid #e1e1e1;
+    line-height: 22px;
+    text-align: center;
+    margin-bottom: 5px;
+    margin-top: 5px;
+    margin-right: 5px;
+    cursor: pointer;
+    background: #f1f1f1;
+    &:hover {
+        // background: white;
+        box-shadow: 0px 0px 4px rgba(70, 43, 43, 0.3);
+    }
+}
+.right-qu {
+    color: #fff;
+    border-color: #19be6b;
+    background: #19be6b;
+}
+.err-qu {
+    color: #ed4014;
+    border-color: #ed4014;
+    background: white;
+}
+.half-qu {
+    color: #ff9900;
+    border-color: #ff9900;
+    background: white;
+}
+.submit-score {
+    width: 236px;
+    margin-left: 2px;
+}
+.score-key-box {
+    margin-top: 10px;
+    height: 120px;
+    transition: height 0.5s;
+    -webkit-transition: height 0.5s;
+    overflow: hidden;
+}
+.hind-key-box {
+    height: 0px;
+}
+.score-key {
+    display: inline-block;
+    width: 30px;
+    height: 30px;
+    border-radius: 50%;
+    border: 1px solid #e1e1e1;
+    line-height: 30px;
+    text-align: center;
+    margin-top: 5px;
+    margin-right: 5px;
+    cursor: pointer;
+    background: #f1f1f1;
+}
+.score-key-active {
+    background: white;
+    border-color: #1cc0f3;
+    color: #1cc0f3;
+}
+.score-input-box {
+    display: flex;
+    align-items: center;
+}
+.score-wrap > :nth-child(2) {
+    background: white;
+}
+.quick-score-box {
+    padding: 10px 15px;
+    border-bottom: 1px solid #e1e1e1;
+}
+
+.toggle-num-status {
+    float: right;
+    margin-right: 5px;
+    font-size: 18px;
+    cursor: pointer;
+    margin-top: 2px;
+    user-select: none;
+}
+.btn-wrap {
+    float: right;
+    // padding-right: 15px;
+}
+.action-btn {
+    margin-left: 30px;
+    cursor: pointer;
+}
+.action-btn-icon {
+    display: inline-block;
+    margin-right: 5px;
+}
+.tmd-logo {
+    width: 35px;
+    height: 35px;
+    margin: 5px 18px 5px 13px;
+    vertical-align: bottom;
+}
+.mark-area {
+    width: 100%;
+    height: 100%;
+    user-select: none;
+    // padding: 5px;
+}
+.mark-header {
+    height: 50px;
+    width: 100%;
+    border-bottom: 1px solid #e1e1e1;
+    background: #fafafa;
+    line-height: 50px;
+    text-align: left;
+    padding: 0px 18px 0px 0px;
+    box-shadow: 0px 2px 3px 0px rgba(100, 100, 100, 0.2);
+    position: relative;
+    z-index: 99;
+    .info-label {
+        color: #909090;
+    }
+    .info-value {
+        margin-right: 30px;
+    }
+}
+.mark-main {
+    display: flex;
+    flex-direction: row;
+    height: calc(100% - 50px);
+}
+.mark-tools-wrap {
+    position: relative;
+    box-shadow: 2px 0px 5px 0px rgba(100, 100, 100, 0.2);
+    z-index: 9;
+    flex: 0 0 40px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    border-right: 1px solid #e1e1e1;
+    background: #fafafa;
+}
+.mark-stage {
+    flex: 1;
+    overflow: hidden;
+    background: white;
+    text-align: center;
+    position: relative;
+    height: 100%;
+}
+.qu-index-box {
+    position: absolute;
+    bottom: 0px;
+    background: #fafafa;
+    width: 100%;
+    left: 0px;
+    height: fit-content;
+    text-align: left;
+    max-height: 180px;
+    padding: 10px 10px 20px 10px;
+    box-shadow: 0 -2px 20px -12px #595959;
+    p {
+        width: fit-content;
+    }
+}
+.score-wrap {
+    position: relative;
+    box-shadow: -2px 0px 5px 0px rgba(100, 100, 100, 0.2);
+    z-index: 9;
+    flex: 0 0 241px;
+    height: 100%;
+    border-left: 1px solid #e1e1e1;
+    background: #fafafa;
+}
+.tool-icon {
+    font-size: 20px;
+    cursor: pointer;
+    height: 40px;
+    line-height: 40px;
+    // border-bottom: 1px solid #e1e1e1;
+    width: 39px;
+    &:hover {
+        color: #1cc0f3;
+        box-shadow: 0 5px 10px -8px #1cc0f3;
+        background: white;
+        border-color: transparent;
+    }
+}
+.exception-icon:hover {
+    box-shadow: 0 5px 10px -8px #ed4014;
+}
+.tool-icon-active {
+    color: #1cc0f3;
+    box-shadow: 0 5px 10px -8px #1cc0f3;
+    background: white;
+    border-color: transparent;
+    &:last-child {
+        box-shadow: 0 5px 10px -8px #ed4014;
+    }
+}
+</style>
+<style>
+.mark-tools-wrap .ivu-poptip-title::after {
+    display: none;
+}
+.textarea:focus {
+    box-shadow: none;
+    overflow: hidden;
+    outline: none !important;
+    line-height: 1.5;
+}
+.score-input-box .ivu-input-number-input {
+    font-size: 18px;
+    color: red;
+    font-weight: 800;
+}
+</style>

+ 696 - 0
TEAMModelOS/ClientApp/src/view/task/mark/MarkCanvas.vue

@@ -0,0 +1,696 @@
+<template>
+    <!-- 未做多语言 -->
+    <div class="mark-stage">
+        <vuescroll ref="canvasScroll">
+            <!-- canvas部分 -->
+            <div class="container-box">
+                <div id="container" @mousemove="canvasMouseMove" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp"></div>
+            </div>
+        </vuescroll>
+    </div>
+</template>
+
+<script>
+import Konva from 'konva'
+export default {
+    name: 'MarkCanvas',
+    props: {
+        // 当前鼠标操作类型
+        mouseStatus: {
+            type: String,
+            default: 'move',
+            required: true
+        },
+        //canvas主图(eg:学生作答数据)
+        bgImg: {
+            type: [String, Object],
+            default: ''
+        },
+        //canvas绘制图片的数据(eg:添加表情包)
+        drawImgData: {
+            type: [String, Object],
+            default: ''
+        }
+    },
+    data() {
+        return {
+            stage: undefined,
+            orgLayer: undefined,
+            markLayer: undefined,
+            tr: undefined,
+            scaleDefault: 1,
+            maxScale: 5,
+            minScale: 0.2,
+            scaleStep: 0.2,
+            imgWidth: 0,
+            imgHeight: 0,
+            isMouseDown: false,
+            clickPoint: {
+                x: 0,
+                y: 0
+            },
+            // mouseStatus: 'move', //move:移动  rect:画矩形 resize:调整图形
+            resizeBefore: '',
+            rect: undefined,
+            oval: undefined,
+            arrow: undefined,
+            text: undefined,
+            line: undefined,
+            img: undefined,
+            isDltText: false,
+        }
+    },
+    methods: {
+        uuid() {
+            function S4() {
+                return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
+            }
+            return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4())
+        },
+        move(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'move'
+        },
+        drawImg(imgIndex) {
+            // this.activeIcon = imgIndex
+            // this.curImg = new Image()
+            // this.curImg.src = this.imgs[imgIndex]
+            // this.curImg.onload = () => {
+            //     this.mouseStatus = 'img'
+            //     this.startImg({ x: 100, y: 100 })
+            // }
+        },
+        startImg(current) {
+            let _this = this
+            this.img = new Konva.Image({
+                id: this.uuid(),
+                draggable: false,
+                strokeScaleEnabled: false,
+                image: _this.curImg,
+                width: 80,
+                height: 80,
+                x: current.x,
+                y: current.y
+            })
+            // 监听点击事件设置选中状态
+            this.img.on('click', (evt) => {
+                _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                _this.mouseStatus = 'resize'
+                evt.target.draggable(true)
+                evt.cancelBubble = true //阻止事件冒泡 
+                _this.tr.nodes([evt.target])
+                _this.markLayer.add(_this.tr)
+            })
+            // 优化一些绘制逻辑
+            this.img.on('mouseout', (evt) => {
+                evt.cancelBubble = true //阻止事件冒泡
+                evt.target.draggable(false)
+                let nodes = _this.tr.nodes()
+                if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+            })
+            // this.img.zIndex(1)
+            this.markLayer.add(this.img)
+            this.stage.add(this.markLayer)
+            this.img = null
+        },
+        drawText(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'text'
+        },
+        startText(current) {
+            let _this = this
+            let nodes = this.tr.nodes()
+            if (nodes.length > 0) return
+            if (!this.text) {
+                this.text = new Konva.Text({
+                    id: this.uuid(),
+                    fontSize: 16,
+                    text: '',
+                    fill: 'red',
+                    lineHeight: 1.5,
+                    width: 180,
+                    height: 30,
+                    x: _this.clickPoint.x / _this.scaleDefault,
+                    y: _this.clickPoint.y / _this.scaleDefault,
+                    draggable: false
+                })
+                // 监听点击事件设置选中状态
+                this.text.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                this.text.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+                this.text.on('dblclick dbltap', (evt) => {
+                    let nodes = _this.tr.nodes()
+                    if (nodes && nodes.length > 0) {
+                        let text = nodes[0]
+                        let textPosition = text.getAbsolutePosition()
+                        let stageBox = _this.stage.container().getBoundingClientRect()
+                        let areaPosition = {
+                            x: stageBox.left + textPosition.x,
+                            y: stageBox.top + textPosition.y,
+                        }
+                        let textarea = document.createElement('textarea');
+                        document.body.appendChild(textarea)
+
+                        textarea.value = text.text()
+                        textarea.style.position = 'absolute'
+                        textarea.style.top = areaPosition.y + 'px'
+                        textarea.style.left = areaPosition.x + 'px'
+                        textarea.style.background = 'transparent'
+                        textarea.style.color = 'red'
+                        textarea.style.fontSize = '16px'
+                        textarea.style.borderColor = 'red'
+                        textarea.style.boxShadow = 'none'
+                        textarea.style.height = text.height() + 'px'
+                        textarea.className = 'textarea'
+                        textarea.style.width = text.width()
+
+                        textarea.focus()
+                        _this.isDltText = true //设置删除按键的状态,文本框状态应该是删除文字不能删除图形
+                        text.hide()
+                        _this.tr.hide()
+                        textarea.addEventListener('input', () => {
+                            textarea.style.height = 'auto'
+                            textarea.style.height = textarea.scrollHeight + 'px'
+                            _this.isDltText = true
+                        })
+                        textarea.addEventListener('blur', function (e) {
+                            if (textarea.value) {
+                                text.text(textarea.value)
+                                text.width(textarea.clientWidth)
+                                text.height(textarea.scrollHeight)
+                                text.show()
+
+                                _this.markLayer.draw()
+                            } else {
+                                text.remove()
+                            }
+                            _this.tr.nodes([])
+                            _this.tr.show()
+                            document.body.removeChild(textarea)
+                            _this.isDltText = false
+                        })
+                    }
+                })
+            } else {
+                this.text.x(current.x)
+                this.text.y(current.y)
+            }
+            // this.text.zIndex(100)
+            this.tr.nodes([this.text])
+            this.markLayer.add(this.tr)
+            this.markLayer.add(this.text)
+            this.stage.add(this.markLayer)
+        },
+        drawLine(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'line'
+        },
+        startLine(current) {
+            let _this = this
+            if (!this.line) {
+                this.line = new Konva.Line({
+                    id: this.uuid(),
+                    stroke: 'red',
+                    strokeWidth: 3,
+                    draggable: false,
+                    tension: 1,
+                    points: [_this.clickPoint.x / _this.scaleDefault, _this.clickPoint.y / _this.scaleDefault]
+                })
+                // 监听点击事件设置选中状态
+                this.line.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.line.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.line.points(this.line.points().concat([current.x / _this.scaleDefault, current.y / _this.scaleDefault]))
+            }
+            // this.line.zIndex(1)
+            this.markLayer.add(this.line)
+            this.stage.add(this.markLayer)
+        },
+        drawArrow(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'arrow'
+        },
+        // 绘制箭头
+        startArrow(current) {
+            let _this = this
+            if (!this.arrow) {
+                this.arrow = new Konva.Arrow({
+                    id: this.uuid(),
+                    fill: 'transparent',
+                    stroke: 'red',
+                    strokeWidth: 4,
+                    pointerLength: 10,
+                    pointerWidth: 8,
+                    draggable: false,
+                    fill: 'red',
+                    lineCap: 'round',
+                    strokeScaleEnabled: false,
+                    points: [_this.clickPoint.x / _this.scaleDefault, _this.clickPoint.y / _this.scaleDefault, (current.x) / _this.scaleDefault, (current.y) / _this.scaleDefault]
+                })
+                // 监听点击事件设置选中状态
+                this.arrow.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.arrow.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.arrow.points([_this.clickPoint.x / _this.scaleDefault, _this.clickPoint.y / _this.scaleDefault, (current.x) / _this.scaleDefault, (current.y) / _this.scaleDefault])
+            }
+            // this.arrow.zIndex(1)
+            this.markLayer.add(this.arrow)
+            this.stage.add(this.markLayer)
+        },
+        drawOval(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'oval'
+        },
+        startOval(current) {
+            console.log('scale', this.scaleDefault)
+            console.log('point', this.clickPoint)
+            let _this = this
+            //判断rect是否初始化
+            let rx = (current.x - _this.clickPoint.x) / _this.scaleDefault * 0.5
+            let ry = (_this.clickPoint.y - current.y) / _this.scaleDefault * 0.5
+            if (!this.oval) {
+                this.oval = new Konva.Ellipse({
+                    id: this.uuid(),
+                    // x: _this.clickPoint.x / _this.scaleDefault,
+                    // y: _this.clickPoint.y / _this.scaleDefault,
+                    x: _this.clickPoint.x,
+                    y: _this.clickPoint.y,
+                    radiusX: rx > 0 ? rx : -rx,
+                    radiusY: ry,
+                    fill: 'transparent',
+                    stroke: 'red',
+                    strokeWidth: 2,
+                    draggable: false,
+                    rotation: rx > 0 ? 0 : 180,
+                    strokeScaleEnabled: false
+                })
+                // 监听点击事件设置选中状态
+                this.oval.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡 
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.oval.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.oval.x(_this.clickPoint.x / _this.scaleDefault + rx)
+                this.oval.y(_this.clickPoint.y / _this.scaleDefault - ry)
+                this.oval.radiusX(rx > 0 ? rx : -rx)
+                this.oval.radiusY(ry)
+                this.oval.rotation(rx > 0 ? 0 : 180)
+            }
+            this.oval.zIndex(1)
+            this.markLayer.add(this.oval)
+            this.stage.add(this.markLayer)
+        },
+        drawIcon(index) {
+            this.activeIcon = index
+        },
+        review(index) {
+            this.activeIcon = index
+        },
+        cancelFull(index) {
+            this.activeIcon = index
+        },
+        //绘制矩形
+        drawRect(index) {
+            this.activeIcon = index
+            this.mouseStatus = 'rect'
+        },
+        startRect(current) {
+            let _this = this
+            //判断rect是否初始化
+            if (!this.rect) {
+                this.rect = new Konva.Rect({
+                    id: this.uuid(),
+                    x: _this.clickPoint.x / _this.scaleDefault,
+                    y: _this.clickPoint.y / _this.scaleDefault,
+                    width: (current.x - _this.clickPoint.x) / _this.scaleDefault,
+                    height: (current.y - _this.clickPoint.y) / _this.scaleDefault,
+                    fill: 'transparent',
+                    stroke: 'red',
+                    strokeWidth: 2,
+                    draggable: false,
+                    strokeScaleEnabled: false
+                })
+                // 监听点击事件设置选中状态
+                this.rect.on('click', (evt) => {
+                    _this.resizeBefore = _this.mouseStatus == 'resize' ? _this.resizeBefore : _this.mouseStatus//记录resize之前的状态
+                    _this.mouseStatus = 'resize'
+                    evt.target.draggable(true)
+                    evt.cancelBubble = true //阻止事件冒泡
+                    //Transformer   
+                    _this.tr.nodes([evt.target])
+                    _this.markLayer.add(_this.tr)
+                })
+                // 优化一些绘制逻辑
+                this.rect.on('mouseout', (evt) => {
+                    evt.cancelBubble = true //阻止事件冒泡
+                    evt.target.draggable(false)
+                    let nodes = _this.tr.nodes()
+                    if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+                })
+            } else {
+                this.rect.x(_this.clickPoint.x / _this.scaleDefault)
+                this.rect.y(_this.clickPoint.y / _this.scaleDefault)
+                this.rect.width((current.x - _this.clickPoint.x) / _this.scaleDefault)
+                this.rect.height((current.y - _this.clickPoint.y) / _this.scaleDefault)
+            }
+            this.rect.zIndex(1)
+            this.markLayer.add(this.rect)
+            this.stage.add(this.markLayer)
+        },
+        //防抖函数
+        debounce(func, delay) {
+            return function () {
+                window.clearTimeout(window.timeout)
+                window.timeout = setTimeout(() => {
+                    clearTimeout(window.timeout)
+                    func.apply(this, arguments)
+                }, delay)
+            }
+        },
+        //还原比例
+        restore() {
+            this.scaleDefault = 1
+            this.myScale()
+        },
+        // 缩小
+        smaller() {
+            console.log(this.stage)
+            this.scaleDefault -= this.scaleStep
+            if (this.scaleDefault < this.minScale) {
+                this.scaleDefault += this.scaleStep
+                //函数防抖,优化重复提示
+                this.debounce(() => {
+                    this.$Message.warning('已经缩小到最小比例')
+                }, 400)()
+            } else {
+                this.myScale()
+            }
+        },
+        //放大
+        larger() {
+            console.log(this.stage)
+            this.scaleDefault += this.scaleStep
+            if (this.scaleDefault > this.maxScale) {
+                this.scaleDefault -= this.scaleStep
+                //函数防抖,优化重复提示
+                this.debounce(() => {
+                    this.$Message.warning('已经放大到最大比例')
+                }, 400)()
+            } else {
+                this.myScale()
+            }
+        },
+        myScale() {
+            this.stage.children.forEach(item => {
+                item.scale({
+                    x: this.scaleDefault,
+                    y: this.scaleDefault
+                })
+            })
+            this.stage.width(this.imgWidth * this.scaleDefault)
+            this.stage.height(this.imgHeight * this.scaleDefault)
+        },
+        //绑定鼠标滚轮事件,操作缩放功能
+        bundleScoll() {
+            let stage = document.getElementById('container')
+            let _this = this
+            stage.addEventListener('mousewheel', function (e) {
+                if (e.preventDefault) {
+                    e.preventDefault()
+                } else {
+                    window.event.returnValue == false
+                }
+                _this.scrollFunc(e)
+            }, false) || stage.addEventListener('DOMMouseScroll', function (e) {
+                if (e.preventDefault) {
+                    e.preventDefault()
+                } else {
+                    window.event.returnValue == false
+                }
+                _this.scrollFunc(e)
+            }, false)
+        },
+        //判断鼠标放大还是缩小
+        scrollFunc(e) {
+            e = e || window.event
+            if (e.wheelDelta) { // 判断浏览器IE,谷歌滑轮事件
+                if (e.wheelDelta > 0) { // 当滑轮向上滚动时
+                    this.larger()
+                }
+                if (e.wheelDelta < 0) { // 当滑轮向下滚动时
+                    this.smaller()
+                }
+            } else if (e.detail) { // Firefox滑轮事件
+                if (e.detail > 0) { // 当滑轮向上滚动时
+                    this.larger()
+                }
+                if (e.detail < 0) { // 当滑轮向下滚动时
+                    this.smaller()
+                }
+            }
+        },
+        //获取在canvas上的坐标
+        getCanvasPoint(x, y) {
+            // let canvas = document.getElementById('container')
+            let canvas = document.getElementsByClassName('konvajs-content')
+            let cs = canvas.length ? canvas[0] : null
+            if (cs) {
+                let canvasRect = cs.getBoundingClientRect()
+                return {
+                    x: x - canvasRect.left,
+                    y: y - canvasRect.top
+                }
+            } else {
+                return {
+                    x, y
+                }
+            }
+
+        },
+        //按下鼠标事件
+        canvasMouseDown(e) {
+            this.isMouseDown = true
+            this.clickPoint = this.getCanvasPoint(e.clientX, e.clientY)
+        },
+        //鼠标弹起事件
+        canvasMouseUp() {
+            this.isMouseDown = false
+            switch (this.mouseStatus) {
+                case 'move':
+                    break
+                case 'rect':
+                    this.rect = null
+                    break
+                case 'oval':
+                    this.oval = null
+                    break
+                case 'arrow':
+                    this.arrow = null
+                    break
+                case 'text':
+                    this.text = null
+                    break
+                case 'line':
+                    this.line = null
+                    break
+                // case 'img':
+                //     this.img = null
+                //     break
+                default:
+                    break
+            }
+        },
+        //鼠标移动事件
+        canvasMouseMove(e) {
+            console.log(this.mouseStatus)
+            if (this.isMouseDown) {
+                let current = this.getCanvasPoint(e.clientX, e.clientY)
+                switch (this.mouseStatus) {
+                    case 'move':
+                        this.$refs["canvasScroll"].scrollBy({
+                            dx: this.clickPoint.x - current.x,
+                            dy: this.clickPoint.y - current.y
+                        }, 0)
+                        break
+                    case 'rect':
+                        this.startRect(current)
+                        break
+                    case 'oval':
+                        this.startOval(current)
+                        break
+                    case 'arrow':
+                        this.startArrow(current)
+                        break
+                    case 'text':
+                        this.startText(current)
+                        break
+                    case 'line':
+                        this.startLine(current)
+                        break
+                    default:
+                        break
+                }
+            }
+        },
+
+        //清除所有批注
+        clear(index) {
+            this.activeIcon = index
+            this.markLayer.removeChildren()
+            this.stage.add(this.markLayer)
+        }
+    },
+    mounted() {
+        let _this = this
+
+        //创建画布
+        _this.stage = new Konva.Stage({
+            container: 'container',
+            id: 'canvas',
+            width: 600,
+            height: 240
+        })
+        _this.orgLayer = new Konva.Layer()
+        _this.markLayer = new Konva.Layer()
+        _this.stage.add(_this.orgLayer)
+        _this.stage.add(_this.markLayer)
+        _this.orgLayer.batchDraw()
+        _this.bundleScoll()
+        _this.stage.on('click', (evt) => {
+            _this.tr.nodes([])
+        })
+        _this.stage.on('mouseover', (evt) => {
+            let nodes = _this.tr.nodes()
+            if (_this.mouseStatus == 'resize' && nodes.length == 0) _this.mouseStatus = _this.resizeBefore
+
+        })
+
+        this.tr = new Konva.Transformer({
+            rotateEnabled: false,
+            anchorStroke: 'red',
+            enabledAnchors: ['top-center', 'top-left', 'top-right', 'middle-left', 'middle-right', 'bottom-left', 'bottom-center', 'bottom-right'],
+            anchorSize: 8,
+            borderStroke: 'red'
+        })
+        this.tr.borderEnabled(true)
+        this.tr.anchorCornerRadius(10)
+
+    },
+    created() {
+        //监听删除按键,删除图形
+        document.onkeydown = e => {
+            if (e.keyCode == 8 && !this.isDltText) {
+                let shape = this.tr ? this.tr.nodes() : []
+                if (shape.length) {
+                    shape[0].remove()
+                    this.tr.nodes([])
+                }
+            }
+        }
+    },
+    watch: {
+        bgImg: {
+            handler(n, o) {
+                this.$nextTick(() => {
+                    this.orgLayer.removeChildren()
+                    this.markLayer.removeChildren()
+                    let imageObj = new Image()
+                    let _this = this
+                    imageObj.onload = function () {
+                        //创建画布
+                        _this.imgWidth = imageObj.width
+                        _this.imgHeight = imageObj.height
+                        _this.stage.width(imageObj.width)
+                        _this.stage.height(imageObj.height)
+                        let img = new Konva.Image({
+                            x: 0,
+                            y: 0,
+                            image: imageObj,
+                            width: imageObj.width,
+                            height: imageObj.height
+                        })
+                        _this.orgLayer.add(img)
+                        _this.orgLayer.batchDraw()
+                    }
+                    imageObj.src = this.bgImg
+                    this.stage.add(this.orgLayer)
+                })
+            },
+            immediate: true,
+            deep: true
+        }
+    }
+}
+</script>
+<style scoped lang="less">
+.mark-stage {
+    flex: 1;
+    overflow: hidden;
+    background: white;
+    text-align: center;
+    position: relative;
+    height: 100%;
+}
+.container-box {
+    display: flex;
+    justify-content: center;
+    width: 100%;
+    padding: 10px;
+}
+</style>
+<style>
+.textarea:focus {
+    box-shadow: none;
+    overflow: hidden;
+    outline: none !important;
+    line-height: 1.5;
+}
+</style>

+ 1 - 2
TEAMModelOS/ClientApp/src/view/teachcontent/index.less

@@ -516,8 +516,7 @@
 }
 }
 .content-filter-wrap{
 .content-filter-wrap{
     position: absolute;
     position: absolute;
-    left: 50%;
+    left: 120px;
     display: flex;
     display: flex;
-    margin-left: -240px;
     margin-top: 12px;
     margin-top: 12px;
 }
 }

+ 65 - 21
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -22,6 +22,8 @@ using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
 using TEAMModelOS.SDK.Helper.Common.StringHelper;
 using TEAMModelOS.SDK.Helper.Common.StringHelper;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using TEAMModelOS.SDK.Models.Table;
 using TEAMModelOS.SDK.Models.Table;
+using Azure.Messaging.ServiceBus;
+using Microsoft.Extensions.Configuration;
 
 
 namespace TEAMModelOS.Controllers
 namespace TEAMModelOS.Controllers
 {
 {
@@ -39,9 +41,10 @@ namespace TEAMModelOS.Controllers
         private readonly Option _option;
         private readonly Option _option;
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureRedisFactory _azureRedis;
         private readonly AzureRedisFactory _azureRedis;
+        public IConfiguration _configuration { get; set; }
 
 
         public ExamController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing,
         public ExamController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing,
-            IOptionsSnapshot<Option> option, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis)
+            IOptionsSnapshot<Option> option, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, IConfiguration configuration)
         {
         {
             _azureCosmos = azureCosmos;
             _azureCosmos = azureCosmos;
             _serviceBus = serviceBus;
             _serviceBus = serviceBus;
@@ -50,6 +53,7 @@ namespace TEAMModelOS.Controllers
             _option = option?.Value;
             _option = option?.Value;
             _azureStorage = azureStorage;
             _azureStorage = azureStorage;
             _azureRedis = azureRedis;
             _azureRedis = azureRedis;
+            _configuration = configuration;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -180,14 +184,20 @@ namespace TEAMModelOS.Controllers
                     {
                     {
                         request.progress = "going";
                         request.progress = "going";
                     }
                     }
+                    var messageBlob = new ServiceBusMessage();
                     if (request.scope.Equals("school"))
                     if (request.scope.Equals("school"))
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"exam/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"exam/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"exam/{request.id}", name = request.school }.ToJsonString());
                     }
                     }
                     else
                     else
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"exam/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"exam/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"exam/{request.id}", name = request.creatorId }.ToJsonString());
                     }
                     }
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     int n = 0;
                     int n = 0;
                     foreach (PaperSimple simple in request.papers)
                     foreach (PaperSimple simple in request.papers)
                     {
                     {
@@ -209,14 +219,22 @@ namespace TEAMModelOS.Controllers
                     {
                     {
                         return Ok(new { v = "活动正在进行中" });
                         return Ok(new { v = "活动正在进行中" });
                     }
                     }
+                    var messageBlob = new ServiceBusMessage();
                     if (request.scope.Equals("school"))
                     if (request.scope.Equals("school"))
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"exam/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"exam/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"exam/{request.id}", name = request.school }.ToJsonString());
+
                     }
                     }
                     else
                     else
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"exam/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"exam/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"exam/{request.id}", name = request.creatorId }.ToJsonString());
+
                     }
                     }
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     request.progress = info.progress;
                     request.progress = info.progress;
                     int n = 0;
                     int n = 0;
                     foreach (PaperSimple simple in request.papers)
                     foreach (PaperSimple simple in request.papers)
@@ -228,6 +246,8 @@ namespace TEAMModelOS.Controllers
                       long SequenceNumber = await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.startTime);
                       long SequenceNumber = await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.startTime);
                       request.sequenceNumber = SequenceNumber;*/
                       request.sequenceNumber = SequenceNumber;*/
                     exam = await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(request, request.id, new PartitionKey($"{request.code}"));
                     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);
                     //await _serviceBus.GetServiceBusClient().SendLeamMessage<ExamInfo>(Constants.TopicName, request.id, request.code, request.startTime);
                 }
                 }
                 //Survey homeWork = await _azureCosmos.SaveOrUpdate<Survey>(request.survey);
                 //Survey homeWork = await _azureCosmos.SaveOrUpdate<Survey>(request.survey);
@@ -269,6 +289,11 @@ namespace TEAMModelOS.Controllers
                 var response = await client.GetContainer("TEAMModelOS", "Common").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"Exam-{code}"));
                 var response = await client.GetContainer("TEAMModelOS", "Common").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"Exam-{code}"));
                 //删除blob 相关资料
                 //删除blob 相关资料
                 await _azureStorage.GetBlobServiceClient().DelectBlobs(code.ToString(), $"exam/{id}");
                 await _azureStorage.GetBlobServiceClient().DelectBlobs(code.ToString(), $"exam/{id}");
+                //通知评测删除信息
+                var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "delete", root = $"exam/{id}", name = $"{code}" }.ToJsonString());
+                messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                 List<ExamClassResult> examClassResults = new List<ExamClassResult>();
                 List<ExamClassResult> examClassResults = new List<ExamClassResult>();
                 await foreach (var item in client.GetContainer("TEAMModelOS", "Common").GetItemQueryIterator<ExamClassResult>(queryText: $"select c.id from c where c.examId = '{id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{code}") }))
                 await foreach (var item in client.GetContainer("TEAMModelOS", "Common").GetItemQueryIterator<ExamClassResult>(queryText: $"select c.id from c where c.examId = '{id}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"ExamClassResult-{code}") }))
                 {
                 {
@@ -323,7 +348,7 @@ namespace TEAMModelOS.Controllers
             {
             {
                 if (!requert.TryGetProperty("code", out JsonElement code)) return BadRequest();
                 if (!requert.TryGetProperty("code", out JsonElement code)) return BadRequest();
                 var client = _azureCosmos.GetCosmosClient();
                 var client = _azureCosmos.GetCosmosClient();
-                List<SchoolYear> years = await _azureStorage.FindListByDict<SchoolYear>(new Dictionary<string, object>() { { "RowKey", code}, { "PartitionKey", "Exam" } });
+                List<SchoolYear> years = await _azureStorage.FindListByDict<SchoolYear>(new Dictionary<string, object>() { { "RowKey", code }, { "PartitionKey", "Exam" } });
                 int year = 0;
                 int year = 0;
                 if (years.Count > 0)
                 if (years.Count > 0)
                 {
                 {
@@ -1532,6 +1557,7 @@ namespace TEAMModelOS.Controllers
             {
             {
                 if (!requert.TryGetProperty("id", out JsonElement id)) return BadRequest();
                 if (!requert.TryGetProperty("id", out JsonElement id)) return BadRequest();
                 if (!requert.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
                 if (!requert.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
+                if (!requert.TryGetProperty("count", out JsonElement count)) return BadRequest();
                 if (!requert.TryGetProperty("tmdId", out JsonElement tId)) return BadRequest();
                 if (!requert.TryGetProperty("tmdId", out JsonElement tId)) return BadRequest();
                 var client = _azureCosmos.GetCosmosClient();
                 var client = _azureCosmos.GetCosmosClient();
                 var redisClient = _azureRedis.GetRedisClient(8);
                 var redisClient = _azureRedis.GetRedisClient(8);
@@ -1554,7 +1580,7 @@ namespace TEAMModelOS.Controllers
                 }
                 }
                 if (requert.TryGetProperty("stuId", out JsonElement stuId))
                 if (requert.TryGetProperty("stuId", out JsonElement stuId))
                 {
                 {
-                    
+
                     var stuAns = await redisClient.HashGetAsync($"Exam:Scoring:{id}-{subjectId}", stuId.ToString());
                     var stuAns = await redisClient.HashGetAsync($"Exam:Scoring:{id}-{subjectId}", stuId.ToString());
                     var value = JsonDocument.Parse(stuAns.ToString());
                     var value = JsonDocument.Parse(stuAns.ToString());
                     //保证此学生与该老师绑定
                     //保证此学生与该老师绑定
@@ -1566,20 +1592,28 @@ namespace TEAMModelOS.Controllers
                 }
                 }
                 else
                 else
                 {
                 {
-                    //随机选取一名学生打分
-                    Random random = new Random();
-                    int n = random.Next(all.Count);
-                    dynamic item = all[n];
-                    //序列化单个学生作答记录、得分情况、批注情况
-                    var cs = JsonSerializer.Serialize(item);
-                    var json = JsonDocument.Parse(cs);
-                    json.RootElement.TryGetProperty("stuId", out JsonElement sId);
-                    json.RootElement.TryGetProperty("ans", out JsonElement ans);
-                    ans.TryGetProperty("score", out JsonElement sc);
-                    ans.TryGetProperty("ans", out JsonElement blob);
-                    //选取后,缓存池相应的减少,以便于分配给其他的老师
-                    await redisClient.HashSetAsync($"Exam:Scoring:{id}-{subjectId}", sId.ToString(), new { ans = blob, score = sc, tmdId = tId }.ToJsonString());
-                    return Ok(item);
+                    if (recs.Count == count.GetInt32())
+                    {
+                        return Ok( new { msg = "分配人数已到上限"});
+                    }
+                    else
+                    {
+                        //随机选取一名学生打分
+                        Random random = new Random();
+                        int n = random.Next(all.Count);
+                        dynamic item = all[n];
+                        //序列化单个学生作答记录、得分情况、批注情况
+                        var cs = JsonSerializer.Serialize(item);
+                        var json = JsonDocument.Parse(cs);
+                        json.RootElement.TryGetProperty("stuId", out JsonElement sId);
+                        json.RootElement.TryGetProperty("ans", out JsonElement ans);
+                        ans.TryGetProperty("score", out JsonElement sc);
+                        ans.TryGetProperty("ans", out JsonElement blob);
+                        //选取后,缓存池相应的减少,以便于分配给其他的老师
+                        await redisClient.HashSetAsync($"Exam:Scoring:{id}-{subjectId}", sId.ToString(), new { ans = blob, score = sc, tmdId = tId }.ToJsonString());
+                        return Ok(item);
+                    }
+                   
                 }
                 }
 
 
                 //var json = JsonDocument.Parse(record);
                 //var json = JsonDocument.Parse(record);
@@ -1638,12 +1672,13 @@ namespace TEAMModelOS.Controllers
                 if (!requert.TryGetProperty("stuId", out JsonElement sId)) return BadRequest();
                 if (!requert.TryGetProperty("stuId", out JsonElement sId)) return BadRequest();
                 if (!requert.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
                 if (!requert.TryGetProperty("subjectId", out JsonElement subjectId)) return BadRequest();
                 if (!requert.TryGetProperty("tmdId", out JsonElement tId)) return BadRequest();
                 if (!requert.TryGetProperty("tmdId", out JsonElement tId)) return BadRequest();
-                if (!requert.TryGetProperty("score", out JsonElement score)) return BadRequest();
-                if (!requert.TryGetProperty("mark", out JsonElement mark)) return BadRequest();
+                if (!requert.TryGetProperty("score", out JsonElement score)) return BadRequest(); 
                 if (!requert.TryGetProperty("count", out JsonElement count)) return BadRequest();
                 if (!requert.TryGetProperty("count", out JsonElement count)) return BadRequest();
                 if (!requert.TryGetProperty("code", out JsonElement code)) return BadRequest();
                 if (!requert.TryGetProperty("code", out JsonElement code)) return BadRequest();
+                //if (!requert.TryGetProperty("mark", out JsonElement mark)) return BadRequest();
+                //requert.TryGetProperty("mark", out JsonElement mark);
                 var client = _azureCosmos.GetCosmosClient();
                 var client = _azureCosmos.GetCosmosClient();
-                var redisClient = _azureRedis.GetRedisClient(8);
+				var redisClient = _azureRedis.GetRedisClient(8);
                 List<ExamClassResult> classResults = new();
                 List<ExamClassResult> classResults = new();
                 List<dynamic> recs = new List<dynamic>();
                 List<dynamic> recs = new List<dynamic>();
                 var record = await redisClient.HashGetAllAsync($"Exam:Scoring:{id}-{subjectId}");
                 var record = await redisClient.HashGetAllAsync($"Exam:Scoring:{id}-{subjectId}");
@@ -1659,7 +1694,16 @@ namespace TEAMModelOS.Controllers
                 var stuAns = await redisClient.HashGetAsync($"Exam:Scoring:{id}-{subjectId}", sId.ToString());
                 var stuAns = await redisClient.HashGetAsync($"Exam:Scoring:{id}-{subjectId}", sId.ToString());
                 var value = JsonDocument.Parse(stuAns.ToString());
                 var value = JsonDocument.Parse(stuAns.ToString());
                 value.RootElement.TryGetProperty("ans", out JsonElement blob);
                 value.RootElement.TryGetProperty("ans", out JsonElement blob);
-                await redisClient.HashSetAsync($"Exam:Scoring:{id}-{subjectId}", sId.ToString(), new { ans = blob, score = score, tmdId = tId, mark = mark }.ToJsonString());
+                StringBuilder builder = new StringBuilder();
+                if (requert.TryGetProperty("mark", out JsonElement mark)) {
+                    //存放老师批注信息到blob                   
+                    builder.Append(id).Append("/");
+                    builder.Append(subjectId).Append("/");
+                    builder.Append(sId).Append("mark").Append("/");
+                    builder.Append("ans.json");
+                    await _azureStorage.UploadFileByContainer(code.ToString(), mark.ToJsonString(), "exam", builder.ToString(), false);
+                }                
+                await redisClient.HashSetAsync($"Exam:Scoring:{id}-{subjectId}", sId.ToString(), new { ans = blob, score = score, tmdId = tId, mark = builder.ToString() }.ToJsonString());
                 if (recs.Count == count.GetInt32())
                 if (recs.Count == count.GetInt32())
                 {
                 {
                     await foreach (var item in client.GetContainer("TEAMModelOS", "Common").GetItemQueryIterator<ExamClassResult>(
                     await foreach (var item in client.GetContainer("TEAMModelOS", "Common").GetItemQueryIterator<ExamClassResult>(

+ 23 - 1
TEAMModelOS/Controllers/Common/SurveyController.cs

@@ -27,6 +27,7 @@ using TEAMModelOS.SDK.Models.Cosmos;
 using Azure.Messaging.ServiceBus;
 using Azure.Messaging.ServiceBus;
 using Azure.Storage.Sas;
 using Azure.Storage.Sas;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
+using Microsoft.Extensions.Configuration;
 
 
 namespace TEAMModelOS.Controllers
 namespace TEAMModelOS.Controllers
 {
 {
@@ -48,8 +49,9 @@ namespace TEAMModelOS.Controllers
         private readonly Option _option;
         private readonly Option _option;
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureStorageFactory _azureStorage;
         private readonly IHttpClientFactory _clientFactory;
         private readonly IHttpClientFactory _clientFactory;
+        public IConfiguration _configuration { get; set; }
         public SurveyController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option, 
         public SurveyController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option, 
-            AzureRedisFactory azureRedis, AzureStorageFactory azureStorage, IHttpClientFactory clientFactory)
+            AzureRedisFactory azureRedis, AzureStorageFactory azureStorage, IHttpClientFactory clientFactory,IConfiguration configuration)
         {
         {
             _snowflakeId= snowflakeId;
             _snowflakeId= snowflakeId;
             _serviceBus = serviceBus;
             _serviceBus = serviceBus;
@@ -59,6 +61,7 @@ namespace TEAMModelOS.Controllers
             _azureRedis = azureRedis;
             _azureRedis = azureRedis;
             _azureStorage = azureStorage;
             _azureStorage = azureStorage;
             _clientFactory = clientFactory;
             _clientFactory = clientFactory;
+            _configuration = configuration;
         }
         }
 
 
 
 
@@ -93,26 +96,38 @@ namespace TEAMModelOS.Controllers
                     {
                     {
                         request.progress = "going";
                         request.progress = "going";
                     }
                     }
+                    var messageBlob = new ServiceBusMessage();
                     if (request.scope.Equals("school"))
                     if (request.scope.Equals("school"))
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"survey/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"survey/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"survey/{request.id}", name = $"{request.school}" }.ToJsonString());
                     }
                     }
                     else
                     else
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"survey/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"survey/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"survey/{request.id}", name = $"{request.creatorId}" }.ToJsonString());
                     }
                     }
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     request = await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
                     request = await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
                 }
                 }
                 else {
                 else {
                     var response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(request.id, new PartitionKey($"{request.code}"));
                     var response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(request.id, new PartitionKey($"{request.code}"));
+                    var messageBlob = new ServiceBusMessage();
                     if (request.scope.Equals("school"))
                     if (request.scope.Equals("school"))
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"survey/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"survey/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"survey/{request.id}", name = $"{request.school}" }.ToJsonString());
                     }
                     }
                     else
                     else
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"survey/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"survey/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"survey/{request.id}", name = $"{request.creatorId}" }.ToJsonString());
                     }
                     }
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     if (response.Status == 200)
                     if (response.Status == 200)
                     {
                     {
                         using var json = await JsonDocument.ParseAsync(response.ContentStream);
                         using var json = await JsonDocument.ParseAsync(response.ContentStream);
@@ -329,6 +344,13 @@ namespace TEAMModelOS.Controllers
                     survey = await client.GetContainer("TEAMModelOS", "Common").UpsertItemAsync(survey, new PartitionKey($"{survey.code}"));
                     survey = await client.GetContainer("TEAMModelOS", "Common").UpsertItemAsync(survey, new PartitionKey($"{survey.code}"));
                     _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Record:{survey.id}");
                     _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Record:{survey.id}");
                     _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Submit:{survey.id}");
                     _azureRedis.GetRedisClient(8).KeyDelete($"Survey:Submit:{survey.id}");
+                    //删除blob 相关资料
+                    await _azureStorage.GetBlobServiceClient().DelectBlobs(code.ToString(), $"survey/{id}");
+                    //通知删除信息
+                    var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "delete", root = $"survey/{id}", name = $"{code}" }.ToJsonString());
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     await _dingDing.SendBotMsg($"{_option.Location}-问卷调查【{survey.name}-{survey.id}】被删除", GroupNames.成都开发測試群組);
                     await _dingDing.SendBotMsg($"{_option.Location}-问卷调查【{survey.name}-{survey.id}】被删除", GroupNames.成都开发測試群組);
                     return Ok(new { flag });
                     return Ok(new { flag });
                 }
                 }

+ 26 - 1
TEAMModelOS/Controllers/Common/VoteController.cs

@@ -25,6 +25,8 @@ using TEAMModelOS.Filter;
 using StackExchange.Redis;
 using StackExchange.Redis;
 using TEAMModelOS.SDK.Models.Cosmos.Common.Inner;
 using TEAMModelOS.SDK.Models.Cosmos.Common.Inner;
 using TEAMModelOS.Services.Common;
 using TEAMModelOS.Services.Common;
+using Azure.Messaging.ServiceBus;
+using Microsoft.Extensions.Configuration;
 
 
 namespace TEAMModelOS.Controllers.Learn
 namespace TEAMModelOS.Controllers.Learn
 {
 {
@@ -45,9 +47,10 @@ namespace TEAMModelOS.Controllers.Learn
         private readonly DingDing _dingDing;
         private readonly DingDing _dingDing;
         private readonly Option _option;
         private readonly Option _option;
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureStorageFactory _azureStorage;
+        public IConfiguration _configuration { get; set; }
 
 
         public VoteController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option,
         public VoteController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option,
-            AzureRedisFactory azureRedis, AzureStorageFactory azureStorage)
+            AzureRedisFactory azureRedis, AzureStorageFactory azureStorage, IConfiguration configuration)
         {
         {
             _azureCosmos = azureCosmos;
             _azureCosmos = azureCosmos;
             _serviceBus = serviceBus;
             _serviceBus = serviceBus;
@@ -56,6 +59,7 @@ namespace TEAMModelOS.Controllers.Learn
             _option = option?.Value;
             _option = option?.Value;
             _azureRedis = azureRedis;
             _azureRedis = azureRedis;
             _azureStorage = azureStorage;
             _azureStorage = azureStorage;
+            _configuration = configuration;
         }
         }
         /// <summary>
         /// <summary>
         /// 新增 或 修改投票活动
         /// 新增 或 修改投票活动
@@ -91,27 +95,39 @@ namespace TEAMModelOS.Controllers.Learn
                     else { 
                     else { 
                         request.progress = "going"; 
                         request.progress = "going"; 
                     }
                     }
+                    var messageBlob = new ServiceBusMessage();
                     if (request.scope.Equals("school"))
                     if (request.scope.Equals("school"))
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"vote/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"vote/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"vote/{request.id}", name = $"{request.school}" }.ToJsonString());                       
                     }
                     }
                     else
                     else
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"vote/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"vote/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"vote/{request.id}", name = $"{request.creatorId}" }.ToJsonString());
                     }
                     }
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     request = await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
                     request = await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
                 }
                 }
                 else
                 else
                 {
                 {
                     var response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(request.id, new PartitionKey($"{request.code}"));
                     var response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(request.id, new PartitionKey($"{request.code}"));
+                    var messageBlob = new ServiceBusMessage();
                     if (request.scope.Equals("school"))
                     if (request.scope.Equals("school"))
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"vote/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"vote/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"vote/{request.id}", name = $"{request.school}" }.ToJsonString());
                     }
                     }
                     else
                     else
                     {
                     {
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"vote/{request.id}");
                         request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"vote/{request.id}");
+                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"vote/{request.id}", name = $"{request.creatorId}" }.ToJsonString());
                     }
                     }
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                     if (response.Status == 200)
                     if (response.Status == 200)
                     {
                     {
                         using var json = await JsonDocument.ParseAsync(response.ContentStream);
                         using var json = await JsonDocument.ParseAsync(response.ContentStream);
@@ -330,6 +346,15 @@ namespace TEAMModelOS.Controllers.Learn
                     _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Record:{vote.id}");
                     _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Record:{vote.id}");
                     _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Count:{vote.id}");
                     _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Count:{vote.id}");
                     await _dingDing.SendBotMsg($"{_option.Location}-投票活动【{vote.name}-{vote.id}】被删除", GroupNames.成都开发測試群組);
                     await _dingDing.SendBotMsg($"{_option.Location}-投票活动【{vote.name}-{vote.id}】被删除", GroupNames.成都开发測試群組);
+
+                    //删除blob 相关资料
+                    await _azureStorage.GetBlobServiceClient().DelectBlobs(code.ToString(), $"vote/{id}");
+                    //通知删除信息
+                    var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "delete", root = $"vote/{id}", name = $"{code}" }.ToJsonString());
+                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
+
                     return Ok(new { flag });
                     return Ok(new { flag });
                 }
                 }
                 else {
                 else {

+ 97 - 29
TEAMModelOS/Controllers/Core/BlobController.cs

@@ -20,7 +20,6 @@ using StackExchange.Redis;
 using Azure.Messaging.ServiceBus;
 using Azure.Messaging.ServiceBus;
 using static TEAMModelOS.SDK.DI.AzureStorageBlobExtensions;
 using static TEAMModelOS.SDK.DI.AzureStorageBlobExtensions;
 using System.Linq;
 using System.Linq;
-using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
 using TEAMModelOS.SDK.Context.Constant.Common;
 using TEAMModelOS.SDK.Context.Constant.Common;
 using HTEXLib.COMM.Helpers;
 using HTEXLib.COMM.Helpers;
@@ -516,20 +515,63 @@ namespace TEAMModelOS.Controllers.Core
              @"(?!((^(con)$)|^(con)\\..*|(^(prn)$)|^(prn)\\..*|(^(aux)$)|^(aux)\\..*|(^(nul)$)|^(nul)\\..*|(^(com)[1-9]$)|^(com)[1-9]\\..*|(^(lpt)[1-9]$)|^(lpt)[1-9]\\..*)|^\\s+|.*\\s$)(^[^\\\\\\:\\<\\>\\*\\?\\\\\\""\\\\|]{1,255}$)");
              @"(?!((^(con)$)|^(con)\\..*|(^(prn)$)|^(prn)\\..*|(^(aux)$)|^(aux)\\..*|(^(nul)$)|^(nul)\\..*|(^(com)[1-9]$)|^(com)[1-9]\\..*|(^(lpt)[1-9]$)|^(lpt)[1-9]\\..*)|^\\s+|.*\\s$)(^[^\\\\\\:\\<\\>\\*\\?\\\\\\""\\\\|]{1,255}$)");
         }
         }
 
 
+
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
         [HttpPost("bloblog-list")]
         [HttpPost("bloblog-list")]
         public async Task<ActionResult> BloblogList(JsonElement request) {
         public async Task<ActionResult> BloblogList(JsonElement request) {
-            request.TryGetProperty("name", out JsonElement name);
-            request.TryGetProperty("type", out JsonElement type);
-            var client = _azureCosmos.GetCosmosClient();
-            var queryslt = $"SELECT  value(c) FROM c WHERE c.type='{type}' c.code = 'Bloblog-{name}'";
             List<Bloblog> bloblogs = new List<Bloblog>();
             List<Bloblog> bloblogs = new List<Bloblog>();
-            await foreach (var item in client.GetDatabaseQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") })) {
-                bloblogs.Add(item);
+            try
+            {
+                request.TryGetProperty("name", out JsonElement name);
+                request.TryGetProperty("type", out JsonElement type);
+                request.TryGetProperty("scope", out JsonElement scope);
+                request.TryGetProperty("period", out JsonElement period);
+                var client = _azureCosmos.GetCosmosClient();
+                var queryslt = $"SELECT  value(c) FROM c WHERE c.type='{type}' and  c.period='{period}'";
+                if (scope.GetString().Equals("school"))
+                {
+                    await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                    {
+                        bloblogs.Add(item);
+                    }
+                }
+                else if (scope.GetString().Equals("teacher"))
+                {
+                    await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                    {
+                        bloblogs.Add(item);
+                    }
+                }
+                return Ok(new { bloblogs = bloblogs });
+            }
+            catch (Exception ex) {
+                return Ok(new { bloblogs = bloblogs });
             }
             }
-            return Ok(new { bloblogs = bloblogs });
         }
         }
 
 
-
+        /*
+         新增 编辑接口
+        {
+            "period": "",
+            "scope": "school",
+            "name": "hbcn",
+            "url": "video/xxx.png",
+            "opt": "add",
+        }
+         */
+        /*
+         {
+            "scope": "school",
+            "name": "hbcn",
+            "opt": "del",
+            "id": "19ccce98-c524-4ea7-aabc-887d1391e551"
+        }
+         */
         /// <summary>
         /// <summary>
         /// 
         /// 
         /// </summary>
         /// </summary>
@@ -546,12 +588,12 @@ namespace TEAMModelOS.Controllers.Core
                 request.TryGetProperty("url", out JsonElement jurl);
                 request.TryGetProperty("url", out JsonElement jurl);
                 request.TryGetProperty("opt", out JsonElement opt);
                 request.TryGetProperty("opt", out JsonElement opt);
                 request.TryGetProperty("id", out JsonElement id);
                 request.TryGetProperty("id", out JsonElement id);
-                request.TryGetProperty("type", out JsonElement type);
                 var url = System.Web.HttpUtility.UrlDecode(jurl.GetString(), Encoding.UTF8);
                 var url = System.Web.HttpUtility.UrlDecode(jurl.GetString(), Encoding.UTF8);
                 string[] uls = url.Split("/");
                 string[] uls = url.Split("/");
+                var u = "";
                 if (uls != null)
                 if (uls != null)
                 {
                 {
-                  var  u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
+                     u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
                     
                     
                 }
                 }
                 var size = await _azureStorage.GetBlobContainerClient($"{name}").GetBlobsSize(url);
                 var size = await _azureStorage.GetBlobContainerClient($"{name}").GetBlobsSize(url);
@@ -560,34 +602,60 @@ namespace TEAMModelOS.Controllers.Core
                 var client = _azureCosmos.GetCosmosClient();
                 var client = _azureCosmos.GetCosmosClient();
                 if (opt.GetString().Equals("add"))
                 if (opt.GetString().Equals("add"))
                 {
                 {
-                    var blob = new Bloblog
-                    {
-                        id = Guid.NewGuid().ToString(),
-                        pk = "Bloblog",
-                        code = $"Bloblog-{name}",
-                        url = url,
-                        time = now,
-                        size = size != null && size.HasValue ? size.Value : 0,
-                        period = $"{period}"
-                    };
-                    if (scope.GetString().Equals("school"))
-                    {
-                        await client.GetContainer("TEAMModelOS", "school").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                    //地址相同的,直接更新
+                    bool exsit = false;
+                    try {
+                        var queryslt = $"SELECT  value(c) FROM c WHERE c.url='{url}'";
+                        if (scope.GetString().Equals("school"))
+                        {
+                            await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                            {
+                                item.size = size != null && size.HasValue ? size.Value : 0;
+                                await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Bloblog>(item, item.id, new Azure.Cosmos.PartitionKey(item.code));
+                                exsit = true;
+                            }
+                        }
+                        else if (scope.GetString().Equals("teacher")) {
+                            await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Bloblog>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
+                            {
+                                item.size = size != null && size.HasValue ? size.Value : 0;
+                                await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Bloblog>(item, item.id, new Azure.Cosmos.PartitionKey(item.code));
+                                exsit = true;
+                            }
+                        }
+                    } catch (Exception ex) {
                     }
                     }
-                    else if (scope.GetString().Equals("teacher"))
-                    {
-                        await client.GetContainer("TEAMModelOS", "teacher").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                    if (!exsit) {
+                        var blob = new Bloblog
+                        {
+                            id = Guid.NewGuid().ToString(),
+                            pk = "Bloblog",
+                            code = $"Bloblog-{name}",
+                            url = url,
+                            time = now,
+                            size = size != null && size.HasValue ? size.Value : 0,
+                            period = $"{period}",
+                            type = u
+                        };
+                        if (scope.GetString().Equals("school"))
+                        {
+                            await client.GetContainer("TEAMModelOS", "School").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                        }
+                        else if (scope.GetString().Equals("teacher"))
+                        {
+                            await client.GetContainer("TEAMModelOS", "Teacher").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
+                        }
                     }
                     }
                 }
                 }
                 else if (opt.GetString().Equals("del"))
                 else if (opt.GetString().Equals("del"))
                 {
                 {
                     if (scope.GetString().Equals("school"))
                     if (scope.GetString().Equals("school"))
                     {
                     {
-                        await client.GetContainer("TEAMModelOS", "school").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
+                        await client.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
                     }
                     }
                     else if (scope.GetString().Equals("teacher"))
                     else if (scope.GetString().Equals("teacher"))
                     {
                     {
-                        await client.GetContainer("TEAMModelOS", "teacher").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
+                        await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemStreamAsync($"{id}", new Azure.Cosmos.PartitionKey($"Bloblog-{name}"));
                     }
                     }
                 }
                 }
                 else { return BadRequest(); }
                 else { return BadRequest(); }

+ 23 - 2
TEAMModelOS/Controllers/Item/ItemController.cs

@@ -17,6 +17,9 @@ using Microsoft.AspNetCore.Authentication;
 using System.Text;
 using System.Text;
 using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
 using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
 using Microsoft.Extensions.Options;
 using Microsoft.Extensions.Options;
+using Azure.Messaging.ServiceBus;
+using Microsoft.Extensions.Configuration;
+using TEAMModelOS.Services.Common;
 
 
 namespace TEAMModelOS.Controllers
 namespace TEAMModelOS.Controllers
 {
 {
@@ -33,13 +36,17 @@ namespace TEAMModelOS.Controllers
         private readonly DingDing _dingDing;
         private readonly DingDing _dingDing;
         private readonly Option _option;
         private readonly Option _option;
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureStorageFactory _azureStorage;
-        public ItemController(AzureCosmosFactory azureCosmos, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option, AzureStorageFactory azureStorage)
+        private readonly AzureServiceBusFactory _serviceBus;
+        public IConfiguration _configuration { get; set; }
+        public ItemController(AzureCosmosFactory azureCosmos, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option, AzureStorageFactory azureStorage, AzureServiceBusFactory serviceBus, IConfiguration configuration)
         {
         {
             _azureCosmos = azureCosmos;
             _azureCosmos = azureCosmos;
             _snowflakeId = snowflakeId;
             _snowflakeId = snowflakeId;
             _dingDing = dingDing;
             _dingDing = dingDing;
             _option = option?.Value;
             _option = option?.Value;
             _azureStorage = azureStorage;
             _azureStorage = azureStorage;
+            _serviceBus = serviceBus;
+            _configuration = configuration;
         }
         }
 
 
         [ProducesDefaultResponseType]
         [ProducesDefaultResponseType]
@@ -62,7 +69,10 @@ namespace TEAMModelOS.Controllers
                 ItemInfo itemInfo;
                 ItemInfo itemInfo;
                 itemInfo = item.ToObject<ItemInfo>();
                 itemInfo = item.ToObject<ItemInfo>();
                 itemInfo.size = await _azureStorage.GetBlobContainerClient(itemInfo.code).GetBlobsSize($"item/{itemInfo.id}");
                 itemInfo.size = await _azureStorage.GetBlobContainerClient(itemInfo.code).GetBlobsSize($"item/{itemInfo.id}");
-
+                var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"item/{itemInfo.id}", name = $"{itemInfo.code}" }.ToJsonString());
+                messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                 if (option.ToString().Equals("insert"))
                 if (option.ToString().Equals("insert"))
                 {
                 {
                     itemInfo.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                     itemInfo.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@@ -226,6 +236,13 @@ namespace TEAMModelOS.Controllers
                 if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
                 if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
                 if (!request.TryGetProperty("scope", out JsonElement scope)) return BadRequest();
                 if (!request.TryGetProperty("scope", out JsonElement scope)) return BadRequest();
                 var client = _azureCosmos.GetCosmosClient();
                 var client = _azureCosmos.GetCosmosClient();
+                //删除blob 相关资料
+                await _azureStorage.GetBlobServiceClient().DelectBlobs(code.ToString().Replace("Item-",""), $"item/{id}");
+                //通知删除信息
+                var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "delete", root = $"item/{id}", name = code.ToString().Replace("Item-", "") }.ToJsonString());
+                messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                 if (scope.ToString().Equals("school"))
                 if (scope.ToString().Equals("school"))
                 {
                 {
                     var response = await client.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"{code}"));
                     var response = await client.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync(id.ToString(), new PartitionKey($"{code}"));
@@ -696,7 +713,11 @@ namespace TEAMModelOS.Controllers
             }
             }
 
 
         }
         }
+        
     }
     }
+}
+
+
 
 
     public class TempItem
     public class TempItem
     {
     {

+ 18 - 1
TEAMModelOS/Controllers/Pager/PaperController.cs

@@ -15,6 +15,8 @@ using Azure.Cosmos;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
 using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
 using System.Text;
 using System.Text;
+using Azure.Messaging.ServiceBus;
+using Microsoft.Extensions.Configuration;
 
 
 namespace TEAMModelOS.Controllers
 namespace TEAMModelOS.Controllers
 {
 {
@@ -28,11 +30,15 @@ namespace TEAMModelOS.Controllers
         private readonly SnowflakeId _snowflakeId;
         private readonly SnowflakeId _snowflakeId;
         private readonly AzureCosmosFactory _azureCosmos;
         private readonly AzureCosmosFactory _azureCosmos;
         private readonly AzureStorageFactory _azureStorage;
         private readonly AzureStorageFactory _azureStorage;
-        public PaperController(AzureCosmosFactory azureCosmos, SnowflakeId snowflakeId,AzureStorageFactory azureStorage)
+        private readonly AzureServiceBusFactory _serviceBus;
+        public IConfiguration _configuration { get; set; }
+        public PaperController(AzureCosmosFactory azureCosmos, SnowflakeId snowflakeId,AzureStorageFactory azureStorage, AzureServiceBusFactory serviceBus, IConfiguration configuration)
         {
         {
             _azureCosmos = azureCosmos;
             _azureCosmos = azureCosmos;
             _snowflakeId = snowflakeId;
             _snowflakeId = snowflakeId;
             _azureStorage = azureStorage;
             _azureStorage = azureStorage;
+            _serviceBus = serviceBus;
+            _configuration = configuration;
         }
         }
         /// <summary>
         /// <summary>
         /// 删除
         /// 删除
@@ -50,6 +56,13 @@ namespace TEAMModelOS.Controllers
             //ResponseBuilder builder = ResponseBuilder.custom();
             //ResponseBuilder builder = ResponseBuilder.custom();
             Paper paper;
             Paper paper;
             var client = _azureCosmos.GetCosmosClient();
             var client = _azureCosmos.GetCosmosClient();
+            //删除blob 相关资料
+            await _azureStorage.GetBlobServiceClient().DelectBlobs(code.ToString().Replace("Paper-",""), $"paper/{id}");
+            //通知删除信息
+            var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "delete", root = $"paper/{id}", name = code.ToString().Replace("Paper-", "")}.ToJsonString());
+            messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+            var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+            await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
             if (scope.ToString().Equals("school"))
             if (scope.ToString().Equals("school"))
             {
             {
                 paper = await client.GetContainer("TEAMModelOS", "School").DeleteItemAsync<Paper>(id.ToString(), new PartitionKey($"{code}"));
                 paper = await client.GetContainer("TEAMModelOS", "School").DeleteItemAsync<Paper>(id.ToString(), new PartitionKey($"{code}"));
@@ -200,6 +213,10 @@ namespace TEAMModelOS.Controllers
             paper = papers.ToObject<Paper>();            
             paper = papers.ToObject<Paper>();            
             paper.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
             paper.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
             paper.size = await _azureStorage.GetBlobContainerClient(paper.code).GetBlobsSize($"paper/{paper.id}");
             paper.size = await _azureStorage.GetBlobContainerClient(paper.code).GetBlobsSize($"paper/{paper.id}");
+            var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"paper/{paper.id}", name = $"{paper.code}" }.ToJsonString());
+            messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+            var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+            await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
             paper.code = "Paper-" + paper.code;
             paper.code = "Paper-" + paper.code;
             if (option.ToString().Equals("insert"))
             if (option.ToString().Equals("insert"))
             {               
             {               

+ 127 - 0
TEAMModelOS/Services/Common/ItemService.cs

@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Models;
+
+namespace TEAMModelOS.Services.Common
+{
+    public static class ItemService
+    {
+        /// <summary>
+        /// 计算题目的条件数量变化
+        /// </summary>
+        /// <param name="newItem"></param>
+        /// <param name="odlItem"></param>
+        /// <param name="cond"></param>
+        public static void CountItemCond(ItemInfo newItem, ItemInfo odlItem, ItemCond cond) {
+            //检查两个对象是否是同一条记录
+            if (newItem != null && odlItem == null)
+            {
+                string newKey = $"{newItem.periodId}-{newItem.subjectId}";
+                List<string> grade = newItem.gradeIds;
+                UpdateItemCond(cond, true,newKey, grade, newItem.type, newItem.level, newItem.field.HasValue?newItem.field.Value:0);
+            }
+            else if (newItem != null && odlItem != null)
+            {
+                //更新时 需要保证两个题的 id code 一致
+                if (newItem.id == odlItem.id && newItem.code == odlItem.code)
+                {
+                    //先增加
+                    string newKey = $"{newItem.periodId}-{newItem.subjectId}";
+                    List<string> newGrade = newItem.gradeIds;
+                    UpdateItemCond(cond, true, newKey, newGrade, newItem.type, newItem.level, newItem.field.HasValue ? newItem.field.Value : 0);
+                    //后变更删除
+                    string oldKey = $"{odlItem.periodId}-{odlItem.subjectId}";
+                    List<string> oldGrade = odlItem.gradeIds;
+                    UpdateItemCond(cond, false, oldKey, oldGrade, odlItem.type, odlItem.level, odlItem.field.HasValue ? odlItem.field.Value : 0);
+                }
+            }
+            else if (newItem == null && odlItem != null)
+            {
+                string oldKey = $"{odlItem.periodId}-{odlItem.subjectId}";
+                List<string> oldGrade = odlItem.gradeIds;
+                UpdateItemCond(cond, false, oldKey, oldGrade, odlItem.type, odlItem.level, odlItem.field.HasValue ? odlItem.field.Value : 0);
+            }
+            else {
+               // throw new Exception();
+            }
+        }
+        /// <summary>
+        /// opt=false  减  true 增
+        /// </summary>
+        /// <param name="cond"></param>
+        /// <param name="opt"></param>
+        public static void UpdateItemCond(ItemCond cond, bool opt,string key,List<string> grade,string type,int level,int field)
+        {
+            int count = 0;
+            if (opt)
+            {
+                count = 1;
+            }
+            else
+            {
+                //未计入的则默认0
+                count = -1;
+            }
+            if (cond.conds.ContainsKey(key))
+            {
+                foreach (var x in grade)
+                {
+                    var exCondCount= cond.conds[key].Where(x => x.grade.Key.Equals(x)).FirstOrDefault();
+                    if (exCondCount != null)
+                    {
+                        exCondCount.grade= new KeyValuePair<string, int> (x, exCondCount.grade.Value + count);
+                        if (exCondCount.type.ContainsKey(type))
+                        {
+                            exCondCount.type[type] = exCondCount.type[type] + count;
+                        }
+                        else {
+                            exCondCount.type.Add(type, count);
+                        }
+                        if (exCondCount.level.ContainsKey(level))
+                        {
+                            exCondCount.level[level] = exCondCount.level[level] + count;
+                        }
+                        else
+                        {
+                            exCondCount.level.Add(level, count);
+                        }
+                        if (exCondCount.field.ContainsKey(field))
+                        {
+                            exCondCount.field[field] = exCondCount.field[field] + count;
+                        }
+                        else
+                        {
+                            exCondCount.field.Add(field, count);
+                        }
+                    }
+                    else {
+                        CondCount condCount = new CondCount
+                        {
+                            grade = new KeyValuePair<string, int>(x, count),
+                            type = new Dictionary<string, int> { { type, count } },
+                            field = new Dictionary<int, int> { { field, count } },
+                            level = new Dictionary<int, int> { { level, count } }
+                        };
+                        cond.conds[key].Add(condCount);
+                    }
+                }
+            }
+            else {
+                List<CondCount> conds = new List<CondCount>();
+                foreach (var x in grade) {
+                    CondCount condCount = new CondCount {
+                        grade = new KeyValuePair<string, int>(x, count),
+                        type = new Dictionary<string, int> { { type, count } },
+                        field = new Dictionary<int, int> { { field, count } },
+                        level= new Dictionary<int, int> { { level, count } }
+                    };
+                    conds.Add(condCount);
+                }
+                cond.conds.Add(key, conds);
+            }
+        }
+
+    }
+}