Ver Fonte

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

zhouj1203@hotmail.com há 1 ano atrás
pai
commit
96a14ee711
51 ficheiros alterados com 1665 adições e 706 exclusões
  1. 18 0
      TEAMModelBI/ClientApp/src/language/lang/zh-cn.js
  2. 18 0
      TEAMModelBI/ClientApp/src/language/lang/zh-tw.js
  3. 383 99
      TEAMModelBI/ClientApp/src/view/product/details.vue
  4. 6 3
      TEAMModelBI/ClientApp/src/view/product/index.vue
  5. 3 3
      TEAMModelBI/TEAMModelBI.csproj
  6. 3 3
      TEAMModelOS.FunctionV4/TEAMModelOS.FunctionV4.csproj
  7. 2 1
      TEAMModelOS.SDK/Extension/JwtAuthExtension.cs
  8. 4 4
      TEAMModelOS.SDK/Models/Service/Common/TeacherService.cs
  9. 1 0
      TEAMModelOS.SDK/Models/Service/LessonService.cs
  10. 3 3
      TEAMModelOS.SDK/TEAMModelOS.SDK.csproj
  11. 4 1
      TEAMModelOS/ClientApp/public/lang/en-US.js
  12. 3 0
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  13. 5 2
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  14. 6 0
      TEAMModelOS/ClientApp/src/api/areaArt.js
  15. 4 0
      TEAMModelOS/ClientApp/src/api/http.js
  16. 4 2
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  17. 1 1
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ShowQues.vue
  18. 3 3
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReportCharts/AudioRecorder.vue
  19. 7 4
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/LessonTestReport.vue
  20. 8 10
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperTest.vue
  21. 3 1
      TEAMModelOS/ClientApp/src/utils/js-fn.js
  22. 358 331
      TEAMModelOS/ClientApp/src/view/artexam/DataView.vue
  23. 45 2
      TEAMModelOS/ClientApp/src/view/dashboard/Research.less
  24. 213 170
      TEAMModelOS/ClientApp/src/view/dashboard/Research.vue
  25. 1 1
      TEAMModelOS/ClientApp/src/view/iot/schooliot.vue
  26. 65 5
      TEAMModelOS/ClientApp/src/view/learnactivity/ByQuMark.vue
  27. 47 2
      TEAMModelOS/ClientApp/src/view/learnactivity/StuReport.vue
  28. 48 0
      TEAMModelOS/ClientApp/src/view/learnactivity/byStu/ByStuMark.less
  29. 22 5
      TEAMModelOS/ClientApp/src/view/learnactivity/byStu/ByStuMark.vue
  30. 69 2
      TEAMModelOS/ClientApp/src/view/learnactivity/byStu/QuAndScore.vue
  31. 4 4
      TEAMModelOS/ClientApp/src/view/signupActivity/createActivity.vue
  32. 36 3
      TEAMModelOS/ClientApp/src/view/signupActivity/infoComponent/ruleDrawer.vue
  33. 3 1
      TEAMModelOS/ClientApp/src/view/signupActivity/infoComponent/updateScore.vue
  34. 1 1
      TEAMModelOS/ClientApp/src/view/student-web/AppNew.vue
  35. 33 7
      TEAMModelOS/ClientApp/src/view/task/marking/mark/ByQu2.vue
  36. 16 3
      TEAMModelOS/ClientApp/src/view/task/marking/mark/ByStu.vue
  37. 12 0
      TEAMModelOS/Controllers/Analysis/ClassAnalysisController.cs
  38. 31 3
      TEAMModelOS/Controllers/Client/AClassONEController.cs
  39. 30 2
      TEAMModelOS/Controllers/Client/HiTAControlller.cs
  40. 7 2
      TEAMModelOS/Controllers/Common/ActivityController.cs
  41. 7 1
      TEAMModelOS/Controllers/OpenApi/Init/BizUsersController.cs
  42. 13 5
      TEAMModelOS/Controllers/School/ArtReviewController.cs
  43. 48 2
      TEAMModelOS/Controllers/Student/StudentController.cs
  44. 7 1
      TEAMModelOS/Controllers/Student/TmdUserController.cs
  45. 3 2
      TEAMModelOS/Controllers/System/CoreController.cs
  46. 30 2
      TEAMModelOS/Controllers/Teacher/InitController.cs
  47. 7 1
      TEAMModelOS/Controllers/XTest/TestController.cs
  48. 14 2
      TEAMModelOS/Filter/AuthTokenAttribute.cs
  49. 4 4
      TEAMModelOS/TEAMModelOS.csproj
  50. 1 1
      TEAMModelOS/appsettings.Development.json
  51. 1 1
      TEAMModelOS/appsettings.json

+ 18 - 0
TEAMModelBI/ClientApp/src/language/lang/zh-cn.js

@@ -602,6 +602,24 @@ const zh_cn = {
             changeError: '储存变更失败',
         },
         parameterError: '参数错误',
+    },
+    auth:{
+        YMPCVCIM: '学情分析模组',
+        IPDYZYLC: '智慧学校管理服务',
+        _3CLYJ6NP: 'AClass ONE智慧学伴',
+        IPALJ6NY: '数据储存服务空间',
+        VABAJ6NV: '卷卡合一阅卷系统',
+        VLY6J6N6: '教研中心模组',
+        _0VPBDZPG: 'Haboard醍摩豆智慧大屏',
+        B9GPJ6NY: '苏格拉底频道管理',
+        LY9AJ6NY: '苏格拉底频道空间',
+        YL9CJ6NY: '苏格拉底通用议课教室数',
+        LL9MJ6NY: '苏格拉底通用议课连线数',
+        B6V5J6NP: '艺术评测服务',
+        LSZYJ6NA: '智慧体育服务',
+        CVGPJ6NN: '智慧德育服务',
+        LSZYJ6NA: '劳动教育服务',
+        YPXSJ6NJ: '五育看板',
     }
 }
 export default zh_cn

+ 18 - 0
TEAMModelBI/ClientApp/src/language/lang/zh-tw.js

@@ -598,6 +598,24 @@ const zh_tw = {
             changeError: '儲存變更失敗',
         },
         parameterError: '參數錯誤',
+    },
+    auth:{
+        YMPCVCIM: '學情分析模組',
+        IPDYZYLC: '智慧學校管理服務',
+        _3CLYJ6NP: 'AClass ONE智慧學伴',
+        IPALJ6NY: '數據儲存服務空間',
+        VABAJ6NV: '卷卡合一閱卷系統',
+        VLY6J6N6: '教研中心模组',
+        _0VPBDZPG: 'Haboard醍摩豆智慧大屏',
+        B9GPJ6NY: '蘇格拉底頻道管理',
+        LY9AJ6NY: '蘇格拉底頻道空間',
+        YL9CJ6NY: '蘇格拉底通用議課教室數',
+        LL9MJ6NY: '蘇格拉底通用議課連線數',
+        B6V5J6NP: '藝術評測服務',
+        LSZYJ6NA: '智慧體育服務',
+        CVGPJ6NN: '智慧德育服務',
+        LSZYJ6NA: '勞動教育服務',
+        YPXSJ6NJ: '五育看板',
     }
 }
 export default zh_tw

+ 383 - 99
TEAMModelBI/ClientApp/src/view/product/details.vue

@@ -21,9 +21,11 @@
       </div>
       <div class="contentbox">
         <div class="school-name">
-          <div class="statistics-address">位于:{{schoolData.region}} {{schoolData.province}}{{schoolData.city}}{{schoolData.dist}}</div>
+          <div class="statistics-address">位于:{{schoolData.region}}
+            {{schoolData.province}}{{schoolData.city}}{{schoolData.dist}}</div>
           <span>{{schoolData.name ? schoolData.name:'暂无名称'}}</span>
-          <div class="statistics-time">统计时间:<span>{{statisticsTime.year}} 年 {{statisticsTime.month}} 月 {{statisticsTime.day}}日</span></div>
+          <div class="statistics-time">统计时间:<span>{{statisticsTime.year}} 年 {{statisticsTime.month}} 月
+              {{statisticsTime.day}}日</span></div>
         </div>
         <div class="basicsdata-box">
           <div class="basicadata-item" v-for="(item,index) in basicaList" :key="index">
@@ -35,8 +37,10 @@
                 </svg>
               </div>
             </div>
-            <div :class="[item.key === 'classtime' || item.key === 'participationnum' ? 'item-nums-special':'item-nums']">
-              <p v-if="item.key === 'classtime' || item.key === 'participationnum'">{{item.valueText}}<span class="timetag">Min</span><br /><span>{{item.valueHText}}<span class="timetag">H</span></span></p>
+            <div
+              :class="[item.key === 'classtime' || item.key === 'participationnum' ? 'item-nums-special':'item-nums']">
+              <p v-if="item.key === 'classtime' || item.key === 'participationnum'">{{item.valueText}}<span
+                  class="timetag">Min</span><br /><span>{{item.valueHText}}<span class="timetag">H</span></span></p>
               <p v-else>{{item.value}}</p>
             </div>
           </div>
@@ -113,6 +117,43 @@
             </div> -->
           </div>
         </div>
+        <div class="minxinbox" v-show="isShowAuth">
+          <div class="lessons-box-maxh">
+            <p class="inuse-title">服務授權</p>
+            <div v-for="(item,index) in serviceTableData" :key="index">
+              <el-card shadow="hover" class="custom-card">
+                <span class="card-column-icon" >
+                  <svg class="cardicon" aria-hidden="true" style="background-color: #ffffff;">
+                    <!-- <use xlink:href="#icon-laoshi1"></use> -->
+                    <use xlink:href="#icon-icon"></use>
+                    
+                    <!-- <use xlink:href="#icon-xuanzexuexiao-01"></use> -->
+                  </svg>
+                </span>                
+                <span class="card-column" style="width: 80%;">{{item.title}}<br/>{{item.expireDate}}</span>
+                <!-- <span class="card-column" style="width: 60%;">{{item.expireDate}}</span> -->
+              </el-card>
+              <el-divider />
+            </div>
+          </div>
+          <div class="inuse-right">
+            <p class="inuse-title">軟體授權</p>
+            <div v-for="(item,index) in softwareTableData" :key="index">
+              <el-card shadow="hover" class="custom-card">
+                <span class="card-column-icon" >
+                  <svg class="cardicon" aria-hidden="true" style="background-color: #ffffff;">
+                    <use xlink:href="#icon-icon-test2"></use>
+                    <!-- <use xlink:href="#icon-xuanzexuexiao-01"></use> -->
+                  </svg>
+                </span>                
+                <span class="card-column" style="width: 80%;">{{item.title}}<br/>{{item.expireDate}}</span>
+                <!-- <span class="card-column" style="width: 60%;">{{item.expireDate}}</span> -->
+              </el-card>
+              <el-divider />
+            </div>
+          </div>
+        </div>
+
       </div>
       <!--上一个或者下一个-->
       <!-- <div class="lastbtn">
@@ -147,97 +188,101 @@
           <div class="areaData">
             <div class="contentbox area-contentbox">
               <div class="header-select">
-        <div class="header-select-title">学区数据详情页</div>
-        <div class="header-select-box">
-          <div class="backbtn">
-            <el-button type="primary" @click="backbtn()">返回</el-button>
-          </div>
-        </div>
-      </div>
-      <div class="contentbox">
-        <div class="school-name">
-          <!-- <div class="statistics-address">位于:{{schoolData.region}} {{schoolData.province}}{{schoolData.city}}{{schoolData.dist}}</div> -->
-          <span>{{schoolData.name ? schoolData.name:'暂无名称'}}</span>
-          <div class="statistics-time">统计时间:<span>{{statisticsTime.year}} 年 {{statisticsTime.month}} 月 {{statisticsTime.day}}日</span></div>
-        </div>
-        <div class="basicsdata-box">
-          <div class="basicadata-item" v-for="(item,index) in basicaList" :key="index">
-            <div class="item-title-images">
-              <div class="item-title">{{item.title}}</div>
-              <div class="item-images">
-                <svg class="analysisicon" aria-hidden="true">
-                  <use :xlink:href="item.icon"></use>
-                </svg>
-              </div>
-            </div>
-            <div :class="[item.key === 'classtime' || item.key === 'participationnum' ? 'item-nums-special':'item-nums']">
-              <p v-if="item.key === 'classtime' || item.key === 'participationnum'">{{item.value}}<span class="timetag">Min</span><br /><span>{{Math.round(item.value/60)}}<span class="timetag">H</span></span></p>
-              <p v-else>{{item.value}}</p>
-            </div>
-          </div>
-        </div>
-        <div class="apparatusAndpower">
-          <div class="inuse-left">
-            <p class="inuse-title">课中互动</p>
-            <div class="inuse-item" v-for="(item,index) in inuseList" :key="index">
-              <div class="item-img">
-                <svg class="inuseicon" aria-hidden="true">
-                  <use :xlink:href="item.icon"></use>
-                </svg>
-              </div>
-              <div class="item-box">
-                <p>{{item.value}}</p>
-                <span>{{item.title}}</span>
-              </div>
-            </div>
-          </div>
-          <div class="apparatus-box">
-            <p class="apparatus-title">设备与授权</p>
-            <div class="apparatus-item" v-for="(item,index) in powerList.facility" :key="index">
-              <div class="item-num">{{item.value}}</div>
-              <span class="item-title">{{item.name}}</span>
-            </div>
-          </div>
-          <div class="power-box">
-            <p class="apparatus-title">课堂与授权</p>
-            <div class="power-item" v-for="(item,index) in powerList.class" :key="index">
-              <div class="item-num">{{item.value}}</div>
-              <span class="item-title">{{item.name}}</span>
-            </div>
-          </div>
-        </div>
-        <div class="minxinbox">
-          <div class="lessons-box">
-            <div class="lessons-item" v-for="(items,indexs) in lessonsList" :key="indexs">
-              <div class="lessons-img-title">
-                <div class="lessons-img">
-                  <svg class="lessonsicon" aria-hidden="true">
-                    <use :xlink:href="items.icon"></use>
-                  </svg>
+                <div class="header-select-title">学区数据详情页</div>
+                <div class="header-select-box">
+                  <div class="backbtn">
+                    <el-button type="primary" @click="backbtn()">返回</el-button>
+                  </div>
                 </div>
-                <!-- <div class="lesson-title">{{items.title}}</div> -->
-              </div>
-              <div class="inquirybox"><span>{{items.content}}</span></div>
-              <div class="less-value">
-                <span class="lessvalue-num">{{items.value}}</span>
-                <!-- {{indexs+1 === lessonsList.length ? items.value:items.value}} -->
-                <span v-if="indexs+1 !== lessonsList.length">/堂课</span>
               </div>
-              <div class="result-box">
-                <svg class="lessRicon" aria-hidden="true">
-                  <use :xlink:href="items.value !==0 ? '#icon-zhengque2':'#icon-cuowu'"></use>
-                </svg>
-              </div>
-            </div>
-          </div>
-          <div class="inuse-right">
-            <div class="inuse-total">
-              <div class="class-title">多形态课堂</div>
-            </div>
-            <div class="echartsX">
-              <Xlines :lineData="echartData.xlines"></Xlines>
-            </div>
-            <!-- <div class="inuse-below">
+              <div class="contentbox">
+                <div class="school-name">
+                  <!-- <div class="statistics-address">位于:{{schoolData.region}} {{schoolData.province}}{{schoolData.city}}{{schoolData.dist}}</div> -->
+                  <span>{{schoolData.name ? schoolData.name:'暂无名称'}}</span>
+                  <div class="statistics-time">统计时间:<span>{{statisticsTime.year}} 年 {{statisticsTime.month}} 月
+                      {{statisticsTime.day}}日</span></div>
+                </div>
+                <div class="basicsdata-box">
+                  <div class="basicadata-item" v-for="(item,index) in basicaList" :key="index">
+                    <div class="item-title-images">
+                      <div class="item-title">{{item.title}}</div>
+                      <div class="item-images">
+                        <svg class="analysisicon" aria-hidden="true">
+                          <use :xlink:href="item.icon"></use>
+                        </svg>
+                      </div>
+                    </div>
+                    <div
+                      :class="[item.key === 'classtime' || item.key === 'participationnum' ? 'item-nums-special':'item-nums']">
+                      <p v-if="item.key === 'classtime' || item.key === 'participationnum'">{{item.value}}<span
+                          class="timetag">Min</span><br /><span>{{Math.round(item.value/60)}}<span
+                            class="timetag">H</span></span></p>
+                      <p v-else>{{item.value}}</p>
+                    </div>
+                  </div>
+                </div>
+                <div class="apparatusAndpower">
+                  <div class="inuse-left">
+                    <p class="inuse-title">课中互动</p>
+                    <div class="inuse-item" v-for="(item,index) in inuseList" :key="index">
+                      <div class="item-img">
+                        <svg class="inuseicon" aria-hidden="true">
+                          <use :xlink:href="item.icon"></use>
+                        </svg>
+                      </div>
+                      <div class="item-box">
+                        <p>{{item.value}}</p>
+                        <span>{{item.title}}</span>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="apparatus-box">
+                    <p class="apparatus-title">设备与授权</p>
+                    <div class="apparatus-item" v-for="(item,index) in powerList.facility" :key="index">
+                      <div class="item-num">{{item.value}}</div>
+                      <span class="item-title">{{item.name}}</span>
+                    </div>
+                  </div>
+                  <div class="power-box">
+                    <p class="apparatus-title">课堂与授权</p>
+                    <div class="power-item" v-for="(item,index) in powerList.class" :key="index">
+                      <div class="item-num">{{item.value}}</div>
+                      <span class="item-title">{{item.name}}</span>
+                    </div>
+                  </div>
+                </div>
+                <div class="minxinbox">
+                  <div class="lessons-box">
+                    <div class="lessons-item" v-for="(items,indexs) in lessonsList" :key="indexs">
+                      <div class="lessons-img-title">
+                        <div class="lessons-img">
+                          <svg class="lessonsicon" aria-hidden="true">
+                            <use :xlink:href="items.icon"></use>
+                          </svg>
+                        </div>
+                        <!-- <div class="lesson-title">{{items.title}}</div> -->
+                      </div>
+                      <div class="inquirybox"><span>{{items.content}}</span></div>
+                      <div class="less-value">
+                        <span class="lessvalue-num">{{items.value}}</span>
+                        <!-- {{indexs+1 === lessonsList.length ? items.value:items.value}} -->
+                        <span v-if="indexs+1 !== lessonsList.length">/堂课</span>
+                      </div>
+                      <div class="result-box">
+                        <svg class="lessRicon" aria-hidden="true">
+                          <use :xlink:href="items.value !==0 ? '#icon-zhengque2':'#icon-cuowu'"></use>
+                        </svg>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="inuse-right">
+                    <div class="inuse-total">
+                      <div class="class-title">多形态课堂</div>
+                    </div>
+                    <div class="echartsX">
+                      <Xlines :lineData="echartData.xlines"></Xlines>
+                    </div>
+                    <!-- <div class="inuse-below">
               <div class="below-item" v-for="(itemA,index) in classType" :key="index">
                 <p>{{itemA.name}}</p>
                 <div class="valuebox">
@@ -246,9 +291,9 @@
                 <div :class="itemA.class"></div>
               </div>
             </div> -->
-          </div>
-        </div>
-      </div>
+                  </div>
+                </div>
+              </div>
             </div>
           </div>
         </el-tab-pane>
@@ -282,6 +327,7 @@ import * as echarts from 'echarts'
 let props = defineProps({
   detailsData: Object,
   pattern:Object,
+  authDetailsData:Object,
 })
 console.log(props.detailsData, '子组件')
 let value1 = ref('')
@@ -787,6 +833,110 @@ let echartData = ref({
     ]
   }
 })
+let serviceTableData = ref([{}])
+let softwareTableData = ref([{}])
+let productData = ref([
+  {   
+    name: 'ezStation 2',
+    prodcode: '3222NIYD',    
+  },
+  {
+    name: 'HiTeach STD',
+    prodcode: 'J223IZ6M',    
+  },
+  {
+    name: 'HiTeach TBL',
+    prodcode: '3222C6D2',    
+  },
+  {
+    name: 'HiTeach PRO',
+    prodcode: 'J223IZAM',
+  },
+  {
+    name: 'HiTeach Lite',
+    prodcode: 'J2236ZCX',
+  },
+  {
+    name: 'HiTeach Mobile',
+    prodcode: '3222DNG2',
+  },
+  {
+    name: 'HiTeach Premium',
+    prodcode: '3222IAVN',
+  },
+  {
+    name: 'HiTeach5',
+    prodcode: 'BYJ6LZ6Z',
+  },
+  {
+    name: 'HiTeachCC',
+    prodcode: 'LZLL6ZEI',
+  },
+  {
+    name: proxy.$t(`auth.YMPCVCIM`),
+    prodcode: 'YMPCVCIM',
+  },
+  {
+    name: proxy.$t(`auth.IPDYZYLC`),
+    prodcode: 'IPDYZYLC',
+  },
+  {
+    name: proxy.$t(`auth._3CLYJ6NP`),
+    prodcode: '3CLYJ6NP',
+  },
+  {
+    name: proxy.$t(`auth.IPALJ6NY`),
+    prodcode: 'IPALJ6NY',
+  },
+  {
+    name: proxy.$t(`auth.VABAJ6NV`),
+    prodcode: 'VABAJ6NV',
+  },
+  {
+    name: proxy.$t(`auth._0VPBDZPG`),
+    prodcode: '0VPBDZPG',
+  },
+  {
+    name: proxy.$t(`auth.B9GPJ6NY`),
+    prodcode: 'B9GPJ6NY',
+  },
+  {
+    name: proxy.$t(`auth.LY9AJ6NY`),
+    prodcode: 'LY9AJ6NY',
+  },
+  {
+    name: proxy.$t(`auth.YL9CJ6NY`),
+    prodcode: 'YL9CJ6NY',
+  },
+  {
+    name: proxy.$t(`auth.LL9MJ6NY`),
+    prodcode: 'LL9MJ6NY',
+  },
+  {
+    name: proxy.$t(`auth.B6V5J6NP`),
+    prodcode: 'B6V5J6NP',
+  },
+  {
+    name: proxy.$t(`auth.LSZYJ6NA`),
+    prodcode: 'LSZYJ6NA',
+  },
+  {
+    name: proxy.$t(`auth.CVGPJ6NN`),
+    prodcode: 'CVGPJ6NN',
+  },
+  {
+    name: proxy.$t(`auth.VDPGJ6NC`),
+    prodcode: 'VDPGJ6NC',
+  },
+  {
+    name: proxy.$t(`auth.YPXSJ6NJ`),
+    prodcode: 'YPXSJ6NJ',
+  },
+])
+let isShowAuth = true
+
+  
+
 function init (againvalue) {
   console.log(againvalue,'是什么值')
   // let totalsArr = []
@@ -953,6 +1103,65 @@ function init (againvalue) {
      console.log(trimData,'清理过后的')
      tableData.value=trimData
   }
+
+  // 服務授權
+  // 取得指定學校的授權資料
+  let foundItem = propsbox.authDetailsData.find(item => item.schId === propsbox.detailsData.schoolId);
+  if (foundItem !== undefined) {
+    isShowAuth = true;
+    let serviceArr = [];
+    foundItem.authService.forEach((item) => {
+      //debugger;
+      // 依照對應的產品代碼取得產品名稱
+      let productDataItem = productData.value.find(itempd => itempd.prodcode === item.prodCode);
+      if (productDataItem !== undefined) {
+        let expireDate = "";
+        if (item.endDate === 0) {
+          expireDate = '服務到期日 : 無限期';
+        } else {
+          expireDate = '服務到期日 : ' + proxy.$common.timestampToTime(item.endDate, 'all')
+        }
+        let serviceItem = {
+          title: productDataItem.name,
+          //expireDate: '服務到期日 : ' + proxy.$common.timestampToTime(item.startDate, 'all') + ' - ' + proxy.$common.timestampToTime(item.endDate, 'all')
+          expireDate: expireDate
+        }
+        serviceArr.push(serviceItem);
+      }
+    })
+    serviceTableData.value = serviceArr;
+
+    // 軟體授權    
+    let softwareArr = [];
+    foundItem.authSerial.forEach((item) => {      
+      // 依照對應的產品代碼取得產品名稱
+      let productDataItem = productData.value.find(itempd => itempd.prodcode === item.prodCode);
+      if (productDataItem !== undefined) {
+        let expireDate = "";
+        if (item.endDate === 0) {
+          expireDate = '服務到期日 : 無限期';
+        } else {
+          expireDate = '服務到期日 : ' + proxy.$common.timestampToTime(item.endDate, 'all')
+        }
+        let softwareItem = {
+          title: productDataItem.name,
+          expireDate: expireDate
+        }
+        softwareArr.push(softwareItem);
+      }
+    })
+    softwareTableData.value = softwareArr;
+
+
+  }else{
+    isShowAuth = false;
+  }
+ 
+  
+
+
+
+
 }
 function detailsSchool(value){
   console.log(value,'查看要传的数据')
@@ -970,6 +1179,7 @@ init()
   width: 100%;
   position: relative;
 }
+
 .header-select {
   width: 100%;
   height: 60px;
@@ -977,6 +1187,7 @@ init()
   background-color: #fff;
   box-shadow: 0 2px 5px #e9e9e9;
 }
+
 .header-select-title,
 .header-select-box {
   display: inline-block;
@@ -986,14 +1197,17 @@ init()
   padding-left: 1%;
   line-height: 60px;
 }
+
 .header-select-title {
   font-size: 18px;
   font-weight: bold;
 }
+
 .header-select-box {
   text-align: right;
   padding-right: 1%;
 }
+
 .select-box-time,
 .select-box-area,
 .select-box-school {
@@ -1001,6 +1215,7 @@ init()
   vertical-align: top;
   padding-top: 1%;
 }
+
 .contentbox {
   width: 98%;
   /* padding: 1% 1%; */
@@ -1009,10 +1224,12 @@ init()
   margin: 0.5% 1%;
   padding-bottom: 0.5%;
 }
+
 .area-contentbox {
   width: 100%;
   margin: 0%;
 }
+
 .analysisicon {
   width: 2.3em;
   height: 2.3em;
@@ -1022,6 +1239,7 @@ init()
   margin-right: 25px;
   margin-left: 0px; */
 }
+
 .basicsdata-box {
   overflow: hidden;
   width: 100%;
@@ -1030,9 +1248,9 @@ init()
   flex-wrap: nowrap;
   justify-content: space-between;
 }
+
 .apparatusAndpower {
-  width: 100%;
-  display: flex;
+  width: 100%;display: flex;
   flex-wrap: nowrap;
   line-height: 60px;
   /* justify-content: space-between; */
@@ -1083,7 +1301,7 @@ init()
   border-bottom: 1px dashed #e9e9e9;
   font-size: 16px;
   font-weight: bold;
-  color: #fff;
+  color:black;
 }
 .item-num {
   font-size: 20px;
@@ -1144,6 +1362,16 @@ init()
   overflow: hidden;
   overflow-y: auto;
 }
+.lessons-box-maxh {
+  width: 50%;
+  
+  border-radius: 5px;
+  background: #fff;
+  box-shadow: 0 2px 5px #e9e9e9;
+  margin: 0.5% 1%;
+  overflow: hidden;
+  overflow-y: auto;
+}
 .lessons-item {
   width: 100%;
   padding: 0% 10%;
@@ -1503,5 +1731,61 @@ init()
 .contentbox .el-divider {
   margin: 10px 0;
 }
+.data-tables .header-class,
+  .data-tables 
+  .el-table-v2__row-cell 
+  {
+    /* width: v-bind(cellWidth + "%") !important; */
+    justify-content: center;
+    text-align: center;
+  }
+  .data-tables .general {
+    /* width: v-bind(cellWidth + "%") !important; */
+    justify-content: center;
+    text-align: center;
+  }
+  .data-tables .generalid {    
+    justify-content: left;
+    text-align: left;
+  }
+  .data-tables .btn-class {
+    /* width: v-bind(cellWidth + "%") !important; */
+    justify-content: center;
+    text-align: center;
+  }
+  .data-tables {
+    width: 93%;
+    padding: 0px 20px;
+    height: 100%;
+  }
+  .el-table__body, .el-scrollbar__view {
+    width: 100%;
+  }
+  .custom-card {
+  line-height: 60px;
+  width: 95%;
+  margin-top: 10px;
+  margin-left: 20px;
+  }
+  .card-column {
+    float:left;
+    text-align: left;   
+    /* margin-right: 80px;   */
+    /* width: 30%; */
+  }
+  .card-column-icon {
+    float:left;
+    text-align: left;   
+    margin-right: 40px;
+    width: 8%;  
+  }
+  .cardicon {
+  width: 3em;
+  height: 3em;
+  vertical-align: -0.5em;
+  fill: currentColor;
+}
+  
+  
 </style>
 

+ 6 - 3
TEAMModelBI/ClientApp/src/view/product/index.vue

@@ -219,7 +219,7 @@
     </div>
   </div>
   <div v-else-if="showState==='details'">
-    <Details @myback="changStateshow" :detailsData="detailsData" :pattern="pattern"></Details>
+    <Details @myback="changStateshow" :detailsData="detailsData" :pattern="pattern" :authDetailsData="authDetailsData"></Details>
   </div>
   <div class="dialog-filter" v-if="exportstate">
     <el-dialog v-model="exportstate" title="筛选导出" width="35%">
@@ -731,6 +731,7 @@ let exportStandard = ref([
   { title: '授权类型', value: 0, key: 'powertype', option: [{ name: '全部', value: 'all' }, { name: '试用', value: 'test' }, { name: '已授权', value: 'power' }] },
 ])
 let detailsData = ref()
+let authDetailsData = ref()
 let pattern=ref({
    state:'school',
    data:''
@@ -802,6 +803,7 @@ function changeState (value) {
   pattern.value.state=clickNum.value.subject === 0 ? 'school':clickNum.value.subject === 1 ? 'area':''
   clickNum.value.subject === 1 ? pattern.value.data=primevalData.value:''
   detailsData.value = value.rowData
+  //debugger
 }
 function changStateshow (value) {
   console.log(value, '状态改变')
@@ -884,7 +886,7 @@ function serachToresult(startTime, endTime, product, schools, unit) {
           } else { item.name = '暂无'; }
         });
 
-
+        
         filterdata.value = [...res.geo, ...res.data];
         primevalData.value = [...res.geo, ...res.data];
       }
@@ -1021,7 +1023,8 @@ function serachToresult(startTime, endTime, product, schools, unit) {
         item.name = item.geoInfo;
       }
     })
-    searchLoading.value = false;
+    authDetailsData.value = res.auth;
+    searchLoading.value = false;    
   }).catch((err) => {
     ElMessage.error('API异常,数据获取失败')
   })

+ 3 - 3
TEAMModelBI/TEAMModelBI.csproj

@@ -65,9 +65,9 @@
 		<SpaRoot>ClientApp\</SpaRoot>
 		<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
 		<UserSecretsId>078b5d89-7d90-4f6a-88fc-7d96025990a8</UserSecretsId>
-		<Version>5.2404.03</Version>
-		<AssemblyVersion>5.2404.03.1</AssemblyVersion>
-		<FileVersion>5.2404.03.1</FileVersion>
+		<Version>5.2404.10</Version>
+		<AssemblyVersion>5.2404.10.1</AssemblyVersion>
+		<FileVersion>5.2404.10.1</FileVersion>
 		<Description>TEAMModelBI(BI)</Description>
 		<PackageReleaseNotes>BI版本说明版本切换标记2022000908</PackageReleaseNotes>
 		<PackageId>TEAMModelBI</PackageId>

+ 3 - 3
TEAMModelOS.FunctionV4/TEAMModelOS.FunctionV4.csproj

@@ -5,9 +5,9 @@
 		<OutputType>Exe</OutputType>
 		<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput>
 		<SignAssembly>true</SignAssembly>
-		<Version>5.2404.03</Version>
-		<AssemblyVersion>5.2404.03.1</AssemblyVersion>
-		<FileVersion>5.2404.03.1</FileVersion>
+		<Version>5.2404.10</Version>
+		<AssemblyVersion>5.2404.10.1</AssemblyVersion>
+		<FileVersion>5.2404.10.1</FileVersion>
 		<PackageId>TEAMModelOS.FunctionV4</PackageId>
 		<Authors>teammodel</Authors>
 		<Company>醍摩豆(成都)信息技术有限公司</Company>

+ 2 - 1
TEAMModelOS.SDK/Extension/JwtAuthExtension.cs

@@ -15,7 +15,7 @@ namespace TEAMModelOS.SDK.Extension
 {
     public static class JwtAuthExtension
     {
-        public static string CreateAuthToken(string issuer, string id, string name, string picture, string salt, string scope, string Website, string areaId = "", string schoolID = "", string standard = "", string[] roles = null, string[] permissions = null, int expire = 1,int year=-1)
+        public static string CreateAuthToken(string issuer, string id, string name, string picture, string salt, string scope, string Website,int timezone, string areaId = "", string schoolID = "", string standard = "", string[] roles = null, string[] permissions = null, int expire = 1,int year=-1)
         {
             // 設定要加入到 JWT Token 中的聲明資訊(Claims)  
             var payload = new JwtPayload {
@@ -30,6 +30,7 @@ namespace TEAMModelOS.SDK.Extension
                 { "standard",standard} ,//登入者的能力点标准
                 { "scope",scope},  //登入者的入口类型。 (teacher 教师端登录的醍摩豆ID、tmduser学生端登录的醍摩豆ID、student学生端登录校内账号的学生ID)
                 { "area",areaId==null?"":areaId},
+                { "timezone",timezone},
                 { JwtRegisteredClaimNames.Website,Website},
             };
             //学生入学年

+ 4 - 4
TEAMModelOS.SDK/Models/Service/Common/TeacherService.cs

@@ -31,7 +31,7 @@ namespace TEAMModelOS.Services
     public static class TeacherService
     {
         public static async Task<TeacherInfo> TeacherInfoLite(AzureCosmosFactory _azureCosmos,  string name, string picture, string id,
-           AzureStorageFactory _azureStorage, Option _option, AzureRedisFactory _azureRedis, string ip, HttpTrigger _httpTrigger, string lang)
+           AzureStorageFactory _azureStorage, Option _option, AzureRedisFactory _azureRedis, string ip, HttpTrigger _httpTrigger, string lang,int timezone)
         {
             Teacher teacher = null;
             string defaultschool = null;
@@ -189,7 +189,7 @@ namespace TEAMModelOS.Services
             catch { }
 
             //換取AuthToken,提供給前端
-            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTeacher, standard:  "", roles: roles.ToArray(), expire: 1);
+            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTeacher, standard:  "", roles: roles.ToArray(), expire: 1);
 
             //取得Teacher Blob 容器位置及SAS 
             await _azureStorage.GetBlobContainerClient(id).CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建Teacher私有容器,如存在則不做任何事,保障容器一定存在
@@ -204,7 +204,7 @@ namespace TEAMModelOS.Services
             };
         }
         public static async Task<TeacherInfo> TeacherInfo(AzureCosmosFactory _azureCosmos, Teacher teacher, string name, string picture, string id,
-            AzureStorageFactory _azureStorage, Option _option, AzureRedisFactory _azureRedis, string ip, HttpTrigger _httpTrigger, string lang)
+            AzureStorageFactory _azureStorage, Option _option, AzureRedisFactory _azureRedis, string ip, HttpTrigger _httpTrigger, string lang,int timezone)
         {
             List<object> schools = new List<object>();
             List<AreaDto> areas = new List<AreaDto>();
@@ -489,7 +489,7 @@ namespace TEAMModelOS.Services
                 }
             }
             //換取AuthToken,提供給前端
-            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTeacher, standard: areaa != null ? areaa.standard : "", roles: roles.ToArray(), expire: 1);
+            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTeacher, standard: areaa != null ? areaa.standard : "", roles: roles.ToArray(), expire: 1);
 
             //用户在线记录
             try

+ 1 - 0
TEAMModelOS.SDK/Models/Service/LessonService.cs

@@ -101,6 +101,7 @@ namespace TEAMModelOS.SDK.Models.Service
                 DateTime dateTimeB = Convert.ToDateTime(DateTimeOffset.UtcNow.ToString("D")).AddDays(1);
                 double dayOf00_00_00 = (dateTimeA - dateTime1970).TotalMilliseconds;
                 double day1Of00_00_00 = (dateTimeB - dateTime1970).TotalMilliseconds;
+              
                 dict.Add(">=.startTime", dayOf00_00_00);
                 dict.Add("<.startTime", day1Of00_00_00);
             }

+ 3 - 3
TEAMModelOS.SDK/TEAMModelOS.SDK.csproj

@@ -2,9 +2,9 @@
 
 	<PropertyGroup>
 		<TargetFramework>net6.0</TargetFramework>
-		<Version>5.2404.03</Version>
-		<AssemblyVersion>5.2404.03.1</AssemblyVersion>
-		<FileVersion>5.2404.03.1</FileVersion>
+		<Version>5.2404.10</Version>
+		<AssemblyVersion>5.2404.10.1</AssemblyVersion>
+		<FileVersion>5.2404.10.1</FileVersion>
 		<PackageReleaseNotes>发版</PackageReleaseNotes>
 	</PropertyGroup>
 

+ 4 - 1
TEAMModelOS/ClientApp/public/lang/en-US.js

@@ -7625,7 +7625,7 @@ const LANG_EN_US = {
             subjectP: 'Subject Percentage'
         },
         class: {
-            total: 'Year Total Data',
+            total: 'This Year',
             lastWeek: 'Last week',
             nowMonth: 'This month',
             vitality: 'Active Status',
@@ -7924,6 +7924,7 @@ const LANG_EN_US = {
             field17: 'Please enter a rule name:',
             field18: 'Please enter a description of the rule:',
             field19: 'Describe the details:',
+            field20: 'Please enter the number of reviews',
         },
         profile: 'Introduction:',
         host: 'Organizer:',
@@ -8069,6 +8070,8 @@ const LANG_EN_US = {
             field50: 'Failed to start announcement:',
             field51: 'Failed to edit:',
             field52: 'Judging has begun and cannot be modified.:',
+            field53: 'No review criteria set',
+            field54: 'Please complete the review criteria',
         },
         buttonInfo: {
             text1: 'Add Judging Expert:',

+ 3 - 0
TEAMModelOS/ClientApp/public/lang/zh-CN.js

@@ -7926,6 +7926,7 @@ const LANG_ZH_CN = {
             field17: '请输入规则名称',
             field18: '请输入规则描述',
             field19: '对分项内容进行描述',
+            field20: '请输入评审次数',
         },
         profile: '简介',
         host: '主办',
@@ -8071,6 +8072,8 @@ const LANG_ZH_CN = {
             field50: '开启公示失败',
             field51: '编辑失败',
             field52: '评审已开始,无法修改',
+            field53: '未设置评审标准',
+            field54: '请完善评审标准',
         },
         buttonInfo: {
             text1: '添加评审专家',

+ 5 - 2
TEAMModelOS/ClientApp/public/lang/zh-TW.js

@@ -5349,7 +5349,7 @@ const LANG_ZH_TW = {
                 nojoin: "您沒有加入該活動",
                 noQamode: "此次紙本測驗沒有圖片示例",
             },
-            answer2: '答',
+            answer2: '答',
         },
         queNaire: {
             submitSuccess: '提交成功',
@@ -5635,7 +5635,7 @@ const LANG_ZH_TW = {
         surveyProgress: '問卷進度',
         addItem: '新增題目',
         single: '單選題',
-        multiple: '複選',
+        multiple: '複選',
         judge: '是非題',
         subjective: '問答題',
         defaultName: '預設問卷名稱',
@@ -7925,6 +7925,7 @@ const LANG_ZH_TW = {
             field17: '請輸入規則名稱',
             field18: '請輸入規則描述',
             field19: '對細項內容進行描述',
+            field20: '請輸入評審次數',
         },
         profile: '簡介',
         host: '主辦',
@@ -8070,6 +8071,8 @@ const LANG_ZH_TW = {
             field50: '開始公布失敗',
             field51: '編輯失敗',
             field52: '評審已開始,無法修改',
+            field53: '未設定評審標準',
+            field54: '請完成評審標準',
         },
         buttonInfo: {
             text1: '新增評審專家',

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

@@ -34,6 +34,12 @@ export default {
     findArtResults: function (data) {
         return post('/school/art/review', data)
     },
+    genArtPdfProgress: function (data) {
+        return post('/school/art/gen-pdf-process', data)
+    },
+    genArtPdf: function (data) {
+        return post('/school/art/gen-pdf', data)
+    },
     /* 查询学生提交作业 */
     findArtWork: function (data) {
         return post('/common/art/find-summary-by-work', data)

+ 4 - 0
TEAMModelOS/ClientApp/src/api/http.js

@@ -176,6 +176,10 @@ function handleHeader(config) {
     config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('access_token')
     config.headers['Content-Type'] = 'application/json'
     config.headers['lang'] = localStorage.getItem('local') || navigator.language.toLowerCase()
+    // 获取当前客户端所在时区偏移数据
+    var MyDate = new Date();
+    var MyOffset = (MyDate.getTimezoneOffset()) / -60;
+    config.headers['Time-Zone'] = MyOffset
 
     let isNeedAuth = true
     for (let apiUrl of NO_AUTH_API) {

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

@@ -350,7 +350,7 @@
 
 				let bodyElement = document.body;
 				console.log(bodyElement.style, "body");
-				bodyElement.style.fontFamily = localStorage.local === "zh-tw" ? "微软雅黑" : "Hm";
+				bodyElement.style.fontFamily = localStorage.local === "zh-tw" ? "微軟正黑體" : "Hm";
 			},
 			getAnalysisAuth() {
 				const proInfo = this.$store.state.user?.schoolProfile?.svcStatus || null;
@@ -370,7 +370,9 @@
 				if(areaId) {
 					areaRoute = await this.getAreaRoute(areaId)
 				}
-				let url = `${window.location.host === 'localhost:5001' ? 'http://localhost:8081' : 'https://contest-test.teammodel.cn'}/${areaRoute || 'teammodel'}/home/homePage?token=${localStorage.getItem("auth_token")}`
+				// 2024.4.11 改为跳转当前学校下的赛课首页
+				let schoolId = this.$store.state.userInfo.hasSchool ? this.$store.state.userInfo.schoolCode : 'teammodel'
+				let url = `${window.location.host === 'localhost:5001' ? 'http://localhost:8081' : 'https://contest-test.teammodel.cn'}/${schoolId}/home/homePage?token=${localStorage.getItem("auth_token")}`
 				window.open(url, "", "noopener");
 			},
 			getAreaRoute(areaId) {

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

@@ -50,7 +50,7 @@
         <div>
             <p class="event-type">
                 {{evtType == 'PopQuesLoad' ? $t("studentWeb.hiteachNote.qA") : $t("studentWeb.hiteachNote.qaAgain")}}
-                <span v-if="irsData.quesType === 'subjective'">({{ $t('evaluation.subjective') }}-{{ $t(`evaluation.newExercise[${irsData.ansType}]`) }})</span>:
+                <span v-if="irsData.quesType === 'subjective'">({{ $t('evaluation.subjective') }}-{{ $t(`evaluation.newExercise.answerType[${irsData.ansType}]`) }})</span>:
             </p>
             <div class="show-ques file-box" v-if="ansData.length">
                 <template v-if="irsData.quesType === 'subjective' && irsData.ansType != 'text' && irsData.ansType != 'text_Image'">

+ 3 - 3
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReportCharts/AudioRecorder.vue

@@ -135,8 +135,7 @@ export default {
             // 停止录音后马上上传到blob
             const blob = this.recorder.getWAVBlob()
             const newBlob = new Blob([blob], {type: 'audio/wav'})
-            let random = this.$jsFn.getBtwRandom(0, 10)
-            const fileBlob = new File([newBlob], `录音${this.index}(${random}).wav`,{type: 'audio/wav'})
+            const fileBlob = new File([newBlob], `audio${this.index + 1}.wav`,{type: 'audio/wav'})
             
             let { scope, cntr, examId, subjectId, stuId } = this.getComposeData
             let sas = '?' + this.sasData.sas
@@ -148,7 +147,8 @@ export default {
                 checkSize: false
             }).then(res => {
                 that.currentUrl = res.blob
-                that.fileFullPath = res.url + that.sasData.sas
+                let random = this.$jsFn.getBtwRandom(0, 1000000)
+                that.fileFullPath = res.url + that.sasData.sas + '&t=' + random
                 document.getElementById(`audioId${that.audioId}`).setAttribute('src', that.fileFullPath)
                 that.$emit("dataGet", that.currentUrl, that.index)
                 that.$Message.warning(that.$t('cusMgt.saveOk'))

+ 7 - 4
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/LessonTestReport.vue

@@ -702,6 +702,7 @@
                     this.ansData = await this.getItem(this.examInfo.stuAns[0])
                     let code = this.getItemTitle.scope === 'school' ? this.getItemTitle.school : this.getItemTitle.creatorId
                     let sas = await this.$tools.getBlobSas(code)
+                    let urlHost = `${sas.url}/${code}/exam/${this.getItemTitle.id}/${this.getPaperInfo.subjectId}/${this.$store.state.userInfo.sub}`
                     let examNum = 0
                     for (let i = 0; i < exam.length; i++) {
                         if (!exam[i].repair) {
@@ -736,8 +737,9 @@
                                     
                                     if(exam[i].children[c].type === 'subjective' && exam[i].children[c]?.stuAns.length) {
                                         // 问答题文件只上传一个
-                                        exam[i].children[c].blobUrl = `${sas.url}/${code}${exam[i].children[c].stuAns[0]}?${sas.sas}`
+                                        // 问答题:课中的地址需截取文件名称重新拼接地址,因此统一重新拼接处理
                                         exam[i].children[c].name = exam[i].children[c].stuAns[0].substr(exam[i].children[c].stuAns[0].lastIndexOf(`/${this.$store.state.userInfo.sub}/`) + (this.$store.state.userInfo.sub.length + 2))
+                                        exam[i].children[c].blobUrl = `${urlHost}/${exam[i].children[c].name}?${sas.sas}`
                                         exam[i].children[c].fileType = exam[i].children[c]?.answerType === 'file' ? this.getFileType(exam[i].children[c].stuAns[0]) : ''
                                     }
                                 }
@@ -750,8 +752,9 @@
                             exam[i].getScore = this.examInfo.stuScore[examNum]
                             if(exam[i].type === 'subjective' && exam[i]?.stuAns.length) {
                                 // 问答题文件只上传一个
-                                exam[i].blobUrl = `${sas.url}/${code}${exam[i].stuAns[0]}?${sas.sas}`
+                                // 问答题:课中的地址需截取文件名称重新拼接地址,因此统一重新拼接处理
                                 exam[i].name = exam[i].stuAns[0].substr(exam[i].stuAns[0].lastIndexOf(`/${this.$store.state.userInfo.sub}/`) + (this.$store.state.userInfo.sub.length + 2))
+                                exam[i].blobUrl = `${urlHost}/${exam[i].name}?${sas.sas}`
                                 exam[i].fileType = exam[i]?.answerType === 'file' ? this.getFileType(exam[i].name) : ''
                             }
                         }
@@ -852,11 +855,11 @@
                     let fullUrl = code.blob.replace("/ans.json", "")
                     let newData = await this.$jsFn.handleStudentAnswer(videoBlob, fullUrl, sas.sas)
                     // $jsFn.handleStudentAnswer将未作答的修改为['未作答'],此处修改为[]
-                    newData.forEach((item, index) => {
+                    /* newData.forEach((item, index) => {
                         if(item.includes(this.$t('learnActivity.score.noStuAns'))) {
                             newData[index] = []
                         }
-                    })
+                    }) */
                     return newData
                 } else {
                     return []

+ 8 - 10
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperTest.vue

@@ -210,7 +210,7 @@
                                 <div class="compose-content" v-if="getQue(queNo).parent !== undefined">
                                     <div class="questionType">
                                         <span>{{getTestType(getQue(queNo).parentInfo.type)}}</span>
-                                        <span v-if="getQue(queNo).type === 'subjective'">({{ showExam[queNo].answerType }})</span>
+                                        <span v-if="getQue(queNo).type === 'subjective'">({{ $t(`evaluation.newExercise.answerType[${showExam[queNo].answerType}]`) }})</span>
                                     </div>
                                     <div class="compose-box">
                                         <span v-if="isWrong" @click="changeStar(!getQue(queNo).star, queNo, true)" style="cursor: pointer;">
@@ -653,7 +653,8 @@
                 isDisorder: false, //艺术评测乱序作答
                 noExam: false,
                 maxSize: 15*1024*1024, //文件最大
-                sasUpload: undefined
+                sasUpload: undefined,
+                fileTypes: ['JPG', 'JPEG', 'BMP', 'TIF', 'PNG', 'GIF', 'SVG', 'MP4', 'DVD', 'MPEG2', 'MPEG4', 'PPT', 'PPTX', 'DOC', 'DOCX', 'PDF', 'XLS', 'XLSX', 'MP3', 'OGG', 'WAV']
             };
         },
         methods: {
@@ -1281,14 +1282,11 @@
             // 文件展示
             async customUpload(file) {
                 // 问答题文件只上传一个
-                // 有限制格式
-                /* if (this.extLimit.length) {
-                    let extension = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase()
-                    if (!this.extLimit.includes(extension)) {
-                        this.$Message.warning(this.$t("studentWeb.homework.extLimit"))
-                        return false
-                    }
-                } */
+                let extension = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toUpperCase()
+                if(!this.fileTypes.includes(extension)) {
+                    this.$Message.warning(this.$t("studentWeb.homework.extLimit"))
+                    return false
+                }
                 if (this.maxSize < file.size) {
                     this.$Message.warning(this.$t("studentWeb.homework.sizeOver"))
                     return false

+ 3 - 1
TEAMModelOS/ClientApp/src/utils/js-fn.js

@@ -408,8 +408,10 @@ function handleStudentAnswer(fullUrl, richPrefix, sas) {
                         ans[index] = [app.$t('learnActivity.score.ansErr')]
                     }
                     // 未作答
+                    // 2024.4.7 题型增加问答题,保存字段为blob地址,需判断是否作答,因此返回[]
                     else if (!item.length) {
-                        ans[index] = [app.$t('learnActivity.score.noStuAns')]
+                        // ans[index] = [app.$t('learnActivity.score.noStuAns')]
+                        ans[index] = []
                     }
                     // 处理富文本中多媒体
                     else {

+ 358 - 331
TEAMModelOS/ClientApp/src/view/artexam/DataView.vue

@@ -1,340 +1,367 @@
 <template>
-  <div class="data-view-wrap">
-    <div class="data-count-wrap">
-      <!-- 学科统计 -->
-      <div class="data-count-item">
-        <p class="data-value">
-          <countTo :startVal='0' :endVal='artInfo.subjects ? artInfo.subjects.length : 0' :duration='1000'></countTo>
-        </p>
-        <p class="data-text">
-          {{$t('learnActivity.simple.sjLabel')}}
-        </p>
-      </div>
-      <!-- 班级 -->
-      <div class="data-count-item">
-        <p class="data-value">
-          <countTo :startVal='0' :endVal='artInfo.classes ? artInfo.classes.length : 0' :duration='1000'></countTo>
-        </p>
-        <p class="data-text">
-          {{$t('learnActivity.simple.classLabel')}}
-        </p>
-      </div>
-      <!-- 总人数 -->
-      <div class="data-count-item">
-        <p class="data-value">
-          <countTo :startVal='0' :endVal='stuCount' :duration='1000'></countTo>
-        </p>
-        <p class="data-text">
-          {{$t('learnActivity.simple.totalPeople')}}
-        </p>
-      </div>
-      <!-- 缺考人数 -->
-      <div class="data-count-item">
-        <p class="data-value" style="color:orange !important">
-          <countTo :startVal='0' :endVal='lostCount' :duration='1000'></countTo>
-        </p>
-        <p class="data-text">
-          {{$t('learnActivity.simple.missExam')}}
-        </p>
-      </div>
-      <!-- 已评分 -->
-      <div class="data-count-item">
-        <p class="data-value">
-          -
-        </p>
-        <p class="data-text">
-          已评分
-        </p>
-      </div>
-      <!-- 平均分 -->
-      <div class="data-count-item">
-        <p class="data-value">
-          -
-        </p>
-        <p class="data-text">
-          平均分
-        </p>
-      </div>
-    </div>
-    <div class="table-header-wrap">
-      <!-- <span>{{$t('ae.ae6')}}:</span> -->
-      <Select v-model="classId" style="width: 200px">
-        <Option v-for="item in classList" :value="item.id" :key="item.id">{{ item.name }}</Option>
-      </Select>
-      <Input search v-model="keyword" placeholder="搜索学生..." style="margin-left:10px;width: 200px" @on-search="searchStudent" />
-    </div>
-    <div class="table-scroll-wrap">
-      <vuescroll>
-        <Table :columns="columns" :data="tableDataShow" border @on-row-dblclick="onRowClick">
-          <template slot-scope="{ row , column }" v-for="s in slotList" :slot="s">
-            <div style="padding:5px 5px">
-              <ul>
-                <li class="quota-score-item" v-for="task in row[s]" :key="task.taskId">
-                  {{task.quotaName}}
-                  <span class="quota-score-value" v-if="task.score >= 0">{{task.score}}分</span>
-                  <span class="quota-score-value" v-else>-</span>
-                </li>
-              </ul>
-              <p v-if="!row[s] || !row[s].length" class="no-score-tag">
-                未评分
-              </p>
-            </div>
-          </template>
-        </Table>
-      </vuescroll>
-    </div>
-    <!-- 分页 -->
-    <div class="page-wrap">
-      <Page show-total size="small" :current="currentPage" :total="tableData.length" :page-size="pageSize" @on-change="pageChange" />
-    </div>
-     <Modal v-model="personArtModal" width="760" footer-hide>
-        <template #header>
-            <p style="text-align:center">
-                <span>艺术评测个人报告</span>
-            </p>
-        </template>
-        <div v-if="personArtModal">
-            <BasePersonArtReport></BasePersonArtReport>
-        </div>
-    </Modal>
-  </div>
+	<div class="data-view-wrap">
+		<div class="data-count-wrap">
+			<!-- 学科统计 -->
+			<div class="data-count-item">
+				<p class="data-value">
+					<countTo :startVal="0" :endVal="artInfo.subjects ? artInfo.subjects.length : 0" :duration="1000"></countTo>
+				</p>
+				<p class="data-text">
+					{{ $t("learnActivity.simple.sjLabel") }}
+				</p>
+			</div>
+			<!-- 班级 -->
+			<div class="data-count-item">
+				<p class="data-value">
+					<countTo :startVal="0" :endVal="artInfo.classes ? artInfo.classes.length : 0" :duration="1000"></countTo>
+				</p>
+				<p class="data-text">
+					{{ $t("learnActivity.simple.classLabel") }}
+				</p>
+			</div>
+			<!-- 总人数 -->
+			<div class="data-count-item">
+				<p class="data-value">
+					<countTo :startVal="0" :endVal="stuCount" :duration="1000"></countTo>
+				</p>
+				<p class="data-text">
+					{{ $t("learnActivity.simple.totalPeople") }}
+				</p>
+			</div>
+			<!-- 缺考人数 -->
+			<div class="data-count-item">
+				<p class="data-value" style="color: orange !important">
+					<countTo :startVal="0" :endVal="lostCount" :duration="1000"></countTo>
+				</p>
+				<p class="data-text">
+					{{ $t("learnActivity.simple.missExam") }}
+				</p>
+			</div>
+			<!-- 已评分 -->
+			<div class="data-count-item">
+				<p class="data-value">-</p>
+				<p class="data-text">已评分</p>
+			</div>
+			<!-- 平均分 -->
+			<div class="data-count-item">
+				<p class="data-value">-</p>
+				<p class="data-text">平均分</p>
+			</div>
+		</div>
+		<div class="table-header-wrap">
+			<!-- <span>{{$t('ae.ae6')}}:</span> -->
+			<Select v-model="classId" style="width: 200px">
+				<Option v-for="item in classList" :value="item.id" :key="item.id">{{ item.name }}</Option>
+			</Select>
+			<Input search v-model="keyword" placeholder="搜索学生..." style="margin-left: 10px; width: 200px" @on-search="searchStudent" />
+			<!-- <Button type="primary" style="margin-left: 10px" @click="onGenPdf" :loading="curGenPdfStatus === 1">{{ genPdfStatusArr[curGenPdfStatus] }}</Button> -->
+		</div>
+		<div class="table-scroll-wrap">
+			<vuescroll>
+				<Table :columns="columns" :data="tableDataShow" border @on-row-dblclick="onRowClick">
+					<template slot-scope="{ row, column }" v-for="s in slotList" :slot="s">
+						<div style="padding: 5px 5px">
+							<ul>
+								<li class="quota-score-item" v-for="task in row[s]" :key="task.taskId">
+									{{ task.quotaName }}
+									<span class="quota-score-value" v-if="task.score >= 0">{{ task.score }}分</span>
+									<span class="quota-score-value" v-else>-</span>
+								</li>
+							</ul>
+							<p v-if="!row[s] || !row[s].length" class="no-score-tag">未评分</p>
+						</div>
+					</template>
+				</Table>
+			</vuescroll>
+		</div>
+		<!-- 分页 -->
+		<div class="page-wrap">
+			<Page show-total size="small" :current="currentPage" :total="tableData.length" :page-size="pageSize" @on-change="pageChange" />
+		</div>
+		<Modal v-model="personArtModal" width="760" footer-hide>
+			<template #header>
+				<p style="text-align: center">
+					<span>艺术评测个人报告</span>
+				</p>
+			</template>
+			<div v-if="personArtModal">
+				<BasePersonArtReport></BasePersonArtReport>
+			</div>
+		</Modal>
+	</div>
 </template>
 
 <script>
-import countTo from 'vue-count-to'
-import BasePersonArtReport from './BasePersonArtReport.vue'
-export default {
-  components: {
-    countTo,
-    BasePersonArtReport
-  },
-  props: {
-    artInfo: {
-      type: Object,
-      default: () => {
-        return {}
-      }
+	import countTo from "vue-count-to";
+	import BasePersonArtReport from "./BasePersonArtReport.vue";
+	export default {
+		components: {
+			countTo,
+			BasePersonArtReport
+		},
+		props: {
+			artInfo: {
+				type: Object,
+				default: () => {
+					return {};
+				}
+			},
+			quotaFirstLevel: {
+				type: Array,
+				default: () => {
+					return [];
+				}
+			},
+			classList: {
+				type: Array,
+				default: () => {
+					return [];
+				}
+			}
+		},
+		computed: {
+			curClass() {
+				if (this.classList && this.classList.length && this.classId) {
+					return this.classList.find((item) => item.id == this.classId);
+				}
+				return {};
+			},
+			columns() {
+				let data = [
+					{
+						title: "姓名",
+						key: "name",
+						align: "center",
+						minWidth: 120,
+						fixed: "left"
+					},
+					{
+						title: "总分",
+						key: "totalScore",
+						align: "center",
+						minWidth: 80,
+						fixed: "right"
+					}
+				];
+				if (this.artInfo?.subjects?.length) {
+					let stList = [];
+					this.artInfo.subjects.forEach((subject) => {
+						let index = data.length - 1;
+						data.splice(index, 0, {
+							title: subject.name,
+							align: "center",
+							children: []
+						});
+						this.quotaFirstLevel.forEach((quo) => {
+							data[index].children.push({
+								title: quo.name,
+								align: "center",
+								minWidth: 180,
+								slot: `${subject.id}-${quo.id}`
+							});
+							stList.push(`${subject.id}-${quo.id}`);
+						});
+					});
+					this.slotList = stList;
+				}
+				return data;
+			}
+		},
+		data() {
+			return {
+				personArtModal: false,
+				stuCount: 0,
+				lostCount: 0,
+				keyword: "",
+				pageSize: 10,
+				currentPage: 1,
+				classId: "",
+				slotList: [],
+				oringinData: [],
+				tableData: [],
+				tableDataShow: [],
+				isGenBtnLoading: false,
+        curGenPdfStatus:0,
+        genPdfStatusArr:['生成报告','正在生成中','下载报告']
+			};
+		},
+    created(){
+      // this.onWatchPdfProcess()
     },
-    quotaFirstLevel: {
-      type: Array,
-      default: () => {
-        return []
-      }
-    },
-    classList: {
-      type: Array,
-      default: () => {
-        return []
-      }
-    }
-  },
-  computed: {
-    curClass() {
-      if (this.classList && this.classList.length && this.classId) {
-        return this.classList.find(item => item.id == this.classId)
-      }
-      return {}
-    },
-    columns() {
-      let data = [
-        {
-          title: '姓名',
-          key: 'name',
-          align: 'center',
-          minWidth: 120,
-          fixed: 'left',
-        },
-        {
-          title: '总分',
-          key: 'totalScore',
-          align: 'center',
-          minWidth: 80,
-          fixed: 'right',
-        }
-      ]
-      if (this.artInfo?.subjects?.length) {
-        let stList = []
-        this.artInfo.subjects.forEach(subject => {
-          let index = data.length - 1
-          data.splice(index, 0, {
-            title: subject.name,
-            align: 'center',
-            children: []
+		methods: {
+			onGenPdf() {
+        if(this.curGenPdfStatus === 0){
+          let params = {
+            artId: this.artInfo.id,
+            schoolId: this.artInfo.school,
+            opt: "gen-pdf",
+            studentIds: this.tableDataShow.map((item) => item.id),
+          };
+          this.isGenBtnLoading = true
+          this.$api.areaArt.genArtPdf(params).then(res => {
+            console.log(res);
+            this.isGenBtnLoading = false
           })
-          this.quotaFirstLevel.forEach(quo => {
-            data[index].children.push({
-              title: quo.name,
-              align: 'center',
-              minWidth: 180,
-              slot: `${subject.id}-${quo.id}`
-            })
-            stList.push(`${subject.id}-${quo.id}`)
-          })
-        })
-        this.slotList = stList
-      }
-      return data
-    }
-  },
-  data() {
-    return {
-      personArtModal:false,
-      stuCount: 0,
-      lostCount: 0,
-      keyword: '',
-      pageSize: 10,
-      currentPage: 1,
-      classId: '',
-      slotList: [],
-      oringinData: [],
-      tableData: [],
-      tableDataShow: []
-    }
-  },
-  methods: {
-    onRowClick(val){
-      // this.personArtModal = true
-      if(window.location.host.includes('rc.teammodel')){
-        this.personArtModal = true
-      }
-    },
-    searchStudent() {
-      this.tableData = this.oringinData.filter(item => item.name.includes(this.keyword))
-      this.pageChange(1)
-    },
-    // 分页页面变化
-    pageChange(page) {
-      let start = this.pageSize * (page - 1)
-      let end = this.pageSize * page
-      this.currentPage = page
-      this.tableDataShow = this.tableData.slice(start, end)
-    },
-    findArtResult() {
-      if (!this.classId) return
-      this.$api.areaArt.findArtResults({
-        "opt": "find",
-        "artId": this.artInfo.id,
-        "subjects": ['subject_music','subject_painting'],
-        "classIds": [this.classId]
-      }).then(res => {
-        if (!res.error) {
-          this.setTableData(res.results)
-        } else {
-          this.$Message.error('Fail1')
         }
-      }).catch(e => {
-        this.$Message.error(e)
-      })
-    },
-    setTableData(results) {
-      let students = this.curClass.members
-      this.oringinData = []
-      students.forEach(student => {
-        let row = {
-          id: student.id,
-          name: student.name
-        }
-        let studentRes = results.find(r => r.studentId === student.id)
-        if (studentRes) {
-          this.slotList.forEach(st => {
-            let arr = st.split('-')
-            row[st] = studentRes.results.filter(res => {
-              return res.subjectId === arr[0] && res.quotaId.includes(arr[1])
-            })
-          })
-          row.totalScore = studentRes.totalScore
-        } else {
-          this.slotList.forEach(st => {
-            row[st] = []
-          })
-          row.totalScore = 0
-        }
-        this.oringinData.push(row)
-      })
-      this.searchStudent()
-    }
-  },
-  watch: {
-    classId: {
-      immediate: true,
-      deep: true,
-      handler(n, o) {
-        if (n) {
-          this.findArtResult()
-        }
-      }
-    },
-    classList: {
-      immediate: true,
-      deep: true,
-      handler(n, o) {
-        if (n && n.length) {
-          this.classId = n[0].id
-          this.stuCount = this.artInfo.stuCount || 0
-          this.lostCount = this.artInfo.miss && this.artInfo.miss.length ? this.artInfo.miss[0] : 0
-        } else {
-          this.classId = ''
-        }
-      }
-    }
-  }
-}
+			},
+      onWatchPdfProcess() {
+				let params = {
+					artId: this.artInfo.id,
+					schoolId: this.artInfo.school,
+					opt: "gen-pdf",
+					studentIds: this.tableDataShow.map((item) => item.id),
+				};
+        this.$api.areaArt.genArtPdfProgress(params).then(res => {
+          this.curGenPdfStatus = res.total ? (res.total === res.finishCount ? 2 : 1) : 0
+        })
+			},
+			onRowClick(val) {
+				// this.personArtModal = true
+				if (window.location.host.includes("rc.teammodel")) {
+					this.personArtModal = true;
+				}
+			},
+			searchStudent() {
+				this.tableData = this.oringinData.filter((item) => item.name.includes(this.keyword));
+				this.pageChange(1);
+			},
+			// 分页页面变化
+			pageChange(page) {
+				let start = this.pageSize * (page - 1);
+				let end = this.pageSize * page;
+				this.currentPage = page;
+				this.tableDataShow = this.tableData.slice(start, end);
+			},
+			findArtResult() {
+				if (!this.classId) return;
+				this.$api.areaArt
+					.findArtResults({
+						opt: "find",
+						artId: this.artInfo.id,
+						subjects: ["subject_music", "subject_painting"],
+						classIds: [this.classId]
+					})
+					.then((res) => {
+						if (!res.error) {
+							this.setTableData(res.results);
+						} else {
+							this.$Message.error("Fail1");
+						}
+					})
+					.catch((e) => {
+						this.$Message.error(e);
+					});
+			},
+			setTableData(results) {
+				let students = this.curClass.members;
+				this.oringinData = [];
+				students.forEach((student) => {
+					let row = {
+						id: student.id,
+						name: student.name
+					};
+					let studentRes = results.find((r) => r.studentId === student.id);
+					if (studentRes) {
+						this.slotList.forEach((st) => {
+							let arr = st.split("-");
+							row[st] = studentRes.results.filter((res) => {
+								return res.subjectId === arr[0] && res.quotaId.includes(arr[1]);
+							});
+						});
+						row.totalScore = studentRes.totalScore;
+					} else {
+						this.slotList.forEach((st) => {
+							row[st] = [];
+						});
+						row.totalScore = 0;
+					}
+					this.oringinData.push(row);
+				});
+				this.searchStudent();
+        this.onWatchPdfProcess()
+			}
+		},
+		watch: {
+			classId: {
+				immediate: true,
+				deep: true,
+				handler(n, o) {
+					if (n) {
+						this.findArtResult();
+					}
+				}
+			},
+			classList: {
+				immediate: true,
+				deep: true,
+				handler(n, o) {
+					if (n && n.length) {
+						this.classId = n[0].id;
+						this.stuCount = this.artInfo.stuCount || 0;
+						this.lostCount = this.artInfo.miss && this.artInfo.miss.length ? this.artInfo.miss[0] : 0;
+					} else {
+						this.classId = "";
+					}
+				}
+			}
+		}
+	};
 </script>
 <style lang="less" scoped>
-.data-count-item {
-  width: 150px;
-  text-align: center;
-  .data-value {
-    font-size: 30px;
-    font-weight: 600;
-    color: #17233d;
-  }
-}
-.data-count-wrap {
-  width: 100%;
-  height: 100px;
-  margin-bottom: 10px;
-  background: white;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-.table-scroll-wrap {
-  width: 100%;
-  background: #ffffff;
-  padding: 0px 10px 10px 10px;
-  height: ~"calc(100% - 200px)";
-}
-.page-wrap {
-  width: 100%;
-  padding: 10px;
-  background: white;
-}
-.quota-score-item {
-  margin-left: 5px;
-  text-align: left;
-  list-style: square;
-  &::marker {
-    color: #085fec;
-  }
-}
-.quota-score-value {
-  color: #2d8cf0;
-  float: right;
-  font-weight: 600;
-}
-.no-score-tag {
-  color: #ed4014;
-}
-.data-view-wrap {
-  width: 100%;
-  height: ~"calc(100% - 50px)";
-  padding: 10px 10px;
-  background: #f6f6f6;
-}
-.table-header-wrap {
-  margin-top: 10px;
-  background: #ffffff;
-  padding: 10px;
-}
-</style>
+	.data-count-item {
+		width: 150px;
+		text-align: center;
+		.data-value {
+			font-size: 30px;
+			font-weight: 600;
+			color: #17233d;
+		}
+	}
+	.data-count-wrap {
+		width: 100%;
+		height: 100px;
+		margin-bottom: 10px;
+		background: white;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+	}
+	.table-scroll-wrap {
+		width: 100%;
+		background: #ffffff;
+		padding: 0px 10px 10px 10px;
+		height: ~"calc(100% - 200px)";
+	}
+	.page-wrap {
+		width: 100%;
+		padding: 10px;
+		background: white;
+	}
+	.quota-score-item {
+		margin-left: 5px;
+		text-align: left;
+		list-style: square;
+		&::marker {
+			color: #085fec;
+		}
+	}
+	.quota-score-value {
+		color: #2d8cf0;
+		float: right;
+		font-weight: 600;
+	}
+	.no-score-tag {
+		color: #ed4014;
+	}
+	.data-view-wrap {
+		width: 100%;
+		height: ~"calc(100% - 50px)";
+		padding: 10px 10px;
+		background: #f6f6f6;
+	}
+	.table-header-wrap {
+		margin-top: 10px;
+		background: #ffffff;
+		padding: 10px;
+	}
+</style>

+ 45 - 2
TEAMModelOS/ClientApp/src/view/dashboard/Research.less

@@ -6,7 +6,6 @@
   // transform-origin: left top;
   overflow: hidden;
   position: relative;
-
   .tools {
     position: absolute;
     right: 30px;
@@ -34,12 +33,56 @@
     display: flex;
     align-items: center;
 
+    .ivu-select-selected-value {
+      font-size: 14px !important;
+    }
+
+    .ivu-select-selection,
+    .ivu-input {
+      background-color: #021f554a !important;
+      border-color: #275dc291 !important;
+      width: 200px;
+      border-radius: 5px;
+      height: 30px;
+      margin-right: 10px;
+      font-size: 14px;
+
+      .ivu-select-dropdown {
+        // min-width: 200px !important;
+        background-color: #2761c5c2;
+      }
+
+      .ivu-select-item-focus {
+        background-color: #01928bd6;
+      }
+
+      .ivu-select-selected-value {
+        font-size: 18px;
+        color: #fff;
+      }
+
+      .ivu-select-item {
+        color: #fff;
+        font-size: 16px;
+
+        &:hover {
+          background-color: #01928bd6;
+        }
+      }
+    }
+
+    .ivu-input {
+      width: 200px;
+      font-size: 12px !important;
+      color: #fff !important;
+    }
+
     &-name {
       margin: 0 10px;
     }
 
     &-period {
-      font-size: 12px;
+      font-size: 14px;
       background-color: #2d2d2d;
       display: inline-block;
       padding: 2px 10px;

+ 213 - 170
TEAMModelOS/ClientApp/src/view/dashboard/Research.vue

@@ -1,180 +1,223 @@
 <template>
-  <div id="index" ref="appRef">
-    <div class="bg">
-      <dv-loading v-if="loading">{{ $t('researchCenter.dashboard.loading') }}</dv-loading>
-      <div v-else class="host-body">
-        <div class="tools">
-          <span class="time-text">{{ dateYear }} <span style="display: inline-block; margin: 0 5px;color: #0fa2fe;">{{ dateDay }}</span> </span>
-          <!-- <span type="iconfont icon-tuichuquanping" @click="goBack" color="#0fa2fe"/> -->
-          <span class="icon iconfont icon-tuichuquanping" style="font-size: 22px;" :title="$t('researchCenter.dashboard.quit')" @click="goBack"></span>
-          <!-- <span class="icon iconfont icon-tuichuquanping" @click="goBack" style="width: 20px;"></span> -->
-        </div>
-        <div class="school-info">
-          <img :src="schoolInfo.schoolLogo">
-          <span class="school-info-name">{{ schoolInfo.schoolName }}</span>
-          <span class="school-info-period">{{ schoolInfo.periodName }}</span>
-          <span class="school-info-semester">{{ schoolInfo.curSemester }}</span>
-        </div>
-        <div class="d-flex jc-center">
-          <dv-decoration-10 class="dv-dec-10" />
-          <div class="d-flex jc-center">
-            <dv-decoration-8 class="dv-dec-8" :color="['#568aea', '#000000']" />
-            <div class="title">
-              <span class="dash-title-text">{{ $t('researchCenter.dashboard.title2') }}</span>
-              <dv-decoration-6 class="dv-dec-6" :reverse="true" :color="['#50e3c2', '#67a1e5']" />
-            </div>
-            <dv-decoration-8 class="dv-dec-8" :reverse="true" :color="['#568aea', '#000000']" />
-          </div>
-          <dv-decoration-10 class="dv-dec-10-s" />
-        </div>
+	<div id="index" ref="appRef">
+		<div class="bg">
+			<dv-loading v-if="loading">{{ $t("researchCenter.dashboard.loading") }}</dv-loading>
+			<div v-else class="host-body">
+				<div class="tools">
+					<span class="time-text"
+						>{{ dateYear }} <span style="display: inline-block; margin: 0 5px; color: #0fa2fe">{{ dateDay }}</span>
+					</span>
+					<!-- <span type="iconfont icon-tuichuquanping" @click="goBack" color="#0fa2fe"/> -->
+					<span class="icon iconfont icon-tuichuquanping" style="font-size: 22px" :title="$t('researchCenter.dashboard.quit')" @click="goBack"></span>
+					<!-- <span class="icon iconfont icon-tuichuquanping" @click="goBack" style="width: 20px;"></span> -->
+				</div>
+				<div class="school-info">
+					<img :src="schoolInfo.schoolLogo" />
+					<span class="school-info-name">{{ schoolInfo.schoolName }}</span>
+					<span class="school-info-period">{{ schoolInfo.periodName }}</span>
+					<Select v-model="semesterValue" @on-change="onSemesterChange" style="width: 160px; margin-right: 60px">
+						<Option v-for="(item, index) in semesterList" :value="index" :key="index">{{ item.year }}{{ item.name }}</Option>
+					</Select>
+					<!-- <span class="school-info-semester">{{ schoolInfo.curSemester }}</span> -->
+				</div>
+				<div class="d-flex jc-center">
+					<dv-decoration-10 class="dv-dec-10" />
+					<div class="d-flex jc-center">
+						<dv-decoration-8 class="dv-dec-8" :color="['#568aea', '#000000']" />
+						<div class="title">
+							<span class="dash-title-text">{{ $t("researchCenter.dashboard.title2") }}</span>
+							<dv-decoration-6 class="dv-dec-6" :reverse="true" :color="['#50e3c2', '#67a1e5']" />
+						</div>
+						<dv-decoration-8 class="dv-dec-8" :reverse="true" :color="['#568aea', '#000000']" />
+					</div>
+					<dv-decoration-10 class="dv-dec-10-s" />
+				</div>
 
-        <div class="body-box">
-          <div class="left-box">
-            <div class="left-box-top">
-              <dv-border-box-12>
-                <LeftTop />
-              </dv-border-box-12>
-            </div>
-            <div class="left-box-bottom">
-              <dv-border-box-12>
-                <LeftBottom />
-              </dv-border-box-12>
-            </div>
-          </div>
+				<div class="body-box">
+					<div class="left-box">
+						<div class="left-box-top">
+							<dv-border-box-12>
+								<LeftTop />
+							</dv-border-box-12>
+						</div>
+						<div class="left-box-bottom">
+							<dv-border-box-12>
+								<LeftBottom />
+							</dv-border-box-12>
+						</div>
+					</div>
 
-          <div class="right-box">
-            <div class="right-box-top">
-              <dv-border-box-12>
-                <RightTop />
-              </dv-border-box-12>
-            </div>
-            <div class="right-box-bottom">
-              <div class="right-bottom-single">
-                <dv-border-box-12>
-                  <RightBotR />
-                </dv-border-box-12>
-              </div>
-              <div class="right-bottom-single right">
-                <dv-border-box-12>
-                  <RightBotL />
-                </dv-border-box-12>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
+					<div class="right-box">
+						<div class="right-box-top">
+							<dv-border-box-12>
+								<RightTop />
+							</dv-border-box-12>
+						</div>
+						<div class="right-box-bottom">
+							<div class="right-bottom-single">
+								<dv-border-box-12>
+									<RightBotR />
+								</dv-border-box-12>
+							</div>
+							<div class="right-bottom-single right">
+								<dv-border-box-12>
+									<RightBotL />
+								</dv-border-box-12>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
 </template>
 
 <script>
-import drawMixin from "@/utils/drawMixin";
-import RightBotR from '@/components/research-dashboard/RightBotR'
-import RightBotL from '@/components/research-dashboard/RightBotL'
-import LeftTop from '@/components/research-dashboard/LeftTop'
-import LeftBottom from '@/components/research-dashboard/LeftBottom'
-import RightTop from '@/components/research-dashboard/RightTop'
-export default {
-  // mixins: [drawMixin],
-  data() {
-    return {
-      timing: null,
-      loading: true,
-      dateDay: null,
-      dateYear: null,
-      dateWeek: null,
-      weekday: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
-    }
-  },
-  components: {
-    RightBotR,
-    RightBotL,
-    LeftTop,
-    LeftBottom,
-    RightTop,
-  },
-  mounted() {
-    this.$tools.fullScreen(document.getElementById('index'))
-    this.timeFn()
-    // this.cancelLoading()
-  },
-  beforeDestroy() {
-    clearInterval(this.timing)
-  },
-  created() {
-    this.curPeriod = this.$store.state.user.curPeriod
-    this.getDashboardData()
-  },
-  methods: {
-    getDashboardData() {
-      let semesterRange = this.$tools.getSemesterTimeRange()
-      this.$api.lessonRecord.getDashboardData({
-        "stime": semesterRange.st,
-        "etime": semesterRange.et,
-        "code": this.$store.state.userInfo.schoolCode,
-        "periodId": this.$store.state.user.curPeriod.id
-      }).then(res => {
-        if (res.code && res.code == 404) {
-          this.$Message.warning(this.$t('lessonRecord.noRecordTip'))
-        } else {
-          this.$store.commit('setRearchDashboardData', res)
-        }
-        this.cancelLoading()
-      })
-    },
-    goBack() {
-      this.$tools.exitFullscreen()
-      this.$router.push('dashCenter')
-    },
-    timeFn() {
-      this.timing = setInterval(() => {
-        this.dateDay = this.$tools.formatTime(new Date(), 'hh:mm:ss')
-        this.dateYear = this.$tools.formatTime(new Date(), 'yyyy-MM-dd')
-        this.dateWeek = this.weekday[new Date().getDay()]
-      }, 1000)
-    },
-    cancelLoading() {
-      setTimeout(() => {
-        this.loading = false
-      }, 1000)
-    },
-    getCurSemeterYear(semesters) {
-      const today = new Date();
-      const currentYear = today.getFullYear();
-      const currentMonth = today.getMonth() + 1;
-      const currentDay = today.getDate();
-      let currentSemester = semesters.find(i => i.start === 1);
-      let year = ''
-      if (currentMonth >= currentSemester.month) {
-        if (currentDay > currentSemester.day) {
-          year = currentYear
-        } else if (currentMonth > currentSemester.month && currentDay < currentSemester.day) {
-          year = currentYear
-        } else {
-          year = currentYear - 1
-        }
-      } else {
-        year = currentYear - 1
-      }
-      return year
-    }
-  },
-  computed: {
-    schoolInfo() {
-      let store_user = this.$store.state.user
-      let semesterRange = this.$tools.getSemesterTimeRange()
-      return {
-        schoolName: store_user.schoolProfile.school_base.name,
-        schoolLogo: store_user.schoolProfile.school_base.picture,
-        periodName: store_user.curPeriod.name,
-        curSemester: semesterRange.name_with_year
-      }
-    }
-  }
-}
+	import drawMixin from "@/utils/drawMixin";
+	import RightBotR from "@/components/research-dashboard/RightBotR";
+	import RightBotL from "@/components/research-dashboard/RightBotL";
+	import LeftTop from "@/components/research-dashboard/LeftTop";
+	import LeftBottom from "@/components/research-dashboard/LeftBottom";
+	import RightTop from "@/components/research-dashboard/RightTop";
+	export default {
+		// mixins: [drawMixin],
+		data() {
+			return {
+				timing: null,
+				loading: true,
+				dateDay: null,
+				dateYear: null,
+				dateWeek: null,
+				weekday: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
+				semesterList: [],
+				filterSemesterId: "",
+				semesterValue: 1,
+				curSemester: null
+			};
+		},
+		components: {
+			RightBotR,
+			RightBotL,
+			LeftTop,
+			LeftBottom,
+			RightTop
+		},
+		mounted() {
+			this.$tools.fullScreen(document.getElementById("index"));
+			this.timeFn();
+			// this.cancelLoading()
+		},
+		beforeDestroy() {
+			clearInterval(this.timing);
+		},
+		created() {
+			this.curPeriod = this.$store.state.user.curPeriod;
+			this.getDashboardData();
+			this.getSemesterList();
+		},
+		methods: {
+			getSemesterList() {
+				let curYear = new Date().getFullYear();
+				let curPeriod = this.$store.state.user.curPeriod;
+				let semesterArr = this._.cloneDeep(curPeriod.semesters).sort((a, b) => b.start - a.start);
+				this.curSemester = this.$store.state.user.curSemester;
+				let gradeCount = curPeriod.grades.length;
+				// let listYearCount = gradeCount + 1; // 可选年份范围是当前学段下年级数量 再往后推一年(可能会查看未来的排课表)
+				let listYearCount = 1; // 可选年份范围是当前学段下年级数量 再往后推一年(可能会查看未来的排课表)
+				let oldYear = curYear - 1;
+				let arr = [];
+				for (let i = 0; i <= listYearCount; i++) {
+					for (let j = 0; j < semesterArr.length; j++) {
+						arr.push({
+							year: oldYear + i,
+							name: semesterArr[j].name,
+							index: curPeriod.semesters.findIndex((k) => k.id === semesterArr[j].id),
+							id: semesterArr[j].id
+						});
+					}
+				}
+				this.semesterList = arr;
+				this.semesterValue = this.semesterList.findIndex((i) => i.year === this.curSemester.year && i.id === this.curSemester.id);
+				this.studyYear = this.curSemester.year;
+			},
+			onSemesterChange(val) {
+				this.getDashboardData(this.semesterList[val]);
+			},
+			getDashboardData(semesterInfo) {
+				let n = semesterInfo || this.$store.state.user.curSemester;
+				let semesterRange = this.$tools.getStTimeByYearAndSemester(n.year, n.index);
+				this.loading = true;
+				this.$api.lessonRecord
+					.getDashboardData({
+						stime: semesterRange.stime,
+						etime: semesterRange.etime,
+						code: this.$store.state.userInfo.schoolCode,
+						periodId: this.$store.state.user.curPeriod.id
+					})
+					.then((res) => {
+						this.cancelLoading();
+						this.$nextTick(() => {
+							if (res.code && res.code == 404) {
+								this.$Message.warning(this.$t("lessonRecord.noRecordTip"));
+							} else {
+								this.$store.commit("setRearchDashboardData", res);
+							}
+						});
+					});
+			},
+			goBack() {
+				this.$tools.exitFullscreen();
+				this.$router.push("dashCenter");
+			},
+			timeFn() {
+				this.timing = setInterval(() => {
+					this.dateDay = this.$tools.formatTime(new Date(), "hh:mm:ss");
+					this.dateYear = this.$tools.formatTime(new Date(), "yyyy-MM-dd");
+					this.dateWeek = this.weekday[new Date().getDay()];
+				}, 1000);
+			},
+			cancelLoading() {
+				setTimeout(() => {
+					this.loading = false;
+				}, 1000);
+			},
+			getCurSemeterYear(semesters) {
+				const today = new Date();
+				const currentYear = today.getFullYear();
+				const currentMonth = today.getMonth() + 1;
+				const currentDay = today.getDate();
+				let currentSemester = semesters.find((i) => i.start === 1);
+				let year = "";
+				if (currentMonth >= currentSemester.month) {
+					if (currentDay > currentSemester.day) {
+						year = currentYear;
+					} else if (currentMonth > currentSemester.month && currentDay < currentSemester.day) {
+						year = currentYear;
+					} else {
+						year = currentYear - 1;
+					}
+				} else {
+					year = currentYear - 1;
+				}
+				return year;
+			}
+		},
+		computed: {
+			schoolInfo() {
+				let store_user = this.$store.state.user;
+				let semesterRange = this.$tools.getSemesterTimeRange();
+				return {
+					schoolName: store_user.schoolProfile.school_base.name,
+					schoolLogo: store_user.schoolProfile.school_base.picture,
+					periodName: store_user.curPeriod.name,
+					curSemester: semesterRange.name_with_year
+				};
+			}
+		}
+	};
 </script>
 
 <style lang="less">
-@import "Research.less";
-@import "style.less";
+	@import "Research.less";
+	@import "style.less";
 </style>

+ 1 - 1
TEAMModelOS/ClientApp/src/view/iot/schooliot.vue

@@ -29,7 +29,7 @@
              <!--time-->
              <div class="timebox">
                 <span class="timebox-text">{{ times.year }} <span style="display: inline-block; margin: 0 5px;color: #0fa2fe;">{{ times.day }}</span> </span>
-                <span class="icon iconfont icon-download" style="font-size: 22px; margin-right: 10px" :title="`下载艺术评测数据表`" @click="exportIotTable"></span>
+                <span class="icon iconfont icon-download" style="font-size: 22px; margin-right: 10px" :title="`iot${$t('totalAnalysis.totalIndex.tab1')}`" @click="exportIotTable"></span>
                 <span class="icon iconfont icon-tuichuquanping" style="font-size: 22px;" :title="$t('researchCenter.dashboard.quit')" @click="goBack"></span>
              </div>
              <!--time end-->

+ 65 - 5
TEAMModelOS/ClientApp/src/view/learnactivity/ByQuMark.vue

@@ -158,7 +158,18 @@
                     </div>
                     <!-- 原始答案 -->
                     <template v-if="!viewMark[item.id]">
-                        <div class="answer-box" v-if="item.answer && item.answer[quIndex]" v-html="item.answer[quIndex].toString()"></div>
+                        <div class="answer-box" v-if="item.answer && item.answer[quIndex].length">
+                            <span v-html="item.answer[quIndex].toString()" v-if="(questionInfo.type === 'subjective' && questionInfo.answerType === 'text') || questionInfo.type != 'subjective'"></span>
+                            <audio v-else-if="questionInfo.answerType === 'audio'" controls>
+                                <source :src="item.answer[quIndex].toString()">
+                                {{$t('teachContent.notAudio')}}
+                            </audio>
+                            <img v-else-if="questionInfo.answerType === 'Image'" :src="item.answer[quIndex].toString()" alt="">
+                            <div v-else class="answer-link">
+                                <Icon type="ios-link" />
+                                <span class="name" @click="onDownload(item.answer[quIndex].toString(), item.id)">{{ item.answer[quIndex].toString() }}</span>
+                            </div>
+                        </div>
                         <div v-else class="answer-box" style="color:#ed4014">
                             {{$t('learnActivity.score.noStuAns')}}
                         </div>
@@ -509,6 +520,14 @@ export default {
                         let urlPrefix = `${blobUrl}/exam/${answerUrl.replace('/ans.json','')}`
                         let fullUrl = `${blobUrl}/exam/${answerUrl}?${sas}`
                         let ansRes = await this.$jsFn.handleStudentAnswer(fullUrl, urlPrefix, sas)
+                        // 问答题:课中的地址需截取文件名称重新拼接地址,因此统一重新拼接处理
+                        ansRes = ansRes.map((item, index) => {
+                            if(this.quNoList[index].type === 'subjective' && this.quNoList[index].answerType != 'text' && item.length) {
+                                let name = item[0].substr(item[0].lastIndexOf(`/${student.id}/`) + (student.id.length + 2))
+                                item = this.quNoList[index].answerType === 'file' ? name : [`${blobUrl}/exam/${this.examInfo.id}/${this.paper.subjectId}/${student.id}/${name}?${sas}`]
+                            }
+                            return item
+                        })
                         this.$set(student, 'answer', ansRes)
                     }
 
@@ -517,7 +536,17 @@ export default {
             } catch (e) {
                 this.$Message.error(this.$t('learnActivity.score.answerDataErr'))
             }
-        }
+        },
+        getFileName(url, stuId) {
+            return url.substr(url.lastIndexOf(`/${stuId}/`) + (stuId.length + 2))
+        },
+        /* 下载 */
+        async onDownload(answer, stuId) {
+            let sas = this.examInfo?.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+            let blobUrl = this.examInfo?.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+            let url = `${blobUrl}/exam/${this.examInfo.id}/${this.paper.subjectId}/${stuId}/${answer}?${sas}`
+            this.$tools.doDownloadByUrl(url, answer)
+        },
     },
     watch: {
         stusInfo: {
@@ -588,7 +617,9 @@ export default {
                                 label: (index + 1) + '-' + (childIndex + 1),
                                 value: i,
                                 score: item.score,
-                                disabled: objectiveQu.includes(childItem.type)
+                                disabled: objectiveQu.includes(childItem.type),
+                                type: childItem.type,
+                                answerType: childItem?.answerType || undefined
                             })
                         })
                     } else {
@@ -597,7 +628,9 @@ export default {
                             label: (index + 1) + '',
                             value: i,
                             score: item.score,
-                            disabled: objectiveQu.includes(item.type)
+                            disabled: objectiveQu.includes(item.type),
+                            type: item.type,
+                            answerType: item?.answerType || undefined
                         })
                     }
                 })
@@ -620,7 +653,19 @@ export default {
                     return [h]
                 }
             }
-            return this.studentsData[this.stuIndex] && this.studentsData[this.stuIndex].answer ? this.studentsData[this.stuIndex].answer[this.quIndex] : this.$t('learnActivity.score.noStuAns')
+            if(this.studentsData[this.stuIndex] && this.studentsData[this.stuIndex].answer) {
+                if(this.questionInfo.type === 'subjective' && !['text', 'file'].includes(this.questionInfo.answerType) && this.studentsData[this.stuIndex].answer[this.quIndex].length) {
+                    let name = this.studentsData[this.stuIndex].answer[this.quIndex][0].substr(this.studentsData[this.stuIndex].answer[this.quIndex][0].lastIndexOf(`/${this.studentsData[this.stuIndex].id}/`) + (this.studentsData[this.stuIndex].id.length + 2))
+                    let name1 = name.substring(0, name.lastIndexOf('?'))
+                    let h = `<img src=\"${this.studentsData[this.stuIndex].answer[this.quIndex][0]}\"/>`
+                    return this.questionInfo.answerType === 'audio' ? [name1] : [h]
+                } else {
+                    return this.studentsData[this.stuIndex].answer[this.quIndex]
+                }
+            } else {
+                return this.$t('learnActivity.score.noStuAns')
+            }
+            // return this.studentsData[this.stuIndex] && this.studentsData[this.stuIndex].answer ? this.studentsData[this.stuIndex].answer[this.quIndex] : this.$t('learnActivity.score.noStuAns')
         }
     }
 }
@@ -691,6 +736,21 @@ export default {
     padding: 5px 15px 5px 0px;
     margin-top: 7px;
     min-height: 50px;
+    
+    .answer-link{
+        background-color: #e6e6e6;
+        border: 2px solid #e8e8e8;
+        padding: 2px 10px;
+        border-radius: 4px;
+        margin: 5px;
+        color: #0086E6;
+        cursor: pointer;
+        
+        &-active{
+            background-color: #1cc0f3;
+            color: #fff;
+        }
+    }
 }
 .answer-wrap {
     width: 100%;

+ 47 - 2
TEAMModelOS/ClientApp/src/view/learnactivity/StuReport.vue

@@ -148,7 +148,19 @@
                   <div class="TitleRec1">
                     <span style="margin:5px;color:#1472c7">{{ $t("studentWeb.exam.report.ansRes") }}:</span>
                   </div>
-                  <div v-if="ansData[index] && ansData[index].length" style="margin-left:10px" v-html=" ansData[index].join('')"></div>
+                  <!-- <div v-if="ansData[index] && ansData[index].length" style="margin-left:10px" v-html=" ansData[index].join('')"></div> -->
+                  <div v-if="ansData[index] && ansData[index].length" style="margin-left: 10px;">
+                      <span v-html="ansData[index].toString()" v-if="(question.type === 'subjective' && question.answerType === 'text') || question.type != 'subjective'"></span>
+                      <audio v-else-if="question.answerType === 'audio'" controls>
+                          <source :src="ansData[index].toString()">
+                          {{$t('teachContent.notAudio')}}
+                      </audio>
+                      <img v-else-if="question.answerType === 'Image'" :src="ansData[index].toString()" alt="">
+                      <div v-else class="answer-link">
+                          <Icon type="ios-link" />
+                          <span class="name" @click="onDownload(ansData[index].toString())">{{ getFileName(ansData[index].toString()) }}</span>
+                      </div>
+                  </div>
                   <span style="color:red" v-else>{{$t('studentWeb.exam.report.noAns')}}</span>
                 </div>
                 <!-- 批注 -->
@@ -356,6 +368,13 @@ export default {
         // datas = await this.$evTools.getComposeItem(code)
         let fullUrl = code.blob.replace("/ans.json", "")
         let newData = await this.$jsFn.handleStudentAnswer(videoBlob, fullUrl, sas.sas)
+        // 问答题的作答需拼接sas
+        newData = newData.map((item, index) => {
+            if(this.paperData[index].type === 'subjective' && this.paperData[index].answerType != 'text' && this.paperData[index].answerType != 'file' && item.length) {
+                item = [`${blobUrl}${item[0]}?${sas}`]
+            }
+            return item
+        })
         return newData
       } else {
         return []
@@ -418,7 +437,19 @@ export default {
       let blobUrl = this.examInfo.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
       let ansStr = JSON.parse(await this.$tools.getFile(`${blobUrl}/exam/${this.stuData.ansBlob[0]}?${sas}`) || "[]")
       let urlPrefix = `${blobUrl}/exam/${answerUrl.replace('/ans.json', '')}`
-    }
+    },
+    getFileName(url) {
+        let sub = this.stuData.id
+        let names = url.substr(url.lastIndexOf(`/${sub}/`) + (sub.length + 2))
+        return names
+    },
+    /* 下载 */
+    async onDownload(answer) {
+        let sas = this.examInfo?.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+        let blobUrl = this.examInfo?.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+        let url = `${blobUrl}${answer}?${sas}`
+        this.$tools.doDownloadByUrl(url, this.getFileName(answer))
+    },
   },
   created() {
 
@@ -710,6 +741,20 @@ export default {
   /deep/ img {
     max-width: 100% !important;
   }
+  .answer-link{
+      background-color: #e6e6e6;
+      border: 2px solid #e8e8e8;
+      padding: 2px 10px;
+      border-radius: 4px;
+      margin: 5px;
+      color: #0086E6;
+      cursor: pointer;
+      
+      &-active{
+          background-color: #1cc0f3;
+          color: #fff;
+      }
+  }
 }
 
 .qAnaly .rightAnalys {

+ 48 - 0
TEAMModelOS/ClientApp/src/view/learnactivity/byStu/ByStuMark.less

@@ -271,4 +271,52 @@
     display: none;
     cursor: pointer;
     user-select: none;
+}
+
+.repair-link-wrap-item-box{
+    display: flex;
+    position: relative;
+    // background-color: #e3e3e3;
+    border-radius: 5px;
+    padding: 10px 0;
+    font-size: 14px;
+    
+    &:hover{
+        background-color: #ebe9e9;
+    }
+    
+    .file-icon{
+        img{
+            width: 45px;
+        }
+    }
+    
+    .file-info{
+        margin-left: 10px;
+        
+        .file-name{
+            font-weight: bold;
+            margin-bottom: 5px;
+        }
+        
+        span{
+            color: #16a3b5;
+            margin-right: 15px;
+            cursor: pointer;
+        }
+    }
+}
+.answer-link{
+    background-color: #e6e6e6;
+    border: 2px solid #e8e8e8;
+    padding: 2px 10px;
+    border-radius: 4px;
+    margin: 5px;
+    color: #0086E6;
+    cursor: pointer;
+    
+    &-active{
+        background-color: #1cc0f3;
+        color: #fff;
+    }
 }

+ 22 - 5
TEAMModelOS/ClientApp/src/view/learnactivity/byStu/ByStuMark.vue

@@ -146,7 +146,8 @@ export default {
         showAnswer: false,
         showQu: false,
         activeIndex: -1,
-        examInfo: _this.examInfo
+        examInfo: _this.examInfo,
+        stuInfo: _this.studentInfo
       },
       isUpd: false
     }
@@ -209,6 +210,14 @@ export default {
               let urlPrefix = `${blobUrl}/exam/${this.studentAnswer.answers[0].replace('/ans.json', '')}`
               let fullUrl = `${blobUrl}/exam/${this.studentAnswer.answers[0]}?${sas}`
               let ansRes = await this.$jsFn.handleStudentAnswer(fullUrl, urlPrefix, sas)
+              // 问答题:课中的地址需截取文件名称重新拼接地址,因此统一重新拼接处理
+              ansRes  = ansRes.map((item, index) => {
+                if(this.quNoList[index].type === 'subjective' && this.quNoList[index].answerType != 'text' && item.length) {
+                  let name = item[0].substr(item[0].lastIndexOf(`/${this.studentInfo.id}/`) + (this.studentInfo.id.length + 2))
+                  item = this.quNoList[index].answerType === 'file' ? name : [`${blobUrl}/exam/${this.examInfo.id}/${this.paper.subjectId}/${this.studentInfo.id}/${name}?${sas}`]
+                }
+                return item
+              })
               this.$set(this.studentAnswer, 'answers', ansRes)
             } catch (e) {
               let full = []
@@ -279,13 +288,17 @@ export default {
                 item.children.forEach(child => {
                   this.quNoList.push({
                     label: child.quNo,
-                    index: child.index
+                    index: child.index,
+                    type: child.type,
+                    answerType: child?.answerType || undefined
                   })
                 })
               } else {
                 this.quNoList.push({
                   label: item.quNo,
-                  index: item.index
+                  index: item.index,
+                  type: item.type,
+                  answerType: item?.answerType || undefined
                 })
               }
             })
@@ -311,13 +324,17 @@ export default {
                     quItem.children.forEach(child => {
                       this.quNoList.push({
                         label: child.quNo,
-                        index: child.index
+                        index: child.index,
+                        type: child.type,
+                        answerType: child?.answerType || undefined
                       })
                     })
                   } else {
                     this.quNoList.push({
                       label: quItem.quNo,
-                      index: quItem.index
+                      index: quItem.index,
+                      type: quItem.type,
+                      answerType: quItem?.answerType || undefined
                     })
                   }
                 })

+ 69 - 2
TEAMModelOS/ClientApp/src/view/learnactivity/byStu/QuAndScore.vue

@@ -73,7 +73,28 @@
                         </div>
                         <!--其余题型答案-->
                         <div v-else-if="stuAnswer" :id="'answer'+ questionItem.index">
-                            <span v-html="stuAnswer.toString()"></span>
+                            <span v-html="stuAnswer.toString()" v-if="(questionItem.type === 'subjective' && questionItem.answerType === 'text') || questionItem.type != 'subjective'"></span>
+                            <audio v-else-if="questionItem.answerType === 'audio'" controls>
+                                <source :src="stuAnswer.toString()">
+                                {{$t('teachContent.notAudio')}}
+                            </audio>
+                            <img v-else-if="questionItem.answerType === 'Image'" :src="stuAnswer.toString()" alt="">
+                            <div v-else class="answer-link">
+                                <Icon type="ios-link" />
+                                <span class="name" @click="onDownload(stuAnswer.toString())">{{ stuAnswer.toString() }}</span>
+                            </div>
+                            <!-- <div v-else class="repair-link-wrap-item-box">
+                                <div class="file-icon">
+                                    <img :src="$tools.getFileThum(getFileType(stuAnswer.toString()), stuAnswer.toString())"/>
+                                </div>
+                                <div class="file-info">
+                                    <p class="file-name">{{ stuAnswer.toString() }}</p>
+                                    <div>
+                                        <span @click="onPreview(stuAnswer.toString())">{{ $t('ability.review.preview')}}</span>
+                                        <span @click="onDownload(stuAnswer)">{{ $t('ability.review.download')}}</span>
+                                    </div>
+                                </div>
+                            </div> -->
                         </div>
                     </template>
                     <!-- 显示批注 -->
@@ -81,6 +102,7 @@
                         <img :src="markImg" style="max-width:100%">
                     </template>
                 </div>
+                <div v-else>{{ $t('learnActivity.score.noStuAns') }}</div>
             </div>
             <!-- 答案以及解析 -->
             <transition name="slide">
@@ -209,7 +231,15 @@ export default {
             //         return [h]
             //     }
             // }
-            return this.stuAnswer || undefined
+            // 问答题中,上传文件回显
+            if(this.questionItem.type === 'subjective' && !['text', 'file'].includes(this.questionItem.answerType) && this.stuAnswer.length) {
+                let name = this.stuAnswer[0].substr(this.stuAnswer[0].lastIndexOf(`/${this.config.stuInfo?.id}/`) + (this.config.stuInfo?.id.length + 2))
+                let name1 = name.substring(0, name.lastIndexOf('?'))
+                let h = `<img src=\"${this.stuAnswer[0]}\"/>`
+                return this.questionItem.answerType === 'audio' ? [name1] : [h]
+            } else {
+                return this.stuAnswer || undefined
+            }
         }
     },
     watch: {
@@ -334,6 +364,43 @@ export default {
                 }
             })
         },
+        getFileType(fileUrl) {
+            let file = fileUrl || ''
+            let suffix = file.substr(file.lastIndexOf(".") + 1)
+            let isImg = ['jpg', 'png', 'jpeg', 'gif'].includes(suffix)
+            let isDoc = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf'].includes(suffix)
+            let isVideo = ['mp4', 'webm'].includes(suffix)
+            let isAudio = ['mp3', 'wav'].includes(suffix)
+            let type = isImg ? 'image' : isVideo ? 'video' : isAudio ? 'audio' : (isDoc ? 'doc' : 'other')
+            return type
+        },
+        /* 预览 */
+        async onPreview(answer) {
+            let sas = this.config.examInfo?.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+            let blobUrl = this.config.examInfo?.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+            let url = `${blobUrl}/exam/${this.config.examInfo?.id}/${this.questionItem.subjectId}/${this.config.stuInfo?.id}/${answer}?${sas}`
+            let type = this.getFileType(answer)
+            if (this.$tools.getSuffix(answer) === 'pdf') {
+                window.open('/web/viewer.html?file=' + encodeURIComponent(url));
+            } else if(type === 'doc') {
+                window.open('https://view.officeapps.live.com/op/view.aspx?src=' + escape(url));
+            } else if(type === 'image') {
+                this.$hevueImgPreview(url)
+            } else if(type === 'link') {
+                window.open(/^(http:|https:)/i.test(url) ? url : "http://" + url)
+            } else {
+                /* this.previewFile = item
+                this.previewStatus = true
+                console.log(this.previewFile.type); */
+            }
+        },
+        /* 下载 */
+        async onDownload(answer) {
+            let sas = this.config.examInfo?.scope == 'school' ? this.$store.state.user.schoolProfile.blob_sas : this.$store.state.user.userProfile.blob_sas
+            let blobUrl = this.config.examInfo?.scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8")).blob_uri
+            let url = `${blobUrl}/exam/${this.config.examInfo?.id}/${this.questionItem.subjectId}/${this.config.stuInfo?.id}/${answer}?${sas}`
+            this.$tools.doDownloadByUrl(url, answer)
+        },
     }
 }
 </script>

+ 4 - 4
TEAMModelOS/ClientApp/src/view/signupActivity/createActivity.vue

@@ -178,8 +178,8 @@
                                                     <FormItem :label="$t('activity.workType')">
                                                         <RadioGroup v-model="contestUpload.type">
                                                             <Radio label="file">{{ $t('studentWeb.courseContent.type.file') }}</Radio>
-                                                            <Radio label="sokrates">{{ $t('auth.attr2') }}</Radio>
-                                                            <Radio label="lesson">{{ $t('schoolStatistics.class.lessonTitle') }}</Radio>
+                                                            <Radio label="sokrates" disabled>{{ $t('auth.attr2') }}</Radio>
+                                                            <Radio label="lesson" disabled>{{ $t('schoolStatistics.class.lessonTitle') }}</Radio>
                                                             <span v-show="contestUpload.type === 'lesson'">
                                                                 <Icon type="md-alert" color="#ffad16" size="17" />
                                                                 必须上传视频
@@ -507,14 +507,14 @@ export default {
                     name: 'Contest',
                     label: this.$t('activity.module.Contest')
                 },
-                {
+                /* {
                     name: 'Training',
                     label: this.$t('activity.module.Training')
                 },
                 {
                     name: 'Research',
                     label: this.$t('activity.module.Research')
-                }
+                } */
             ],
             tabListShow: [],
             posterFile: { //封面文件

+ 36 - 3
TEAMModelOS/ClientApp/src/view/signupActivity/infoComponent/ruleDrawer.vue

@@ -29,8 +29,8 @@
                 </div>
             </div>
             <template v-else>
-                <Form :model="ruleInfo" :label-width="80" class="create-form light-iview-form">
-                    <FormItem :label="$t('activity.reviewRuleInfo.name')">
+                <Form ref="ruleInfo" :rules="reviewDataRule" :model="ruleInfo" :label-width="80" class="create-form light-iview-form">
+                    <FormItem prop="name" :label="$t('activity.reviewRuleInfo.name')">
                         <Input v-special-char v-model="ruleInfo.name" :placeholder="$t('activity.placeholder.field17')" />
                     </FormItem>
                     <FormItem :label="$t('activity.reviewRuleInfo.desc')">
@@ -46,7 +46,7 @@
                             </Radio>
                         </RadioGroup>
                     </FormItem>
-                    <FormItem :label="$t('activity.reviewNum')">
+                    <FormItem prop="taskCount" :label="$t('activity.reviewNum')">
                         <InputNumber v-model="ruleInfo.taskCount" :min="1" :precision="0" />
                     </FormItem>
                     <FormItem :label="$t('activity.scoreRule')">
@@ -150,6 +150,10 @@ export default {
                 subject: this.$t('activity.distributeWord.subject'),
                 periodAndSubject: this.$t('activity.distributeWord.periodAndSubject'),
             },
+            reviewDataRule: {
+                name: [{ required: true, message: this.$t('activity.placeholder.field17'), trigger: 'blur' }],
+                taskCount: [{ required: true, type: 'number', message: this.$t('activity.placeholder.field20'), trigger: 'blur' }],
+            },
         }
     },
     created () {
@@ -346,9 +350,38 @@ export default {
         },
         // 新建保存
         save(type) {
+            this.$refs['ruleInfo'].validate((valid) => {
+                if (!valid) {
+                    this.$Message.error(this.$t('activity.message.field10'))
+                    return
+                } else {
+                    
+                }
+            })
+            if(!this.ruleInfo.trees.length) {
+                this.$Message.error(this.$t('activity.message.field53'))
+                return
+            }
+            let scoreType = this.inspectScore(this.ruleInfo.trees)
+            if(scoreType) {
+                this.$Message.error(this.$t('activity.message.field54'))
+                return
+            }
             this.ruleInfo.upsertAsTemplate = type === 'mould' ? 1 : 0
             this.$emit('saveRule', {info: this.ruleInfo, isEdit: false})
         },
+        inspectScore(arr) {
+            let isEmpty = false
+            arr.forEach(item => {
+                if(item.children) {
+                    isEmpty = this.inspectScore(item.children)
+                }
+                if(!item.score || !item.label) {
+                    isEmpty = true
+                }
+            })
+            return isEmpty
+        },
         // 编辑完成后保存
         saveEdit() {
             if(this.needEdit === 2) {

+ 3 - 1
TEAMModelOS/ClientApp/src/view/signupActivity/infoComponent/updateScore.vue

@@ -120,7 +120,7 @@ export default {
         },
         'scoreInfo.levelType': {
             handler(n, o) {
-                this.scoreLevels = []
+                this.scoreLevels = n === this.scoreSet.levelType ? this.scoreSet.scoreLevels : []
                 this.levelInfo = {
                     lable: '',
                     max: 1,
@@ -189,6 +189,8 @@ export default {
             }
             this.$api.areaActivity.manageAct(params).then(res => {
                 if(res.code === 200) {
+                    let nowDate = new Date().getTime()
+                    params.actState = nowDate < params.stime ? 'notStart' : (nowDate > params.etime ? 'finish' : 'going')
                     this.$Message.success(this.$t('cusMgt.setOk'))
                     this.$emit('scoreLevelChange', params)
                 } else {

+ 1 - 1
TEAMModelOS/ClientApp/src/view/student-web/AppNew.vue

@@ -461,7 +461,7 @@ export default {
             this.classChange(index)
         })
         //新增语系完善
-        this.fontChange=localStorage.local ==='zh-tw' ? 'Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,微软雅黑,Arial,sans-serif':localStorage.local ==='zh-cn' ? 'Hm':'Hm'   //7.18日  与杰哥讨论 cn使用Hm  TW使用微软雅黑
+        this.fontChange=localStorage.local ==='zh-tw' ? 'Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,微軟正黑體,Arial,sans-serif':localStorage.local ==='zh-cn' ? 'Hm':'Hm'   //7.18日  与杰哥讨论 cn使用Hm  微軟正黑體
     },
     beforeDestroy() {
         window.removeEventListener("resize", this.onResize);

+ 33 - 7
TEAMModelOS/ClientApp/src/view/task/marking/mark/ByQu2.vue

@@ -32,8 +32,7 @@
                         <div v-if="item.isLoading" class="answer-loading">
                             <Loading :top="50"></Loading>
                         </div>
-                        <div v-else class="answer-info-wrap" v-html="getCurAnswerData(item)">
-                        </div>
+                        <div v-else class="answer-info-wrap" v-html="getCurAnswerData(item)"></div>
                         <Icon v-show="right.includes(item.stuId)" class="mark-tag" type="md-checkmark" />
                         <Icon v-show="wrong.includes(item.stuId)" class="mark-tag" type="md-close" />
                         <Icon v-show="half.includes(item.stuId)" class="mark-tag" custom="iconfont icon-half-right" />
@@ -294,7 +293,23 @@ export default {
             if (!answer[this.quIndex]) {
                 return `<p style="color:red">${this.$t('learnActivity.score.ansErr')}</p>`
             }
-            return answer[this.quIndex].join('')
+            if (!answer[this.quIndex].length) {
+                return `<p style="color:red">${this.$t('learnActivity.score.noStuAns')}</p>`
+            }
+            if(this.quNoList[this.quIndex].type === 'subjective') {
+                if(this.quNoList[this.quIndex].answerType === 'Image') {
+                    return `<img src=\"${answer[this.quIndex][0]}\"/>`
+                /* } else if(this.quNoList[this.quIndex].answerType === 'audio') {
+                    return `<audio controls><source src=\"${answer[this.quIndex][0]}\">${this.$t('teachContent.notAudio')}</audio>`
+                } else {
+                    return `<div class="answer-link"><Icon type="ios-link" /><span class="name" onClick="onDownload(${stuInfo})">${answer[this.quIndex][0]}</span></div>`
+                } */
+                } else {
+                    return answer[this.quIndex].join('')
+                }
+            } else {
+                return answer[this.quIndex].join('')
+            }
         },
         getPageData() {
             let startIndex = this.curPage * this.pageSize
@@ -320,7 +335,14 @@ export default {
                         let urlPrefix = `${blobUrl}/exam/${item.blob.replace('/ans.json', '')}`
                         this.$jsFn.handleStudentAnswer(fullUrl, urlPrefix, sas).then(
                             res => {
-                                this.$set(this.stusInfoShow[index], 'answer', res)
+                                let answer = res.map((answer, index) => {
+                                    if(this.quNoList[index].type === 'subjective' && answer.length) {
+                                        let name = answer[0].substr(answer[0].lastIndexOf(`/${item.stuId}/`) + (item.stuId.length + 2))
+                                        answer = ['Image'].includes(this.quNoList[index].answerType) ? [`${blobUrl}/exam/${item.examId}/${item.subjectId}/${item.stuId}/${name}?${sas}`] : [name]
+                                    }
+                                    return answer
+                                })
+                                this.$set(this.stusInfoShow[index], 'answer', answer)
                                 this.stusInfoShow[index].isLoading = false
                             },
                             err => {
@@ -375,7 +397,7 @@ export default {
         },
         quitMark() {
             this.$router.go(-1)
-        }
+        },
     },
     mounted() {
 
@@ -425,14 +447,18 @@ export default {
                             data.push({
                                 label: (index + 1) + '-' + (childIndex + 1),
                                 value: realIndex++,
-                                score: childItem.score
+                                score: childItem.score,
+                                type: childItem.type,
+                                answerType: childItem?.answerType || undefined
                             })
                         })
                     } else {
                         data.push({
                             label: index + 1,
                             value: realIndex++,
-                            score: item.score
+                            score: item.score,
+                            type: item.type,
+                            answerType: item?.answerType || undefined
                         })
                     }
                 })

+ 16 - 3
TEAMModelOS/ClientApp/src/view/task/marking/mark/ByStu.vue

@@ -591,6 +591,15 @@ export default {
       //直接赋值可以渲染,当时stage.toDataUrl会报错
       // this.ansImg = url
     },
+    //获取批注数据
+    getSubjectiveHtml(blob, type) {
+      let sas = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_sas
+      let blobUrl = JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8")).blob_uri
+      let name = blob.substr(blob.lastIndexOf(`/${this.stuData.stuId}/`) + (this.stuData.stuId.length + 2))
+      let url = `${blobUrl}/exam/${this.stuData.examId}/${this.stuData.subjectId}/${this.stuData.stuId}/${name}?${sas}`
+      let h = type === 'Image' ? `<img src=\"${url}\"/>` : `<div class="answer-link"><span class="name">${name}</span></div>`
+      return [h]
+    },
   },
   mounted() {
     this.ansToImg()
@@ -636,7 +645,7 @@ export default {
       if (this.stuAnswer.length) {
         let index = this.quIndex
         this.score = this.stuScore[index] == -1 ? null : this.stuScore[index]
-        let answerOrMark = this.markArr[index] ? this.getMarkHtml(this.markArr[index]) : this.stuAnswer[index]
+        let answerOrMark = this.markArr[index] ? this.getMarkHtml(this.markArr[index]) : (this.quNoList[index].type === 'subjective' && this.quNoList[index].answerType != 'text' && this.stuAnswer[index].length ? this.getSubjectiveHtml(this.stuAnswer[index].toString(), this.quNoList[index].answerType) : this.stuAnswer[index])
         return answerOrMark
       } else {
         return this.stuId + this.$t('learnActivity.mark.noAnswer')
@@ -658,7 +667,9 @@ export default {
                 value: i,
                 disabled: objectiveQu.includes(childItem.type),
                 err: this.stuData.item ? this.stuData.item[i].err : undefined,
-                improve: this.stuData.item ? this.stuData.item[i].improve : undefined
+                improve: this.stuData.item ? this.stuData.item[i].improve : undefined,
+                type: childItem.type,
+                answerType: childItem?.answerType || undefined
               })
             })
           } else {
@@ -668,7 +679,9 @@ export default {
               value: i,
               disabled: objectiveQu.includes(item.type),
               err: this.stuData.item ? this.stuData.item[i].err : undefined,
-              improve: this.stuData.item ? this.stuData.item[i].improve : undefined
+              improve: this.stuData.item ? this.stuData.item[i].improve : undefined,
+              type: item.type,
+              answerType: item?.answerType || undefined
             })
           }
         })

+ 12 - 0
TEAMModelOS/Controllers/Analysis/ClassAnalysisController.cs

@@ -653,6 +653,18 @@ namespace TEAMModelOS.Controllers.Analysis
                     records.Add(item);
                 }
                 //班级信息
+                var tokenData = HttpContext.GetAuthTokenKey("TimeZone");
+                int TimeZone = 0;
+                if (!string.IsNullOrWhiteSpace(tokenData.keyData)) 
+                {
+                    if (int.TryParse(tokenData.keyData, out int tz)) { 
+                        TimeZone= tz;
+                    }
+                }
+
+
+                DateTimeOffset now = DateTimeOffset.UtcNow.GetGMTTime(-TimeZone);
+
                 List<string> classes = await getStudentsAsync(pId.GetString(), client, "Class", "School");
                 if (records.Count > 0)
                 {

+ 31 - 3
TEAMModelOS/Controllers/Client/AClassONEController.cs

@@ -25,6 +25,7 @@ using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Reflection;
+using System.Security.Policy;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
@@ -160,6 +161,11 @@ namespace TEAMModelOS.Controllers
                 {
                     head_lang = $"{_lang}";
                 }
+                int timezone = 8;
+                if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone,out int tz))
+                {
+                    timezone=tz;
+                }
                 if (!json.TryGetProperty("code", out JsonElement _code)) return BadRequest("code is null");
                 var phoneInfo = await GetWeChatPhoneNumber(_code.ToString());
                 //phoneInfo.code=200;
@@ -172,7 +178,7 @@ namespace TEAMModelOS.Controllers
                     {
                         (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
                         Teacher teacher = null;
-                        TeacherInfo teacherInfo = await TeacherService.TeacherInfo(_azureCosmos, teacher, $"{coreUser.name}", $"{coreUser.picture}", coreUser.id, _azureStorage, _option, _azureRedis, ip, _httpTrigger, $"{_lang}");
+                        TeacherInfo teacherInfo = await TeacherService.TeacherInfo(_azureCosmos, teacher, $"{coreUser.name}", $"{coreUser.picture}", coreUser.id, _azureStorage, _option, _azureRedis, ip, _httpTrigger, $"{_lang}",timezone);
                         return Ok(new
                         {
                             teacherInfo.auth_token,
@@ -365,8 +371,30 @@ namespace TEAMModelOS.Controllers
                 roles.Add("area");
             }
             areas.ForEach(x => { { if (x.setting != null) { x.setting.accessConfig = x.setting.accessConfig; } } });
-
-            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName,teacher. id, teacher.name, teacher.picture, _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTeacher, schoolID: school_code.ToString(), areaId: currAreaId, standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray(), expire: 1);
+            int timezone = 8;
+            if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+            {
+                timezone=tz;
+            }
+            if (!string.IsNullOrWhiteSpace(school_base.timeZone?.value)) 
+            {
+                string timeZoneOffsetString = school_base.timeZone.value;
+                bool plus = true;
+                if (timeZoneOffsetString.Contains("-"))
+                {
+                    plus=false;
+                }
+               
+                // 去除时区偏移字符串中的正负号
+                timeZoneOffsetString = timeZoneOffsetString.Replace("+", "").Replace("-", "");
+                // 尝试解析格式化后的时区偏移字符串
+                if (TimeSpan.TryParse(timeZoneOffsetString, out TimeSpan timeZoneOffset))
+                {
+                    // 将时区偏移转换为小时数
+                    timezone = plus ? (int)timeZoneOffset.TotalHours : -(int)timeZoneOffset.TotalHours;
+                }
+            }
+            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName,teacher. id, teacher.name, teacher.picture, _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTeacher, schoolID: school_code.ToString(), areaId: currAreaId, standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray(), expire: 1);
             string school_code_blob = school_code.ToLower();
             var container = _azureStorage.GetBlobContainerClient(school_code_blob);
             await container.CreateIfNotExistsAsync(PublicAccessType.None); //嘗試創建School容器,如存在則不做任何事,保障容器一定存在

+ 30 - 2
TEAMModelOS/Controllers/Client/HiTAControlller.cs

@@ -231,6 +231,29 @@ namespace TEAMModelOS.Controllers.Client
                     });
                 }
             }
+            int timezone = 8;
+            if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+            {
+                timezone=tz;
+            }
+            if (!string.IsNullOrWhiteSpace(school.timeZone?.value))
+            {
+                string timeZoneOffsetString = school.timeZone.value;
+                bool plus = true;
+                if (timeZoneOffsetString.Contains("-"))
+                {
+                    plus=false;
+                }
+
+                // 去除时区偏移字符串中的正负号
+                timeZoneOffsetString = timeZoneOffsetString.Replace("+", "").Replace("-", "");
+                // 尝试解析格式化后的时区偏移字符串
+                if (TimeSpan.TryParse(timeZoneOffsetString, out TimeSpan timeZoneOffset))
+                {
+                    // 将时区偏移转换为小时数
+                    timezone = plus ? (int)timeZoneOffset.TotalHours : -(int)timeZoneOffset.TotalHours;
+                }
+            }
             var id_token = new JwtSecurityToken($"{join.id_token}");
             var auth_token = string.Empty;
             var response = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(id_token.Payload.Sub, new PartitionKey($"Teacher-{join.school}"));
@@ -238,7 +261,7 @@ namespace TEAMModelOS.Controllers.Client
                 id_token.Payload.TryGetValue("name", out object name);
                 id_token.Payload.TryGetValue("picture", out object picture);
                 SchoolTeacher schoolTeacher =   JsonDocument.Parse(response.ContentStream).RootElement.ToObject<SchoolTeacher>();
-                auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id_token.Payload.Sub, $"{name}",$"{picture}", _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTeacher, schoolID: join.school, areaId: school.areaId, standard: school.standard, roles: schoolTeacher.roles.ToArray(), permissions:schoolTeacher.permissions.ToArray(), expire: 1);
+                auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id_token.Payload.Sub, $"{name}",$"{picture}", _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTeacher, schoolID: join.school, areaId: school.areaId, standard: school.standard, roles: schoolTeacher.roles.ToArray(), permissions:schoolTeacher.permissions.ToArray(), expire: 1);
 
             }
             await SystemService.RecordAccumulateData(_azureRedis, _dingDing, new SDK.Models.Dtos.Accumulate { client="hita", count=1, id=school.id, key="teacher_login", name=school.name, scope="school", target=school.id });
@@ -250,6 +273,11 @@ namespace TEAMModelOS.Controllers.Client
         [HttpGet("check-login")]
         public async Task<IActionResult> CheckLogin([FromQuery] HiTAJoinSchool join)
         {
+            int timezone = 8;
+            if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+            {
+                timezone=tz;
+            }
             var data = await _azureRedis.GetRedisClient(8).StringGetAsync($"HiTA:Login:{join.code}");
             if (data.HasValue)
             {
@@ -275,7 +303,7 @@ namespace TEAMModelOS.Controllers.Client
                 jwt.Payload.TryGetValue("picture", out picture);
                 jwt.Payload.TryGetValue("lang", out object _jwtlang);
                 (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
-                TeacherInfo teacherInfo=  await TeacherService.TeacherInfoLite(_azureCosmos, $"{name}", $"{picture}", $"{jwt.Payload.Sub}", _azureStorage, _option, _azureRedis, "", _httpTrigger, $"{_jwtlang}");
+                TeacherInfo teacherInfo=  await TeacherService.TeacherInfoLite(_azureCosmos, $"{name}", $"{picture}", $"{jwt.Payload.Sub}", _azureStorage, _option, _azureRedis, "", _httpTrigger, $"{_jwtlang}",timezone);
                 string x_auth_token = string.Empty;
                 if (teacherInfo!=null) {
                     x_auth_token= teacherInfo.auth_token;

+ 7 - 2
TEAMModelOS/Controllers/Common/ActivityController.cs

@@ -2716,7 +2716,7 @@ namespace TEAMModelOS.Controllers
             }
             catch (Exception ex)
             {
-                await _dingDing.SendBotMsg($"{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+                await _dingDing.SendBotMsg($"{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.成都开发測試群組);
                 return Ok(new { code = 500, msg = ex.Message });
             }
             return Ok();
@@ -2732,6 +2732,11 @@ namespace TEAMModelOS.Controllers
         [HttpPost("login-portal")]
         public async Task<IActionResult> LoginPortal(JsonElement request)
         {
+            int timezone = 8;
+            if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+            {
+                timezone=tz;
+            }
             string tmdid = null;
             if (!request.TryGetProperty("route", out JsonElement _route)) return BadRequest();
             request.TryGetProperty("ticket", out JsonElement _ticket);
@@ -2784,7 +2789,7 @@ namespace TEAMModelOS.Controllers
                 else { return Ok(new { code = 3, msg = "凭证验证失败" }); }
             }
 
-            teacherInfo = await TeacherService.TeacherInfoLite(_azureCosmos, $"{name}", $"{picture}", tmdid, _azureStorage, _option, _azureRedis, ip, _httpTrigger, head_lang);
+            teacherInfo = await TeacherService.TeacherInfoLite(_azureCosmos, $"{name}", $"{picture}", tmdid, _azureStorage, _option, _azureRedis, ip, _httpTrigger, head_lang,timezone);
             string sql = $"select value c from c where c.route='{_route}'";
             var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).GetList<ActivityWebsite>(sql, "ActivityWebsite");
             ActivityWebsite website = null;

+ 7 - 1
TEAMModelOS/Controllers/OpenApi/Init/BizUsersController.cs

@@ -115,9 +115,15 @@ namespace TEAMModelOS.Controllers
                     }
                 }
                 else return Ok(new { state = RespondCode.NotFound, msg = "未找到该用户!" });
+                int timezone = 8;
+                if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+                {
+                    timezone=tz;
+                }
+                 
                 if (businessUsers.id != null)
                 {
-                    var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, businessUsers.id, businessUsers.name, businessUsers.picture, _option.JwtSecretKey, scope: "business", Website: "IES", roles: new[] { "business" }, expire: 1);
+                    var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, businessUsers.id, businessUsers.name, businessUsers.picture, _option.JwtSecretKey, scope: "business", Website: "IES", timezone: timezone, roles: new[] { "business" }, expire: 1);
                     var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
                     var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
                     var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, _option.Location.Replace("-Dep", "").Replace("-Test", ""));

+ 13 - 5
TEAMModelOS/Controllers/School/ArtReviewController.cs

@@ -28,6 +28,7 @@ using DocumentFormat.OpenXml.Office2010.Excel;
 using static SKIT.FlurlHttpClient.Wechat.TenpayV3.Models.CreateApplyForSubjectApplymentRequest.Types;
 using TEAMModelOS.SDK.Models.Cosmos.Student;
 using TEAMModelOS.Controllers.Analysis;
+using System.Threading;
 
 namespace TEAMModelOS.Controllers
 {
@@ -183,7 +184,12 @@ namespace TEAMModelOS.Controllers
         [HttpPost("get-pdf")]
         public async Task<IActionResult> getPdf(JsonElement request)
         {
-            string head_lang = "zh-cn";         
+            string head_lang = "zh-cn";
+            List<string> schools = new List<string>();
+            if (request.TryGetProperty("schools", out JsonElement _schools) && _schools.ValueKind.Equals(JsonValueKind.Array))
+            {
+                schools = _schools.ToObject<List<string>>();
+            }
             var client = _azureCosmos.GetCosmosClient();
             string sql = "select * from c where c.pk = 'Art' and c.pId in ('2f74d38e-80c1-4c55-9dd0-de0d8f6fdf6d','306fa576-7ae4-4baa-ac24-0b5ad4dd1bc2')";           
             List<ArtEvaluation> infos = new();
@@ -212,13 +218,15 @@ namespace TEAMModelOS.Controllers
                 {
                     continue;
                 }
-                else {
+                else
+                {
+                    
                     var studentIds = artResults.Where(c => c.artId.Equals(item.id)).Select(z => z.studentId).ToList();
-
-                    await ArtService.GenArtPDF(studentIds, item.id,item.school, head_lang, _serviceBus, _configuration);
+                    await ArtService.GenArtPDF(studentIds, item.id, item.school, head_lang, _serviceBus, _configuration);
+                    await Task.Delay(30000);
                 }
             }
-            return Ok(new { code = 0, msg = "加入PDF报告生成队列中。" });
+            return Ok(new { code = 0, msg = "加入PDF报告生成队列中。", schools= infos.Select(x=>x.school)});
         }
 
         /// <summary>

+ 48 - 2
TEAMModelOS/Controllers/Student/StudentController.cs

@@ -717,9 +717,31 @@ namespace TEAMModelOS.Controllers
 
             // BLOB(學校,唯讀)
             var (blob_uri, blob_sas) = _azureStorage.GetBlobContainerSAS(school_code.ToLower(), BlobContainerSasPermissions.Read);
+            int timezone = 8;
+            if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+            {
+                timezone=tz;
+            }
+            if (!string.IsNullOrWhiteSpace(schoolBase.timeZone?.value))
+            {
+                string timeZoneOffsetString = schoolBase.timeZone.value;
+                bool plus = true;
+                if (timeZoneOffsetString.Contains("-"))
+                {
+                    plus=false;
+                }
 
+                // 去除时区偏移字符串中的正负号
+                timeZoneOffsetString = timeZoneOffsetString.Replace("+", "").Replace("-", "");
+                // 尝试解析格式化后的时区偏移字符串
+                if (TimeSpan.TryParse(timeZoneOffsetString, out TimeSpan timeZoneOffset))
+                {
+                    // 将时区偏移转换为小时数
+                    timezone = plus ? (int)timeZoneOffset.TotalHours : -(int)timeZoneOffset.TotalHours;
+                }
+            }
             //換取AuthToken,提供給前端
-            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name, picture, _option.JwtSecretKey, scope: Constant.ScopeStudent, Website: "IES", areaId: areaId, schoolID: school_code, roles: new[] { "student" }, expire: 4,year: student.year);
+            var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name, picture, _option.JwtSecretKey, scope: Constant.ScopeStudent, Website: "IES", timezone: timezone, areaId: areaId, schoolID: school_code, roles: new[] { "student" }, expire: 4,year: student.year);
 
             //用户在线记录
             try
@@ -1287,8 +1309,32 @@ namespace TEAMModelOS.Controllers
                                 }
                             }
                         }
+
+                        int timezone = 8;
+                        if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+                        {
+                            timezone=tz;
+                        }
+                        if (!string.IsNullOrWhiteSpace(schoolInfo.timeZone?.value))
+                        {
+                            string timeZoneOffsetString = schoolInfo.timeZone.value;
+                            bool plus = true;
+                            if (timeZoneOffsetString.Contains("-"))
+                            {
+                                plus=false;
+                            }
+
+                            // 去除时区偏移字符串中的正负号
+                            timeZoneOffsetString = timeZoneOffsetString.Replace("+", "").Replace("-", "");
+                            // 尝试解析格式化后的时区偏移字符串
+                            if (TimeSpan.TryParse(timeZoneOffsetString, out TimeSpan timeZoneOffset))
+                            {
+                                // 将时区偏移转换为小时数
+                                timezone = plus ? (int)timeZoneOffset.TotalHours : -(int)timeZoneOffset.TotalHours;
+                            }
+                        }
                         //換取AuthToken,提供給前端
-                        var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey, Website: "IES", areaId: schoolInfo.areaId, scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" }, expire: 4);
+                        var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, areaId: schoolInfo.areaId, scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" }, expire: 4);
                         
                         //用户在线记录
                         try

+ 7 - 1
TEAMModelOS/Controllers/Student/TmdUserController.cs

@@ -157,8 +157,14 @@ namespace TEAMModelOS.Controllers
                         teacher = await _azureCosmos.GetCosmosClient().GetContainer("TEAMModelOS", "Student").CreateItemAsync<TmdUser>(teacher, new PartitionKey("Base"));
                     }
                 }
+                int timezone = 8;
+                if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+                {
+                    timezone=tz;
+                }
+                 
                 //換取AuthToken,提供給前端
-                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTmdUser, roles: new[] { "student" }, expire: 1);
+                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTmdUser, roles: new[] { "student" }, expire: 1);
 
                 //用户在线记录
                 try

+ 3 - 2
TEAMModelOS/Controllers/System/CoreController.cs

@@ -697,7 +697,8 @@ namespace TEAMModelOS.Controllers
             if (bver > aver) {
                 version = _option.Version;
             }
-            long nowtime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            var date = DateTimeOffset.UtcNow;
+            long nowtime = date.ToUnixTimeMilliseconds();
             var IpPort = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
             if (string.IsNullOrEmpty(IpPort))
             {
@@ -723,7 +724,7 @@ namespace TEAMModelOS.Controllers
             //} 
             string region = await _searcher.SearchIpAsync(ip);
           
-            return Ok(new {  version, description, nowtime, region, ip  });
+            return Ok(new {  version, description, nowtime, region, ip, date });
         }
         /// <summary>
         /// 等待P1V3,啟動Dcoker,支持EMF GDI+

+ 30 - 2
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -491,6 +491,11 @@ namespace TEAMModelOS.Controllers
             if (HttpContext.Request.Headers.TryGetValue("lang", out var _lang)) {
                 head_lang = $"{_lang}";
             }
+            int timezone = 8;
+            if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+            {
+                timezone=tz;
+            }
             string OAuthShow_domain = HttpContext?.Request?.Host.Host;
             if (OAuthShow_domain.Equals("teammodelos.chinacloudsites.cn"))
             {
@@ -516,7 +521,7 @@ namespace TEAMModelOS.Controllers
             try
             {
                 Teacher teacher = null;
-                TeacherInfo teacherInfo = await TeacherService.TeacherInfo(_azureCosmos, teacher, $"{name}", $"{picture}", id, _azureStorage, _option, _azureRedis, ip, _httpTrigger, $"{_lang}");
+                TeacherInfo teacherInfo = await TeacherService.TeacherInfo(_azureCosmos, teacher, $"{name}", $"{picture}", id, _azureStorage, _option, _azureRedis, ip, _httpTrigger, $"{_lang}",timezone);
                 teacherInfo.areas.ForEach(x => { if (x.setting != null) { x.setting.accessConfig = x.setting.accessConfig; } });
                 LoginService.LoginLog(HttpContext, _option, _logger, _dingDing, ip, region, id, $"{name}", 200);
                 int lessonLimit = teacherInfo.teacher.lessonLimit;
@@ -820,8 +825,31 @@ namespace TEAMModelOS.Controllers
                 {
                     roles.Add("area");
                 }
+                int timezone = 8;
+                if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+                {
+                    timezone=tz;
+                }
+                if (!string.IsNullOrWhiteSpace(school_base.timeZone?.value))
+                {
+                    string timeZoneOffsetString = school_base.timeZone.value;
+                    bool plus = true;
+                    if (timeZoneOffsetString.Contains("-"))
+                    {
+                        plus=false;
+                    }
+
+                    // 去除时区偏移字符串中的正负号
+                    timeZoneOffsetString = timeZoneOffsetString.Replace("+", "").Replace("-", "");
+                    // 尝试解析格式化后的时区偏移字符串
+                    if (TimeSpan.TryParse(timeZoneOffsetString, out TimeSpan timeZoneOffset))
+                    {
+                        // 将时区偏移转换为小时数
+                        timezone = plus ? (int)timeZoneOffset.TotalHours : -(int)timeZoneOffset.TotalHours;
+                    }
+                }
                 //TODO JJ,更新Token时,在取得学校资讯时,没有传入schoolId
-                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", scope: Constant.ScopeTeacher, schoolID: school_code.ToString(), areaId: currAreaId, standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray(), expire: 1);
+                var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES",timezone: timezone, scope: Constant.ScopeTeacher, schoolID: school_code.ToString(), areaId: currAreaId, standard: school_base.standard, roles: roles.ToArray(), permissions: permissions.ToArray(), expire: 1);
 
                 //取得班级
                 (List < Class > school_classes,_) = await SchoolService.DoGraduateClasses(_httpTrigger, _azureCosmos, null, school_base, _option,_dingDing);

+ 7 - 1
TEAMModelOS/Controllers/XTest/TestController.cs

@@ -1856,9 +1856,15 @@ namespace TEAMModelOS.Controllers
             if (!request.TryGetProperty("picture", out JsonElement picture)) return BadRequest();
             if (!request.TryGetProperty("areaId", out JsonElement areaId)) return BadRequest();
             if (!request.TryGetProperty("school", out JsonElement school)) return BadRequest();
+            int timezone = 8;
+            if (HttpContext.Request.Headers.TryGetValue("Time-Zone", out var Time_Zone) && int.TryParse(Time_Zone, out int tz))
+            {
+                timezone=tz;
+            }
+            
             var auth_token = JwtAuthExtension.CreateAuthToken(host.ToString(), id.ToString(), name.ToString(), 
                 picture.ToString(), _option.JwtSecretKey,
-                scope: "student", Website: "IES", areaId: areaId.ToString(),schoolID: school.ToString(), roles: new[] { "student" }, expire: 1);
+                scope: "student", Website: "IES",timezone, areaId: areaId.ToString(),schoolID: school.ToString(), roles: new[] { "student" }, expire: 1);
             //var techs= teachers.Where(t => string.IsNullOrWhiteSpace(t.tmdid));
             return Ok(auth_token);
         }

+ 14 - 2
TEAMModelOS/Filter/AuthTokenAttribute.cs

@@ -12,6 +12,8 @@ using TEAMModelOS.SDK.DI;
 using Microsoft.AspNetCore.Http;
 using static OpenXmlPowerTools.RevisionProcessor;
 using TEAMModelOS.SDK.Models;
+using Microsoft.Extensions.Primitives;
+using System.Net.Http;
 
 namespace TEAMModelOS.Filter
 {
@@ -47,11 +49,15 @@ namespace TEAMModelOS.Filter
                 
 
                 bool pass = false;
-                string id = string.Empty, name = string.Empty, picture = string.Empty, school = string.Empty, standard = string.Empty, scope = string.Empty, website = string.Empty, area = string.Empty;
+                string id = string.Empty, name = string.Empty, picture = string.Empty, school = string.Empty, standard = string.Empty, scope = string.Empty, website = string.Empty, area = string.Empty, timzone = string.Empty ;
                 List<string> _role = new List<string>();
                 List<string> _permission = new List<string>();
                 var authtoken = context.HttpContext.GetXAuth("AuthToken");
-               
+                var TimeZone = 8;
+                if (context.HttpContext.Request.Headers.TryGetValue("Time-Zone", out StringValues value)  && int.TryParse($"{value}",out int tz)) 
+                {
+                    TimeZone=tz;
+                }
                 if (!string.IsNullOrWhiteSpace(authtoken) && JwtAuthExtension.ValidateAuthToken(authtoken, _option.JwtSecretKey))
                 {
                     var jwt = new JwtSecurityTokenHandler().ReadJwtToken(authtoken);
@@ -63,6 +69,11 @@ namespace TEAMModelOS.Filter
                     scope = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("scope"))?.Value;
                     website = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("website"))?.Value;
                     area = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("area"))?.Value;
+                    timzone = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("timzone"))?.Value;
+                    if (!string.IsNullOrEmpty(timzone)  && int.TryParse(timzone, out int _tz))
+                    {
+                        TimeZone= _tz;
+                    }
                     if (!string.IsNullOrWhiteSpace(_roles))
                     {
                         var roles = jwt.Claims.Where(c => c.Type.Equals("roles"));
@@ -163,6 +174,7 @@ namespace TEAMModelOS.Filter
                         context.HttpContext.Items.Add("Website", website);
                         context.HttpContext.Items.Add("Area", area);
                         context.HttpContext.Items.Add("Permissions", _permission);
+                        context.HttpContext.Items.Add("TimeZone", TimeZone);
                     }
                     else {
                         context.Result = new BadRequestObjectResult(new { }) { StatusCode = 501 };

+ 4 - 4
TEAMModelOS/TEAMModelOS.csproj

@@ -79,11 +79,11 @@
 		<SpaRoot>ClientApp\</SpaRoot>
 		<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
 		<UserSecretsId>078b5d89-7d90-4f6a-88fc-7d96025990a8</UserSecretsId>
-		<Version>5.2404.03</Version>
-		<AssemblyVersion>5.2404.03.1</AssemblyVersion>
-		<FileVersion>5.2404.03.1</FileVersion>
+		<Version>5.2404.10</Version>
+		<AssemblyVersion>5.2404.10.1</AssemblyVersion>
+		<FileVersion>5.2404.10.1</FileVersion>
 		<Description>TEAMModelOS(IES5)</Description>
-		<PackageReleaseNotes>IES版本说明版本切换标记5.2404.03.1</PackageReleaseNotes>
+		<PackageReleaseNotes>IES版本说明版本切换标记5.2404.10.1</PackageReleaseNotes>
 		<PackageId>TEAMModelOS</PackageId>
 		<Authors>teammodel</Authors>
 		<Company>醍摩豆(成都)信息技术有限公司</Company>

+ 1 - 1
TEAMModelOS/appsettings.Development.json

@@ -18,7 +18,7 @@
     "IdTokenSalt": "8263692E2213497BB55E74792B7900B4",
     "HttpTrigger": "https://teammodelosfunction-test.chinacloudsites.cn/api/",
     //"HttpTrigger": "http://localhost:7071/api/"
-    "Version": "5.2404.03.1"
+    "Version": "5.2404.10.1"
   },
   "Azure": {
 

+ 1 - 1
TEAMModelOS/appsettings.json

@@ -18,7 +18,7 @@
     "Exp": 86400,
     "IdTokenSalt": "8263692E2213497BB55E74792B7900B4",
     "HttpTrigger": "https://teammodelosfunction.chinacloudsites.cn/api/",
-    "Version": "5.2404.03.1"
+    "Version": "5.2404.10.1"
   },
   "Azure": {
     "Storage": {