Преглед изворни кода

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

jeff пре 1 година
родитељ
комит
ba3f70bc86
18 измењених фајлова са 1063 додато и 61 уклоњено
  1. 57 16
      TEAMModelOS.SDK/Models/Cosmos/Common/Activity.cs
  2. 187 3
      TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html
  3. 19 3
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css
  4. 1 1
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js
  5. 28 0
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.json
  6. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf
  7. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff
  8. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2
  9. 48 0
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/ArtTestReport.less
  10. 439 0
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/ArtTestReport.vue
  11. 48 14
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/ArtView.vue
  12. 14 1
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperTest.less
  13. 6 2
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperTest.vue
  14. 7 2
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventList.vue
  15. 1 1
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/HomeView.vue
  16. 72 0
      TEAMModelOS/ClientApp/src/components/student-web/WrongQusetion/AnswerBox.vue
  17. 5 5
      TEAMModelOS/Controllers/Analysis/ArtAnalysisController.cs
  18. 131 13
      TEAMModelOS/Controllers/Common/ActivityController.cs

+ 57 - 16
TEAMModelOS.SDK/Models/Cosmos/Common/Activity.cs

@@ -300,7 +300,7 @@ namespace TEAMModelOS.SDK.Models
         /// <summary>
         /// 一个作品需要被评审多少次,作品被分配给多少个专家评审
         /// </summary>
-        public int taskInt { get; set; }
+        public int taskCount { get; set; }
         /// <summary>
         /// 统分规则 
         /// only        分配一次的情况直接获取分数=1
@@ -660,9 +660,9 @@ namespace TEAMModelOS.SDK.Models
         public string cipher { get; set; }
 
         /// <summary>
-        /// 是否是团队队长0,队员,1 队长
+        ///-1个人组的默认值, 是否是团队队长0,队员,1 队长
         /// </summary>
-        public int leader { get; set; }
+        public int leader { get; set; } = -1;
         public string teamName { get; set; }
         /// <summary>
         /// 表单填报信息
@@ -691,7 +691,10 @@ namespace TEAMModelOS.SDK.Models
         [Required(ErrorMessage = "Required")]
         public int type { get; set; }
         public string cipher { get; set; }
-        public int leader { get; set; }
+        /// <summary>
+        /// -1个人组的默认值, 是否是团队队长0,队员,1 队长
+        /// </summary>
+        public int leader { get; set; } = -1;
         public string teamName { get; set; }
         /// <summary>
         /// 表单填报信息
@@ -735,31 +738,69 @@ namespace TEAMModelOS.SDK.Models
     /// <summary>
     /// 专家分配的任务
     /// </summary>
-    public class ActivityExpertTask:CosmosEntity
+    public class ActivityExpertTask : CosmosEntity
     {
-        //id  教师id,
+        //id  专家id ,
         //code  ActivityExpertTask-活动id
-
-      
+        /// <summary>
+        /// 专家在优课评选中的评审任务
+        /// </summary>
+        public List<ExpertContestTask>  contestTasks{ get; set; }
     }
     /// <summary>
     /// 专家在优课评选模块的任务分配
     /// </summary>
-    public class ExpertContestTask { 
-        public List<ContestExpert> experts { get; set; }
+    public class ExpertContestTask {
+        /// <summary>
+        /// 作品id
+        /// </summary>
+        public string uploadId { get; set; }
+        /// <summary>
+        /// 组队口令
+        /// </summary>
+        public string cipher { get; set; }
+        /// <summary>
+        /// 参赛类型 0个人,1 团队
+        /// </summary>
+        public string type { get; set; }
 
-    
-    }
-    public class ContestExpert {
+        /// <summary>
+        /// -1 个人组的默认值, 0 队员,1队长
+        /// </summary>
+        public int leader { get; set; } = -1;
+        /// <summary>
+        /// 队员醍摩豆id ,包含队长,id,name(填报信息的name,真实姓名),code ,nickname 醍摩豆名称
+        /// </summary>
+        public List<IdNameCode> members { get; set; }
+        /// <summary>
+        /// 如果是个人组,则是个人的tmdid, 如果是团队组,则是队长的tmdid
+        /// </summary>
         public string tmdid { get; set; }
-        public string name { get; set; }
-        public string picture { get; set; }
+        //public string name { get; set; }
+        //public string picture { get; set; }
+        //public string nickname { get; set; }
+        /// <summary>
+        /// 评分状态
+        /// </summary>
         public int status { get; set; } = -1;
         public double score { get; set; } = -1;
-    }
 
+    }
+    
     public class ExpertDto : Expert
     {
+        /// <summary>
+        /// 任务数量
+        /// </summary>
+        public int taskCount {  get; set; }
+        /// <summary>
+        /// 完成数量
+        /// </summary>
+        public int completeCount {  get; set; }
+        /// <summary>
+        /// 
+        /// </summary>
+        public int teacherCount { get; set; }
 
     }
 

+ 187 - 3
TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html

@@ -54,6 +54,54 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe75a;</span>
+                <div class="name">点击</div>
+                <div class="code-name">&amp;#xe75a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7d3;</span>
+                <div class="name">点击</div>
+                <div class="code-name">&amp;#xe7d3;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe8bd;</span>
+                <div class="name">对勾小</div>
+                <div class="code-name">&amp;#xe8bd;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6b2;</span>
+                <div class="name">AK-YK_圆圈_fill</div>
+                <div class="code-name">&amp;#xe6b2;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6b1;</span>
+                <div class="name">所有直接归档记录</div>
+                <div class="code-name">&amp;#xe6b1;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6cf;</span>
+                <div class="name">互动</div>
+                <div class="code-name">&amp;#xe6cf;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ad;</span>
+                <div class="name">活动</div>
+                <div class="code-name">&amp;#xe6ad;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6b0;</span>
+                <div class="name">智慧课堂</div>
+                <div class="code-name">&amp;#xe6b0;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe6a9;</span>
                 <div class="name">提交失败</div>
@@ -1518,9 +1566,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1700017106516') format('woff2'),
-       url('iconfont.woff?t=1700017106516') format('woff'),
-       url('iconfont.ttf?t=1700017106516') format('truetype');
+  src: url('iconfont.woff2?t=1702624481449') format('woff2'),
+       url('iconfont.woff?t=1702624481449') format('woff'),
+       url('iconfont.ttf?t=1702624481449') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -1546,6 +1594,78 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont icon-click"></span>
+            <div class="name">
+              点击
+            </div>
+            <div class="code-name">.icon-click
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-dianji"></span>
+            <div class="name">
+              点击
+            </div>
+            <div class="code-name">.icon-dianji
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-duigouxiao"></span>
+            <div class="name">
+              对勾小
+            </div>
+            <div class="code-name">.icon-duigouxiao
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-yk_yuanquan_fill"></span>
+            <div class="name">
+              AK-YK_圆圈_fill
+            </div>
+            <div class="code-name">.icon-yk_yuanquan_fill
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-suoyouzhijieguidangjilu"></span>
+            <div class="name">
+              所有直接归档记录
+            </div>
+            <div class="code-name">.icon-suoyouzhijieguidangjilu
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-hudong2"></span>
+            <div class="name">
+              互动
+            </div>
+            <div class="code-name">.icon-hudong2
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-activity-line"></span>
+            <div class="name">
+              活动
+            </div>
+            <div class="code-name">.icon-activity-line
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-zhihuiketang"></span>
+            <div class="name">
+              智慧课堂
+            </div>
+            <div class="code-name">.icon-zhihuiketang
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont icon-tijiaoshibai"></span>
             <div class="name">
@@ -3742,6 +3862,70 @@
       <div class="content symbol">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-click"></use>
+                </svg>
+                <div class="name">点击</div>
+                <div class="code-name">#icon-click</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-dianji"></use>
+                </svg>
+                <div class="name">点击</div>
+                <div class="code-name">#icon-dianji</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-duigouxiao"></use>
+                </svg>
+                <div class="name">对勾小</div>
+                <div class="code-name">#icon-duigouxiao</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-yk_yuanquan_fill"></use>
+                </svg>
+                <div class="name">AK-YK_圆圈_fill</div>
+                <div class="code-name">#icon-yk_yuanquan_fill</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-suoyouzhijieguidangjilu"></use>
+                </svg>
+                <div class="name">所有直接归档记录</div>
+                <div class="code-name">#icon-suoyouzhijieguidangjilu</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-hudong2"></use>
+                </svg>
+                <div class="name">互动</div>
+                <div class="code-name">#icon-hudong2</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-activity-line"></use>
+                </svg>
+                <div class="name">活动</div>
+                <div class="code-name">#icon-activity-line</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-zhihuiketang"></use>
+                </svg>
+                <div class="name">智慧课堂</div>
+                <div class="code-name">#icon-zhihuiketang</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-tijiaoshibai"></use>

+ 19 - 3
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2000444 */
-  src: url('iconfont.woff2?t=1700123569341') format('woff2'),
-       url('iconfont.woff?t=1700123569341') format('woff'),
-       url('iconfont.ttf?t=1700123569341') format('truetype');
+  src: url('iconfont.woff2?t=1702624481449') format('woff2'),
+       url('iconfont.woff?t=1702624481449') format('woff'),
+       url('iconfont.ttf?t=1702624481449') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,22 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-click:before {
+  content: "\e75a";
+}
+
+.icon-dianji:before {
+  content: "\e7d3";
+}
+
+.icon-duigouxiao:before {
+  content: "\e8bd";
+}
+
+.icon-yk_yuanquan_fill:before {
+  content: "\e6b2";
+}
+
 .icon-suoyouzhijieguidangjilu:before {
   content: "\e6b1";
 }

Разлика између датотеке није приказан због своје велике величине
+ 1 - 1
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js


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

@@ -5,6 +5,34 @@
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "2205528",
+      "name": "点击",
+      "font_class": "click",
+      "unicode": "e75a",
+      "unicode_decimal": 59226
+    },
+    {
+      "icon_id": "15900229",
+      "name": "点击",
+      "font_class": "dianji",
+      "unicode": "e7d3",
+      "unicode_decimal": 59347
+    },
+    {
+      "icon_id": "2076374",
+      "name": "对勾小",
+      "font_class": "duigouxiao",
+      "unicode": "e8bd",
+      "unicode_decimal": 59581
+    },
+    {
+      "icon_id": "6141084",
+      "name": "AK-YK_圆圈_fill",
+      "font_class": "yk_yuanquan_fill",
+      "unicode": "e6b2",
+      "unicode_decimal": 59058
+    },
     {
       "icon_id": "11661880",
       "name": "所有直接归档记录",

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


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


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


+ 48 - 0
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/ArtTestReport.less

@@ -0,0 +1,48 @@
+.art-paper-content {
+    height: 100%;
+
+    .art-box {
+        margin: 10px 0;
+        height: 100%;
+
+        .subject-box {
+            margin-bottom: 50px;
+
+            .subject-title {
+                width: 100%;
+                background-color: #79b29c;
+                color: #fff;
+                font-size: 20px;
+                padding: 10px;
+                border-radius: 5px;
+                margin: 20px 0;
+            }
+
+            .paper-answer {
+                .scoreboard {
+                    width: 100%;
+                    background: rgb(240, 240, 240);
+                    border-radius: 8px;
+                    padding: 15px;
+
+                    .ivu-card {
+                        margin-bottom: 20px;
+                    }
+                }
+            }
+        }
+    }
+}
+
+.top-icon {
+    position: fixed;
+    right: 50px;
+    bottom: 20px;
+    width: 50px;
+    height: 50px;
+    line-height: 55px;
+    text-align: center;
+    background: #ccc;
+    border-radius: 5px;
+    cursor: pointer;
+}

+ 439 - 0
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/ArtTestReport.vue

@@ -0,0 +1,439 @@
+<template>
+    <div class="art-paper-content">
+        <Loading v-show="isLoad" bgColor="rgba(0, 0, 0, 0.3)"></Loading>
+        <vuescroll>
+            <EventBasicInfo :info="nowActive" />
+            <div class="art-box">
+                <div class="subject-box" v-for="item in artExam" :key="item.id">
+                    <p class="subject-title">{{ item.subject.name }}</p>
+                    <div class="paper-answer" v-if="item.exam[0]">
+                        <div class="title-rect-group">
+                            <h2 class="title-rect-name">
+                                {{ item.exam[0].quotaName }}
+                                <Icon custom="iconfont icon-yk_yuanquan_fill" size="25" color="#ccc" v-show="item.testState === 1" />
+                                <Icon custom="iconfont icon-duigouxiao" color="#1aba1a" size="25" v-show="item.testState > 1" />
+                            </h2>
+                        </div>
+                        <div class="scoreboard">
+                            <div v-if="!item.paperInfo.length" style="padding: 25px;">
+                                <Icon type="md-alert" size="18" color="orange" class="warm-icon" style="margin-right: 5px;" />
+                                <span class="warm-hint">
+                                    试卷存在问题,无法查看
+                                </span>
+                            </div>
+                            <template v-else>
+                                <div v-show="item.testState === 1" @click="showTest(item)" style="width: 100%; font-size: 25px; font-weight: 800; padding: 17px;cursor: pointer;">
+                                    <Icon custom="iconfont icon-dianji" size="30" color="#03966a" />
+                                    <span style="color: #03966a; margin-left: 10px;">{{ $t("studentWeb.exam.report.anwser") }}</span>
+                                </div>
+                                <h4 v-show="item.testState === 2 || item.testState === 3" style="padding: 25px;">
+                                    <Icon type="md-checkmark-circle-outline" class="warm-icon" color="green" />
+                                    {{ $t("studentWeb.exam.report.noRes") }}
+                                </h4>
+                            </template>
+                        </div>
+                        <template v-if="item.homework.length">
+                            <div v-for="(hw, index) in item.homework" :key="index">
+                                <ArtView ref="art" :hwInfo="hw" :hwIndex="index" @changeHwAnswer="changeHwAnswer" @openMask="openMask" />
+                            </div>
+                        </template>
+                    </div>
+                    <div v-else class="paper-answer">
+                        暂无科目相关内容
+                    </div>
+                </div>
+            </div>
+        </vuescroll>
+        <!-- <div class="top-icon" @click="gotoTop">
+            <Icon type="ios-arrow-up" size="30" color="#249e35" />
+        </div> -->
+		<div v-if="previewStatus" class="image-viewer">
+			<div style="width: fit-content; position: relative; margin: auto;">
+				<Icon type="md-close" class="close-icon" @click="previewStatus = false" />
+				<video v-if="previewFile.type == 'video'" id="previewVideo" :src="previewFile.url" width="870"
+					controls="controls" style="max-height: 800px;">
+					{{$t('teachContent.tips8')}}
+				</video>
+				<audio v-else-if="previewFile.type == 'audio'" controls>
+					<source :src="previewFile.url">
+					{{$t('teachContent.notAudio')}}
+				</audio>
+			</div>
+		</div>
+    </div>
+</template>
+
+<script>
+import { mapGetters, mapState } from 'vuex';
+import EventBasicInfo from "../../../EventBasicInfo";
+import ArtView from "./ArtView.vue";
+export default {
+    components: {
+        EventBasicInfo,
+        ArtView
+    },
+    data () {
+        return {
+            isLoad: false,
+            paperData: [], //试卷信息:学生作答记录、批注、知识点等
+            artExam: [],
+            artInfo: undefined, //艺术活动信息
+            musicInfo: undefined, //智音信息
+            stusInfo: [],// 艺术评测接口返回的stus
+            previewStatus: false,
+            previewFile: null,
+            testState: 0, //1:未作答  2:未评分  3:已评分
+        }
+    },
+    computed: {
+        ...mapState({
+            userInfo: state => state.userInfo
+        }),
+        ...mapGetters(['getItemTitle']),
+        nowActive() {
+            return this.getItemTitle
+        },
+    },
+    watch: {
+        nowActive: {
+            handler(n, o) {
+                this.artInfo = undefined
+                this.musicInfo = undefined
+                this.artExam = []
+                // 艺术评测需要先获取详细信息
+                this.getArtInfo()
+            }
+        },
+    },
+    async created () {
+        this.sasInfo = await this.getSas()
+        this.getArtInfo()
+    },
+    methods: {
+        getSas() {
+            return new Promise(async (r, j) => {
+                let code = this.getItemTitle.school
+                let sasInfo = await this.$tools.getBlobSas(code)
+                r(sasInfo)
+            })
+        },
+        async getArtInfo() {
+            this.isLoad = true
+            let params = {
+                id: this.getItemTitle.id,
+                code: this.getItemTitle.school
+            }
+            let nowTime = new Date()
+            this.$api.studentWeb.getArtInfo(params).then(async res => {
+                if(res.art) {
+                    this.artInfo = res.art
+                    this.musicInfo = res.music
+                    let subList = []
+                    res.art.settings.forEach(item => {
+                        item.task.forEach(task => {
+                            // 0:评分,1:评测,2:作业
+                            if(task.type) {
+                                let examInfo = undefined
+                                let quotaId = this.$parent.quotas.find(quotas => {return quotas.id === item.id})
+                                let files = undefined
+                                if(task.type === 1) {
+                                    examInfo = res.stus.find(stu => {
+                                        return stu.id === task.acId
+                                    })
+                                    if(examInfo) {
+                                        examInfo.isOrder = task.isOrder //0:默认,1:乱序
+                                        examInfo.quotaId = item.id
+                                        examInfo.quotaName = item.quotaname
+                                        examInfo.quotaId = item.id
+                                    }
+                                } else if(task.type === 2) {
+                                    files = res.works[0].results.find(work => {
+                                        return work.taskId === task.acId
+                                    })
+                                    if(files.files) {
+                                        files.files = files.files.map(item => {
+                                            item.url = item.url + '?' + this.sasInfo.sas
+                                            return item
+                                        })
+                                    }
+                                }
+                                // 按照科目来区分试卷
+                                let subIndex = subList.findIndex(sub => {
+                                    return task.subject === sub.subject.id
+                                })
+                                console.log(examInfo);
+                                // 没有该科目,就保存一次,再增加对应的试卷id:acId
+                                if(subIndex === -1) {
+                                    let examList = {
+                                        subject: {
+                                            id: task.subject,
+                                            name: ""
+                                        },
+                                        exam: [],
+                                        examInfo: [],
+                                        homework: [],
+                                        paperInfo: [],
+                                        testState: 0,
+                                    }
+                                    if(this.getItemTitle.ext) {
+                                        examList.subject.name = this.getItemTitle.ext.subjects.find(sub => {
+                                            return sub.id === task.subject
+                                        }).name
+                                    }
+                                    if(task.type === 1 && examInfo) {
+                                        examList.exam.push(examInfo)
+                                    } else if(task.type === 2 && item.id === 'quota_22') {
+                                        examList.homework.push({
+                                            id: task.acId,
+                                            quotaName: item.quotaname,
+                                            quotaId: item.id,
+                                            subject: task.subject,
+                                            workDesc: task.workDesc,
+                                            workEnd: this.$tools.formatTime(task.workEnd, 'yyyy-MM-dd hh:mm'),
+                                            answer: files.files ? files.files : undefined,
+                                            overTime: nowTime > task.workEnd,
+                                            musicId: this.musicInfo.questionId,
+                                            musicNm: this.musicInfo.questionName,
+                                            isAnswer: res.works[0].zyanswer.thirdAnswerId ? 1 : 0,
+                                        })
+                                    }
+                                    subList.push(examList)
+                                } else {
+                                    if(task.type === 1 && examInfo) {
+                                        subList[subIndex].exam.push(examInfo)
+                                    } else if(task.type === 2 && item.id === "quota_22" && task.subject != 'subject_painting') {
+                                        subList[subIndex].homework.push({
+                                            id: task.acId,
+                                            quotaName: item.quotaname,
+                                            quotaId: item.id,
+                                            subject: task.subject,
+                                            workDesc: task.workDesc,
+                                            workEnd: this.$tools.formatTime(task.workEnd, 'yyyy-MM-dd hh:mm'),
+                                            answer: files.files ? files.files : undefined,
+                                            overTime: nowTime > task.workEnd, //作业提交已经到期
+                                            musicId: this.musicInfo.questionId, //智音id
+                                            musicNm: this.musicInfo.questionName,
+                                            isAnswer: res.works[0].zyanswer.thirdAnswerId ? 1 : 0,
+                                        })
+                                    }
+                                }
+                            }
+                        })
+                    })
+                    this.stusInfo = res.stus
+                    
+                    this.artExam = subList
+                    let listss = await this.getSubPaper()
+                    console.log('试卷列表', listss);
+                    if(listss.length) {
+                        let infos = await this.getPaper()
+                    }
+                }
+            }).finally(() => {
+                this.isLoad = false
+            })
+        },
+        getSubPaper() {
+            return new Promise((resolve, reject) => {
+                let promiseArr = []
+                this.artExam.forEach(item => {
+                    promiseArr.push(new Promise((r, j) => {
+                        try {
+                            if(!item.exam[0]) {
+                                r(undefined)
+                            }
+                            let params = {
+                                cIds: this.artInfo.classes,
+                                code: this.artInfo.school,
+                                id: item.exam[0].id,
+                                scode: `Exam-${this.artInfo.school}`,
+                                studentId: this.userInfo.sub,
+                            }
+                            this.$api.studentWeb.FindStudentPaper(params).then(res => {
+                                if(res.status === 200) {
+                                    if(res.papers.length) {
+                                        let blob = this.stusInfo.find(stu => {
+                                            return stu.paper[0].subject === res.subjects[0].id
+                                        })
+                                        if(blob) {
+                                            let paperArt = res.papers.find(papers => {
+                                                return papers.blob === blob.paper[0].blob
+                                            })
+                                            paperArt.source = this.getItemTitle.source || null
+                                            paperArt.qamode = this.getItemTitle.qamode
+                                            item.examInfo.push(paperArt)
+                                        }
+                                        for (let i = 0; i < item.examInfo.length; i++) {
+                                            item.examInfo[i].acId = item.exam[i].id
+                                            item.examInfo[i].subject = res.subjects[i]
+                                            item.examInfo[i].allClass = res.claId
+                                            item.examInfo[i].isOrder = this.getItemTitle.type === 'Art' ? item.exam[0].isOrder : 0
+                                            if(res.stuAns.length) {
+                                                console.log(res.stuAns, i, res.stuAns[i]);
+                                                if (!res.stuAns[i].length) {
+                                                    item.examInfo[i].stuAns = []
+                                                    item.examInfo[i].stuScore = []
+                                                } else {
+                                                    item.examInfo[i].stuAns = res.stuAns[i]
+                                                    // 批注
+                                                    item.examInfo[i].mark = res.mark[i]
+                                                    item.examInfo[i].stuScore = res.stuScore[i]
+                                                }
+                                            } else {
+                                                item.examInfo[i].stuAns = []
+                                                item.examInfo[i].stuScore = []
+                                            }
+                                            item.examInfo[i].taskStatus = item.exam[0].taskStatus
+                                        }
+                                        r(true)
+                                    } else {
+                                        r(undefined)
+                                    }
+                                } else if(res.status === 500) {
+                                    // 统一给500,服务器内部错误
+                                    this.$Message.error(this.$t("studentWeb.exam.dataError"))
+                                    j(undefined)
+                                } else {
+                                    j(undefined)
+                                }
+                            })
+                        } catch (error) {
+                            j(error)
+                        }
+                    }))
+                })
+                Promise.allSettled(promiseArr).then(result => {
+                    resolve(result)
+                }).catch(e => {
+                    console.log(e);
+                    reject(e)
+                })
+            })
+        },
+        // 获取试卷信息
+        getPaper() {
+            return new Promise((resolve, reject) => {
+                let promiseArr = []
+                this.artExam.forEach(item => {
+                    promiseArr.push(new Promise(async (r, j) => {
+                        let codes = this.getItemTitle.scope == 'school' ? this.getItemTitle.school : this.getItemTitle.creatorId
+                        if(item.examInfo[0]?.blob) {
+                            let code = {
+                                scope: this.getItemTitle.scope,
+                                code: codes,
+                                blob: item.examInfo[0].blob,
+                                examId: codes
+                            }
+                            try {
+                                item.paperInfo.push(await this.getStuPaper(code))
+                            } catch (error) {
+                                this.$Message.error(this.$t("studentWeb.exam.examError"))
+                                this.isLoad = false
+                                r(undefined)
+                            }
+                            if(item.examInfo[0].stuScore != undefined) {
+                                if (item.examInfo[0].stuScore[0] == undefined) {
+                                    let score = []
+                                    for (let info of item.paperInfo[0].slides) {
+                                        if (info.type !== 'compose') {
+                                            score.push(-1)
+                                        }
+                                    }
+                                    item.examInfo[0].stuScore = score
+                                }
+                            }
+                            let k = 0
+                            for (let score of item.examInfo[0].stuScore) {
+                                if (score == -1) {   //有未打分
+                                    k++
+                                }
+                            }
+                            item.testState = k ? (item.examInfo[0].stuAns ? 1 : 2) : 3
+                            console.error('试卷信息', item.paperInfo)
+                            r(true)
+                        } else {
+                            r(undefined)
+                        }
+                    }))
+                })
+                Promise.allSettled(promiseArr).then(result => {
+                    console.log('Promise', result);
+                    resolve(result)
+                }).catch(e => {
+                    console.log(e);
+                    reject(e)
+                })
+            })
+        },
+        getStuPaper(code) {
+            return new Promise(async(r, j) => {
+                try {
+                    let a = await this.$evTools.getStuPaper(code)
+                    r(a)
+                } catch(e) {
+                    j(undefined)
+                }
+            })
+        },
+        showTest(art) {
+            let textArr = {
+                scope: this.getItemTitle.scope,
+                cntr: this.getItemTitle.scope === 'school' ? this.getItemTitle.school : this.getItemTitle.creatorId,
+                examId: this.getItemTitle.id,
+                subjectId: art.subject.id,
+                stuId: this.$store.state.userInfo.sub
+            }
+            this.$store.commit("setComposeData", textArr)
+            localStorage.setItem("composeData", encodeURIComponent(JSON.stringify(textArr)))
+            // 存储一个字段:该艺术评测是否要乱序作答
+            // getExamInfo 可能会有,就不用localStorage,如果用localStorage,要注意退出作答时清除之前的artExam
+            localStorage.setItem("artExam", encodeURIComponent(JSON.stringify(art.examInfo[0].isOrder)))
+            this.$router.push({
+                name: "examView/evaluation",
+            })
+            let ids = {
+                examId: this.getItemTitle.id,
+                subjectId: art.subject.id,
+            }
+            sessionStorage.setItem("ids", encodeURIComponent(JSON.stringify(ids)))
+            if (art.subject) {
+                this.$store.commit("ToggleLessonTestPopWithSubject", art.examInfo[0])
+                localStorage.setItem("subjectNow", encodeURIComponent(JSON.stringify(art.examInfo[0])))
+
+                let code = {
+                    scope: this.getItemTitle.scope,
+                    code: textArr.cntr,
+                    blob: art.examInfo[0].blob,
+                    examId: textArr.cntr
+                }
+                this.$store.commit("SetPaperInfo", art.paperInfo[0])
+                this.$store.commit("SetExamInfo", art.examInfo[0])
+                // 不在localStorage存题目内容,改为在作答页面重新获取
+                // PaperTest.vue:通过paperCoed重新获取题目信息
+                localStorage.setItem('paperCoed', encodeURIComponent(JSON.stringify(code)))
+                localStorage.setItem("examInfo", encodeURIComponent(JSON.stringify(art.examInfo[0])))
+            }
+        },
+        gotoTop() {
+            document.getElementsByClassName("art-box")[0].scrollIntoView()
+        },
+        changeHwAnswer(answer, index) {
+            let attach = [...answer.attachments]
+            attach = attach.map(item => {
+                item.url = item.url + '?' + this.sasInfo.sas
+                return item
+            })
+            this.artExam.homework[index].answer = attach
+            this.$forceUpdate()
+        },
+        openMask(item) {
+            this.previewFile = item
+            this.previewStatus = true
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+@import "./ArtTestReport.less";
+</style>

+ 48 - 14
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/ArtView.vue

@@ -1,34 +1,56 @@
 <template>
-  <div>
+  <div class="art-view">
     <template v-if="hwInfo">
       <!-- <div v-for="(hw, index) in hwList" :key="index"> -->
       <div class="title-rect-group">
-        <h2 class="title-rect-name">{{ hwInfo.quotaName }}</h2>
+        <h2 class="title-rect-name">
+          {{ hwInfo.quotaId === 'quota_22' ? '演唱测验' : hwInfo.quotaName }}
+          <Icon custom="iconfont icon-yk_yuanquan_fill" size="25" color="#ccc" v-show="!hwInfo.isAnswer" />
+          <Icon custom="iconfont icon-duigouxiao" color="#1aba1a" size="25" v-show="hwInfo.isAnswer" />
+        </h2>
       </div>
       <!-- 音乐、基本技能 => 智音答题 -->
       <template v-if="hwInfo.subject === 'subject_music' && hwInfo.quotaId === 'quota_22'">
         <p class="head-box">{{ $t('learnActivity.mark.quLabel') }}:{{ hwInfo.musicNm }}</p>
-        <!-- <p class="head-box">要求:{{ `xxxxx需要` }}</p> -->
         <p class="head-box">{{ $t("learnActivity.createEv.endTime") }}:{{ hwInfo.workEnd }}</p>
-        <p class="head-box">{{ $t("studentWeb.exam.answer2") }}:
-          <!-- <a :href="openUrl" target="_blank">前往作答</a> -->
-          <!-- <span @click="toSong" v-show="!finishSong">{{ $t("studentWeb.exam.report.anwser") }}</span> -->
+        <!-- <p class="head-box">{{ $t("studentWeb.exam.answer2") }}:
           <span v-if="hwInfo.overTime && !hwInfo.isAnswer">{{ $t("survey.finish") }}</span>
           <span v-else-if="hwInfo.isAnswer">
             <span>{{ $t("studentWeb.exam.finishOk") }}</span>
             <Button v-if="hwInfo.overTime" type="success" @click="zyModule('aqd')" class="detail">{{ $t('log.details') }}</Button>
           </span>
           <Button v-else type="success" @click="toSong">{{ $t("studentWeb.exam.report.anwser") }}</Button>
-          <!-- <Button v-else type="success">
-            <a :href="openUrl" target="_blank" style="color: #fff;">{{ $t("studentWeb.exam.report.anwser") }}</a>
-          </Button> -->
-        </p>
+        </p> -->
+        <div class="scoreboard">
+            <h4 v-if="hwInfo.overTime && !hwInfo.isAnswer" style="padding: 25px;">
+                <Icon type="md-alert" size="18" color="orange" class="warm-icon" style="margin-right: 5px;" />
+                <span class="warm-hint">
+                  {{ $t("survey.finish") }}
+                </span>
+            </h4>
+            <h4 v-else-if="!hwInfo.overTime && hwInfo.isAnswer" style="padding: 25px;">
+                <Icon type="md-checkmark-circle-outline" class="warm-icon" color="green" />
+                已完成作答
+            </h4>
+            <div v-else-if="hwInfo.overTime && hwInfo.isAnswer">
+                <h4 style="width: 100%; font-size: 25px; font-weight: 800; padding: 17px; cursor: pointer;" @click="zyModule('aqd')">
+                    <Icon custom="iconfont icon-dianji" size="30" color="#03966a" />
+                    <span style="color: #03966a;cursor: pointer; margin-left: 10px;">{{ $t('log.details') }}</span>
+                </h4>
+            </div>
+            <div v-else>
+                <div style="width: 100%; font-size: 25px; font-weight: 800; padding: 17px; cursor: pointer;" @click="toSong">
+                    <Icon custom="iconfont icon-dianji" size="30" color="#03966a" />
+                    <span style="color: #03966a; margin-left: 10px;">开始演唱</span>
+                </div>
+            </div>
+        </div>
         <!-- 结束后才能查看详情 -->
         <!-- <p class="head-box" v-if="hwInfo.overTime && hwInfo.isAnswer">
-                        不展示得分
-                        得分:{{ 'xxx' }}
-                        <Button type="success" @click="zyModule('aqd')">{{ $t('log.details') }}</Button>
-                    </p> -->
+            不展示得分
+            得分:{{ 'xxx' }}
+            <Button type="success" @click="zyModule('aqd')">{{ $t('log.details') }}</Button>
+        </p> -->
         <!-- <iframe :src="openUrl" allow='microphone;camera;' frameborder="0" width="100%" height="800"></iframe> -->
       </template>
       <template v-else>
@@ -637,6 +659,18 @@ export default {
     border-radius: 5px;
   }
 }
+.art-view {
+    .scoreboard {
+        width: 100%;
+        background: rgb(240, 240, 240);
+        border-radius: 8px;
+        padding: 15px;
+
+        .ivu-card {
+            margin-bottom: 20px;
+        }
+    }
+}
 </style>
 
 <style>

+ 14 - 1
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperTest.less

@@ -109,6 +109,18 @@
         color: #6d7278;
         z-index: 4;
     }
+    .artsubmitBtn {
+        outline: none;
+        min-width: 100px;
+        padding: 0 10px;
+        font-size: 14px;
+        cursor: pointer;
+        border: 1px solid #979797;
+        border-radius: 15px;
+        background: transparent;
+        color: #6d7278;
+        z-index: 4;
+    }
 
     .messageCardIcon {
         font-size: 40px;
@@ -376,7 +388,8 @@
     }
 
     .pageCtl2 .hintClick,
-    .submitBtn.hintClick {
+    .submitBtn.hintClick,
+    .artsubmitBtn.hintClick {
         background-color: #64ae16 !important;
         color: white;
         border: none;

+ 6 - 2
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperTest.vue

@@ -135,7 +135,7 @@
             </div> -->
             <div v-show="showExam.length">
                 <button v-if="closeTest && isWrong" class="submitBtn" @click="openWarmMessage(5)" :class="{ hintClick: hintHandon() }">{{ showEnd ? $t("studentWeb.exam.testpop.againPractice") : $t("studentWeb.exam.testpop.submitted1") }}</button>
-                <button v-if="closeTest && !isWrong" class="submitBtn" @click="openWarmMessage(2)" :class="{ hintClick: hintHandon() }">{{ $t("studentWeb.exam.testpop.submitted")}}</button>
+                <button v-if="closeTest && !isWrong && getItemTitle.type != 'Art'" class="submitBtn" @click="openWarmMessage(2)" :class="{ hintClick: hintHandon() }">{{ $t("studentWeb.exam.testpop.submitted")}}</button>
                 <button v-if="!closeTest" class="submitBtn" :class="{ hintClick: hintHandon()}">{{$t("studentWeb.exam.testpop.finish")}}</button>
             </div>
         </div>
@@ -280,7 +280,7 @@
                                         <div class="testbg">
                                             <div style="display:flex">
                                                 <span>{{ getQue(queNo).option[index].code }}. </span>
-                                                <div v-html="item.value" @click.stop="showImg($event)"></div>
+                                                <div v-html="item.value" @click.stop.native.prevent="showImg($event)"></div>
                                             </div>
                                             <div style="clear:both"></div>
                                         </div>
@@ -415,6 +415,9 @@
                         {{ $t("studentWeb.exam.report.noScore") }}
                     </span>
                 </div>
+                <div v-show="getItemTitle.type === 'Art'" style="width: 100%; margin-top: 10px;">
+                    <Button v-if="closeTest && !isWrong" long class="artsubmitBtn" :class="{ hintClick: hintHandon() }" @click="openWarmMessage(2)">{{ $t("studentWeb.exam.testpop.submitted")}}</Button>
+                </div>
             </i-col>
         </Row>
         <!-- 补救资源 -->
@@ -1067,6 +1070,7 @@
             },
             //放大图片
             showImg(e) {
+                console.log(e);
                 if (e.target.tagName == 'IMG') {
                     this.$hevueImgPreview(e.target.src)
                 }

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

@@ -364,9 +364,12 @@
                 <div v-if="nowActivity.type === 'Homework'" class="event-content">
                     <Homework />
                 </div>
-                <div v-if="nowActivity.type === 'Exam' || nowActivity.type === 'Art'" class="event-content">
+                <div v-if="nowActivity.type === 'Exam' || (nowActivity.type === 'Art' && nowActivity.progress === 'finish')" class="event-content">
                     <PaperView />
                 </div>
+                <div v-if="nowActivity.type === 'Art' && nowActivity.progress === 'going'" class="event-content">
+                    <ArtTestReport />
+                </div>
                 <div v-if="nowActivity.type === 'Vote'" class="event-content">
                     <Vote />
                 </div>
@@ -392,6 +395,7 @@ import Vote from "./EventContentTypeTemplate/Vote";
 import PaperView from "./EventContentTypeTemplate/PaperViewBox/PaperView.vue";
 import VoteHint from "./EventContentTypeTemplate/VoteHint";
 import QuesNaire from "./EventContentTypeTemplate/QuesNaire";
+import ArtTestReport from "./EventContentTypeTemplate/PaperViewBox/ArtTestReport.vue";
     export default {
         name: "EventList",
         components: {
@@ -400,7 +404,8 @@ import QuesNaire from "./EventContentTypeTemplate/QuesNaire";
             PaperView,
             Vote,
             VoteHint,
-            QuesNaire
+            QuesNaire,
+            ArtTestReport
         },
         props: {
             showType: {

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

@@ -215,7 +215,7 @@
                                     <div class="wrong-box">
                                         <div v-for="(item) in wrongSub" :key="item.id" class="wrong-text" @click="toWrongExam(item)">
                                             <p :style="{'color': item.type === 'subject' ? '#499c8d' : ''}">{{ item.name }}</p>
-                                            <p>{{ item.itsNum }}{{ $t('answerSheet.tip14') }}</p>
+                                            <p>{{ item.itsNum }} {{ $t('answerSheet.tip14') }}</p>
                                         </div>
                                     </div>
                                 </template>

+ 72 - 0
TEAMModelOS/ClientApp/src/components/student-web/WrongQusetion/AnswerBox.vue

@@ -110,6 +110,26 @@
                             <span class="answer-tip">【{{ $t('evaluation.explain') }}】</span>
                             <p v-html="quesInfo.explain"></p>
                         </div>
+                        <div class="answer-area" v-if="quesInfo.repair">
+                            <span class="answer-tip">【{{ $t('evaluation.newExercise.repair') }}】</span>
+                            <div v-if="quesInfo.repair.length != 0">
+                                <div v-for="(repairSource, normalIndex) in quesInfo.repair" :key="normalIndex" class="repair-link-wrap-item-box">
+                                    <div class="file-icon">
+                                        <img :src="$tools.getFileThum(repairSource.type, repairSource.name)"/>
+                                    </div>
+                                    <div class="file-info">
+                                        <p class="file-name">{{ repairSource.name }}</p>
+                                        <div>
+                                            <span @click.stop="onPreview(repairSource)" v-if="repairSource.type !== 'other'">{{ $t('ability.review.preview')}}</span>
+                                            <span @click.stop="onDownload(repairSource)" v-if="repairSource.type !== 'link'">{{ $t('ability.review.download')}}</span>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div v-else-if="!quesInfo.repair.length">
+                                {{ $t('studentWeb.exam.report.noSource') }}
+                            </div>
+                        </div>
                     </div>
                 </vuescroll>
             </div>
@@ -288,6 +308,25 @@
                 </p>
                 <Button type="success" long style="margin-top: 30px;" @click="startExe()">{{ $t('train.create.noLeader4') }}</Button>
             </Modal>
+            <!-- 补救资源 -->
+            <Modal v-model="previewStatus" footer-hide width="65%" @on-cancel="closePreview">
+                <p slot="header" 
+                    style="color:#f60;text-align:center">
+                    <Icon type="ios-information-circle"></Icon>
+                    <span>{{$t("studentWeb.exam.report.fileView")}}</span>
+                </p>
+                <div class="file-box" v-if="previewStatus">
+                    <video v-if="previewFile.type == 'video'" id="previewVideo" :src="previewFile.blobUrl" width="870"
+                        controls="controls" style="max-height: 800px;">
+                        {{$t('teachContent.tips8')}}
+                    </video>
+                    <audio v-else-if="previewFile.type == 'audio'" controls>
+                        <source :src="previewFile.blobUrl">
+                        {{$t('teachContent.notAudio')}}
+                    </audio>
+                    <span v-else>{{$t("studentWeb.exam.report.noReview")}}</span>
+                </div>
+            </Modal>
         </template>
     </div>
 </template>
@@ -348,6 +387,8 @@ export default {
             parentOpen: false, //主题干是否展开
             noInfoExe: 0,
             isNoInfo: false,
+            previewStatus: false, //展示补救资源
+            previewFile: {},
         }
     },
     computed: {
@@ -384,6 +425,11 @@ export default {
         courseNow() {
             return this.getNowCourse || JSON.parse(decodeURIComponent(localStorage.course, "utf-8"))
         },
+        getSuffix() {
+            return name => {
+                return name.substr(name.lastIndexOf(".") + 1)
+            }
+        },
     },
     watch: {
         nowIndex: {
@@ -446,6 +492,7 @@ export default {
                             examInfo.pid = info.items.pid
                             examInfo.showAns = []
                             examInfo.qamode = info.qamode
+                            examInfo.repair = examInfo.repair || []
                             examInfo.attachments = info.attachments.map(attach => {
                                 return sas.url + "/" + code + '/exam/' + newList[i].activityId + '/paper/' + courId + '/' + attach + '?' + sas.sas
                             })
@@ -877,6 +924,31 @@ export default {
             this.isNoInfo = false
             this.timeAlarmTWO = setInterval(this.timer, 1000)
         },
+        /* 预览 */
+        async onPreview(item) {
+            let url = item.blobUrl
+            if (this.getSuffix(item.name) === 'pdf') {
+                window.open('/web/viewer.html?file=' + encodeURIComponent(url));
+            } else if(item.type === 'doc') {
+                window.open('https://view.officeapps.live.com/op/view.aspx?src=' + escape(url));
+            } else if(item.type === 'image') {
+                this.$hevueImgPreview(url)
+            } else if(item.type === 'link') {
+                window.open(/^(http:|https:)/i.test(url) ? url : "http://" + url)
+            } else {
+                this.previewFile = item
+                this.previewStatus = true
+            }
+        },
+        /* 下载 */
+        async onDownload(item) {
+            this.$tools.doDownloadByUrl(item.blobUrl, item.name)
+        },
+        // 关闭Modal
+        closePreview() {
+            this.previewStatus = false
+            this.previewFile = {}
+        },
     },
     destroyed () {
         document.onkeyup = null

+ 5 - 5
TEAMModelOS/Controllers/Analysis/ArtAnalysisController.cs

@@ -383,11 +383,11 @@ namespace TEAMModelOS.Controllers.Analysis
                 {
                     id = x.gradeId,
                     name = perMore[int.Parse(x.gradeId)],
-                    score = x.list.Count() > 0 ? Math.Round((double)(x.list.Sum() / x.list.Count()), 2) : 0,
-                    max = x.list.Count() > 0 ? x.list.Max(s => Math.Abs((double)s)) : 0,
-                    min = x.list.Count() > 0 ? x.list.Min(s => Math.Abs((double)s)) : 0,
-                    excellent = x.list.Count() > 0 ? Math.Round(x.list.Where(s => s >= 80).Count() * 1.0 / x.list.Count(), 2) : 0,
-                    pass = x.list.Count() > 0 ? Math.Round(x.list.Where(s => s >= 60).Count() * 1.0 / x.list.Count(), 2) : 0
+                    score = x.list.Any() ? Math.Round((double)(x.list.Sum() / x.list.Count()), 2) : 0,
+                    max = x.list.Any() ? x.list.Max(s => Math.Abs((double)s)) : 0,
+                    min = x.list.Any() ? x.list.Min(s => Math.Abs((double)s)) : 0,
+                    excellent = x.list.Any() ? Math.Round(x.list.Where(s => s >= 80).Count() * 1.0 / x.list.Count(), 2) : 0,
+                    pass = x.list.Any() ? Math.Round(x.list.Where(s => s >= 60).Count() * 1.0 / x.list.Count(), 2) : 0
                 });
                 //获奖次数
                 List<ArtAttachment> artAttachments = new();

+ 131 - 13
TEAMModelOS/Controllers/Common/ActivityController.cs

@@ -44,6 +44,7 @@ using System.ComponentModel.DataAnnotations;
 using Flurl.Http.Configuration;
 using Newtonsoft.Json;
 using IHttpClientFactory = System.Net.Http.IHttpClientFactory;
+using Microsoft.AspNetCore.Components.Routing;
 
 namespace TEAMModelOS.Controllers
 {
@@ -1588,14 +1589,11 @@ namespace TEAMModelOS.Controllers
             var access_token = await CoreTokenExtensions.CreateAccessToken(core_clientID, core_clientSecret, location);
             return Ok(new { auth_token = new { access_token= access_token .AccessToken, token_type= access_token.TokenType, expires_in= access_token.ExpiresOn}, code =200,token =serializeToken, schools= teacherInfo.teacher.schools.Where(z=>z.status.Equals("join"))});
         }
-        /// <summary>
-        /// 
-        /// </summary>
-        /// <param name="request"></param>
-        /// <returns></returns>
+
         [ProducesDefaultResponseType]
         [HttpPost("get-website")]
-        public async Task<IActionResult> GetWebsite(JsonElement request) {
+        public async Task<IActionResult> GetWebsite(JsonElement request)
+        {
             if (!request.TryGetProperty("route", out JsonElement _route)) return BadRequest();
             string sql = $"select value c from c where c.route='{_route}'";
             var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).GetList<ActivityWebsiteDto>(sql, "ActivityWebsite");
@@ -1614,7 +1612,7 @@ namespace TEAMModelOS.Controllers
             if (website!= null)
             {
                 List<ActivityWebsiteDto> websites = new List<ActivityWebsiteDto>();
-                if (website.route.Equals("teammodel") )
+                if (website.route.Equals("teammodel"))
                 {
                     string sqlAll = $"select value c from c where IS_DEFINED(c.route) = true  and c.route<> null  and  c.route<>'' ";
                     var resultAll = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).GetList<ActivityWebsiteDto>(sqlAll, "ActivityWebsite");
@@ -1626,7 +1624,7 @@ namespace TEAMModelOS.Controllers
                     {
                         cnt="02944f32-f534-3397-ea56-e6f1fc6c3714";
                     }
-                    z.sas= _azureStorage.GetBlobContainerSAS(cnt ,BlobContainerSasPermissions.Read).sas;
+                    z.sas= _azureStorage.GetBlobContainerSAS(cnt, BlobContainerSasPermissions.Read).sas;
                 });
                 string cnt = website.id;
                 if (website.id.Equals("teammodel"))
@@ -1634,10 +1632,117 @@ namespace TEAMModelOS.Controllers
                     cnt="02944f32-f534-3397-ea56-e6f1fc6c3714";
                 }
                 website.sas= _azureStorage.GetBlobContainerSAS(cnt, BlobContainerSasPermissions.Read).sas;
-                return Ok(new { code = 200, website ,websites });
+                return Ok(new { code = 200, website, websites });
+            }
+            else
+            {
+                return Ok(new { code = 2, msg = "未匹配分站" });
+            }
+        }
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("read-activity")]
+        public async Task<IActionResult> ReadActivity(JsonElement request)
+        {
+            if (!request.TryGetProperty("activityId", out JsonElement _activityId)) return BadRequest();
+            Azure.Response response = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).ReadItemStreamAsync(_activityId.GetString(), new PartitionKey("Activity"));
+            if (response.Status==200)
+            {
+                ActivityDto activity = JsonDocument.Parse(response.Content).RootElement.ToObject<ActivityDto>();
+                if (activity.publish!=1  && activity.publish!=2)
+                {
+                    return Ok(new { code = 1, msg = "活动未发布!" });
+                }
+                Contest contest = null;
+                ReviewRuleTree reviewRule = null;
+                Azure.Response responseContest = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).ReadItemStreamAsync(_activityId.GetString(), new PartitionKey("Contest"));
+                if (responseContest.Status==200)
+                {
+                    contest= JsonDocument.Parse(responseContest.Content).RootElement.ToObject<Contest>();
+                    if (contest.modules.Contains("review"))
+                    {
+                        Azure.Response reviewRuleResponse = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemStreamAsync(_activityId.GetString(), new PartitionKey("ReviewRule-disposable"));
+                        if (reviewRuleResponse.Status==200)
+                        {
+                            ReviewRule reviewRuleDB = JsonDocument.Parse(reviewRuleResponse.Content).RootElement.ToObject<ReviewRule>();
+                            var tree = ActivityService.ListToTree(reviewRuleDB.configs);
+                            reviewRule=new ReviewRuleTree
+                            {
+                                id=reviewRuleDB.id,
+                                name= reviewRuleDB.name,
+                                owner= reviewRuleDB.owner,
+                                sourceName= reviewRuleDB.sourceName,
+                                trees=tree,
+                                desc=reviewRuleDB.desc
+                            };
+                        }
+                    }
+                }
+                Training training = null;
+                TEAMModelOS.SDK.Models.Research research = null;
+                var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(activity.owner, BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List);
+                activity.sas=blob_sas;
+                return Ok(new {code =200, activity,contest,reviewRule, training,  research});
+            }
+            else
+            {
+                return Ok(new { code = 2, msg = "活动不存在" });
+            }
+        }
+            
+        /// <summary>
+        /// 获取活动的站点
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("get-activity-website")]
+        public async Task<IActionResult> GetActivityWebsite(JsonElement request) {
+            if (!request.TryGetProperty("activityId", out JsonElement _activityId)) return BadRequest();
+            Azure.Response response = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Common).ReadItemStreamAsync(_activityId.GetString(), new PartitionKey("Activity"));
+            if (response.Status==200)
+            {
+                Activity activity= JsonDocument.Parse(response.Content).RootElement.ToObject<Activity>();
+
+                if (activity.publish!=1  && activity.publish!=2) {
+                    return Ok(new { code = 1, msg = "活动未发布!" });
+                }
+                string routeId = "";
+                if (activity.scope.Equals("public"))
+                {
+                    routeId="";
+                }
+                else {
+                    routeId=activity.owner;
+                }
+                Azure.Response responseActivityWebsite = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemStreamAsync(routeId, new PartitionKey("ActivityWebsite"));
+                ActivityWebsiteDto website = null;
+                if (responseActivityWebsite.Status==200)
+                {
+                    website=JsonDocument.Parse(responseActivityWebsite.Content).RootElement.ToObject<ActivityWebsiteDto>();
+                }
+               
+                if (website!= null)
+                {
+                    string cnt = website.id;
+                    if (website.id.Equals("teammodel"))
+                    {
+                        cnt="02944f32-f534-3397-ea56-e6f1fc6c3714";
+                    }
+                    website.sas= _azureStorage.GetBlobContainerSAS(cnt, BlobContainerSasPermissions.Read).sas;
+                    return Ok(new { code = 200, website });
+                }
+                else
+                {
+                    return Ok(new { code = 2, msg = "未匹配分站" });
+                }
             }
             else {
-                return Ok(new { code = 2, msg = "未匹配分站" }) ;
+                return Ok(new { code = 3, msg = "活动不存在" });
             }
         }
 
@@ -1827,7 +1932,14 @@ namespace TEAMModelOS.Controllers
                                         enroll= JsonDocument.Parse(responseActivityEnroll.Content).RootElement.ToObject<ActivityEnroll>();
                                         if (enroll.contest!=null  && enroll.contest.leader==1)
                                         {
-                                            return Ok(new { code = 3, msg = "请移交队长后再退出参赛!" });
+                                            // 如果没有其他队员,则可以直接退出
+                                            //检查是否还有其他成员
+                                            string cipherSQL = $"select value  c.id  from c where c.contest!=null  and c.activityId='{_activityId.GetString()}' and c.contest.type=1 and  c.contest.cipher='{enroll.contest.cipher}' and c.id<>'{tmdid}' ";
+                                            var cipherResult = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<string>(cipherSQL, $"ActivityEnroll-{_activityId}");
+                                            if (cipherResult.list.Count>0)
+                                            {
+                                                return Ok(new { code = 3, msg = "队伍中还有其他参赛队员,请移交队长后或移除队员后再退出参赛!" });
+                                            }
                                         }
                                         Azure.Response responseActivityEnrollDel = await client.GetContainer(Constant.TEAMModelOS, Constant.Teacher).DeleteItemStreamAsync(enroll.id, new PartitionKey($"ActivityEnroll-{_activityId.GetString()}"));
                                         if (responseActivityEnrollDel.Status==201)
@@ -2063,7 +2175,7 @@ namespace TEAMModelOS.Controllers
                                                                 //队长
                                                                 if (cipherResult.list.IsNotEmpty())
                                                                 {
-                                                                    //组队口令已被其他团队使用
+                                                                    //组队口令已被其他团队使用,此处已顺便处理成员篡位的逻辑
                                                                     var otherTeam = cipherResult.list.FindAll(z => !z.id.Equals(tmdid));
                                                                     if (otherTeam.IsNotEmpty())
                                                                     {
@@ -2093,6 +2205,11 @@ namespace TEAMModelOS.Controllers
                                                             }
                                                             else
                                                             {
+                                                                //队长跑路
+                                                                if (enroll!=null  && enroll.contest!=null && enroll.contest.leader==1 && enrollData.leader==0) {
+                                                                    return Ok(new { code = 17, msg = "你是队长,不能变更身份!" });
+                                                                }
+
                                                                 //队员
                                                                 if (cipherResult.list.IsNotEmpty())
                                                                 {
@@ -2391,7 +2508,8 @@ namespace TEAMModelOS.Controllers
                                                             foreach (var z in cipherResult.list)
                                                             {
                                                                 z.upload=enroll.upload;
-                                                                z.upload.uploadId=Guid.NewGuid().ToString();
+                                                                //队长统一上传的作品不需要单独生成作品id.
+                                                                // z.upload.uploadId=Guid.NewGuid().ToString();
                                                                 await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).UpsertItemAsync(z, new PartitionKey(z.code));
                                                             }
                                                         }