Browse Source

Merge branch 'develop6.0-tmd' of http://52.130.252.100:10000/TEAMMODEL/TEAMModelOS into develop6.0-tmd

Li 3 years ago
parent
commit
e80ce9bbeb
29 changed files with 830 additions and 347 deletions
  1. 10 7
      TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs
  2. 1 0
      TEAMModelOS.SDK/Models/Cosmos/Common/GroupList.cs
  3. 1 0
      TEAMModelOS.SDK/Models/Cosmos/Common/Inner/CourseChange.cs
  4. 10 0
      TEAMModelOS.SDK/Models/Service/ActivityService.cs
  5. 92 62
      TEAMModelOS/ClientApp/src/api/http.js
  6. 4 1
      TEAMModelOS/ClientApp/src/api/learnActivity.js
  7. 8 0
      TEAMModelOS/ClientApp/src/api/studentWeb.js
  8. 118 3
      TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html
  9. 23 3
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css
  10. 1 1
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js
  11. 35 0
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.json
  12. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf
  13. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff
  14. BIN
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2
  15. 26 0
      TEAMModelOS/ClientApp/src/assets/student-web/component_styles/course-content.less
  16. 44 40
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseListView.vue
  17. 162 200
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseView/SchoolReport.vue
  18. 2 0
      TEAMModelOS/ClientApp/src/locale/lang/en-US/cusMgt.js
  19. 2 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-CN/cusMgt.js
  20. 2 0
      TEAMModelOS/ClientApp/src/locale/lang/zh-TW/cusMgt.js
  21. 6 1
      TEAMModelOS/ClientApp/src/view/jyzx/application.vue
  22. 3 2
      TEAMModelOS/ClientApp/src/view/jyzx/newHomePage.vue
  23. 0 4
      TEAMModelOS/ClientApp/src/view/newcourse/CoursePlan.vue
  24. 13 5
      TEAMModelOS/ClientApp/src/view/newcourse/MyCourse.vue
  25. 4 1
      TEAMModelOS/ClientApp/src/view/newcourse/NewCusMgt.vue
  26. 148 4
      TEAMModelOS/ClientApp/src/view/newcourse/tabs/Grade.vue
  27. 16 6
      TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/EvaluationList/TotalIndex.vue
  28. 63 1
      TEAMModelOS/Controllers/Both/GroupListController.cs
  29. 36 6
      TEAMModelOS/Controllers/Both/LessonRecordController.cs

+ 10 - 7
TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs

@@ -509,7 +509,13 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                 //名单变动修改学生投票关联信息
                 await ActivityService.FixActivity(client, _dingDing, groupChange, "Vote");
                 //名单变动修改学生评测关联信息
-                await ActivityService.FixActivity(client, _dingDing, groupChange, "Exam");              
+                await ActivityService.FixActivity(client, _dingDing, groupChange, "Exam");
+                //名单变动修改学生研修关联信息
+                await ActivityService.FixActivity(client, _dingDing, groupChange, "Study");
+                //名单变动修改学生简易评测关联信息
+                await ActivityService.FixActivity(client, _dingDing, groupChange, "ExamLite");
+                //名单变动修改学生作业活动信息
+                await ActivityService.FixActivity(client, _dingDing, groupChange, "Homework");
                 //TODO学习活动
                 //await FixActivity(client, stuListChange, "Learn");
 
@@ -517,12 +523,9 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                 {
                     //课程名单变动修改学生课程关联信息
                     await ActivityService.FixStuCourse(client, _dingDing, groupChange);
-                    //名单变动修改学生研修关联信息
-                    await ActivityService.FixActivity(client, _dingDing, groupChange, "Study");
-                    //名单变动修改学生简易评测关联信息
-                    await ActivityService.FixActivity(client, _dingDing, groupChange, "ExamLite");
-                    //名单变动修改学生作业活动信息
-                    await ActivityService.FixActivity(client, _dingDing, groupChange, "Homework");
+                    //名单变动修改课例关联信息
+                    await ActivityService.FixLessonRecord(client, _dingDing, groupChange);
+
                 }
             }
             catch (Exception ex)

+ 1 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/GroupList.cs

@@ -292,5 +292,6 @@ namespace TEAMModelOS.SDK.Models
         public string name { get; set; }
         public string code { get; set; }
         public string picture { get; set; }
+        public string nickname { get; set; }
     }
 }

+ 1 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/Inner/CourseChange.cs

@@ -28,5 +28,6 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
         public string scope { get; set; }
         public string school { get; set; }
         public string creatorId { get; set; }
+        public string status { get; set; }
     }
 }

+ 10 - 0
TEAMModelOS.SDK/Models/Service/ActivityService.cs

@@ -540,6 +540,16 @@ namespace TEAMModelOS.SDK
                 }
             }
         }
+
+        public  static async  Task FixLessonRecord(CosmosClient client, DingDing dingDing, GroupChange groupChange)
+        {
+            if (groupChange.status.Equals("delete")) {
+                string sql = "select value(c) from c where (c.status<>404 or IS_DEFINED(c.status) = false ) and  array_length(c.groupIds)>0  ";
+               
+            }
+          
+        }
+
         public static async Task<string> SaveStuActivity(CosmosClient client, DingDing _dingDing, List<StuActivity> stuActivities, List<StuActivity> tmdActivities, List<StuActivity> tchActivities)
         {
             try

+ 92 - 62
TEAMModelOS/ClientApp/src/api/http.js

@@ -3,6 +3,7 @@ import { Message } from 'view-design'
 import router from '@/router/index'
 import config from '@/store/module/config'
 import { app } from '@/boot-app.js'
+import { reject } from 'lodash'
 // 不需要携带access_token
 const NO_ACCESS_API = [
     '/core/system-info',
@@ -35,18 +36,29 @@ const NO_WARNING = [
     'http://localhost:59995/'
 ]
 
-let refreshing = false
-// let isProduction = process.env.NODE_ENV == 'development' ? false : true
-let isProduction = true //暂时本地和线上都要进行验证,如果验证流程没有问题则用上面的设置线上验证
+let requestStack = [] //token刷新中,挂载的请求
+//记录token刷新中发起的请求
+function track(prms) {
+    requestStack.push(prms)
+}
+//token 刷新完成触发挂起的请求继续请求
+function trigger() {
+    let index = requestStack.length
+    while (index > 0) {
+        requestStack[index - 1]()
+        requestStack.pop()
+        index--
+    }
+}
+
+let refreshing = false //是否刷新token中
 axios.defaults.timeout = 30000 // 设置超时时长
 axios.defaults.baseURL = ''
 
 // http request 拦截器
 axios.interceptors.request.use(
-    async (config) => {
-        /***
-         * 登录及登录之前的API 不需要任何检查
-         */
+    (config) => {
+        // 1. 登录及登录之前的API 不需要任何检查
         let isNeedAccess = true
         for (let apiUrl of NO_ACCESS_API) {
             if (config.url.includes(apiUrl)) {
@@ -56,69 +68,43 @@ axios.interceptors.request.use(
         }
         if (!isNeedAccess) return config
 
-        /**
-         * 登录之后,获取auth_token之前
-         * 1、检查用户操作时间 
-         * 2、检查access_token及有效时间
-         */
+        // 2. 登录之后,获取auth_token之前:检查用户操作时间;检查access_token及有效时间
+        // 检查操作时间
         let webEndTime = localStorage.getItem('webEndTime')
         let time_now = new Date().getTime()
         if (webEndTime && time_now > webEndTime) {
-            console.log('长时间未操作,重新登录', config)
             loginOut()
             sessionStorage.setItem('loginOut', '长时间未操作,重新登录')
             return
         }
+        // 检查是否有access_token
         let access_token = localStorage.getItem('access_token')
-        //有access_token才刷新
-        if (access_token) {
-            //检查access_token有效时间
-            let flag = checkToken()
-            if (flag && !refreshing) {
-                console.log('token即将过期,刷新token')
-                try {
-                    refreshing = true
-                    let freshRes = await refreshToken()
-                    localStorage.setItem("access_token", freshRes.data.access_token)
-                    localStorage.setItem("expires_in", freshRes.data.expires_in)
-                    refreshing = false
-                } catch (e) {
-                    refreshing = false
-                    loginOut()
-                    sessionStorage.setItem('loginOut', 'token刷新失败,退出重新登录')
-                }
-
-            }
-            config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('access_token')
-        }
-        //通过验证,设置对应参数
-        config.headers['Content-Type'] = 'application/json'
-        config.headers['lang'] = localStorage.getItem('local')
-        let isNeedAuth = true
-        for (let apiUrl of NO_AUTH_API) {
-            if (config.url.includes(apiUrl)) {
-                console.log('auth_token无效', config)
-                isNeedAuth = false
-                break
-            }
-        }
-        if (!isNeedAuth) return config
-
-        /**
-         * 获取auth_token之后的API
-         * 1、检查auth_token是否存在
-         */
-        let identity = sessionStorage.getItem('identity')
-        let auth_token = identity === 'student' ? localStorage.getItem('stu_auth_token') : localStorage.getItem('auth_token')
-        if (!auth_token) {
-            console.log('auth_token失败', config)
+        if (!access_token) {
             loginOut()
-            sessionStorage.setItem('loginOut', 'localStorage没有auth_token:auth_token失败,重新登录')
+            sessionStorage.setItem('loginOut', 'token无效')
             return
         }
-        //通过验证设置对应参数
-        config.headers['X-Auth-AuthToken'] = auth_token
-        return config
+        //检查是否快到期
+        let isExpired = checkToken()
+        // let isExpired = true
+
+        //token 未过期
+        if (!isExpired) {
+            return handleHeader(config)
+        }
+        //刷新token
+        let handleRefresh = new Promise((resolve, reject) => {
+            track(() => {
+                handleHeader(config)
+                resolve(config)
+            })
+        })
+        sessionStorage.setItem('apiCount', requestStack.length)
+        if (!refreshing) {
+            refreshing = true
+            refreshToken()
+        }
+        return handleRefresh
     },
     error => {
         return Promise.reject(error)
@@ -151,7 +137,7 @@ axios.interceptors.response.use(
         } else if (error.response && error.response.status === 401) {
             localStorage.clear()
             sessionStorage.setItem('loginOut', error.config.url + ':API401,重新登录')
-            sessionStorage.setItem('APIInfo', JSON.stringify(error)) 
+            sessionStorage.setItem('APIInfo', JSON.stringify(error))
             window.location.href = window.location.origin + '/login'
             Message.error(app.$t('http.error401'))
         } else if (error.response.status === 500) {
@@ -165,6 +151,35 @@ axios.interceptors.response.use(
     }
 )
 
+function handleHeader(config) {
+    config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('access_token')
+    config.headers['Content-Type'] = 'application/json'
+    config.headers['lang'] = localStorage.getItem('local')
+
+    let isNeedAuth = true
+    for (let apiUrl of NO_AUTH_API) {
+        if (config.url.includes(apiUrl)) {
+            console.log('auth_token无效', config)
+            isNeedAuth = false
+            break
+        }
+    }
+    if (!isNeedAuth) return config
+
+    // 3. 获取auth_token之后的API:检查auth_token是否存在
+    let identity = sessionStorage.getItem('identity')
+    let auth_token = identity === 'student' ? localStorage.getItem('stu_auth_token') : localStorage.getItem('auth_token')
+    if (!auth_token) {
+        console.log('auth_token失败', config)
+        loginOut()
+        sessionStorage.setItem('loginOut', 'localStorage没有auth_token:auth_token失败,重新登录')
+        return
+    }
+    //通过验证设置对应参数
+    config.headers['X-Auth-AuthToken'] = auth_token
+    return config
+}
+
 /**检测token是否快过期 */
 function checkToken() {
     if (!localStorage.getItem('expires_in')) return false
@@ -185,13 +200,28 @@ function checkToken() {
 
 /**刷新token */
 function refreshToken() {
+    refreshing = true
     let srvAdr = localStorage.getItem('srvAdr')
     let url = config.state[srvAdr].coreAPIUrl
-    return axios.post(url + '/oauth2/token', {
+    axios.post(url + '/oauth2/token', {
         "grant_type": "refresh_token",
         'client_id': config.state[srvAdr].clientID,
         "access_token": localStorage.getItem('access_token')
-    })
+    }).then(
+        res => {
+            localStorage.setItem("access_token", res.data.access_token)
+            localStorage.setItem("expires_in", res.data.expires_in)
+            // token刷新完成,触发挂载的API
+            trigger()
+            refreshing = false
+        },
+        err => {
+            refreshing = false
+            requestStack = []
+            loginOut()
+            sessionStorage.setItem('loginOut', 'token刷新失败,退出重新登录')
+        }
+    )
 }
 
 // 超时退出重新登录

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

@@ -344,10 +344,13 @@ export default {
     findMarkProgress: function (data) {
         return post('/common/exam/analysis-scoring', data)
     },
-	
 	/* 修改活动信息 */
 	updateAcInfo: function (data) {
 	    return post('/common/update-end-time', data)
 	},
+    /* 查询班级历次评测总分数据 */
+	getGradeInfo: function (data) {
+	    return post('/common/exam/score', data)
+	},
 
 }

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

@@ -323,4 +323,12 @@ export default {
     getTeacherName: function(data) {
         return post("/tmduser/init/get-tmd-info", data)
     },
+    // 获取课程的成绩表
+    getClassScore: function(data) {
+        return post("/common/exam/score", data)
+    },
+    // 修改名单中的昵称
+    setNickName: function(data) {
+        return post("/grouplist/upsert-group-member", data)
+    },
 }

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

@@ -54,6 +54,36 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe67d;</span>
+                <div class="name">发布信息</div>
+                <div class="code-name">&amp;#xe67d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe67e;</span>
+                <div class="name">我的笔记</div>
+                <div class="code-name">&amp;#xe67e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe67f;</span>
+                <div class="name">笔试</div>
+                <div class="code-name">&amp;#xe67f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe71f;</span>
+                <div class="name">物品-书笔</div>
+                <div class="code-name">&amp;#xe71f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe67c;</span>
+                <div class="name">有料笔记</div>
+                <div class="code-name">&amp;#xe67c;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe67a;</span>
                 <div class="name">课堂互动行为</div>
@@ -1206,9 +1236,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1648442676494') format('woff2'),
-       url('iconfont.woff?t=1648442676494') format('woff'),
-       url('iconfont.ttf?t=1648442676494') format('truetype');
+  src: url('iconfont.woff2?t=1650510580419') format('woff2'),
+       url('iconfont.woff?t=1650510580419') format('woff'),
+       url('iconfont.ttf?t=1650510580419') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -1234,6 +1264,51 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont icon-fabuxinxi"></span>
+            <div class="name">
+              发布信息
+            </div>
+            <div class="code-name">.icon-fabuxinxi
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-wodebiji"></span>
+            <div class="name">
+              我的笔记
+            </div>
+            <div class="code-name">.icon-wodebiji
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-bishi"></span>
+            <div class="name">
+              笔试
+            </div>
+            <div class="code-name">.icon-bishi
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-wp-sb"></span>
+            <div class="name">
+              物品-书笔
+            </div>
+            <div class="code-name">.icon-wp-sb
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-mynote"></span>
+            <div class="name">
+              有料笔记
+            </div>
+            <div class="code-name">.icon-mynote
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont icon-ketanghudonghangwei"></span>
             <div class="name">
@@ -2962,6 +3037,46 @@
       <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-fabuxinxi"></use>
+                </svg>
+                <div class="name">发布信息</div>
+                <div class="code-name">#icon-fabuxinxi</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-wodebiji"></use>
+                </svg>
+                <div class="name">我的笔记</div>
+                <div class="code-name">#icon-wodebiji</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-bishi"></use>
+                </svg>
+                <div class="name">笔试</div>
+                <div class="code-name">#icon-bishi</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-wp-sb"></use>
+                </svg>
+                <div class="name">物品-书笔</div>
+                <div class="code-name">#icon-wp-sb</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-mynote"></use>
+                </svg>
+                <div class="name">有料笔记</div>
+                <div class="code-name">#icon-mynote</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-ketanghudonghangwei"></use>

+ 23 - 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=1648442676494') format('woff2'),
-       url('iconfont.woff?t=1648442676494') format('woff'),
-       url('iconfont.ttf?t=1648442676494') format('truetype');
+  src: url('iconfont.woff2?t=1650510580419') format('woff2'),
+       url('iconfont.woff?t=1650510580419') format('woff'),
+       url('iconfont.ttf?t=1650510580419') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,26 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-fabuxinxi:before {
+  content: "\e67d";
+}
+
+.icon-wodebiji:before {
+  content: "\e67e";
+}
+
+.icon-bishi:before {
+  content: "\e67f";
+}
+
+.icon-wp-sb:before {
+  content: "\e71f";
+}
+
+.icon-mynote:before {
+  content: "\e67c";
+}
+
 .icon-ketanghudonghangwei:before {
   content: "\e67a";
 }

File diff suppressed because it is too large
+ 1 - 1
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js


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

@@ -5,6 +5,41 @@
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "525987",
+      "name": "发布信息",
+      "font_class": "fabuxinxi",
+      "unicode": "e67d",
+      "unicode_decimal": 59005
+    },
+    {
+      "icon_id": "1876350",
+      "name": "我的笔记",
+      "font_class": "wodebiji",
+      "unicode": "e67e",
+      "unicode_decimal": 59006
+    },
+    {
+      "icon_id": "1929143",
+      "name": "笔试",
+      "font_class": "bishi",
+      "unicode": "e67f",
+      "unicode_decimal": 59007
+    },
+    {
+      "icon_id": "6536038",
+      "name": "物品-书笔",
+      "font_class": "wp-sb",
+      "unicode": "e71f",
+      "unicode_decimal": 59167
+    },
+    {
+      "icon_id": "1870133",
+      "name": "有料笔记",
+      "font_class": "mynote",
+      "unicode": "e67c",
+      "unicode_decimal": 59004
+    },
     {
       "icon_id": "6917844",
       "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


+ 26 - 0
TEAMModelOS/ClientApp/src/assets/student-web/component_styles/course-content.less

@@ -145,6 +145,32 @@
             font-size: 16px;
         }
     }
+
+    .mates-head {
+        margin-bottom: 10px;
+        display: flex;
+        justify-content: space-between;
+
+        .change-name-icon {
+            // display: none;
+            opacity: 0;
+            margin-left: 10px;
+            cursor: pointer;
+        }
+
+        .nick-name {
+            margin-right: 50px;
+        }
+
+        &:hover {
+            .change-name-icon {
+                // display: inline-block;
+                opacity: 1;
+            }
+        }
+
+        
+    }
 }
 .courseContentEn {
     width: 50% !important ;

+ 44 - 40
TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseListView.vue

@@ -189,16 +189,28 @@
                     <ActivityView :needParam="needParam" />
                 </TabPane> -->
                 <!-- 成绩清单 -->
-                <!-- <TabPane :label="$t('studentWeb.courseContent.scoreList')" name="tab4">
-                    <SchoolReport :classInfo="courseNow" />
-                </TabPane> -->
+                <TabPane :label="$t('studentWeb.courseContent.scoreList')" name="tab4">
+                    <SchoolReport :classInfo="courseNow" ref="schoolReport" />
+                </TabPane>
                 <!-- 同学名单 -->
                 <TabPane :label="$t('studentWeb.courseContent.classmates') + '(' + stuTotal + ')'" name="tab3">
-                    <div style="margin-bottom: 10px;" v-if="classList.length">
-                        {{ $t("studentWeb.exam.studentScore.class") }}:
-                        <Select v-model="courseIndex" style="width:200px">
-                            <Option v-for="(item, index) in classList" :value="index" :key="index">{{ item.name }}</Option>
-                        </Select>
+                    <div class="mates-head" v-if="classList.length">
+                        <div>
+                            {{ $t("studentWeb.exam.studentScore.class") }}:
+                            <Select v-model="courseIndex" style="width:200px">
+                                <Option v-for="(item, index) in classList" :value="index" :key="index">{{ item.name }}</Option>
+                            </Select>
+                        </div>
+                        <!-- <div v-if="classList[courseIndex].type === 'teach'" class="nick-name">
+                            我的昵称:
+                            <span v-show="!isShowInput">
+                                {{ nickName.nickname ? nickName.nickname : '未设置' }}
+                            </span>
+                            <span class="change-name" v-show="isShowInput">
+                                <Input v-model="nickName.nickname" style="width: 150px" />
+                            </span>
+                            <Icon type="md-create" class="change-name-icon" @click="isShowInput = true" />
+                        </div> -->
                     </div>
                     <div v-if="isChangeGroupView == false">
                         <!-- <vuescroll> -->
@@ -514,6 +526,10 @@ export default {
                     title: vm.$t("studentWeb.courseContent.name"),
                     key: "name",
                 },
+                /* {
+                    title: vm.$t("studentWeb.courseContent.name"),
+                    key: "nickname",
+                }, */
                 {
                     title: vm.$t("studentWeb.courseContent.accountType"),
                     key: "type",
@@ -549,6 +565,8 @@ export default {
             stuTotal: 0, //学生总人数
             recordNum: 0, //课堂记录总数
             classList: [],
+            nickName: undefined, //昵称
+            isShowInput: false,
         };
     },
 
@@ -594,6 +612,7 @@ export default {
                     let arrlist = arr.map((item) => {
                         if(item.id === this.$store.state.userInfo.sub) {
                             item.name = `${item.name}(${this.$t("studentWeb.courseContent.me")})`
+                            this.nickName = item
                         }
                         if(item.type === 2) {
                             item.type = this.$t("studentWeb.courseContent.stuAccount")
@@ -602,6 +621,7 @@ export default {
                         }
                         return item
                     })
+                    this.nickName.scope = res.groups[0].scope
                     this.stuList.push(arrlist)
                     /* this.stuList = arr.filter((item) => {
                         return item.type === 2
@@ -889,6 +909,7 @@ export default {
                 this.$store.commit("ToggleSidebar", false);
             }
             this.showInfo = true
+            this.$refs['schoolReport'].getReportList(this.courseNow)
         },
         changeView(type) {
             this.showTable = true
@@ -1026,6 +1047,21 @@ export default {
         uploadNote(info) {
             this.$tools.doDownloadByUrl(info.eNote, info.name)
         },
+        changeNickName() {
+            let param = {
+                opt: "up-nickname",
+                scope: this.nickName.scope,
+                id: this.nickName.classId,
+                code: this.nickName.code,
+                type: "teach",
+                members: [{
+                    id: this.nickName.id,
+                    type: this.nickName.type,
+                    nickname: this.nickName.nickname,
+                }]
+            }
+            this.$api.studentWeb.setNickName(param).then(res => {})
+        },
     },
 
     created() {
@@ -1069,38 +1105,6 @@ export default {
                 } */
             },
         },
-        /* fixList: {
-            deep: true,
-            handler(n, o) {
-                if(n.length) {
-                    n.forEach(item => {
-                        let num = this.showFixList.findIndex(show => {
-                            return show.id === item.id && show.teaId === item.teaId
-                        })
-                        if(num === -1) {
-                            let obj = {...item}
-                            obj.show = false
-                            obj.time = item.timeId ? [{
-                                timeId: item.timeId,
-                                classTime: item.classTime
-                            }] : []
-                            this.showFixList.push(obj)
-                        } else {
-                            let numChild = this.showFixList[num].time.findIndex(showChild => {
-                                return showChild.timeId === item.timeId
-                            })
-                            if(numChild === -1 && item.timeId) {
-                                let objChild = {
-                                    timeId: item.timeId,
-                                    classTime: item.classTime
-                                }
-                                this.showFixList[num].time.push(objChild)
-                            }
-                        }
-                    })
-                }
-            }
-        }, */
     },
     /* beforeRouteLeave(to, from, next) {
         this.$store.commit("ChangeItemName", null)

+ 162 - 200
TEAMModelOS/ClientApp/src/components/student-web/HomeView/CourseView/SchoolReport.vue

@@ -1,25 +1,25 @@
 <template>
-    <div>
+    <div class="achievement-report">
         <div class="report-head student-check">
             <div class="filter-type">
                 <div>
                     <span class="type-name">{{ $t('studentWeb.baseInfo.examMode') }}:</span>
-                    <RadioGroup v-model="sourceType" type="button" button-style="solid">
+                    <RadioGroup v-model="filterType.sourceType" type="button" button-style="solid">
                         <Radio v-for="(item, index) in sourceTypeList" :key="index" :label="item.type">{{ item.name }}</Radio>
                     </RadioGroup>
                 </div>
-                <div>
+                <!-- <div>
                     <span class="type-name">{{ $t('studentWeb.baseInfo.examStatus') }}:</span>
                     <RadioGroup v-model="examType" type="button" button-style="solid">
                         <Radio v-for="(item, index) in examTypeList" :key="index + 1" :label="item.type">{{ item.name }}</Radio>
                     </RadioGroup>
-                </div>
-                <!-- <div>
-                    <span class="type-name">{{ $t('studentWeb.wrongTopic.subject') }}:</span>
-                    <RadioGroup v-model="ownerType" type="button" button-style="solid">
+                </div> -->
+                <div>
+                    <span class="type-name">{{ $t('studentWeb.baseInfo.examStatus') }}:</span>
+                    <RadioGroup v-model="filterType.ownerType" type="button" button-style="solid">
                         <Radio v-for="(item, index) in ownerTypeList" :key="index" :label="item.type">{{ item.name }}</Radio>
                     </RadioGroup>
-                </div> -->
+                </div>
             </div>
             <p class="base-info">
                 <span v-if="classInfo.teaName">
@@ -36,18 +36,19 @@
                 </span>
             </p>
         </div>
-        <Table :columns="schoolRepCol" :data="showSchoolRep" row-key="id">
+        <!-- <Table :columns="schoolRepCol" :data="showSchoolRep" :span-method="handleSpan"> -->
+        <Table :columns="schoolRepCol" :data="showSchoolRep" height="550">
             <template slot-scope="{ row }" slot="tag">
                 <!-- <p>{{ row.name }}</p> -->
-                <span class="tag-style">{{ row.tag.owner }}</span>
-                <span class="tag-style">{{ row.tag.source }}</span>
-                <span class="tag-style">{{ row.tag.type }}</span>
+                <span class="tag-style">{{ row.tag.owner === 'school' ? $t('studentWeb.public.school') : $t('studentWeb.public.private')}}</span>
+                <span class="tag-style" v-if="row.tag.source">{{ row.tag.source }}</span>
+                <span class="tag-style" v-if="row.tag.type">{{ row.tag.type }}</span>
                 <!-- <span class="tag-style">{{ row.tag.class }}</span> -->
-                <span class="tag-style">{{ row.tag.list }}</span>
+                <!-- <span class="tag-style">{{ row.tag.list }}</span> -->
             </template>
-            <template slot-scope="{ row }" slot="score">
+            <!-- <template slot-scope="{ row }" slot="score">
                 <span v-for="(item, index) in row.score" :key="index" style="margin-right: 10px;">{{ item }}</span>
-            </template>
+            </template> -->
         </Table>
     </div>
 </template>
@@ -55,6 +56,8 @@
 <script>
 export default {
     name: "",
+    components: {
+    },
     props: {
         classInfo: {
             type: Object,
@@ -66,188 +69,88 @@ export default {
     data () {
         return {
             schoolRepCol: [
+                /* {
+                    width: 50,
+                    type: 'expand',
+                }, */
                 {
                     title: '评测名称',
                     // slot: "name",
-                    key: 'name',
-                    tree: true,
+                    key: 'examName',
+                    align: "center"
+                    // tree: true,
                     // width: 500,
                 },
                 /* {
-                    title: '课程',
-                    key: 'class'
+                    title: '创建人',
+                    key: 'creator',
+                    align: "center"
+                },
+                {
+                    title: '学科',
+                    key: 'class',
+                    align: "center"
                 }, */
                 {
-                    title: '创建人',
-                    key: 'creator'
+                    title: '分数',
+                    // slot: "score",
+                    key: "score",
+                    align: "center"
                 },
                 {
-                    title: '标签',
+                    title: '类型',
                     slot: "tag",
                     width: 500,
+                    align: "center"
                 },
                 /* {
                     title: '名单',
                     key: 'list'
                 }, */
-                {
-                    title: '分数',
-                    slot: "score",
-                },
                 {
                     title: '时间',
-                    key: 'time'
+                    key: 'time',
+                    align: "center"
                 }
             ],
-            schoolRep: [
-                {
-                    id: "1111111111111111",
-                    name: '评测名称11111111',
-                    class: '课程111',
-                    tag: {
-                        owner: "校级",
-                        source: "课中评测",
-                        type: "正规考",
-                        class: '课程111',
-                        list: '名单1111111111',
-                    },
-                    creator: '罗老师',
-                    list: '名单1111111111',
-                    score: [67, 85],
-                    time: '2021-06-27',
-                    children: [
-                        {
-                            id: "1111111111111112",
-                            name: '英语',
-                            class: '课程111',
-                            tag: {
-                                owner: "校级",
-                                source: "课中评测",
-                                type: "正规考",
-                                class: '课程111',
-                                list: '名单1111111111',
-                            },
-                            creator: '罗老师',
-                            list: '名单1111111111',
-                            score: [67],
-                            time: '2021-06-27',
-                        },
-                        {
-                            id: "1111111111111113",
-                            name: '数学',
-                            class: '课程111',
-                            tag: {
-                                owner: "校级",
-                                source: "课中评测",
-                                type: "正规考",
-                                class: '课程111',
-                                list: '名单1111111111',
-                            },
-                            creator: '罗老师',
-                            list: '名单1111111111',
-                            score: [85],
-                            time: '2021-06-27',
-                        },
-                    ]
-                },
-                {
-                    id: "2222222222222222",
-                    name: '评测名称222222222',
-                    class: '课程3222222222',
-                    tag: {
-                        owner: "个人",
-                        source: "线上评测",
-                        type: "模拟考",
-                        class: '课程3222222222',
-                        list: '名单22222',
-                    },
-                    creator: '罗老师',
-                    list: '名单22222',
-                    score: [97],
-                    time: '2022-01-14',
-                },
-                {
-                    id: "33333333333",
-                    name: '评测名称3333333',
-                    class: '课程3333333',
-                    tag: {
-                        owner: "个人",
-                        source: "线上评测",
-                        type: "模拟考",
-                        class: '课程3333333',
-                        list: '名单43333333333',
-                    },
-                    creator: '罗老师',
-                    list: '名单43333333333',
-                    score: [10],
-                    time: '2022-01-14',
-                },
-                {
-                    id: "4444444444",
-                    name: '评测名称4444',
-                    class: '课程444',
-                    tag: {
-                        owner: "个人",
-                        source: "线上评测",
-                        type: "模拟考",
-                        class: '课程444',
-                        list: '名单4444',
-                    },
-                    creator: '罗老师',
-                    list: '名单4444',
-                    score: [61],
-                    time: '2022-01-14',
-                },
-                {
-                    id: "555555555555",
-                    name: '评测名称55',
-                    class: '课程5555555555555555555',
-                    tag: {
-                        owner: "个人",
-                        source: "线上评测",
-                        type: "模拟考",
-                        class: '课程5555555555555555555',
-                        list: '名单5555555',
-                    },
-                    creator: '罗老师',
-                    list: '名单5555555',
-                    score: [27],
-                    time: '2022-01-14',
-                },
-            ],
-            nowRow: undefined,
+            schoolRep: [],
+            showSchoolRep: [],
+            filterType: {
+                ownerType: "all",
+                sourceType: "all",
+            },
             ownerType: "all",
-            sourceType: -1,
+            sourceType: "all",
             examType: "all",
             ownerTypeList: [
                 {
                     type: "all",
                     name: this.$t('studentWeb.type.all')
                 },
-                {
-                    type: "private",
-                    name: this.$t('studentWeb.public.private')
-                },
                 {
                     type: "school",
                     name: this.$t('studentWeb.public.school')
                 },
+                {
+                    type: "teacher",
+                    name: this.$t('studentWeb.public.private')
+                },
             ],
             sourceTypeList: [
                 {
-                    type: -1,
+                    type: "all",
                     name: this.$t('studentWeb.type.all')
                 },
                 {
-                    type: 0,
+                    type: "0",
                     name: this.$t("studentWeb.exam.source.evMode1")
                 },
                 {
-                    type: 1,
+                    type: "1",
                     name: this.$t("studentWeb.exam.source.evMode2")
                 },
                 {
-                    type: 2,
+                    type: "2",
                     name: this.$t("studentWeb.exam.source.evMode3")
                 },
             ],
@@ -269,74 +172,127 @@ export default {
                     name: this.$t('totalAnalysis.ti_text6')
                 },
             ],
-            showSchoolRep: [],
+            num: [],
         }
     },
     mounted () {
-        this.showSchoolRep = this.schoolRep
+        /* this.showSchoolRep.forEach((item, index) => {
+            for (let i = index + 1; i < this.showSchoolRep.length; i++) {
+                if(item.id === this.showSchoolRep[i].id && !this.num.includes(index)) {
+                    this.num.push(i)
+                }
+            }
+        }); */
+        this.getReportList(this.classInfo)
     },
     methods: {
+        getReportList(classInfo) {
+            this.showSchoolRep = []
+            console.log(classInfo);
+            let cId = classInfo.classId.concat(classInfo.stuList)
+            let param = {
+                courseId: classInfo.id,
+                // code: classInfo.scope === "school" ? classInfo.school : classInfo.creatorId,
+                cId,
+                stuId: this.$store.state.userInfo.sub,
+            }
+            if(classInfo.subject.id) {
+                param.subjectId = classInfo.subject.id
+            }
+            this.$api.studentWeb.getClassScore(param).then(res => {
+                // 404表示没有评测记录
+                if(res.code === 404) {
+                    // this.showSchoolRep = []
+                } else if(res.info) {
+                    res.info.forEach(item => {
+                        item.tag = {
+                            source: item.source === '0' ? this.$t("studentWeb.exam.source.evMode1")
+                                    : (item.source === '1' ? this.$t("studentWeb.exam.source.evMode2")
+                                    : (item.source === '2' ? this.$t("studentWeb.exam.source.evMode3") : null)),
+                            type: (item.eType && item.eType.id) ? item.eType.name : null,
+                            owner: item.owner
+                        }
+                        item.total = 0
+                        // 学校评测才会存在多科,要匹配对应的科目
+                        if(item.owner === 'school') {
+                            let subIndex = item.subject.findIndex(sub => {
+                                return sub.id === classInfo.subject.id
+                            })
+                            if(subIndex != -1) {
+                                item.point[subIndex].forEach(point => {
+                                    item.total += point
+                                })
+                            }
+                        } else {
+                            // 个人评测只存在一张试卷
+                            item.point[0].forEach(point => {
+                                item.total += point
+                            })
+                        }
+                        item.time = this.dateFormat(item.createTime)
+                        item.score = `${item.sum} / ${item.total}`
+                        this.schoolRep.push(item)
+                    })
+                    this.schoolRep.reverse()
+                    this.showSchoolRep = [...this.schoolRep]
+                }
+            })
+        },
+        //时间格式化处理
+        dateFormat(timestamp) {
+            var date = new Date(timestamp)
+            var Y = date.getFullYear() + '-'
+            var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
+            var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' '
+            var H = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ":"
+            var Min = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes())
+            var S = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()) + " "
+            return Y + M + D + H + Min;
+        },
         handleSpan ({ row, column, rowIndex, columnIndex }) {
-            console.log(this.schoolRep[rowIndex + 1]);
-            if(this.schoolRep[rowIndex + 1]) {
-                if(row.id === this.schoolRep[rowIndex + 1].id) {
-                    // if (columnIndex === 0) {
-                        return [row + 1, 1];
-                    // }
-                    // return [2, 3]
+            // 要把后面相同的使用[0, 0]隐藏掉
+            if(this.num.includes(rowIndex)) {
+                if(columnIndex != 2 && columnIndex != 3) {
+                    return [0, 0]
+                }
+            } else {
+                if(columnIndex != 2 && columnIndex != 3) {
+                    let x = row.mergeCol ? row.mergeCol : 1
+                    let y = 1
+                    return [x, y]
                 }
             }
-            // console.log(row, column, rowIndex, columnIndex);
-            /* if (rowIndex === 0 && columnIndex === 0) {
-                return [2, 3]; //2行3列
-            } else if (rowIndex === 0 && columnIndex === 1) {
-                return  [0, 0];
-            } */
-            /* if (rowIndex === 1 && columnIndex === 0) {
-                return {
-                    rowspan: 3, //向下合并3格
-                    colspan: 4, //向右合并4格
-                };
-            } else if (rowIndex === 3 && columnIndex === 0) {
-                return {
-                    rowspan: 0,
-                    colspan: 0
-                };
-            } */
         }
     },
     watch: {
-        ownerType: {
+        filterType: {
+            deep: true,
             handler(n, o) {
-                if(n === "private") {
+                this.showSchoolRep = []
+                if(n.sourceType === '0') {
+                    this.showSchoolRep = this.schoolRep.filter(item => {
+                        return item.source === '0'
+                    })
+                } else if(n.sourceType === '1') {
                     this.showSchoolRep = this.schoolRep.filter(item => {
-                        return item.tag.owner === "个人"
+                        return item.source === '1'
                     })
-                } else if(n === "school") {
+                } else if(n.sourceType === '2') {
                     this.showSchoolRep = this.schoolRep.filter(item => {
-                        return item.tag.owner === "校级"
+                        return item.source === '2'
                     })
                 } else {
                     this.showSchoolRep = this.schoolRep
                 }
-            }
-        },
-        sourceType: {
-            handler(n, o) {
-                if(n === 0) {
-                    this.showSchoolRep = this.schoolRep.filter(item => {
-                        return item.tag.owner === "线上评测"
-                    })
-                } else if(n === 1) {
-                    this.showSchoolRep = this.schoolRep.filter(item => {
-                        return item.tag.owner === "课中评测"
+
+                if(n.ownerType === "teacher") {
+                    this.showSchoolRep = this.showSchoolRep.filter(item => {
+                        return item.owner === "teacher"
                     })
-                } else if(n === 2) {
-                    this.showSchoolRep = this.schoolRep.filter(item => {
-                        return item.tag.owner === "阅卷评测"
+                } else if(n.ownerType === "school") {
+                    this.showSchoolRep = this.showSchoolRep.filter(item => {
+                        return item.owner === "school"
                     })
-                } else {
-                    this.showSchoolRep = this.schoolRep
                 }
             }
         }
@@ -350,4 +306,10 @@ export default {
 
 <style lang="less">
 @import "../../WrongQusetion/iViewStyle.less";
+
+.achievement-report {
+    .ivu-tabs-tabpane td:nth-child(2) {
+        text-align: center !important;
+    }
+}
 </style>

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

@@ -170,6 +170,8 @@ export default {
     autoShare:'自動發布:',
     autoShareTips:'開啟後,上傳課堂課堂記錄的同時將發布給學生',
     nicknameTips:'請輸入學生昵稱',
+    evRcd:'曆次評測',
+    gradeErr:'查詢成績數據失敗',
 
     //ManageClass.vue
     stuMgt: 'Student Management',

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

@@ -170,6 +170,8 @@ export default {
     autoShare:'自动发布:',
     autoShareTips:'开启后,上传课堂课堂记录的同时将发布给学生',
     nicknameTips:'请输入学生昵称',
+    evRcd:'历次评测',
+    gradeErr:'查询成绩数据失败',
 
     //ManageClass.vue
     stuMgt:'学生管理',

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

@@ -170,6 +170,8 @@ export default {
     autoShare:'自動發布:',
     autoShareTips:'開啟後,上傳課堂課堂記錄的同時將發布給學生',
     nicknameTips:'請輸入學生昵稱',
+    evRcd:'曆次評測',
+    gradeErr:'查詢成績數據失敗',
 
     //ManageClass.vue
     stuMgt: '學生管理',

+ 6 - 1
TEAMModelOS/ClientApp/src/view/jyzx/application.vue

@@ -442,7 +442,12 @@ export default {
 		  this.$Message.warning(`学校暂未开启互评!请联系学校管理员处理!`)
 		  return
 	  }
-      if (this.isFirstAppraiseOther && this.hasNoAppraise && data.sub.otherScore.filter(i => i.roleType === 'member').length > 0) {
+	  // 进入互评第一次评价 并且 成员中存在无互评的教师 并且 选择的组员没有我的评价记录
+	  let myId = this.$store.state.userInfo.TEAMModelId
+	  let hasMemberAppraise = data.sub.otherScore.filter(i => i.roleType === 'member').length
+	  let hasMyAppraise = data.sub.otherScore.filter(i => i.roleType === 'member' && i.tmdid === myId).length
+	  
+      if (this.isFirstAppraiseOther && this.hasNoAppraise && !hasMyAppraise && hasMemberAppraise) {
         this.$Message.warning(`请优先对互评次数为0的老师进行互评!`)
       } else {
         let reviewData = {

+ 3 - 2
TEAMModelOS/ClientApp/src/view/jyzx/newHomePage.vue

@@ -279,8 +279,9 @@ export default {
                     // 线上研修
                     this.onlineInfo.total = res.setting.onlineTime
                     this.onlineInfo.value = res.teacherTrain.onlineTime > res.setting.onlineTime ? res.setting.onlineTime : res.teacherTrain.onlineTime
-                    this.onlineInfo.minute = res.teacherTrain.currency.videoTime
-                    this.onlineInfo.minuteTotal = res.setting.onlineTime * res.setting.lessonMinutes
+                    // limitMinutes: -1(按照之前的时间)  limitMinutes > -1(取limitMinutes,并且所学时间超过limitMinutes,就取limitMinutes)
+                    this.onlineInfo.minute = (res.setting.limitMinutes != -1 && (res.teacherTrain.currency.videoTime > res.setting.limitMinutes)) ? res.setting.limitMinutes : res.teacherTrain.currency.videoTime
+                    this.onlineInfo.minuteTotal = res.setting.limitMinutes != -1 ? res.setting.limitMinutes : (res.setting.onlineTime * res.setting.lessonMinutes)
                     
                     // 校本研修
                     this.offlineInfo.total = res.setting.offlineTime

+ 0 - 4
TEAMModelOS/ClientApp/src/view/newcourse/CoursePlan.vue

@@ -3,10 +3,6 @@
         <div class="course-table-header">
             <b class="title">{{$t('cusMgt.schdTable')}}</b>
             <div class="action-btn-wrap">
-                <span v-if="$access.can('admin.*|course-upd')" @click="showImportCus()" class="action-btn" style="color:#808080;cursor: not-allowed;">
-                    <Icon custom="iconfont icon-upload" size="16" />
-                    <span>{{$t('cusMgt.importLabel')}}</span>
-                </span>
                 <span v-if="$access.can('admin.*|course-upd')" @click="toggleView()" class="action-btn" style="margin-right:40px">
                     <Icon custom="iconfont icon-kecheng" size="16" />
                     <span>{{$t('cusMgt.cusMode')}}</span>

+ 13 - 5
TEAMModelOS/ClientApp/src/view/newcourse/MyCourse.vue

@@ -139,9 +139,7 @@
                             </div>
                         </div>
                         <!-- 成绩统计 -->
-                        <div v-show="tabName == 'data'" class="animated fadeIn class-record-wrap">
-                            成绩统计
-                        </div>
+                        <Grade v-if="tabName == 'data'" class="animated fadeIn class-record-wrap" :paramsInfo="paramsInfo" :student="students"></Grade>
                         <!-- 学生名单 -->
                         <div class="course-classroom-info-content animated fadeIn" v-if="tabName == 'stus'">
                             <vuescroll style="height:100%;">
@@ -521,10 +519,11 @@ import PersonalPhoto from '@/components/public/personalPhoto/Index.vue'
 import StudentList from '@/components/coursemgt/StudentList.vue'
 import TeaTable from './TeaTable.vue'
 import Video from '../video/Video.vue'
+import Grade from './tabs/Grade.vue'
 import excel from '@/utils/excel.js'
 export default {
     components: {
-        StudentList, PersonalPhoto, TeaTable, RcdPoster, Video
+        StudentList, PersonalPhoto, TeaTable, RcdPoster, Video, Grade
     },
     inject: ['reload'],
     data() {
@@ -1671,7 +1670,7 @@ export default {
             }
             // 当前名单对应的课程安排用于更新UI
             let schedule = this.courseListShow[this.curCusIndex].schedule.find(item => {
-                return item.stulist == this.teaClassList[this.curClassIndex].stulist && !item.classId 
+                return item.stulist == this.teaClassList[this.curClassIndex].stulist && !item.classId
             })
             let stulist = this.groupList.find(item => {
                 return item.id == this.teaClassList[this.curClassIndex].stulist
@@ -2444,6 +2443,15 @@ export default {
             periods: 'user/getPeriods',
             lessonShow: 'user/getTeacherLessonShow'//是否自动发布课堂记录
         }),
+        //查询成绩表所需参数(courseId, cId, code)
+        paramsInfo() {
+            let data = {
+                courseId: this.courseListShow[this.curCusIndex]?.id,
+                subjectId: this.courseListShow[this.curCusIndex]?.subject?.id,
+                cId: this.teaClassList[this.curClassIndex]?.classId || this.teaClassList[this.curClassIndex]?.stulist
+            }
+            return data
+        },
         //判断是否可以添加学生
         canAddStu() {
             return this.addStuStatus && (!this.teaClassList[this.curClassIndex].listSchool || this.teaClassList[this.curClassIndex].listSchool == this.$store.state.userInfo.schoolCode)

+ 4 - 1
TEAMModelOS/ClientApp/src/view/newcourse/NewCusMgt.vue

@@ -9,6 +9,10 @@
                     <Icon custom="iconfont icon-schedule" size="16" />
                     <span>{{$t('cusMgt.schdTable')}}</span>
                 </span>
+                <span v-if="$access.can('admin.*|course-upd')" @click="showImportCus()" class="action-btn" style="color:#808080;cursor: not-allowed;">
+                    <Icon custom="iconfont icon-upload" size="16" />
+                    <span>{{$t('cusMgt.importLabel')}}</span>
+                </span>
             </div>
         </div>
         <Split v-model="split1">
@@ -312,7 +316,6 @@ export default {
             classList: [],  //班级列表
             addType: 'class',
             modalLoading: true,
-            listSelections: [],
             isUpd: false,
             addTeaStatus: false,
             addListStatus: false,

+ 148 - 4
TEAMModelOS/ClientApp/src/view/newcourse/tabs/Grade.vue

@@ -1,18 +1,162 @@
 <template>
-    <div>
-
+    <div id="grade-container" class="grade-container">
+        <Table row-key="id" :columns="columns" :data="tableData" border stripe :max-height="tableHieght">
+            <template slot-scope="{ row }" slot="name">
+                <p style="margin-top:10px">
+                    <strong>{{ row.name }}</strong>
+                </p>
+                <div style="margin-bottom:10px;margin-top:5px">
+                    <Tag color="blue">
+                        {{ row.owner == 'school' ? $t('learnActivity.mgtScEv.listLabel1') : $t('learnActivity.mgtScEv.listLabel2') }}
+                    </Tag>
+                    <Tag color="green">
+                        {{getModeLabel(row.source,row.qamode)}}
+                    </Tag>
+                    <Tag color="cyan" v-show="row.eType">
+                        {{row.eType}}
+                    </Tag>
+                </div>
+            </template>
+        </Table>
     </div>
 </template>
 <script>
 export default {
+    props: {
+        student: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        //查询成绩表API参数
+        paramsInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        }
+    },
     data() {
+        return {
+            tableHieght: 600,
+            tableData: []
+        }
+    },
+    computed: {
+        columns() {
+            let c = [{
+                title: this.$t('cusMgt.evRcd'),
+                slot: 'name',
+                tree: true,
+                fixed: 'left',
+                minWidth: 260,
+                maxWidth: 500
+            }]
+            this.student.forEach(item => {
+                c.push({
+                    title: item.name,
+                    key: item.id,
+                    minWidth: 80
+                })
+            })
+            return c
+        }
+    },
+    methods: {
+        /**获取mode对应的label */
+        getModeLabel(code, qamode) {
+            if (qamode == 1) {
+                return this.$t('learnActivity.mgtScEv.paperExam')
+            }
+            for (let item of this.$GLOBAL.EV_MODE()) {
+                if (item.value == code) {
+                    return item.label
+                }
+            }
+        },
+        toTableData(data) {
+            let sIds = this.student.map(item => item.id)
+            this.tableData = data.map(item => {
+                let info = {
+                    id: item.examId,
+                    name: item.examName,
+                    source: item.source,
+                    owner: item.owner,
+                    qamode: item.qamode,
+                    eType: item.eType?.name
+                }
+                sIds.forEach(id => {
+                    let index = item.studentIds.findIndex(i => i === id)
+                    let score
+                    if (index > -1) {
+                        score = item.sum[index]
+                    } else {
+                        score = 0
+                    }
+                    info[id] = score
+                })
+                return info
+            })
+        },
+        getGradeInfo() {
+            this.tableData = []
+            let { courseId, cId, subjectId } = this.paramsInfo
+            if (courseId && cId) {
+                this.$api.learnActivity.getGradeInfo({ courseId, cId: [cId], subjectId }).then(
+                    res => {
+                        if (res.info) {
+                            this.toTableData(res.info)
+                        }
+                    },
+                    err => {
+                        this.$Message.error(this.$t('cusMgt.gradeErr'))
+                    }
+                )
+            }
+        }
+    },
+    watch: {
+        paramsInfo: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                this.getGradeInfo()
+            }
+        }
+    },
+    mounted() {
+        let dom = document.getElementById('grade-container')
+        if (dom) {
+            this.tableHieght = dom.clientHeight - 10
+        }
 
     }
 }
 </script>
 <style lang="less" scoped>
-
+.grade-container {
+    width: ~"calc(100% - 10px)";
+}
+.mutl-subject-tag {
+    background: #2d8cf0;
+    color: white;
+    padding: 0px 4px;
+    border-radius: 3px;
+    text-align: center;
+    margin-right: 10px;
+    font-size: 12px;
+}
 </style>
 <style lang="less">
-
+.grade-container .ivu-table-cell-slot {
+    display: inline-block;
+    user-select: none;
+}
+.grade-container ::-webkit-scrollbar {
+    height: 8px;
+}
+.grade-container ::-webkit-scrollbar-thumb {
+    background: #e9e9e9;
+}
 </style>

+ 16 - 6
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/EvaluationList/TotalIndex.vue

@@ -100,7 +100,8 @@
 							<div class="el-filter-item">
 								<span class="el-filter-title">{{ $t("totalAnalysis.condition4") }}:</span>
 								<RadioGroup v-model="filterType" type="button" @on-change="filterTypeChange">
-									<Radio v-for="(item, index) in filterData.typeList" :label="item.id" :key="index">{{item.name }}</Radio>
+									<Radio :label="'0'">{{ $t('totalAnalysis.all') }}</Radio>
+									<Radio v-for="(item, index) in filterTypeList" :label="item.id" :key="index">{{item.name }}</Radio>
 								</RadioGroup>
 							</div>
 							<div class="el-filter-item">
@@ -496,7 +497,12 @@
 					this.filterSubject = this.$t('totalAnalysis.all')
 					this.filterSubjectChange()
 				}
-				this.doFilter();
+				// 切换学科后学科会变化,需要初始化学科状态 默认选中全部
+				if (this.filterTypeList && this.filterTypeList.length) {
+					this.filterType = '0'
+					this.filterTypeChange()
+				}
+				
 			},
 			filterGradeChange() {
 				this.filterConditions.grade =
@@ -514,9 +520,6 @@
 				this.doFilter();
 			},
 			filterTypeChange() {
-				//评测返回的是key eg:normal,不能直接用中文做过滤条件
-				// this.filterConditions.type =
-				//     this.filterType === "全部" ? null : this.filterType;
 				console.log(this.filterType)
 				if (this.filterType === '0') {
 					this.filterConditions.type = null
@@ -611,9 +614,10 @@
 				for (var key in obj) {
 					//处理筛选条件是类型的情况
 					if (key == 'type' && obj[key]) {
+						let findType = this.filterTypeList.find(i => i.id === this.filterType)
 						arr.push({
 							keyName: key,
-							val: this.typeList.find(i => i.id === this.filterType).name,
+							val: findType?.name,
 						});
 						continue
 					}
@@ -723,6 +727,12 @@
 				})
 				return res ? res.semesters : []
 			},
+			filterTypeList() {
+				let res = this.schoolData.period.find(item => {
+					return item.name == this.filterConditions.period
+				})
+				return res ? res.analysis.type : []
+			},
 		},
 		watch: {
 			filterList(val) {

+ 63 - 1
TEAMModelOS/Controllers/Both/GroupListController.cs

@@ -782,7 +782,7 @@ namespace TEAMModelOS.Controllers
             }
             return Ok(new { groupList });
         }
-
+       
         /// <summary>
         /// 保存或更新通用名单
         /// </summary>
@@ -927,5 +927,67 @@ namespace TEAMModelOS.Controllers
                 return Ok(new { error=400 });
             }
         }
+
+        /// <summary>
+        /// 保存或更新通用名单
+        /// </summary>
+        /// <param name="json"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [AuthToken(Roles = "teacher,admin")]
+        [HttpPost("upsert-group-member")]
+        [Authorize(Roles = "IES")]
+        public async Task<IActionResult> UpsertGroupMember(JsonElement json)
+        {
+            if (!json.TryGetProperty("opt", out JsonElement _opt)) { return BadRequest(); }
+            if (!json.TryGetProperty("scope", out JsonElement _scope)) { return BadRequest(); }
+            if (!json.TryGetProperty("id", out JsonElement _id)) { return BadRequest(); }
+            if (!json.TryGetProperty("code", out JsonElement _code)) { return BadRequest(); }
+            if (!json.TryGetProperty("type", out JsonElement _type)) { return BadRequest(); }
+            try {
+                GroupList groupList = null;
+                string tbname = $"{_scope}".Equals("private") ? "Teacher" : "School";
+                string code = "";
+                if (string.IsNullOrEmpty($"{_type}") || !$"{_type}".Equals("class"))
+                {
+                    if ($"{_scope}".Equals("private"))
+                    {
+                        //私人名单
+                        code = "GroupList";
+                    }
+                    else
+                    {
+                        //学校自定义名单
+                        code = !code.StartsWith("GroupList-") ? $"GroupList-{_code}" : code;
+                    }
+                    groupList = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).ReadItemAsync<GroupList>($"{_id}", new PartitionKey($"{code}"));
+                }
+                switch (true)
+                {
+                    case bool when $"{_opt}".Equals("up-nickname", StringComparison.OrdinalIgnoreCase) && json.TryGetProperty("members", out JsonElement _members):
+                        var members=   _members.ToObject<List<Member>>();
+                        List<Member> updateSuccess = new List<Member>();
+                        List<Member> updateFailed = new List<Member>();
+                        members.ForEach(x => {
+                            var member = groupList.members.Find(z => z.type == x.type && z.id.Equals(x.id));
+                            if (member != null)
+                            {
+                                member.nickname = x.nickname;
+                                updateSuccess.Add(member);
+                            }
+                            else {
+                                updateFailed.Add(x);
+                            }
+                        });
+                        await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).ReplaceItemAsync<GroupList>(groupList,$"{_id}", new PartitionKey(code));
+                        return Ok(new { updateSuccess, updateFailed });
+                }
+            } catch (Exception ex){
+               await _dingDing.SendBotMsg($"{_option.Location},更新名单成员信息异常{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+                return BadRequest();
+            }
+            
+            return Ok();
+        }
     }
 }

+ 36 - 6
TEAMModelOS/Controllers/Both/LessonRecordController.cs

@@ -246,17 +246,19 @@ namespace TEAMModelOS.Controllers
             }
             if (!request.TryGetProperty("scope", out JsonElement _scope)) return BadRequest();
             StringBuilder sql = new StringBuilder();
-            sql.Append("select value(count(1)) from c   ");
+            sql.Append("select c.id,c.groupIds,c.courseId from c   ");
             Dictionary<string ,object> dict =  GetLessonCond(request);
             AzureCosmosQuery cosmosDbQuery = SQLHelper.GetSQL(dict, sql);
             string tbname = "";
             string code = "";
+            string school = null;
             List<string> autoTch = new List<string>();
             if (_scope.GetString().Equals("school"))
             {
                 if (!request.TryGetProperty("school", out JsonElement _school)) return BadRequest();
                 if (!string.IsNullOrEmpty($"{_school}"))
                 {
+                    school = $"{_school}";
                     code = $"LessonRecord-{_school}";
                     tbname = "School";
                     List<string> ids = new List<string>();
@@ -329,12 +331,40 @@ namespace TEAMModelOS.Controllers
                 }
                 sqlShow = $" and (array_contains(c.show,'student') or array_contains(c.show,'all')  {autoSql} ) ";
             }
-            cosmosDbQuery.QueryText = cosmosDbQuery.QueryText.Replace("where", $" where (c.status<>404 or IS_DEFINED(c.status) = false ) {sqlShow}  and  ");
-            await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetItemQueryIterator<int>(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey(code) }))
+            cosmosDbQuery.QueryText = cosmosDbQuery.QueryText.Replace("where", $" where (c.status<>404 or IS_DEFINED(c.status) = false ) and  array_length(c.groupIds)>0 {sqlShow}  and  ");
+            List<LessonRecord> records = new List<LessonRecord>();
+            await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetItemQueryIterator<LessonRecord>(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey(code) }))
             {
-                count = item;
+                records.Add(item);
+            }
+            if (records.Any()) {
+                var groupIds= records.SelectMany(x => x.groupIds).ToHashSet();
+                if (groupIds.Any()) {
+                    var groups = await GroupListService.GetGroupListListids(_azureCosmos.GetCosmosClient(), _dingDing, groupIds.ToList(), school," c.id ");
+                    //获取已经被删除的名单。
+                    var idsExp =  groupIds.Except(groups.Select(x => x.id));
+                    if (idsExp.Any()) {
+
+                   
+                        foreach(var item in records)
+                        {
+                            int countRmv = item.groupIds.RemoveAll(x => idsExp.Contains(x));
+                            if (countRmv > 0)
+                            {
+                                try {
+                                    LessonRecord record = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).ReadItemAsync<LessonRecord>(item.id, new PartitionKey(code));
+                                    record.groupIds = item.groupIds;
+                                    await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).ReplaceItemAsync<LessonRecord>(record, item.id, new PartitionKey(code));
+                                } catch (CosmosException ex) when (ex.Status == 404) {
+                                    continue;
+                                }
+                            }
+                        }
+                    }
+                }
             }
-            return Ok(new { count=count });
+            count = records.Count;
+            return Ok(new { count=count, records });
         }
       
         /// <summary>
@@ -465,7 +495,7 @@ namespace TEAMModelOS.Controllers
                     }
                     sqlShow = $" and (array_contains(c.show,'student') or array_contains(c.show,'all')  {autoSql} ) ";
                 }
-                cosmosDbQuery.QueryText = cosmosDbQuery.QueryText.Replace("where", $" where (c.status<>404 or IS_DEFINED(c.status) = false ) {sqlShow} and  ");
+                cosmosDbQuery.QueryText = cosmosDbQuery.QueryText.Replace("where", $" where (c.status<>404 or IS_DEFINED(c.status) = false ) and  array_length(c.groupIds)>0  {sqlShow} and  ");
                 await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname)
                    .GetItemQueryStreamIterator(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, continuationToken: continuationToken,
                    requestOptions: new QueryRequestOptions() { MaxItemCount = pageCount, PartitionKey = new PartitionKey(code) }))