Jelajahi Sumber

update .解决冲突

CrazyIter_Bin 3 tahun lalu
induk
melakukan
177c216442
100 mengubah file dengan 14155 tambahan dan 7881 penghapusan
  1. 1 1
      TEAMModelBI/ClientApp/public/index.html
  2. 4 0
      TEAMModelBI/ClientApp/src/api/index.js
  3. 1 1
      TEAMModelBI/ClientApp/src/components/Ability.vue
  4. 3 0
      TEAMModelBI/ClientApp/src/components/AbilityTree.vue
  5. 69 69
      TEAMModelBI/ClientApp/src/components/echarts/commonBar.vue
  6. 0 1
      TEAMModelBI/ClientApp/src/until/http.js
  7. 888 759
      TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue
  8. 472 459
      TEAMModelBI/ClientApp/src/view/common/aside.vue
  9. 88 10
      TEAMModelBI/ClientApp/src/view/created/created.vue
  10. 1 1
      TEAMModelBI/ClientApp/src/view/home.vue
  11. 2760 2186
      TEAMModelBI/ClientApp/src/view/index/index.vue
  12. 127 49
      TEAMModelBI/ClientApp/src/view/participation/setAbility.vue
  13. 870 860
      TEAMModelBI/ClientApp/src/view/schoolServe/school.vue
  14. 241 207
      TEAMModelBI/ClientApp/src/view/systemConfig/operate.vue
  15. 1342 1319
      TEAMModelBI/ClientApp/src/view/teachermanage/traitmanage.vue
  16. 112 15
      TEAMModelBI/Controllers/BIHome/AnalyseFileController.cs
  17. 43 19
      TEAMModelBI/Controllers/BIHome/OnLineController.cs
  18. 36 24
      TEAMModelBI/Controllers/BISchool/AreaRelevantController.cs
  19. 156 104
      TEAMModelBI/Controllers/BISchool/BatchAreaController.cs
  20. 61 16
      TEAMModelBI/Controllers/BISchool/SchoolController.cs
  21. 97 3
      TEAMModelBI/Controllers/BITest/TestController.cs
  22. 32 0
      TEAMModelBI/DI/BIAzureCosmosFactoryExtensions.cs
  23. 36 0
      TEAMModelBI/DI/BIAzureRedisFactoryExtensions.cs
  24. 37 0
      TEAMModelBI/DI/BIAzureServiceBusFactoryExtensions.cs
  25. 32 0
      TEAMModelBI/DI/BIAzureStorageFactoryExtensions.cs
  26. 6 6
      TEAMModelBI/JsonFile/Preset/LangSchoolConfig.json
  27. 32 4
      TEAMModelBI/Startup.cs
  28. 10 0
      TEAMModelBI/Tool/Context/BIConst.cs
  29. 24 0
      TEAMModelBI/appsettings.Development.json
  30. 18 0
      TEAMModelBI/appsettings.json
  31. 3 1
      TEAMModelOS.FunctionV4/CosmosDB/CommonTrigger.cs
  32. 11 5
      TEAMModelOS.FunctionV4/HttpTrigger/IESHttpTrigger.cs
  33. 5 1
      TEAMModelOS.FunctionV4/Program.cs
  34. 285 18
      TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs
  35. 3 3
      TEAMModelOS.FunctionV4/TEAMModelOS.FunctionV4.csproj
  36. 4 1
      TEAMModelOS.FunctionV4/local.settings.json
  37. 106 2
      TEAMModelOS.SDK/Context/Constant/Constant.cs
  38. 3 1
      TEAMModelOS.SDK/DI/DingDing/DingDing.cs
  39. 50 0
      TEAMModelOS.SDK/Models/Cosmos/BI/AreaQuoteRecord.cs
  40. 10 0
      TEAMModelOS.SDK/Models/Cosmos/Common/Inner/CodeValue.cs
  41. 6 1
      TEAMModelOS.SDK/Models/Cosmos/Common/Notice.cs
  42. 31 11
      TEAMModelOS.SDK/Models/Cosmos/Common/LessonStudentRecord.cs
  43. 0 1
      TEAMModelOS.SDK/Models/Cosmos/School/Course.cs
  44. 1 1
      TEAMModelOS.SDK/Models/Cosmos/School/School.cs
  45. 39 7
      TEAMModelOS.SDK/Models/Cosmos/School/SchoolProduct.cs
  46. 201 22
      TEAMModelOS.SDK/Models/Service/LessonService.cs
  47. 101 14
      TEAMModelOS.SDK/Models/Service/StatisticsService.cs
  48. 90 0
      TEAMModelOS.SDK/Models/Service/StudyService.cs
  49. 8 0
      TEAMModelOS.SDK/Models/Service/Third/Xkw/XkwOAuthModel.cs
  50. 1 0
      TEAMModelOS.SDK/TEAMModelOS.SDK.csproj
  51. 134 67
      TEAMModelOS/ClientApp/public/lang/en-US.js
  52. 79 12
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  53. 108 41
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  54. 4 0
      TEAMModelOS/ClientApp/src/api/lessonRecord.js
  55. 4 0
      TEAMModelOS/ClientApp/src/api/serviceDriveAuth.js
  56. 4 0
      TEAMModelOS/ClientApp/src/api/studentWeb.js
  57. 26 3
      TEAMModelOS/ClientApp/src/assets/iconfont/demo_index.html
  58. 7 3
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.css
  59. 1 1
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js
  60. 7 0
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.json
  61. TEMPAT SAMPAH
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf
  62. TEMPAT SAMPAH
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff
  63. TEMPAT SAMPAH
      TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2
  64. TEMPAT SAMPAH
      TEAMModelOS/ClientApp/src/assets/image/qrcode_en.png
  65. TEMPAT SAMPAH
      TEAMModelOS/ClientApp/src/assets/image/qrcode_tw.png
  66. 8 5
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  67. 515 530
      TEAMModelOS/ClientApp/src/components/questionnaire/BaseQnForm.vue
  68. 114 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Buzr.vue
  69. 7 6
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.less
  70. 160 105
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.vue
  71. 249 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Exam.vue
  72. 283 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ExamQu.vue
  73. 359 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ExamTable.vue
  74. 116 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Pick.vue
  75. 2 1
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Receive.vue
  76. 256 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ReceiveBack.vue
  77. 204 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ScoreBarChart.vue
  78. 9 7
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/StuReceive.vue
  79. 231 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/myWorks.vue
  80. 529 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/newClassRecord.less
  81. 789 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/newClassRecord.vue
  82. 184 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/newDataCount.vue
  83. 30 25
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/ChartHome/HomeworkPoint.vue
  84. 70 46
      TEAMModelOS/ClientApp/src/components/student-web/HomeView/HomeViewnnnnew.vue
  85. 729 747
      TEAMModelOS/ClientApp/src/components/vote/BaseVoteForm.vue
  86. 2 2
      TEAMModelOS/ClientApp/src/css/common-style.less
  87. 14 4
      TEAMModelOS/ClientApp/src/css/site.css
  88. 63 12
      TEAMModelOS/ClientApp/src/router/routes.js
  89. 4 4
      TEAMModelOS/ClientApp/src/static/Global.js
  90. 0 3
      TEAMModelOS/ClientApp/src/utils/blobTool.js
  91. 8 8
      TEAMModelOS/ClientApp/src/utils/editorLangTw.js
  92. 40 1
      TEAMModelOS/ClientApp/src/utils/public.js
  93. 18 2
      TEAMModelOS/ClientApp/src/view/Home.vue
  94. 2 2
      TEAMModelOS/ClientApp/src/view/ability/Review.vue
  95. 212 37
      TEAMModelOS/ClientApp/src/view/auth/Product.vue
  96. 7 0
      TEAMModelOS/ClientApp/src/view/auth/Serial.less
  97. 6 3
      TEAMModelOS/ClientApp/src/view/auth/Serial.vue
  98. 1 0
      TEAMModelOS/ClientApp/src/view/classmgt/ClassNotice.less
  99. 3 2
      TEAMModelOS/ClientApp/src/view/classmgt/ClassNotice.vue
  100. 0 0
      TEAMModelOS/ClientApp/src/view/classmgt/CreateNotice.vue

+ 1 - 1
TEAMModelBI/ClientApp/public/index.html

@@ -11,7 +11,7 @@
     </title>
 </head>
 <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
-<script src="https://at.alicdn.com/t/font_2934132_i31vghogqe.js"></script>
+<script src="https://at.alicdn.com/t/font_2934132_xd5xs04ekk.js"></script>
 
 <body>
     <noscript>

+ 4 - 0
TEAMModelBI/ClientApp/src/api/index.js

@@ -143,6 +143,10 @@ export default {
     operateClassroom(data) {
         return post('/schoolroom/set-bind', data)
     },
+    //删除学校
+    removeSchool(data) {
+        return post('/schoolcheck/set-del', data)
+    },
 
     //首页dashboard数据接口
     //获取各城市的学校数量(bar)

+ 1 - 1
TEAMModelBI/ClientApp/src/components/Ability.vue

@@ -474,7 +474,7 @@ export default {
     /* width: 600px !important; */
     position: sticky !important;
     top: 0px;
-    /* background-color: #e9eef3;*/
+    background-color: #e9eef3;
     z-index: 999;
     height: 40px;
     line-height: 40px;

+ 3 - 0
TEAMModelBI/ClientApp/src/components/AbilityTree.vue

@@ -836,6 +836,9 @@ export default {
     max-width: 1100px;
     max-height: 600px;
 }
+.el-tree{
+    background-color:#e9eef3;
+}
 #previewVideo {
     width: 100%;
 }

+ 69 - 69
TEAMModelBI/ClientApp/src/components/echarts/commonBar.vue

@@ -1,85 +1,85 @@
 <!--基础折线图-->
 <template>
-    <div ref="myEcharts" :style="{ height, width }"></div>
+  <div ref="myEcharts" :style="{ height, width }" class="boxs"></div>
 </template>
 <script>
 import { ref, onMounted, nextTick, watch, getCurrentInstance } from 'vue'
 import * as echarts from 'echarts'
 export default {
-    name: 'baseBar',
-    props: {
-        width: {
-            type: String,
-            default: '100%',
-        },
-        height: {
-            type: String,
-            default: '100%',
-        },
-        barData: {
-            type: Object,
-            default: () => { },
-        },
-        title: {
-            type: String,
-            default: '',
-        },
+  name: 'baseBar',
+  props: {
+    width: {
+      type: String,
+      default: '100%',
     },
-    setup(props) {
-        const myEcharts = ref(null)
-        let { proxy } = getCurrentInstance()
-        const chart = new InitChart(props, myEcharts)
-        onMounted(() => {
-            chart.init(props.barData, proxy)
-        })
-        watch(
-            props,
-            (nweProps) => {
-                nextTick(() => {
-                    nweProps ? chart.init(props.barData, proxy) : ''
-                })
-            },
-            { immediate: true, deep: true }
-        )
-        return {
-            myEcharts,
-        }
+    height: {
+      type: String,
+      default: '100%',
+    },
+    barData: {
+      type: Object,
+      default: () => { },
+    },
+    title: {
+      type: String,
+      default: '',
     },
+  },
+  setup (props) {
+    const myEcharts = ref(null)
+    let { proxy } = getCurrentInstance()
+    const chart = new InitChart(props, myEcharts)
+    onMounted(() => {
+      chart.init(props.barData, proxy)
+    })
+    watch(
+      props,
+      (nweProps) => {
+        nextTick(() => {
+          nweProps ? chart.init(props.barData, proxy) : ''
+        })
+      },
+      { immediate: true, deep: true }
+    )
+    return {
+      myEcharts,
+    }
+  },
 }
 class InitChart {
-    constructor(props, myEcharts) {
-        this.props = props
-        this.myEcharts = myEcharts
-        this.state = {
-            chart: null,
-        }
-    }
-    init(datas, proxy) {
-        console.log(datas, '柱状图的调用')
-        this.state.chart && this.destory()
-        this.state.chart = echarts.init(this.myEcharts.value)
-        this.state.chart.setOption({
-            color: datas.color ? datas.color : '',
-            title: datas.title ? datas.title : '',
-            tooltip: datas.tooltip ? datas.tooltip : '',
-            legend: datas.legend ? datas.legend : '',
-            grid: datas.grid ? datas.grid : '',
-            xAxis: datas.xAxis ? datas.xAxis : '',
-            yAxis: datas.yAxis ? datas.yAxis : '',
-            dataZoom: datas.dataZoom ? datas.dataZoom : '',
-            series: datas.series ? datas.series : '',
-        })
-        window.addEventListener('resize', () => {
-            this.state.chart.resize()
-        })
+  constructor(props, myEcharts) {
+    this.props = props
+    this.myEcharts = myEcharts
+    this.state = {
+      chart: null,
     }
+  }
+  init (datas, proxy) {
+    console.log(datas, '柱状图的调用')
+    this.state.chart && this.destory()
+    this.state.chart = echarts.init(this.myEcharts.value)
+    this.state.chart.setOption({
+      color: datas.color ? datas.color : '',
+      title: datas.title ? datas.title : '',
+      tooltip: datas.tooltip ? datas.tooltip : '',
+      legend: datas.legend ? datas.legend : '',
+      grid: datas.grid ? datas.grid : '',
+      xAxis: datas.xAxis ? datas.xAxis : '',
+      yAxis: datas.yAxis ? datas.yAxis : '',
+      dataZoom: datas.dataZoom ? datas.dataZoom : '',
+      series: datas.series ? datas.series : '',
+    })
+    window.addEventListener('resize', () => {
+      this.state.chart.resize()
+    })
+  }
 
-    destory() {
-        this.state.chart.dispose()
-        window.removeEventListener('resize', () => {
-            console.log('事件移除')
-        })
-    }
+  destory () {
+    this.state.chart.dispose()
+    window.removeEventListener('resize', () => {
+      console.log('事件移除')
+    })
+  }
 }
 </script>
 <style lang="less">

+ 0 - 1
TEAMModelBI/ClientApp/src/until/http.js

@@ -20,7 +20,6 @@ axios.interceptors.request.use(
         } else {
             config.headers = {
                 'Content-Type': 'application/json',
-                'Authorization': ""
             }
         }
         return config;

File diff ditekan karena terlalu besar
+ 888 - 759
TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue


File diff ditekan karena terlalu besar
+ 472 - 459
TEAMModelBI/ClientApp/src/view/common/aside.vue


+ 88 - 10
TEAMModelBI/ClientApp/src/view/created/created.vue

@@ -264,10 +264,13 @@
             <div class="batch-List" v-show="batchList === true">
                 <p>
                     <div class="schoolnums">{{ $t(`schoolManages.createSchools.totalList`) }}:{{ batchData.length }}</div>
-                    <div class="schoolbatch-title"><span>批量创校名单表</span></div>
+                    <div class="schoolbatch-title">
+                        <span v-if="batchCreatedSchool===false">批量创校名单表</span>
+                        <span v-else>批量创校结果</span>
+                    </div>
                 </p>
                 <div class="batch-table">
-                    <el-table :data="batchData" height="300" style="width: 100%">
+                    <el-table :data="batchData" height="300" style="width: 100%" :row-class-name="tableRowClassName">
                         <el-table-column prop="index" :label="$t(`schoolManages.createSchools.schoolinfo.serialnum`)"
                             type=index align="center" />
                         <el-table-column prop="name" :label="$t(`schoolManages.createSchools.schoolinfo.name`)"
@@ -288,10 +291,24 @@
                         <el-table-column prop="dist" :label="$t(`areaManages.selector.areaName`)" align="center" />
                         <el-table-column prop="address" :label="$t(`schoolManages.createSchools.schoolinfo.address`)"
                             align="center" />
+                        <el-table-column prop="state" label="创建结果" align="center" v-if="batchCreatedSchool">
+                            <template #default="scope">
+                                 <svg class="resultIcon" aria-hidden="true" v-if="scope.row.state ===true">
+                                    <use xlink:href="#icon-tijiaochenggong"></use>
+                                 </svg>
+                                <svg class="resultIcon" aria-hidden="true" v-else>
+                                    <use xlink:href="#icon-shibai"></use>
+                                 </svg>
+                            </template>
+                        </el-table-column>    
                     </el-table>
                     <div class="batchs-btn">
-                        <el-button type="primary" size="medium" @click="quantity(); createdSchoolLoading = true"
-                            :loading="createdSchoolLoading">{{ $t(`schoolManages.createSchools.batchBtn`) }}</el-button>
+                        <el-button type="primary" size="medium" @click="quantity(); createdSchoolLoading = true" :loading="createdSchoolLoading" v-if="batchCreatedSchool===false">
+                        {{ $t(`schoolManages.createSchools.batchBtn`) }}
+                        </el-button>
+                        <el-button type="primary" size="medium" @click="batchList=false,batchCreatedSchool=false,batchData=[]"  v-else-if="batchCreatedSchool===true">
+                        确认
+                        </el-button>
                     </div>
                 </div>
             </div>
@@ -418,7 +435,9 @@ export default {
             checkStrictly: false,
         })
         let loadingCreatedArea=ref(false)
-        let loadingCreatedSchool=ref(false)
+        //批量创建学校显示结果状态
+        let batchCreatedSchool=ref(false)
+        let batchCreatedResult=ref()
         onMounted(() => {
             formArea.value = JSON.parse(JSON.stringify(areaData))
             console.log(formArea, '初步的数据')
@@ -700,12 +719,56 @@ export default {
             console.log(datas, '批量最后的数据')
             proxy.$api.createdSchools(datas).then((res) => {
                 console.log(res, '批量创校的返回')
-                res.state === 200
-                    ? (ElMessage.success(proxy.$t(`commonMsg.batchCreatedSuccess`)), (batchList.value = false), (batchData.value = []), router.push({ path: '/home/schoolmanage' }))
-                    : (ElMessage.error(proxy.$t(`commonMsg.batchCreatedError`)), (batchList.value = false), (batchData.value = []))
-                createdSchoolLoading.value = false
+                // res.state === 200
+                //     ? (ElMessage.success(proxy.$t(`commonMsg.batchCreatedSuccess`)), (batchList.value = false), (batchData.value = []), router.push({ path: '/home/schoolmanage' }))
+                //     : res.state ===201 ?res.schools.forEach((x)=>{
+                //         let ndata=x;x.state=true;
+                //         batchData.value.forEach((y)=>{
+                //             ndata.name ===y.name && ndata.type ===y.type ? y.state =false:''
+                //             })
+                //      })
+                //     :(ElMessage.error(proxy.$t(`commonMsg.batchCreatedError`)), (batchList.value = false), (batchData.value = []))
+                // createdSchoolLoading.value = false
+                if(res.state ===200) {
+                batchData.value.forEach((y)=>{y.state=true})
+                batchCreatedSchool.value=true
+                ElMessage.success(proxy.$t(`commonMsg.batchCreatedSuccess`))
+                // batchList.value = false;
+                // batchData.value = []; 
+                 router.push({ path: '/home/schoolmanage' })
+            }else if(res.state ===201){
+              for(let i  in batchData.value){
+                 batchData.value[i].state=true
+                 res.schools.forEach((x)=>{
+                    x.name === batchData.value[i].name && x.type ===batchData.value[i].type ? batchData.value[i].state=false:''
+                 })
+            }
+            
+            ElMessage.warning('部分学校已存在,请不要重新创建!')
+            // batchCreatedResult.value=batchData.value
+            batchCreatedSchool.value=true
+            console.log(batchData.value)      
+            }else{
+                ElMessage.error(proxy.$t(`commonMsg.batchCreatedError`))
+                batchList.value = false;batchData.value = []
+            }
+            createdSchoolLoading.value = false
             })
         }
+        function tableRowClassName(row){
+            console.log(row.row.state)
+            // if (row.row.zyl >= 80) {
+            //         return 'warm-row';
+            //     }
+            //         return '';
+             if(!row.row.hasOwnProperty('state')){return}
+            
+            if(row.row.state){
+                 return 'success-row';
+            }else{
+                return 'warning-row';
+            }
+        }
         function closeDialog(done) {
             console.log(done)
             batchData.value = []
@@ -805,7 +868,10 @@ export default {
             createdSchoolforms,
             cascaderSchool,
             fileList,
-            loadingCreatedArea
+            loadingCreatedArea,
+            batchCreatedSchool,
+            batchCreatedResult,
+            tableRowClassName
         }
     },
 }
@@ -1103,6 +1169,12 @@ export default {
     font-size:18px;
     font-weight:700;
 }
+.resultIcon{
+    width: 24px;
+    height: 24px;
+    fill: currentColor;
+    overflow: hidden;
+}
 </style>
 <style>
 .areabox .el-form {
@@ -1179,4 +1251,10 @@ export default {
 .el-dialog__headerbtn {
     line-height: 20px
 }
+.el-table .warning-row {
+  background:rgba(255, 118, 117,0.4);
+}
+.el-table .success-row {
+  background:rgba(85, 239, 196,0.4)
+}
 </style>

+ 1 - 1
TEAMModelBI/ClientApp/src/view/home.vue

@@ -89,7 +89,7 @@ li {
 .viewbox .el-sub-menu__icon-arrow {
     /* top: 53% !important; */
     font-size: 15px !important;
-    right: 7px;
+    right: 5%;
 }
 .viewbox .el-menu-vertical-demo {
     min-height: 5px;

File diff ditekan karena terlalu besar
+ 2760 - 2186
TEAMModelBI/ClientApp/src/view/index/index.vue


+ 127 - 49
TEAMModelBI/ClientApp/src/view/participation/setAbility.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="abilitysInfo">
-        <div class="ability-names" v-show="treeModel === 'default'">微能力点管理</div>
+        <!-- <div class="ability-names" v-show="treeModel === 'default'">微能力点管理</div> -->
         <!--外部呈现-->
         <div class="allbox" v-if="treeModel === 'default'">
             <!-- <el-tabs type="border-card">
@@ -10,13 +10,28 @@
                 </el-tab-pane>
             </el-tabs> -->
             <div class="cardlist-card" v-if="showPattern.state">
-                <div class="cardbox" v-for="(item, index) in abilityProject" :key="item.id">
-                    <el-card class="box-card">
-                        <div class="card-number">{{ index + 1 }}</div>
+                 <div class="readybox">
+                <div class="cardlist-card-title">
+                    <div class="cardlist-card-title-icon">
+                        <svg class="cardtitleIcon" aria-hidden="true">
+                            <use xlink:href="#icon-tubiaozhizuomoban"></use>
+                        </svg>
+                    </div>
+                    <div class="cardlist-card-title-text"><span>我参与的</span></div>
+                </div>
+                <div class="preinstallbox">
+                     <div class="cardbox" v-for="(item, index) in abilityProject" :key="item.id">
+                        <div class="mask1" @click="pitch(item)">
+                             <el-image  :src="redactImg" :fit="fit" />
+                             <p class="hovertitles">编辑当前微能力点</p>
+                        </div>
+                        <el-card class="box-card">
+                        <!-- <div class="card-number">{{ index + 1 }}</div> -->
                         <template #header>
                             <div class="card-header">
                                 <span class="card-names" :title="item.standardName">{{ item.standardName }}</span>
-                                <el-button class="button" type="text" @click="pitch(item)">编辑</el-button>
+                                <!-- <el-button class="button" type="text" @click="pitch(item)" v-if="PowerShow">编辑
+                                </el-button> -->
                             </div>
                         </template>
                         <div class="information-box">
@@ -31,6 +46,8 @@
                         </div>
                     </el-card>
                 </div>
+                </div>
+             </div>
             </div>
             <div class="cutlist-table" v-else-if="showPattern.state === false">
                 <el-table :data="abilityProject" :highlight-current-row="true" style="width: 100%"
@@ -272,18 +289,15 @@
                                 </i>
                             </div>
                         </div>
-                        <div v-else-if="taskFormLabel.task[0].titles.length === 0"><span class="zw-title">{{
-                                $t(`abilityManages.practice.editPractice.nodata`)
-                        }}</span><span class="tj-title" @click="addordelegrade('add', 'excellent')">{{
-        $t(`abilityManages.practice.editPractice.add`)
-}}</span>
+                        <div v-else-if="taskFormLabel.task[0].titles.length === 0">
+                        <span class="zw-title">{{$t(`abilityManages.practice.editPractice.nodata`)}}</span>
+                        <span class="tj-title" @click="addordelegrade('add', 'excellent')">{{$t(`abilityManages.practice.editPractice.add`)}}</span>
                         </div>
                     </el-form-item>
                     <el-form-item>
                         <div class="std-title-grade">{{ $t(`abilityManages.practice.editPractice.qualified`) }}</div>
                         <!-- <div class="std-title-item" v-for="(items,index) in taskFormLabel.task[0].titles"> -->
-                        <div class="std-title-item" v-for="(items, index) in taskFormLabel.task[0].score"
-                            v-if="taskFormLabel.task[0].score.length !== 0">
+                        <div class="std-title-item" v-for="(items, index) in taskFormLabel.task[0].score"  v-if="taskFormLabel.task[0].score.length !== 0">
                             <div class="taskvalue" v-if="items.value == '2'">
                                 <span>{{ index + 1 }}.</span>
                                 <div class="content-input">
@@ -536,6 +550,7 @@ export default {
         ])
         //为配合tree新增而声明的变量,完整数据
         let treeDataInfo = ref([])
+        const redactImg=require('@/assets/img/redacts.png')
         //获取到方案下的能力点,并默认选中第一个
         function pitch(data) {
             console.log(data, '查询册别传入的data')
@@ -810,16 +825,26 @@ export default {
             addTask.value = false
             console.log(data, newData, '处理过后的')
         }
-        onMounted(() => {
-            console.log(store.state.point.length)
+        function init(){
             store.state.point.length
                 ? abilityProject.push(...store.state.point)
                 : proxy.$api.getCapacity({}).then((res) => {
-                    res.areas.splice(2)
+                     res.areas.splice(2)
                     abilityProject.push(...res.areas)
-                    store.commit('getPoint', res.areas)
+                    // store.commit('getPoint', res.areas)
                 })
-        })
+        }
+        // onMounted(() => {
+        //     console.log(store.state.point.length)
+        //     store.state.point.length
+        //         ? abilityProject.push(...store.state.point)
+        //         : proxy.$api.getCapacity({}).then((res) => {
+        //             res.areas.splice(2)
+        //             abilityProject.push(...res.areas)
+        //             // store.commit('getPoint', res.areas)
+        //         })
+        // })
+        init()
         watch(
             showPattern,
             (newValue) => {
@@ -875,6 +900,8 @@ export default {
             areaIds,
             NowformLabelAlign,
             showPattern,
+            redactImg,
+            init
         }
     },
 }
@@ -887,24 +914,24 @@ export default {
     position: relative;
 }
 
+.box-card {
+    position: relative;
+}
 .cardbox {
-    width: 25%;
+    width: 19%;
+    height:270px;
     display: inline-block;
-    padding: 10px 15px;
+    padding: 5px 10px;
+    margin-top:10px;
+    position: relative;
+    cursor: pointer;
 }
-
-.cardbox:hover {
-    -webkit-transform: translateY(-18px);
-    -ms-transform: translateY(-18px);
-    transform: translateY(-18px);
-    -webkit-transition: all 0.4s ease-out;
-    transition: all 0.4s ease-out;
+.cardbox .box-card{
+    background:rgba(116, 185, 255,.3)
 }
-
-.box-card {
-    position: relative;
+.cardbox:hover .mask1{
+    opacity:1;
 }
-
 .card-number {
     position: absolute;
     width: 25px;
@@ -968,7 +995,7 @@ export default {
     width: 355px;
     height: 85vh;
     border-right: 1px dashed rgb (180, 178, 178);
-    margin-top: 35px;
+    margin-top: 44px;
     margin-left: 0%;
     overflow: hidden;
 }
@@ -1401,32 +1428,82 @@ export default {
     width: 100%;
     padding: 1%;
     position: relative;
-    animation: listdata 0.5s ease-out;
-    -webkit-animation: listdata 0.5s ease-out;
-    -webkit-animation: listdata 0.5s ease-out 0ms infinite normal forwards;
-    -moz-animation: listdata 0.5s ease-out 0ms infinite normal forwards;
-    -o-animation: listdata 0.5s ease-out 0ms infinite normal forwards;
-    animation: listdata 0.5s ease-out 0ms infinite normal forwards;
-    animation-iteration-count: 1;
 }
 
 .cardlist-card {
     width: 100%;
-    padding: 1%;
-    overflow: auto;
+    /* padding: 1%; */
+    overflow: hidden;
     display: flex;
     flex-wrap: wrap;
     justify-content: flex-start;
     position: relative;
-    animation: mymove 0.5s ease-out;
-    -webkit-animation: mymove 0.5s ease-out;
-    -webkit-animation: mymove 0.5s ease-out 0ms infinite normal forwards;
-    -moz-animation: mymove 0.5s ease-out 0ms infinite normal forwards;
-    -o-animation: mymove 0.5s ease-out 0ms infinite normal forwards;
-    animation: mymove 0.5s ease-out 0ms infinite normal forwards;
-    animation-iteration-count: 1;
 }
-
+.readybox{
+    width: 100%;
+    height:80vh;
+    padding:1%;
+    overflow: auto;
+    position: relative;
+    border:1px solid #ccc;
+    border-radius: 10px;
+    background-color: rgba(255,255,255,.6);
+    margin-top:1%;
+}
+.cardlist-card-title-icon,.cardlist-card-title-text{
+    display: inline-block;
+    vertical-align: top;
+}
+.cardlist-card-title{
+    text-align: left;
+}
+.cite .box-card{
+    background-color:rgba(129, 236, 236,.4)
+}
+.citeboxs{
+    height:48vh;
+}
+.citeboxs .preinstallbox{
+    height:40vh;
+}
+.cardtitleIcon{
+    width:30px;
+    height:30px;
+    fill: currentColor;
+    overflow: hidden;
+    margin-right: 10px;
+}
+.cardlist-card-title-text{
+    margin-top: 3px;
+    text-align:left;
+    font-weight:bold;
+}
+.mask1{
+    position: absolute;
+    width: 94%;
+    height: 98%;
+    background:rgba(178, 190, 195,0.9);
+    opacity: 0;
+    border-radius: 15px;
+    background-size:100% 100%;
+    z-index:999;
+    background-position: center;
+}
+.mask1 .el-image{
+    width:20%;
+    margin-top:25%;
+}
+.hovertitles{
+    font-size:16px;
+    font-weight:bold;
+    color: rgba(255,255,255,.9);
+}
+.preinstallbox{
+    width:100%;
+    display:flex;
+    flex-wrap: wrap;
+    /* overflow: auto; */
+}
 @keyframes mymove {
     0% {
         top: -35px;
@@ -1470,7 +1547,8 @@ export default {
 <style>
 .box-card .el-card__header,
 .box-card .el-card__body {
-    line-height: 60px;
+    line-height: 20px ;
+     padding: 10px;
 }
 
 .allbox .el-tabs__nav-scroll {

File diff ditekan karena terlalu besar
+ 870 - 860
TEAMModelBI/ClientApp/src/view/schoolServe/school.vue


+ 241 - 207
TEAMModelBI/ClientApp/src/view/systemConfig/operate.vue

@@ -1,258 +1,292 @@
 <template>
-    <div class="operatebox">
-        <div class="nowuser">
-            <p class="identitybox-title">当前操作用户:</p>
-            <div class="userlist">
-                <div class="photobox">
-                    <PersonalPhoto style="cursor: pointer;" :name="nowUsers.name" width="40px" height="40px" fontSize="12px" class="pigpicture" v-if="!nowUsers.picture"></PersonalPhoto>
-                    <el-image style="width: 40px; height: 40px;border-radius:50%" :src="nowUsers.picture" fit="fill" v-else></el-image>
-                </div>
-                <div class="userlist-name">{{nowUsers.name}}({{nowUsers.mobile}})</div>
-            </div>
+  <div class="operatebox">
+    <div class="nowuser">
+      <p class="identitybox-title">当前操作用户:</p>
+      <div class="userlist">
+        <div class="photobox">
+          <PersonalPhoto style="cursor: pointer;" :name="nowUsers.name" width="40px" height="40px" fontSize="12px" class="pigpicture" v-if="!nowUsers.picture"></PersonalPhoto>
+          <el-image style="width: 40px; height: 40px;border-radius:50%" :src="nowUsers.picture" fit="fill" v-else></el-image>
         </div>
-        <div class="identitybox">
-            <p class="identitybox-title">当前身份:</p>
-            <div class="identitybox-select">
-                <el-select v-model="identityValue" size="small" placeholder="当前身份" multiple @change="changeIdentity">
-                    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
-                </el-select>
-            </div>
+        <div class="userlist-name">{{nowUsers.name}}({{nowUsers.mobile}})</div>
+      </div>
+    </div>
+    <div class="identitybox">
+      <p class="identitybox-title">当前身份:</p>
+      <div class="identitybox-select">
+        <el-select v-model="identityValue" size="small" placeholder="当前身份" multiple @change="changeIdentity">
+          <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </div>
+    </div>
+    <div class="authoritybox">
+      <p class="identitybox-title">权限列表:</p>
+      <div class="authority-block" v-for="(item,index) in authorityList" :key="index">
+        <p class="authority-block-title">{{item.name}}</p>
+        <div class="authority-block-item" v-for="(items,indexs) in item.children" :key="indexs">
+          <div class="authority-block-item-name">{{items.name}}</div>
+          <div class="authority-block-item-state">
+            <el-switch v-model="items.state" @change="changeAuthority" />
+          </div>
         </div>
-        <div class="authoritybox">
-            <p class="identitybox-title">权限列表:</p>
-            <div class="authority-block" v-for="(item,index) in authorityList" :key="index">
-                <p class="authority-block-title">{{item.name}}</p>
-                <div class="authority-block-item" v-for="(items,indexs) in item.children" :key="indexs">
-                    <div class="authority-block-item-name">{{items.name}}</div>
-                    <div class="authority-block-item-state">
-                        <el-switch v-model="items.state" @change="changeAuthority" />
-                    </div>
-                </div>
-            </div>
+      </div>
+      <div class="authority-block">
+        <p class="authority-block-title">站点数据</p>
+        <div class="sitebox" v-for="item in siteList" :key="item.id">
+          <div class="sitebox-name">{{item.label}}</div>
+          <div class="sitebox-state">
+            <el-switch v-model="item.state" />
+            <!-- <el-switch v-model="item.state" class="ml-2" active-color="#13ce66" inactive-color="#ff4949" /> -->
+          </div>
         </div>
+      </div>
     </div>
+  </div>
 </template>
 <script>
 import { ref, watch, getCurrentInstance } from 'vue'
 import { ElMessage, ElLoading, ElMessageBox } from 'element-plus'
 export default {
-    emits: ['changeShow'],
-    props: {
-        userdata: {
-            type: Object,
-            default: () => {},
-        },
+  emits: ['changeShow'],
+  props: {
+    userdata: {
+      type: Object,
+      default: () => { },
     },
-    setup(props, context) {
-        console.log(props)
-        let { proxy } = getCurrentInstance()
-        const identityValue = ref([])
-        const options = [
-            {
-                value: 'channelcrew',
-                label: '渠道人员',
-            },
-            {
-                value: 'sellcrew',
-                label: '销售',
-            },
-            {
-                value: 'assist',
-                label: '顾问',
-            },
-            // {
-            //     value: 'research',
-            //     label: '研发部门人员',
-            // },
-            {
-                value: 'leader',
-                label: '领导',
-            },
-            {
-                value: 'admin',
-                label: '系统管理员',
-            },
-        ]
+  },
+  setup (props, context) {
+    console.log(props)
+    let { proxy } = getCurrentInstance()
+    const identityValue = ref([])
+    const options = [
+      {
+        value: 'channelcrew',
+        label: '渠道人员',
+      },
+      {
+        value: 'sellcrew',
+        label: '销售',
+      },
+      {
+        value: 'assist',
+        label: '顾问',
+      },
+      // {
+      //     value: 'research',
+      //     label: '研发部门人员',
+      // },
+      // {
+      //     value: 'leader',
+      //     label: '领导',
+      // },
+      {
+        value: 'admin',
+        label: '系统管理员',
+      },
+    ]
 
-        let authorityList = ref([
-            // { id: 1, name: '系统配置', children: [{ id: 1 - 1, name: '操作系统配置', key: '123456', state: false }] },
-            // {
-            //     id: 2,
-            //     name: '人员管理',
-            //     children: [
-            //         { id: 2 - 1, name: '查看人员名单信息', key: '123456', state: false },
-            //         { id: 2 - 2, name: '编辑人员名单信息', key: '123456', state: true },
-            //     ],
-            // },
-            {
-                id: 3,
-                name: '学区情况',
-                children: [
-                    { id: 3 - 1, name: '查看学区数据信息', key: 'areadata-read', state: false },
-                    { id: 3 - 2, name: '修改学区相关信息', key: 'areadata-upd', state: false },
-                ],
-            },
-            {
-                id: 4,
-                name: '学校情况',
-                children: [
-                    { id: 4 - 1, name: '查看学校数据信息', key: 'schooldata-read', state: false },
-                    { id: 4 - 2, name: '修改学校相关信息', key: 'schooldata-upd', state: false },
-                ],
-            },
-        ])
-        let nowUsers = ref()
-        let nowAuthority = ref([])
-        let nowIdentity = ref([])
-        function init() {
-            let userInfo = nowUsers.value.handlePermissions
-            console.log(userInfo)
-            for (let i in userInfo) {
-                let authorityName = userInfo[i]
-                for (let y in authorityList.value) {
-                    let listData = authorityList.value[y].children
-                    for (let e in listData) {
-                        listData[e].key === authorityName ? (listData[e].state = true) : ''
-                    }
-                }
-            }
-        }
-        function initialize() {
-            for (let y in authorityList.value) {
-                let listData = authorityList.value[y].children
-                listData.map((x) => (x.state = false))
-            }
-            for (let i in nowUsers.value.handleRoles) {
-                let role = nowUsers.value.handleRoles[i]
-                for (let u in options) {
-                    options[u].value === role ? identityValue.value.push(options[u].value) : ''
-                }
-            }
-            identityValue.value = [...new Set(identityValue.value)]
-            nowAuthority.value = []
-            nowIdentity.value = []
+    let authorityList = ref([
+      // { id: 1, name: '系统配置', children: [{ id: 1 - 1, name: '操作系统配置', key: '123456', state: false }] },
+      // {
+      //     id: 2,
+      //     name: '人员管理',
+      //     children: [
+      //         { id: 2 - 1, name: '查看人员名单信息', key: '123456', state: false },
+      //         { id: 2 - 2, name: '编辑人员名单信息', key: '123456', state: true },
+      //     ],
+      // },
+      {
+        id: 3,
+        name: '学区情况',
+        children: [
+          { id: 3 - 1, name: '查看学区数据信息', key: 'areadata-read', state: false },
+          { id: 3 - 2, name: '修改学区相关信息', key: 'areadata-upd', state: false },
+        ],
+      },
+      {
+        id: 4,
+        name: '学校情况',
+        children: [
+          { id: 4 - 1, name: '查看学校数据信息', key: 'schooldata-read', state: false },
+          { id: 4 - 2, name: '修改学校相关信息', key: 'schooldata-upd', state: false },
+        ],
+      },
+    ])
+    let siteList = ref([
+      { id: 1, label: '中国站点', state: true },
+      { id: 2, label: '国际站点', state: false }
+    ])
+    let nowUsers = ref()
+    let nowAuthority = ref([])
+    let nowIdentity = ref([])
+    function init () {
+      let userInfo = nowUsers.value.handlePermissions
+      console.log(userInfo)
+      for (let i in userInfo) {
+        let authorityName = userInfo[i]
+        for (let y in authorityList.value) {
+          let listData = authorityList.value[y].children
+          for (let e in listData) {
+            listData[e].key === authorityName ? (listData[e].state = true) : ''
+          }
         }
-        function changeIdentity(value) {
-            console.log(value)
-            nowIdentity.value = value
-            console.log(identityValue.value)
-            context.emit('changeShow', true)
+      }
+    }
+    function initialize () {
+      for (let y in authorityList.value) {
+        let listData = authorityList.value[y].children
+        listData.map((x) => (x.state = false))
+      }
+      for (let i in nowUsers.value.handleRoles) {
+        let role = nowUsers.value.handleRoles[i]
+        for (let u in options) {
+          options[u].value === role ? identityValue.value.push(options[u].value) : ''
         }
-        function changeAuthority(value) {
-            console.log(value)
-            for (let i in authorityList.value) {
-                let data = authorityList.value[i].children
-                for (let u in data) {
-                    data[u].state === true ? nowAuthority.value.push(data[u].key) : ''
-                }
-            }
-            context.emit('changeShow', true)
-            nowAuthority.value = [...new Set(nowAuthority.value)]
+      }
+      identityValue.value = [...new Set(identityValue.value)]
+      nowAuthority.value = []
+      nowIdentity.value = []
+    }
+    function changeIdentity (value) {
+      console.log(value)
+      nowIdentity.value = value
+      console.log(identityValue.value)
+      context.emit('changeShow', true)
+    }
+    function changeAuthority (value) {
+      console.log(value)
+      for (let i in authorityList.value) {
+        let data = authorityList.value[i].children
+        for (let u in data) {
+          data[u].state === true ? nowAuthority.value.push(data[u].key) : ''
         }
-        function notarizeChange() {
-            changeAuthority()
-            console.log('触发子组件的修改方法')
-            let rolesinfo = identityValue.value
-            let permissionsinfo = nowAuthority.value
-            let data = { partitionKey: nowUsers.value.partitionKey, userId: nowUsers.value.userId, tmdId: nowUsers.value.tmdId, permissions: permissionsinfo, roles: rolesinfo }
-            proxy.$api
-                .setRolesandPower(data)
-                .then((res) => {
-                    console.log(res, '变更返回')
-                    res.state === 200 ? (ElMessage.success('保存成功'), context.emit('changeShow', false)) : ElMessage.error('变更保存失败')
-                })
-                .catch((err) => {
-                    ElMessage.error('变更API保存失败')
-                })
-        }
-        watch(
-            props,
-            (newuser) => {
-                newuser ? (nowUsers.value = newuser.userdata) : ''
-                initialize(), init()
-            },
-            { immediate: true, deep: true }
-        )
-        return { identityValue, options, authorityList, nowUsers, init, changeIdentity, changeAuthority, notarizeChange }
-    },
+      }
+      context.emit('changeShow', true)
+      nowAuthority.value = [...new Set(nowAuthority.value)]
+    }
+    function notarizeChange () {
+      changeAuthority()
+      console.log('触发子组件的修改方法')
+      let rolesinfo = identityValue.value
+      let permissionsinfo = nowAuthority.value
+      let data = { partitionKey: nowUsers.value.partitionKey, userId: nowUsers.value.userId, tmdId: nowUsers.value.tmdId, permissions: permissionsinfo, roles: rolesinfo }
+      proxy.$api
+        .setRolesandPower(data)
+        .then((res) => {
+          console.log(res, '变更返回')
+          res.state === 200 ? (ElMessage.success('保存成功'), context.emit('changeShow', false)) : ElMessage.error('变更保存失败')
+        })
+        .catch((err) => {
+          ElMessage.error('变更API保存失败')
+        })
+    }
+    watch(
+      props,
+      (newuser) => {
+        newuser ? (nowUsers.value = newuser.userdata) : ''
+        initialize(), init()
+      },
+      { immediate: true, deep: true }
+    )
+    return { identityValue, options, authorityList, nowUsers, init, changeIdentity, changeAuthority, notarizeChange, siteList }
+  },
 }
 </script>
 <style scoped>
 .operatebox {
-    width: 100%;
-    line-height: 20px;
-    text-align: left;
+  width: 100%;
+  line-height: 20px;
+  text-align: left;
 }
 .identitybox {
-    padding: 1% 0% 4% 0%;
-    border-bottom: 1px solid #b2bec3;
+  padding: 1% 0% 4% 0%;
+  border-bottom: 1px solid #b2bec3;
 }
 .identitybox-title {
-    font-size: 14px;
-    color: #7f8fa6;
+  font-size: 14px;
+  color: #7f8fa6;
 }
 .identitybox-select {
-    width: 100%;
+  width: 100%;
 }
 .authoritybox {
-    width: 100%;
-    padding: 1% 0%;
+  width: 100%;
+  padding: 1% 0%;
 }
 .authority-block {
-    width: 100%;
-    padding: 1%;
-    line-height: 30px;
+  width: 100%;
+  padding: 1%;
+  line-height: 30px;
 }
 .authority-block-title {
-    font-size: 14px;
-    color: #b2bec3;
+  font-size: 14px;
+  color: #b2bec3;
 }
 .authority-block-item-name {
-    width: 67%;
-    display: inline-block;
-    vertical-align: top;
-    font-size: 16px;
-    padding-left: 3%;
-    color: #636e72;
+  width: 67%;
+  display: inline-block;
+  vertical-align: top;
+  font-size: 16px;
+  padding-left: 10px;
+  color: #636e72;
 }
 .authority-block-item-state {
-    width: 25%;
-    display: inline-block;
-    vertical-align: top;
-    text-align: right;
-    padding-right: 5%;
+  width: 25%;
+  display: inline-block;
+  vertical-align: top;
+  text-align: right;
+  padding-right: 5%;
 }
 .authority-block-item {
-    padding: 1% 0%;
+  padding: 1% 0%;
 }
 .userlist {
-    width: 100%;
-    text-align: center;
-    display: flex;
-    justify-content: center;
-    align-items: center;
+  width: 100%;
+  text-align: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 .userlist-name {
-    font-size: 14px;
-    color: #7f8c8d;
+  font-size: 14px;
+  color: #7f8c8d;
 }
 .photobox {
-    display: inline-block;
-    width: 20%;
-    vertical-align: top;
+  display: inline-block;
+  width: 20%;
+  vertical-align: top;
 }
 .userlist-name {
-    text-align: left;
-    display: inline-block;
-    width: 45%;
-    vertical-align: top;
-    line-height: 40px;
-    margin-left: 0%;
-    font-size: 18px;
+  text-align: left;
+  display: inline-block;
+  width: 45%;
+  vertical-align: top;
+  line-height: 40px;
+  margin-left: 0%;
+  font-size: 18px;
+}
+.sitebox {
+  display: inline-block;
+  vertical-align: top;
+  width: 50%;
+  border-right: 1px solid #ccc;
+}
+.sitebox:last-child {
+  border: 0px;
+}
+.sitebox-name,
+.sitebox-state {
+  display: inline-block;
+  vertical-align: top;
+}
+.sitebox-name {
+  width: 70%;
+  font-size: 16px;
+  padding-left: 10px;
+  color: #636e72;
 }
 </style>
 <style>
 .identitybox-select .el-select--small {
-    width: 50%;
+  width: 50%;
 }
 </style>

File diff ditekan karena terlalu besar
+ 1342 - 1319
TEAMModelBI/ClientApp/src/view/teachermanage/traitmanage.vue


+ 112 - 15
TEAMModelBI/Controllers/BIHome/AnalyseFileController.cs

@@ -1,12 +1,18 @@
-using Microsoft.AspNetCore.Hosting;
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
+using System.Text.Json;
 using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
+using TEAMModelOS.SDK.Models.Cosmos.BI;
 
 namespace TEAMModelBI.Controllers.BIHome
 {
@@ -15,43 +21,134 @@ namespace TEAMModelBI.Controllers.BIHome
     public class AnalyseFileController : ControllerBase
     {
         private readonly IWebHostEnvironment _environment; //读取文件流
+        private readonly AzureStorageFactory _azureStorage;
 
-        public AnalyseFileController(IWebHostEnvironment environment) 
+        public AnalyseFileController(IWebHostEnvironment environment, AzureStorageFactory azureStorage) 
         {
             _environment = environment;
+            _azureStorage = azureStorage;
         }
 
         [HttpPost("get-visitjson")]
-        public async Task<IActionResult> GetVisitJson() 
+        public async Task<IActionResult> GetVisitJson(JsonElement jsonElement) 
         {
+            jsonElement.TryGetProperty("path", out JsonElement _path);
+            jsonElement.TryGetProperty("time", out JsonElement _time);
             var path = $"{_environment.ContentRootPath}/JsonFile/TempFile/PT1H.json";
             StreamReader streamReader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.UTF8);
             List<object> objs = new(); 
-            StringBuilder visits = new();
+            StringBuilder visits = new StringBuilder("[");
             string text;
             while ((text = streamReader.ReadLine()) != null) 
             {
-                objs.Add(text.ToString());
-                visits.Append($"{text},");
+                if (streamReader.EndOfStream)
+                    visits.Append($"{text.ToString()}");
+                else
+                    visits.Append($"{text.ToString()},");                
             }
-
+            visits.Append("]");
             streamReader.Close();
-
             string input = visits.ToString();
             List<AGInfo> aGInfos = input.ToObject<List<AGInfo>>();
+            DateTimeOffset dtime = DateTimeOffset.UtcNow;
+            string cHour = dtime.ToString("yyyyMMddHH");
+            string cDay = dtime.ToString("yyyyMMdd");
+            if (aGInfos.Count > 0) 
+            {
+                cHour = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMddHH")).First();
+                cDay = aGInfos.Select(s => DateTimeOffset.Parse(s.time).ToString("yyyyMMdd")).First();
+            }
+
+            RecCnt saveCnts = new();
+            //var ipGroup = aGInfos.GroupBy(g => g.properties.clientIp).ToDictionary(k => k.Key, k => k.Count()).ToList();
+
+            List<RecGWInfo> recInfo = aGInfos.Select(s => new RecGWInfo { hour = cHour, ip = s.properties.clientIp, api = s.properties.requestUri.Split("?").ToList().Count() > 1 ? s.properties.requestUri.Split("?").ToList()[0] : s.properties.requestUri, hostName = s.properties.hostname }).ToList();
+
+            List<RecApiCnt> apiCnt = recInfo.GroupBy(a => a.api).Select(g => new RecApiCnt { api = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), ip = g.Select(i => i.ip).Distinct().ToList() }).ToList();
+            saveCnts.apiCnt= apiCnt;
+
+            List<RecIpCnt> ipCnt = recInfo.GroupBy(a => a.ip).Select(g => new RecIpCnt { ip = g.Key, count = g.Count(), hour = cHour, hostName = g.Select(h => h.hostName).Distinct().ToList(), api = g.Select(i => i.api).Distinct().ToList() }).ToList();
+            saveCnts.ipCnt = ipCnt;
+
+            ////保存存至Blob文件
+            var url = await _azureStorage.UploadFileByContainer("0-public", saveCnts.ToJsonString(), $"visitCnt/{cDay}", $"{cHour}.json");
+            var blob = _azureStorage.GetBlobContainerClient($"0-public");
+
+            List<RecCnt> recCnts = new();
+            await foreach (BlobItem blobItem in blob.GetBlobsAsync(BlobTraits.None, BlobStates.None, $"visitCnt/{cDay}"))
+            {
+
+                BlobClient blobClient = blob.GetBlobClient(blobItem.Name);
+                if (await blobClient.ExistsAsync()) 
+                {
+                    using (var meomoryStream = new MemoryStream())
+                    {
+                        var response = blob.GetBlobClient($"{blobItem.Name}").DownloadTo(meomoryStream);
+                        //var response = await blob.GetBlobClient($"{blobItem.Name}").DownloadToAsync(meomoryStream);
+
+                        var temps = meomoryStream.ToString();
+                        var temp1 = Encoding.UTF8.GetString(meomoryStream.ToArray());
+                        var temp = Encoding.UTF8.GetString(meomoryStream.ToArray()).ToObject<RecCnt>();
+
+                        RecCnt recCnt = Encoding.UTF8.GetString(meomoryStream.ToArray()).ToString().ToObject<RecCnt>();
+                        recCnts.Add(recCnt);
+                    }
+                }
+            }
+
+
+            //var url = await _azureStorage.UploadFileByContainer("0-public", kvList.ToList().ToJsonString(), $"visitCnt/{cDay}", $"{cHour}.json");
+            return Ok(new { state = 200, recCnts,  apiCon = apiCnt.Count(), apiSum = apiCnt.Select(ap => ap.count).Sum(), ipCount = ipCnt.Count, ipSum = ipCnt.Select(ip => ip.count).Sum(), ipCnt, apiCnt }); ;
+
+            //return Ok(new { state = 200, cnt = recInfo.Count, apiCon = apiCnt.Count(), apiSum = apiCnt.Select(ap=> ap.count).Sum(), ipCount = ipCnt.Count, ipSum = ipCnt.Select(ip=>ip.count).Sum(), ipCnt,apiCnt, recInfo   });;
+        }
 
-            //var temp = visits.Split("||");
+        public record RecCnt
+        {
+            public List<RecApiCnt> apiCnt { get; set; }
+            public List<RecIpCnt> ipCnt { get; set; }
+        }
 
-            //var temp1 = string.Join("||");
+        public record RecCntBas
+        {
+            public int count { get; set; }
+            public string hour { get; set; }
+            public List<string> hostName { get; set; }
+        }
 
-            return Ok(new { state = 200, aGInfos, objs, visits });
+        public record RecIpCnt : RecCntBas
+        {
+            public string ip { get; set; }
+            public List<string> api { get; set; }
         }
 
+        public record RecApiCnt : RecCntBas
+        {
+            public string api { get; set; }
+            public List<string> ip { get; set; }
+        }
 
+        public record RecGWInfo
+        {
+            public string hour { get; set; }
+            public string ip { get; set; }
+            public string api { get; set; }
+            public string hostName { get; set; }
+        }
+
+
+
+
+
+        public record StatisNameCnt 
+        {
+            public string name { get; set; }
+            public int cnt { get; set; }
+        }
 
         public record AGInfo 
         {
-            public string resourceId { get; set; }
+            //public string resourceId { get; set; }
 
             public string operationName { get; set; }
             public string time { get; set; }
@@ -61,7 +158,7 @@ namespace TEAMModelBI.Controllers.BIHome
 
         public record Properties 
         {
-            public string instanceId { get; set; }
+            //public string instanceId { get; set; }
             public string clientIp { get; set; }
             public string clientPort { get; set; }
             public string requestUri { get; set; }
@@ -69,10 +166,10 @@ namespace TEAMModelBI.Controllers.BIHome
             public string ruleSetVersion { get; set; }
             public string ruleId { get; set;}
             public string ruleGroup { get; set; }
-            public string message { get; set; }
+            //public string message { get; set; }
             public string action { get; set; }
             public string site { get; set; }
-            public Datails datails { get; set; }
+            //public Datails datails { get; set; }
             public string hostname { get; set; }
             public string transactionId { get; set; }
         }

+ 43 - 19
TEAMModelBI/Controllers/BIHome/OnLineController.cs

@@ -8,6 +8,7 @@ using System.Linq;
 using System.Text.Json;
 using System.Threading.Tasks;
 using TEAMModelBI.Tool;
+using TEAMModelBI.Tool.Context;
 using TEAMModelOS.SDK.DI;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;
@@ -35,10 +36,17 @@ namespace TEAMModelBI.Controllers.BIHome
         /// </summary>
         /// <returns></returns>
         [HttpPost("get-count")]
-        public async Task<IActionResult> GetCount() 
+        public async Task<IActionResult> GetCount(JsonElement jsonElement) 
         {
             var cosmosClient = _azureCosmos.GetCosmosClient();
             var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
+            jsonElement.TryGetProperty("site", out JsonElement site);
+            if ($"{site}".Equals(BIConst.GlobalSite)) 
+            {
+                cosmosClient = _azureCosmos.GetCosmosClient(name: BIConst.GlobalSite);
+                table = _azureStorage.GetCloudTableClient(BIConst.GlobalSite).GetTableReference("IESLogin");
+            }
+
             DateTimeOffset dateTime = DateTimeOffset.UtcNow;
 
             var (daySt, dayEt) = TimeHelper.GetStartOrEnd(dateTime);  //今天开始时间    13位
@@ -105,13 +113,18 @@ namespace TEAMModelBI.Controllers.BIHome
         [HttpPost("get-trend")]
         public async Task<IActionResult> GetTrend(JsonElement jsonElement) 
         {
-            jsonElement.TryGetProperty("hour", out JsonElement hour);
+
             var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
-            DateTimeOffset dateTime =  DateTimeOffset.UtcNow;
-            if (!string.IsNullOrEmpty($"{hour}")) 
+            var redisClinet = _azureRedis.GetRedisClient(8);
+            jsonElement.TryGetProperty("site", out JsonElement site);
+            if ($"{site}".Equals(BIConst.GlobalSite))
             {
-                DateTimeOffset.UtcNow.AddHours(hour.GetInt32());
+                table = _azureStorage.GetCloudTableClient(BIConst.GlobalSite).GetTableReference("IESLogin");
+                redisClinet = _azureRedis.GetRedisClient(dbnum: 8, name: BIConst.GlobalSite);
             }
+
+            DateTimeOffset dateTime =  DateTimeOffset.UtcNow;
+
             var (daySt, dayEt) = TimeHelper.GetStartOrEnd(dateTime);  //今天开始时间    13位
             var (strDaySt, strDayEt) = TimeHelper.GetUnixToDate(daySt, dayEt, "yyyyMMddHH");
             var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
@@ -122,14 +135,14 @@ namespace TEAMModelBI.Controllers.BIHome
             Dictionary<long, int> stuDays = new();  //学生在线人数
             Dictionary<long, int> tmdDays = new();  //醍摩豆账户学生
 
-            SortedSetEntry[] tchDay = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:teacher:{dateDay}");
+            SortedSetEntry[] tchDay = redisClinet.SortedSetRangeByScoreWithScores($"Login:IES:teacher:{dateDay}");
             if (tchDay.Length > 0)
             {
                 foreach (var item in tchDay)
                 {
                     int val = ((int)item.Score);
                     int key = ((int)item.Element);
-                    var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, hour.GetInt32() + key, 0, 0)).Hour;
+                    var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, key, 0, 0)).Hour;
                     //var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {key}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
                     tchDays.Add(utcTo, val);
                     if (allDays.ContainsKey(utcTo))
@@ -147,8 +160,8 @@ namespace TEAMModelBI.Controllers.BIHome
                 {
                     foreach (var item in hourLoginsTch)
                     {
-                        await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:teacher:{dateDay}", $"{item.Hour}", item.Teacher);//存一天24小时
-                        var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, hour.GetInt32() + item.Hour, 0, 0)).Hour;
+                        await redisClinet.SortedSetIncrementAsync($"Login:IES:teacher:{dateDay}", $"{item.Hour}", item.Teacher);//存一天24小时
+                        var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, item.Hour, 0, 0)).Hour;
                         //var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {item.Hour}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
                         tchDays.Add(utcTo, item.Teacher);
                         if (allDays.ContainsKey(utcTo))
@@ -159,14 +172,14 @@ namespace TEAMModelBI.Controllers.BIHome
                 }
             }
 
-            SortedSetEntry[] stuDay = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:student:{dateDay}");
+            SortedSetEntry[] stuDay = redisClinet.SortedSetRangeByScoreWithScores($"Login:IES:student:{dateDay}");
             if (stuDay.Length > 0)
             {
                 foreach (var item in stuDay)
                 {
                     int val = (int)item.Score;
                     int key = (int)item.Element;
-                    var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, hour.GetInt32() + key, 0, 0)).Hour;
+                    var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, key, 0, 0)).Hour;
                     //var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {key}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
                     stuDays.Add(utcTo, val);
                     if (allDays.ContainsKey(utcTo))
@@ -185,8 +198,8 @@ namespace TEAMModelBI.Controllers.BIHome
                 {
                     foreach (var item in hourLoginsStu)
                     {
-                        await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:student:{dateDay}", $"{item.Hour}", item.Student);//存一天24小时
-                        var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, hour.GetInt32() + item.Hour, 0, 0)).Hour;
+                        await redisClinet.SortedSetIncrementAsync($"Login:IES:student:{dateDay}", $"{item.Hour}", item.Student);//存一天24小时
+                        var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, item.Hour, 0, 0)).Hour;
                         //var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {item.Hour}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
                         stuDays.Add(utcTo, item.Student);
                         if (allDays.ContainsKey(utcTo))
@@ -197,14 +210,14 @@ namespace TEAMModelBI.Controllers.BIHome
                 }
             }
 
-            SortedSetEntry[] tmdDay = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:tmduser:{dateDay}");
+            SortedSetEntry[] tmdDay = redisClinet.SortedSetRangeByScoreWithScores($"Login:IES:tmduser:{dateDay}");
             if (tmdDay.Length > 0)
             {
                 foreach (var item in stuDay)
                 {
                     int val = (int)item.Score;
                     int key = (int)item.Element;
-                    var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, hour.GetInt32() + key, 00, 00)).Hour;
+                    var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, key, 00, 00)).Hour;
                     //var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {key}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
                     tmdDays.Add(utcTo, val);
                     if (allDays.ContainsKey(utcTo))
@@ -223,8 +236,8 @@ namespace TEAMModelBI.Controllers.BIHome
                 {
                     foreach (var item in hourLoginsTmd)
                     {
-                        await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:tmduser:{dateDay}", $"{item.Hour}", item.TmdUser);//存一天24小时
-                        var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, hour.GetInt32() + item.Hour, 00, 00)).Hour;
+                        await redisClinet.SortedSetIncrementAsync($"Login:IES:tmduser:{dateDay}", $"{item.Hour}", item.TmdUser);//存一天24小时
+                        var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, item.Hour, 00, 00)).Hour;
                         //var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {item.Hour}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
                         tmdDays.Add(utcTo, item.TmdUser);
                         if (allDays.ContainsKey(utcTo))
@@ -243,10 +256,15 @@ namespace TEAMModelBI.Controllers.BIHome
         /// </summary>
         /// <returns></returns>
         [HttpPost("get-lessontrend")]
-        public async Task<IActionResult> GetLessonTrend()
+        public async Task<IActionResult> GetLessonTrend(JsonElement jsonElement)
         {
             DateTimeOffset dateTime = DateTimeOffset.UtcNow;
             var cosmosClient = _azureCosmos.GetCosmosClient();
+            jsonElement.TryGetProperty("site", out JsonElement site);
+            if ($"{site}".Equals(BIConst.GlobalSite))
+            {
+                cosmosClient = _azureCosmos.GetCosmosClient(name: BIConst.GlobalSite);
+            }
             int year = dateTime.Year;   //当前年
             int month = dateTime.Month;  //当前月
             int day = dateTime.Day;      //当天
@@ -316,9 +334,15 @@ namespace TEAMModelBI.Controllers.BIHome
         /// </summary>
         /// <returns></returns>
         [HttpPost("get-edition")]
-        public async Task<IActionResult> GetEdition() 
+        public async Task<IActionResult> GetEdition(JsonElement jsonElement) 
         {
             var cosmosClient = _azureCosmos.GetCosmosClient();
+            jsonElement.TryGetProperty("site", out JsonElement site);
+            if ($"{site}".Equals(BIConst.GlobalSite))
+            {
+                cosmosClient = _azureCosmos.GetCosmosClient(name: BIConst.GlobalSite);
+            }
+
             int beCnt = 0; //基础班
             int seCnt = 0; //标准版
             int peCnt = 0; //专业版

+ 36 - 24
TEAMModelBI/Controllers/BISchool/AreaRelevantController.cs

@@ -48,17 +48,17 @@ namespace TEAMModelBI.Controllers.BISchool
             try
             {
                 jsonElement.TryGetProperty("areaId", out JsonElement _areaId);
-                jsonElement.TryGetProperty("isManyArea", out JsonElement isManyArea);
                 var cosmosClient = _azureCosmos.GetCosmosClient();
                 List<JoinAreaSchool> joinAreaSchools = new List<JoinAreaSchool>();
-                string slqtxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period FROM c WHERE c.areaId='{_areaId}'";
+                string sqltxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c WHERE c.areaId='{_areaId}'";
 
-                if (!string.IsNullOrEmpty($"{isManyArea}"))
+                if (!string.IsNullOrEmpty($"{_areaId}"))
                 {
-                    slqtxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period FROM c join m in c.manyAreas where m.areaId='{_areaId}' or c.areaId='{_areaId}'";
+                    sqltxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c join m in c.manyAreas WHERE c.areaId ='{_areaId}' or m.areaId='{_areaId}'";
+                    //sqltxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period FROM c join m in c.manyAreas where m.areaId='{_areaId}' or c.areaId='{_areaId}'";
                 }
 
-                await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: slqtxt,requestOptions:new QueryRequestOptions() { PartitionKey = new PartitionKey("Base")})) 
+                await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: sqltxt, requestOptions:new QueryRequestOptions() { PartitionKey = new PartitionKey("Base")})) 
                 {
                     using var json = await JsonDocument.ParseAsync(item.ContentStream);
                     if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0) 
@@ -74,7 +74,9 @@ namespace TEAMModelBI.Controllers.BISchool
                                 city = obj.GetProperty("city").GetString(),
                                 dist = obj.GetProperty("dist").GetString(),
                                 picture = obj.GetProperty("picture").GetString(),
-                                period = obj.GetProperty("period").ToObject<List<Period>>().Select(x => x.name).ToList()
+                                period = obj.GetProperty("period").ToObject<List<Period>>().Select(x => x.name).ToList(),
+                                areaId = obj.GetProperty("areaId").GetString(),
+                                standard = obj.GetProperty("standard").GetString()
                             };
 
                             try
@@ -102,7 +104,7 @@ namespace TEAMModelBI.Controllers.BISchool
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        [AuthToken(Roles = "admin,assist")]
+        [AuthToken(Roles = "admin,,rdc,assist")]
         [HttpPost("set-areashiftschool")]
         public async Task<IActionResult> SetAreaShiftSchool(JsonElement jsonElement)
         {
@@ -117,21 +119,37 @@ namespace TEAMModelBI.Controllers.BISchool
 
                 var cosmosClient = _azureCosmos.GetCosmosClient();
                 School tempSchool = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{schoolId}", new PartitionKey("Base"));
-                if (string.IsNullOrEmpty($"{isDefault}"))
+                if (bool.Parse($"{isDefault}") == true)
                 {
                     tempSchool.areaId = null;
                     tempSchool.standard = null;
-                    var temp = tempSchool.manyAreas.Find(ma => ma.areaId == $"{areaId}");
-                    if (temp != null)
-                        tempSchool.manyAreas.Remove(temp);
                 }
-                else
+
+                if (tempSchool.manyAreas.Count > 0)
                 {
-                    var temp = tempSchool.manyAreas.Find(ma => ma.areaId == $"{areaId}");
-                    if (temp == null)
-                        tempSchool.manyAreas.Add(new ManyArea { areaId = $"{areaId}", standard = $"{standard}" });
-                }
+                    if (bool.Parse($"{isDefault}") == true) 
+                    {
+                        tempSchool.areaId = "";
+                        tempSchool.standard = "";
+                    }
 
+                    if (tempSchool.manyAreas != null)
+                    {
+                        if (!string.IsNullOrEmpty($"{areaId}"))
+                        {
+                            var temp = tempSchool.manyAreas.Find(ma => ma.areaId == $"{areaId}");
+                            if (temp != null)
+                                tempSchool.manyAreas.Remove(temp);
+                        }
+
+                        if (!string.IsNullOrEmpty($"{standard}"))
+                        {
+                            var temp = tempSchool.manyAreas.Find(ma => ma.standard == $"{standard}");
+                            if (temp != null)
+                                tempSchool.manyAreas.Remove(temp);
+                        }
+                    }
+                }
                 School school = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(tempSchool, tempSchool.id, new PartitionKey("Base"));
 
                 //保存操作记录
@@ -152,21 +170,15 @@ namespace TEAMModelBI.Controllers.BISchool
         public record JoinAreaSchool
         {
             public string id { get; set; }
-
             public string name { get; set; }
-
             public string schoolCode { get; set; }
-
             public string picture { get; set; }
-
+            public string areaId{ get; set; }
+            public string standard { get; set; }
             public List<string> period { get; set; }
-
             public string province { get; set; }
-
             public string city { get; set; }
-
             public string dist { get; set; }
-
             public List<ManyArea> manyAreas { get; set; } = new List<ManyArea>();
         }
 

+ 156 - 104
TEAMModelBI/Controllers/BISchool/BatchAreaController.cs

@@ -56,22 +56,21 @@ namespace TEAMModelBI.Controllers.BISchool
         {
             try
             {
-                List<Area> tempAreas = new();
                 List<RecArea> areas = new();
-
+                var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
                 var azureClient = _azureCosmos.GetCosmosClient();
-                await foreach (var item in azureClient.GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<Area>(queryText: $"select value(c) from c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base-Area") }))
+                await foreach (var item in azureClient.GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<RecArea>(queryText: $"select c.id,c.code,c.pk,c.name,c.provCode,c.provName,c.cityCode,c.cityName,c.standard,c.standardName,c.institution from c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base-Area") }))
                 {
-                    tempAreas.Add(item);
+                    areas.Add(item);
                 }
 
-                foreach (var area in tempAreas) 
+                foreach (var recArea in areas)
                 {
-                    RecArea recArea = new() { id = area.id, code = area.code, pk = area.pk, name = area.name, provCode = area.provCode, provName = area.provName, cityCode = area.cityCode, cityName = area.cityName, standard = area.standard, standardName = area.standardName, institution = area.institution };
-                    recArea.schoolCount = await CommonFind.FindTotals(azureClient, $"select count(c.id) as totals from c where c.areaId='{area.id}' and c.standard='{area.standard}'", "School", "Base");
-                    areas.Add(recArea);
+                    recArea.schoolCount = await CommonFind.GetSqlValueCount(azureClient, "School", $"select value(count(c.id)) from c where c.areaId='{recArea.id}' and c.standard='{recArea.standard}'", "Base");
+                    List<AreaQuoteRecord> aqr = await table.QueryWhereString<AreaQuoteRecord>($"PartitionKey eq 'QuoteRecord' and  areaId eq '{recArea.id}'");
+                    aqr.Sort((x, y) => y.RowKey.CompareTo(x.RowKey));
+                    recArea.aquoteRec = aqr;
                 }
-
                 return Ok(new { state = 200, areas });
             }
             catch (Exception ex)
@@ -161,12 +160,14 @@ namespace TEAMModelBI.Controllers.BISchool
                 jsonElement.TryGetProperty("institution", out JsonElement institution);
                 jsonElement.TryGetProperty("oldId", out JsonElement _oldId);
                 jsonElement.TryGetProperty("oldStandard", out JsonElement oldStandard);
+                jsonElement.TryGetProperty("oldName", out JsonElement oldName);
 
                 var (_tmdId, _tmdName, pic, did, dname, dpic) = HttpJwtAnalysis.JwtXAuthBI(HttpContext.GetXAuth("AuthToken"), _option);
+                var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
 
                 //操作记录实体
                 var tempStandard = !string.IsNullOrEmpty($"{oldStandard}") && !string.IsNullOrEmpty($"{_oldId}") ? $"{oldStandard}" : "standard2";
-                
+
                 var cosmosClient = _azureCosmos.GetCosmosClient();//数据库连接
                 //查询新的是否存在
                 await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<Area>(queryText: $"select value(c) from c where c.standard='{standard}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-Area") }))
@@ -196,6 +197,9 @@ namespace TEAMModelBI.Controllers.BISchool
                 //创建区域
                 await cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync<Area>(addArea, new PartitionKey("Base-Area"));
 
+                //保存引用记录
+                await table.SaveOrUpdate<AreaQuoteRecord>(new AreaQuoteRecord() { PartitionKey = "QuoteRecord", RowKey = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}", areaId = $"{areaId}", quoteId = $"{_oldId}", quoteName = $"{oldName}", standard = tempStandard });
+
                 //消息分区键
                 string partitionCode = "copyAbility-mark";
 
@@ -241,51 +245,64 @@ namespace TEAMModelBI.Controllers.BISchool
                         //添加区能力标准点
                         abilities.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(item, new PartitionKey($"Ability-{standard}")));
                     }
-
-                    if (abilities.Count < 256)
-                    {
-                        await Task.WhenAll(abilities);
-                    }
-                    else
+                    try
                     {
-                        int pages = (abilities.Count + 255) / 256;
-                        for (int i = 0; i < pages; i++)
+                        if (abilities.Count < 256)
                         {
-                            List<Task<ItemResponse<Ability>>> tempAbility = abilities.Skip((i) * 256).Take(256).ToList();
-                            await Task.WhenAll(tempAbility);
+                            await Task.WhenAll(abilities);
                         }
+                        else
+                        {
+                            int pages = (abilities.Count + 255) / 256;
+                            for (int i = 0; i < pages; i++)
+                            {
+                                List<Task<ItemResponse<Ability>>> tempAbility = abilities.Skip((i) * 256).Take(256).ToList();
+                                await Task.WhenAll(tempAbility);
+                            }
+                        }
+                    }
+                    catch
+                    {
+                        return Ok(new { state = 200, msg = "创区成功,能力标准点复制失败,遗留数据影响!" });
                     }
 
-                    //微能力点
-                    await foreach (var atask in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<AbilityTask>(queryText: $"select value(c) from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"AbilityTask-{oldStandard}") }))
+                    try
                     {
-                        List<Tnode> tnodes = new List<Tnode>();
-                        foreach (Tnode tnode in atask.children)
+                        //微能力点
+                        await foreach (var atask in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<AbilityTask>(queryText: $"select value(c) from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"AbilityTask-{oldStandard}") }))
                         {
-                            if (tnode.rnodes != null)
+                            List<Tnode> tnodes = new List<Tnode>();
+                            foreach (Tnode tnode in atask.children)
                             {
-                                List<Rnode> rnodes = new List<Rnode>();
-                                foreach (Rnode rnode in tnode.rnodes)
+                                if (tnode.rnodes != null)
                                 {
-                                    if (!string.IsNullOrEmpty($"{rnode.link}"))
+                                    List<Rnode> rnodes = new List<Rnode>();
+                                    foreach (Rnode rnode in tnode.rnodes)
                                     {
-                                        rnode.link = rnode.link.Replace($"/{oldStandard}/", $"/{standard}/");
+                                        if (!string.IsNullOrEmpty($"{rnode.link}"))
+                                        {
+                                            rnode.link = rnode.link.Replace($"/{oldStandard}/", $"/{standard}/");
+                                        }
+                                        rnodes.Add(rnode);
                                     }
-                                    rnodes.Add(rnode);
+                                    tnode.rnodes = rnodes;
                                 }
-                                tnode.rnodes = rnodes;
+                                tnodes.Add(tnode);
                             }
-                            tnodes.Add(tnode);
-                        }
 
-                        atask.children = tnodes;
-                        atask.code = $"AbilityTask-{standard}";
-                        atask.standard = $"{standard}";
-                        atask.codeval = $"{standard}";
+                            atask.children = tnodes;
+                            atask.code = $"AbilityTask-{standard}";
+                            atask.standard = $"{standard}";
+                            atask.codeval = $"{standard}";
 
-                        //添加区能力标准点中的节点 
-                        //abilityTasks.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}")));
-                        await cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}"));
+                            //添加区能力标准点中的节点 
+                            //abilityTasks.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}")));
+                            await cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}"));
+                        }
+                    }
+                    catch
+                    {
+                        return Ok(new { state = 200, msg = "创区成功,能力标准创建成功,微能力点复制失败,遗留数据影响!" });
                     }
 
                     //if (abilityTasks.Count > 0) 
@@ -344,52 +361,67 @@ namespace TEAMModelBI.Controllers.BISchool
                             abilities.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(item, new PartitionKey($"Ability-{standard}")));
                         }
 
-                        if (abilities.Count < 256)
+                        try
                         {
-                            await Task.WhenAll(abilities);
-                        }
-                        else
-                        {
-                            int pages = (abilities.Count + 255) / 256;
-                            for (int i = 0; i < pages; i++)
+                            if (abilities.Count < 256)
                             {
-                                List<Task<ItemResponse<Ability>>> tempAbility = abilities.Skip((i) * 256).Take(256).ToList();
-                                await Task.WhenAll(tempAbility);
+                                await Task.WhenAll(abilities);
                             }
+                            else
+                            {
+                                int pages = (abilities.Count + 255) / 256;
+                                for (int i = 0; i < pages; i++)
+                                {
+                                    List<Task<ItemResponse<Ability>>> tempAbility = abilities.Skip((i) * 256).Take(256).ToList();
+                                    await Task.WhenAll(tempAbility);
+                                }
+                            }
+                        }
+                        catch
+                        {
+                            return Ok(new { state = 200, msg = "创区成功,能力标准点复制失败,遗留数据影响!" });
                         }
 
-                        //微能力点
-                        await foreach (var atask in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<AbilityTask>(queryText: $"select value(c) from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"AbilityTask-{area.standard}") }))
+                        try
                         {
-                            List<Tnode> tnodes = new List<Tnode>();
-                            foreach (Tnode tnode in atask.children)
+                            //微能力点
+                            await foreach (var atask in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<AbilityTask>(queryText: $"select value(c) from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"AbilityTask-{area.standard}") }))
                             {
-                                if (tnode.rnodes != null)
+                                List<Tnode> tnodes = new List<Tnode>();
+                                foreach (Tnode tnode in atask.children)
                                 {
-                                    List<Rnode> rnodes = new List<Rnode>();
-                                    foreach (Rnode rnode in tnode.rnodes)
+                                    if (tnode.rnodes != null)
                                     {
-                                        if (!string.IsNullOrEmpty($"{rnode.link}"))
+                                        List<Rnode> rnodes = new List<Rnode>();
+                                        foreach (Rnode rnode in tnode.rnodes)
                                         {
-                                            rnode.link = rnode.link.Replace($"/{area.standard}/", $"/{standard}/");
+                                            if (!string.IsNullOrEmpty($"{rnode.link}"))
+                                            {
+                                                rnode.link = rnode.link.Replace($"/{area.standard}/", $"/{standard}/");
+                                            }
+                                            rnodes.Add(rnode);
                                         }
-                                        rnodes.Add(rnode);
+                                        tnode.rnodes = rnodes;
                                     }
-                                    tnode.rnodes = rnodes;
+                                    tnodes.Add(tnode);
                                 }
-                                tnodes.Add(tnode);
-                            }
 
-                            atask.children = tnodes;
-                            atask.code = $"AbilityTask-{standard}";
-                            atask.standard = $"{standard}";
-                            atask.codeval = $"{standard}";
+                                atask.children = tnodes;
+                                atask.code = $"AbilityTask-{standard}";
+                                atask.standard = $"{standard}";
+                                atask.codeval = $"{standard}";
 
-                            //添加区能力标准点中的节点 
-                            //abilityTasks.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}")));
-                            await cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}"));
+                                //添加区能力标准点中的节点 
+                                //abilityTasks.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}")));
+                                await cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{standard}"));
+                            }
+                        }
+                        catch
+                        {
+                            return Ok(new { state = 200, msg = "创区成功,能力标准创建成功,微能力点复制失败,遗留数据影响!" });
                         }
 
+
                         //if (abilityTasks.Count > 0)
                         //{
                         //    for (int i = 0; i < abilityTasks.Count; i++)
@@ -486,10 +518,16 @@ namespace TEAMModelBI.Controllers.BISchool
                 if (!jsonElement.TryGetProperty("oldStandard", out JsonElement _oldStandard)) return BadRequest();
                 if (!jsonElement.TryGetProperty("newId", out JsonElement _newId)) return BadRequest();
                 if (!jsonElement.TryGetProperty("newStandard", out JsonElement _newStandard)) return BadRequest();
+                jsonElement.TryGetProperty("newName", out JsonElement newName);
 
                 var (_tmdId, _tmdName, pic, did, dname, dpic) = HttpJwtAnalysis.JwtXAuthBI(HttpContext.GetXAuth("AuthToken"), _option);
+                var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
+
+                //保存引用记录
+                await table.SaveOrUpdate<AreaQuoteRecord>(new AreaQuoteRecord() { PartitionKey = "QuoteRecord", RowKey = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}", areaId = $"{_oldId}", quoteId = $"{_newId}", quoteName = $"{newName}", standard = $"{_newStandard}" });
+
                 //操作记录实体
-                string blobOrTable = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();                
+                string blobOrTable = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
                 var cosmosClient = _azureCosmos.GetCosmosClient();
 
                 List<string> abilityIds = new List<string>();  //册别的ID集合
@@ -535,53 +573,68 @@ namespace TEAMModelBI.Controllers.BISchool
                     abilities.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(item, new PartitionKey($"Ability-{_oldStandard}")));
                 }
 
-                if (abilities.Count < 256)
-                {
-                    await Task.WhenAll(abilities);
-                }
-                else
+                try
                 {
-                    int pages = (abilities.Count + 255) / 256;
-                    for (int i = 0; i < pages; i++)
+                    if (abilities.Count < 256)
                     {
-                        List<Task<ItemResponse<Ability>>> tempAbility = abilities.Skip((i) * 256).Take(256).ToList();
-                        await Task.WhenAll(tempAbility);
+                        await Task.WhenAll(abilities);
                     }
+                    else
+                    {
+                        int pages = (abilities.Count + 255) / 256;
+                        for (int i = 0; i < pages; i++)
+                        {
+                            List<Task<ItemResponse<Ability>>> tempAbility = abilities.Skip((i) * 256).Take(256).ToList();
+                            await Task.WhenAll(tempAbility);
+                        }
+                    }
+                }
+                catch
+                {
+                    return Ok(new { state = 200, msg = "创区成功,能力标准点复制失败,遗留数据影响!" });
                 }
 
-                //微能力点
-                await foreach (var atask in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<AbilityTask>(queryText: $"select value(c) from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"AbilityTask-{_newStandard}") }))
+                try
                 {
-                    List<Tnode> tnodes = new List<Tnode>();
-                    foreach (Tnode tnode in atask.children)
+                    //微能力点
+                    await foreach (var atask in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<AbilityTask>(queryText: $"select value(c) from c ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"AbilityTask-{_newStandard}") }))
                     {
-                        if (tnode.rnodes != null)
+                        List<Tnode> tnodes = new();
+                        foreach (Tnode tnode in atask.children)
                         {
-                            List<Rnode> rnodes = new List<Rnode>();
-                            foreach (Rnode rnode in tnode.rnodes)
+                            if (tnode.rnodes != null)
                             {
-                                if (!string.IsNullOrEmpty($"{rnode.link}"))
+                                List<Rnode> rnodes = new();
+                                foreach (Rnode rnode in tnode.rnodes)
                                 {
-                                    rnode.link = rnode.link.Replace($"/{_newStandard}/", $"/{_oldStandard}/");
+                                    if (!string.IsNullOrEmpty($"{rnode.link}"))
+                                    {
+                                        rnode.link = rnode.link.Replace($"/{_newStandard}/", $"/{_oldStandard}/");
+                                    }
+                                    rnodes.Add(rnode);
                                 }
-                                rnodes.Add(rnode);
+                                tnode.rnodes = rnodes;
                             }
-                            tnode.rnodes = rnodes;
+                            tnodes.Add(tnode);
                         }
-                        tnodes.Add(tnode);
-                    }
 
-                    atask.children = tnodes;
-                    atask.code = $"AbilityTask-{_oldStandard}";
-                    atask.standard = $"{_oldStandard}";
-                    atask.codeval = $"{_oldStandard}";
+                        atask.children = tnodes;
+                        atask.code = $"AbilityTask-{_oldStandard}";
+                        atask.standard = $"{_oldStandard}";
+                        atask.codeval = $"{_oldStandard}";
 
-                    ////添加区能力标准点中的节点 
-                    //abilityTasks.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{_oldStandard}")));
-                    await cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{_oldStandard}"));
+                        ////添加区能力标准点中的节点 
+                        //abilityTasks.Add(cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{_oldStandard}")));
+                        await cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").CreateItemAsync(atask, new PartitionKey($"AbilityTask-{_oldStandard}"));
+                    }
+
+                }
+                catch
+                {
+                    return Ok(new { state = 200, msg = "创区成功,能力标准创建成功,微能力点复制失败,遗留数据影响!" });
                 }
 
-                StandardFile saveFile = new StandardFile();
+                StandardFile saveFile = new();
 
                 //新政策文件
                 await foreach (StandardFile standardFile in cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryIterator<StandardFile>(queryText: $"select value(c) from c where  c.id='{_newId}'", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"StandardFile") }))
@@ -689,7 +742,7 @@ namespace TEAMModelBI.Controllers.BISchool
         /// <summary>
         /// 区域列表
         /// </summary>
-        public record RecArea 
+        public record RecArea
         {
             public string id { get; set; }
             public string code { get; set; }
@@ -703,8 +756,7 @@ namespace TEAMModelBI.Controllers.BISchool
             public string standardName { get; set; }
             public string institution { get; set; }
             public int schoolCount { get; set; }
+            public List<AreaQuoteRecord> aquoteRec { get; set; } = new List<AreaQuoteRecord>();
         }
-
-
-}
+    }
 }

+ 61 - 16
TEAMModelBI/Controllers/BISchool/SchoolController.cs

@@ -63,12 +63,31 @@ namespace TEAMModelBI.Controllers.BISchool
             {
                 jsonElement.TryGetProperty("areaId", out JsonElement areaId);
                 var cosmosClient = _azureCosmos.GetCosmosClient();
-                string sqltxt = "SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.manyAreas FROM c WHERE c.standard=null or c.areaId=null";
-                if (!string.IsNullOrEmpty($"{areaId}"))
-                    sqltxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.manyAreas FROM c join m in c.manyAreas WHERE c.areaId!='{areaId}' or m.areaId!='{areaId}'";
+                //默认不指定返回大小
+                int? pageSize = null;
+                string continuationToken = string.Empty;
+                string pageToken = default;
+                if (jsonElement.TryGetProperty("pageSize", out JsonElement jsonPageSize))
+                {
+                    if (!jsonPageSize.ValueKind.Equals(JsonValueKind.Undefined) && !jsonPageSize.ValueKind.Equals(JsonValueKind.Null) && jsonPageSize.TryGetInt32(out int tempPageSize))
+                    {
+                        pageSize = tempPageSize;
+                    }
+                }
+                //是否需要进行分页查询,默认不分页
+                bool iscontinuation = false;
+                if (pageSize != null && pageSize.Value > 0)
+                {
+                    iscontinuation = true;
+                }
+                if (jsonElement.TryGetProperty("contToken", out JsonElement ContToken))
+                {
+                    pageToken = ContToken.GetString();
+                }
 
-                List<NotAreaSchool> notAreaSchools = new List<NotAreaSchool>();
-                await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: sqltxt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
+                string sqltxt = "SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c WHERE c.areaId !='{areaId}'";
+                List<NotAreaSchool> tempNotAreaSchools = new();
+                await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: sqltxt, continuationToken: pageToken, requestOptions: new QueryRequestOptions() { MaxItemCount = pageSize, PartitionKey = new PartitionKey("Base") }))
                 {
                     using var json = await JsonDocument.ParseAsync(item.ContentStream);
                     if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
@@ -85,21 +104,42 @@ namespace TEAMModelBI.Controllers.BISchool
                                 period = obj.GetProperty("period").ToObject<List<Period>>().Select(x => x.name).ToList(),
                                 province = obj.GetProperty("province").GetString(),
                                 city = obj.GetProperty("city").GetString(),
-                                dist = obj.GetProperty("dist").GetString()
+                                dist = obj.GetProperty("dist").GetString(),
+                                areaId = obj.GetProperty("areaId").GetString(),
+                                standard = obj.GetProperty("standard").GetString()
                             };
                             try
                             {
                                 notAreaSchool.manyAreas = obj.GetProperty("manyAreas").ToObject<List<ManyArea>>();
                             }
                             catch { }
-                            notAreaSchools.Add(notAreaSchool);
+                            tempNotAreaSchools.Add(notAreaSchool);
+                        }
+
+                        if (iscontinuation)
+                        {
+                            continuationToken = item.GetContinuationToken();
+                            break;
                         }
                     }
                 }
+                List<NotAreaSchool> notAreaSchools = new List<NotAreaSchool>();
+                tempNotAreaSchools.ForEach(nas =>
+                {
+                    if (nas.manyAreas == null)
+                        notAreaSchools.Add(nas);
+                    else
+                    {
+                        var not = nas.manyAreas.Find(f => f.areaId == $"{areaId}");
+                        if (not == null)
+                            notAreaSchools.Add(nas);
+                    }
+                });
+
                 //if (!string.IsNullOrEmpty($"{areaId}"))                    
                 //    notAreaSchools = notAreaSchools.Select(na => new NotAreaSchool { id = na.id, name = na.name, schoolCode = na.schoolCode, picture = na.picture, period = na.period, province = na.province, city = na.city = na.city, dist = na.dist,  manyAreas =  new List<ManyArea> { na.manyAreas.Find(m => m.areaId != $"{areaId}") } }).ToList();
 
-                return Ok(new { state = 200, notAreaSchools });
+                return Ok(new { state = 200, continuationToken,cont= notAreaSchools.Count, notAreaSchools });
             }
             catch (Exception ex)
             {
@@ -114,7 +154,7 @@ namespace TEAMModelBI.Controllers.BISchool
         /// <param name="jsonElement"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        [AuthToken(Roles = "admin,assist")]
+        [AuthToken(Roles = "admin,rdc,assist")]
         [HttpPost("set-schooljoinarea")]
         public async Task<IActionResult> SetSchoolJoinArea(JsonElement jsonElement)
         {
@@ -138,19 +178,22 @@ namespace TEAMModelBI.Controllers.BISchool
                         School school = await cosmosCliet.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>(tempCode, new PartitionKey("Base"));
                         if (school != null)
                         {
-                            if (string.IsNullOrEmpty($"{isDefault}"))
+                            if (bool.Parse($"{isDefault}") == true)
                             {
                                 school.standard = $"{standard}";
                                 school.areaId = $"{_areaId}";
-                                var marea = school.manyAreas.Find(ma => ma.areaId == $"{_areaId}" && ma.standard == $"{standard}");
-                                if (marea == null)
-                                    school.manyAreas.Add(new ManyArea { areaId = $"{_areaId}", standard = $"{standard}" });
                             }
-                            else 
+
+                            if (school.manyAreas == null) 
+                            {
+                                //ManyArea manyArea = new ManyArea() { areaId = $"{_areaId}", standard = $"{standard}" };
+                                school.manyAreas = new List<ManyArea>() { new ManyArea() { areaId = $"{_areaId}", standard = $"{standard}" } };
+                            }
+                            else
                             {
                                 var marea = school.manyAreas.Find(ma => ma.areaId == $"{_areaId}" && ma.standard == $"{standard}");
                                 if (marea == null)
-                                    school.manyAreas.Add(new ManyArea { areaId = $"{_areaId}", standard = $"{standard}" });
+                                    school.manyAreas.Add(new ManyArea() { areaId = $"{_areaId}", standard = $"{standard}" });
                             }
                             await cosmosCliet.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(school, school.id, new PartitionKey(school.code));
                         }
@@ -256,7 +299,7 @@ namespace TEAMModelBI.Controllers.BISchool
         /// <param name="school"></param>
         /// <returns></returns>
         [ProducesDefaultResponseType]
-        [AuthToken(Roles = "admin,assist")]
+        [AuthToken(Roles = "admin,rdc,assist")]
         [HttpPost("upd-school")]
         public async Task<IActionResult> UpdSchool(School school)
         {
@@ -978,6 +1021,8 @@ namespace TEAMModelBI.Controllers.BISchool
             public string province { get; set; }
             public string city { get; set; }
             public string dist { get; set; }
+            public string areaId { get; set; } 
+            public string standard { get; set; }
             public List<ManyArea> manyAreas { get; set; } = new List<ManyArea>();
         }
 

+ 97 - 3
TEAMModelBI/Controllers/BITest/TestController.cs

@@ -45,6 +45,7 @@ using System.Net.Http.Json;
 using System.Net;
 using TEAMModelBI.Tool.CosmosBank;
 using System.Diagnostics;
+using StackExchange.Redis;
 
 namespace TEAMModelBI.Controllers.BITest
 {
@@ -54,20 +55,22 @@ namespace TEAMModelBI.Controllers.BITest
     {
 
         private readonly AzureCosmosFactory _azureCosmos;
+        private readonly AzureStorageFactory _azureStorage;
+        private readonly AzureRedisFactory _azureRedis;
         private readonly DingDing _dingDing;
         private readonly Option _option;
-        private readonly AzureStorageFactory _azureStorage;
         private readonly IWebHostEnvironment _environment; //读取文件
         //读取配置文件
         private readonly IConfiguration _configuration;
         private readonly CoreAPIHttpService _coreAPIHttpService;
         private readonly HttpClient _httpClient;
 
-        public TestController(AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, IOptionsSnapshot<Option> option, IWebHostEnvironment hostingEnvironment, IConfiguration configuration, CoreAPIHttpService coreAPIHttpService, HttpClient httpClient)
+        public TestController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, DingDing dingDing, IOptionsSnapshot<Option> option, IWebHostEnvironment hostingEnvironment, IConfiguration configuration, CoreAPIHttpService coreAPIHttpService, HttpClient httpClient)
         {
             _azureCosmos = azureCosmos;
-            _dingDing = dingDing;
             _azureStorage = azureStorage;
+            _azureRedis = azureRedis;
+            _dingDing = dingDing;
             _option = option?.Value;
             _environment = hostingEnvironment;
             _configuration = configuration;
@@ -1263,7 +1266,98 @@ namespace TEAMModelBI.Controllers.BITest
             return Ok(new { state = 200, linqTests, set });
         }
 
+        /// <summary>
+        /// 多个连接字符串方式
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("get-manydb")]
+        public async Task<IActionResult> GetMany()
+        {
+
+            #region  依赖注入的方式 
+
+            DateTimeOffset dateTime = DateTimeOffset.UtcNow;
+            var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
+            Dictionary<long, int> allDays = new();  //所有在线人数
+            Dictionary<long, int> tchDays = new();  //教师在线人数
+            SortedSetEntry[] tchDay = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:teacher:{dateDay}");
+            if (tchDay.Length > 0)
+            {
+                foreach (var item in tchDay)
+                {
+                    int val = ((int)item.Score);
+                    int key = ((int)item.Element);
+                    var utcTo = new DateTimeOffset(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, key, 0, 0)).Hour;
+                    //var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {key}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
+                    tchDays.Add(utcTo, val);
+                    if (allDays.ContainsKey(utcTo))
+                        allDays[utcTo] = (allDays[utcTo] + val);
+                    else
+                        allDays.Add(utcTo, val);
+                }
+            }
+            var redisGl = _azureRedis.GetRedisClient(dbnum: 0, name: "Global");
+
+            var temps= await _azureRedis.GetRedisClient(dbnum: 0, name: "Global").SortedSetIncrementAsync($"Login:IES:Test", $"1", 1);//一天24小时  小时为单位
+
+
 
+            var cosmosDefaulat = _azureCosmos.GetCosmosClient(); //默认数据库
+            List<School> schools = new();
+            await foreach (var item in cosmosDefaulat.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<School>(queryText: "select value(c) from c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
+            {
+                schools.Add(item);
+            }
+            var cosmosGlobal = _azureCosmos.GetCosmosClient(name: "Global"); //默认数据库
+            List<School> schoolGlobals = new();
+            await foreach (var item in cosmosGlobal.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<School>(queryText: "select value(c) from c", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
+            {
+                schoolGlobals.Add(item);
+            }
+            return Ok(new { state = 200, dcnt = schools.Count, schools, dgcnt = schoolGlobals.Count, schoolGlobals });
+
+
+            ////table多库连接
+            //var tableDefulat = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");  //Table默认表
+            //List<DayLogin> hourLoginsDefualt = await tableDefulat.FindListByDict<DayLogin>(new Dictionary<string, object> { { "PartitionKey", $"DayLogin" } });
+
+            //var table = _azureStorage.GetCloudTableClient("Global").GetTableReference("IESLogin");  //Table国际站
+            //List<DayLogin> hourLogins= await  table.FindListByDict<DayLogin>(new Dictionary<string, object> { { "PartitionKey", $"DayLogin" } });
+
+            //return Ok(new { state = 200, hourLogins, hourLoginsDefualt });
+            #endregion
+
+            #region  原始方式
+            //cosmosDB连接字符串
+            //var clientUrl = _configuration.GetValue<string>("Azure:CosmosUrl:ConnectionString");
+            //var clientKey = _configuration.GetValue<string>("Azure:CosmosKey:ConnectionString");
+            //CosmosClient cosmosClient = new(clientUrl, clientKey);
+            //var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync("hbcn", new PartitionKey("Base"));
+            //School school = new();
+            //if (response.Status == 200)
+            //{
+            //    using var json = await JsonDocument.ParseAsync(response.ContentStream);
+            //    school = json.ToObject<School>();
+            //}
+
+            //Table连接字符串
+            //var tableUrl = _configuration.GetValue<string>("Azure:StorageUrl:ConnectionString");
+            //Uri url = new(tableUrl, UriKind.Absolute);
+            //var tableKey = _configuration.GetValue<string>("Azure:StorageKey:ConnectionString");
+            //StorageCredentials sta = new StorageCredentials("teammodeltest", tableKey);
+            //CloudTableClient cloudTableClient = new CloudTableClient(url, sta);
+            //var table1 = cloudTableClient.GetTableReference("IESLogin");
+            //var tableGlobal = _configuration.GetValue<string>("AzureGlobal");
+
+            //Dictionary<string, object> dic = new() { { "PartitionKey", $"HourLogin" } };
+            //List<HourLogin> hourLogin = await table1.FindListByDict<HourLogin>(dic);
+
+            //List<HourLogin> hourLogin1= await table.FindListByDict<HourLogin>(dic);
+
+            //var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
+            //return Ok(new { state = 200 }); 
+            #endregion
+        }
 
         public class linqTest
         {

+ 32 - 0
TEAMModelBI/DI/BIAzureCosmosFactoryExtensions.cs

@@ -0,0 +1,32 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelBI.DI
+{
+    public static class BIAzureCosmosFactoryExtensions
+    {
+        /// <summary>
+        /// cosmosDB注入
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="connectionInfo"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        public static IServiceCollection AddBIAzureCosmos(this IServiceCollection services, List<(string name, string connectionString)> connectionInfo)
+        {
+            if (services == null) throw new ArgumentNullException(nameof(services));
+            if (connectionInfo == null) throw new ArgumentNullException(nameof(connectionInfo));
+            services.TryAddSingleton<AzureCosmosFactory>();
+            connectionInfo.ForEach(connection =>
+            {
+                services.Configure<AzureCosmosFactoryOptions>(connection.name, o => { o.Name = connection.name; o.CosmosConnectionString = connection.connectionString; });
+            });  //多个数据库注入
+            //services.Configure<AzureCosmosFactoryOptions>(name, o => { o.Name = name; o.CosmosConnectionString = connectionString; });  //单个数据库注入
+            return services;
+        }
+    }
+}

+ 36 - 0
TEAMModelBI/DI/BIAzureRedisFactoryExtensions.cs

@@ -0,0 +1,36 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelBI.DI
+{
+    public static class BIAzureRedisFactoryExtensions
+    {
+        /// <summary>
+        /// Redis数据库注入
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="connectionInfos"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        public static IServiceCollection AddBIAzureRedis(this IServiceCollection services, List<(string name ,string  connectionString)> connectionInfos)
+        {
+            if (services == null) throw new ArgumentNullException(nameof(services));
+            if (connectionInfos == null) throw new ArgumentNullException(nameof(connectionInfos));
+
+            services.TryAddSingleton<AzureRedisFactory>();
+            //多个连接字符串注入
+            connectionInfos.ForEach(connection =>
+            {
+                services.Configure<AzureRedisFactoryOptions>(connection.name, o => { o.Name = connection.name; o.RedisConnectionString = connection.connectionString; });
+            });
+            ////单个连接字符注入
+            //services.Configure<AzureRedisFactoryOptions>(name, o => { o.Name = name; o.RedisConnectionString = connectionString; });
+
+            return services;
+        }
+    }
+}

+ 37 - 0
TEAMModelBI/DI/BIAzureServiceBusFactoryExtensions.cs

@@ -0,0 +1,37 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelBI.DI
+{
+    public static class BIAzureServiceBusFactoryExtensions
+    {
+        /// <summary>
+        /// Function注入
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="connectInfos"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        /// <exception cref="AccessViolationException"></exception>
+        public static IServiceCollection AddBIAzureServiceBus(this IServiceCollection services, List<(string name, string connectionString)> connectInfos) 
+        {
+            if (services == null) throw new ArgumentNullException(nameof(services));
+            if (connectInfos == null) throw new AccessViolationException(nameof(connectInfos));
+
+            services.TryAddSingleton<AzureServiceBusFactory>();
+
+            //多个注入
+            connectInfos.ForEach(connect => {
+                services.Configure<AzureServiceBusFactoryOptions>(connect.name, o => { o.Name = connect.name; o.ServiceBusConnectionString = connect.connectionString; });            
+            });
+
+            //services.Configure<AzureServiceBusFactoryOptions>(name, o => { o.Name = name; o.ServiceBusConnectionString = connectionString; });//单一注入
+
+            return services;
+        }
+    }
+}

+ 32 - 0
TEAMModelBI/DI/BIAzureStorageFactoryExtensions.cs

@@ -0,0 +1,32 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelBI.DI
+{
+    public static class BIAzureStorageFactoryExtensions
+    {
+        /// <summary>
+        /// Storage依赖注入
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="connectionInfo"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentNullException"></exception>
+        public static IServiceCollection AddBIAzureStorage(this IServiceCollection services, List<(string name, string connectionString)> connectionInfo)
+        {
+            if (services == null) throw new ArgumentNullException(nameof(services));
+            if (connectionInfo == null) throw new ArgumentNullException(nameof(connectionInfo));
+
+            services.TryAddSingleton<AzureStorageFactory>();
+            connectionInfo.ForEach(connection =>
+            {
+                services.Configure<AzureStorageFactoryOptions>(connection.name, o => { o.Name = connection.name; o.StorageAccountConnectionString = connection.connectionString; });
+            });
+            return services;
+        }
+    }
+}

+ 6 - 6
TEAMModelBI/JsonFile/Preset/LangSchoolConfig.json

@@ -2,13 +2,13 @@
     "Lang": "en-US",
     "semester": [
       {
-        "term": "Last Semester",
+        "term": "Next Semester",
         "start": 0,
         "month": 3,
         "day": 1
       },
       {
-        "term": "Next Semester",
+        "term": "Last Semester",
         "start": 1,
         "month": 9,
         "day": 1
@@ -59,13 +59,13 @@
     "Lang": "zh-CN",
     "semester": [
       {
-        "term": "学期",
+        "term": "学期",
         "start": 0,
         "month": 3,
         "day": 1
       },
       {
-        "term": "学期",
+        "term": "学期",
         "start": 1,
         "month": 9,
         "day": 1
@@ -116,13 +116,13 @@
     "Lang": "zh-TW",
     "semester": [
       {
-        "term": "學期",
+        "term": "學期",
         "start": 0,
         "month": 3,
         "day": 1
       },
       {
-        "term": "學期",
+        "term": "學期",
         "start": 1,
         "month": 9,
         "day": 1

+ 32 - 4
TEAMModelBI/Startup.cs

@@ -15,6 +15,7 @@ using System.Collections.Generic;
 using System.IdentityModel.Tokens.Jwt;
 using System.Linq;
 using System.Threading.Tasks;
+using TEAMModelBI.DI;
 using TEAMModelBI.Models;
 using TEAMModelOS.Models;
 using TEAMModelOS.SDK;
@@ -87,10 +88,37 @@ namespace TEAMModelBI
                     .AllowAnyMethod();
                 });
             });
-            services.AddAzureStorage(Configuration.GetValue<string>("Azure:Storage:ConnectionString"));
-            services.AddAzureRedis(Configuration.GetValue<string>("Azure:Redis:ConnectionString"));
-            services.AddAzureCosmos(Configuration.GetValue<string>("Azure:Cosmos:ConnectionString"));
-            services.AddAzureServiceBus(Configuration.GetValue<string>("Azure:ServiceBus:ConnectionString"));
+
+            //Table和blob注入
+            List<(string name, string connectionString)> storageConnects = new();
+            storageConnects.Add(("Default", Configuration.GetValue<string>("Azure:Storage:ConnectionString")));      //大路站ClientString
+            storageConnects.Add(("Global", Configuration.GetValue<string>("GlobalAzure:Storage:ConnectionString"))); //国际站ClientString
+            services.AddBIAzureStorage(storageConnects);
+
+            //cosmosDB注入
+            List<(string name,string connectionString)> cosmosDBConnects = new();
+            cosmosDBConnects.Add(("Default", Configuration.GetValue<string>("Azure:Cosmos:ConnectionString")));      //大路站ClientString
+            cosmosDBConnects.Add(("Global", Configuration.GetValue<string>("GlobalAzure:Cosmos:ConnectionString"))); //国际站ClientString
+            services.AddBIAzureCosmos(cosmosDBConnects);
+
+            //redis注入
+            List<(string name, string connectionString)> redisConnects = new();
+            redisConnects.Add(("Default", Configuration.GetValue<string>("Azure:Redis:ConnectionString")));
+            redisConnects.Add(("Global", Configuration.GetValue<string>("GlobalAzure:Redis:ConnectionString")));
+            services.AddBIAzureRedis(redisConnects);
+
+            //serverBus 注入 
+            List<(string name, string connectionString)> funConnects = new();
+            funConnects.Add(("Default", Configuration.GetValue<string>("Azure:Redis:ConnectionString")));
+            funConnects.Add(("Global", Configuration.GetValue<string>("GlobalAzure:Redis:ConnectionString")));
+            services.AddBIAzureServiceBus(funConnects);
+            
+            //单一注入
+            //services.AddAzureStorage(Configuration.GetValue<string>("Azure:Storage:ConnectionString"));
+            //services.AddAzureCosmos(Configuration.GetValue<string>("Azure:Cosmos:ConnectionString"));
+            //services.AddAzureRedis(Configuration.GetValue<string>("Azure:Redis:ConnectionString"));
+            //services.AddAzureServiceBus(Configuration.GetValue<string>("Azure:ServiceBus:ConnectionString"));     
+
             services.AddSnowflakeId(Convert.ToInt64(Configuration.GetValue<string>("Option:LocationNum")), 1);
             services.AddHttpClient();
             services.AddHttpClient<DingDing>(); 

+ 10 - 0
TEAMModelBI/Tool/Context/BIConst.cs

@@ -0,0 +1,10 @@
+namespace TEAMModelBI.Tool.Context
+{
+    public class BIConst
+    {
+        /// <summary>
+        /// 国际站点
+        /// </summary>
+        public static readonly string GlobalSite = "Global";
+    }
+}

+ 24 - 0
TEAMModelBI/appsettings.Development.json

@@ -19,6 +19,7 @@
     "HttpTrigger": "https://teammodelosfunction-test.chinacloudsites.cn/api/"
     //"HttpTrigger": "http://localhost:7071/api/"
   },
+  //大陆站连接字符串
   "Azure": {
     "Storage": {
       "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodeltest;AccountKey=O2W2vadCqexDxWO+px+QK7y1sHwsYj8f/WwKLdOdG5RwHgW/Dupz9dDUb4c1gi6ojzQaRpFUeAAmOu4N9E+37A==;EndpointSuffix=core.chinacloudapi.cn"
@@ -26,6 +27,29 @@
     "Cosmos": {
       "ConnectionString": "AccountEndpoint=https://cdhabookdep-free.documents.azure.cn:443/;AccountKey=JTUVk92Gjsx17L0xqxn0X4wX2thDPMKiw4daeTyV1HzPb6JmBeHdtFY1MF1jdctW1ofgzqkDMFOtcqS46by31A==;"
     },
+    "CosmosUrl": {
+      "ConnectionString": "https://cdhabookdep-free.documents.azure.cn:443/"
+    },
+    "CosmosKey": {
+      "ConnectionString": "JTUVk92Gjsx17L0xqxn0X4wX2thDPMKiw4daeTyV1HzPb6JmBeHdtFY1MF1jdctW1ofgzqkDMFOtcqS46by31A=="
+    },
+    "Redis": {
+      "ConnectionString": "52.130.252.100:6379,password=habook,ssl=false,abortConnect=False,writeBuffer=10240"
+    },
+    "ServiceBus": {
+      "ConnectionString": "Endpoint=sb://teammodelos.servicebus.chinacloudapi.cn/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=Sy4h4EQ8zP+7w/lOLi1X3tGord/7ShFHimHs1vC50Dc=",
+      "ActiveTask": "dep-active-task",
+      "ItemCondQueue": "dep-itemcond"
+    }
+  },
+  //国际站连接字符串  暂时是本地
+  "GlobalAzure": {
+    "Storage": {
+      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodelstorage;AccountKey=Yq7D4dE6cFuer2d2UZIccTA/i0c3sJ/6ITc8tNOyW+K5f+/lWw9GCos3Mxhj47PyWQgDL8YbVD63B9XcGtrMxQ==;EndpointSuffix=core.chinacloudapi.cn" // 之前未删除
+    },
+    "Cosmos": {
+      "ConnectionString": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" //本地
+    },
     "Redis": {
       "ConnectionString": "52.130.252.100:6379,password=habook,ssl=false,abortConnect=False,writeBuffer=10240"
     },

+ 18 - 0
TEAMModelBI/appsettings.json

@@ -25,6 +25,7 @@
     "HttpTrigger": "https://teammodelosfunction-test.chinacloudsites.cn/api/"
     //"HttpTrigger": "http://localhost:7071/api/"
   },
+  //大陆站连接字符串
   "Azure": {
     //
     "Storage": {
@@ -47,6 +48,23 @@
       "ItemCondQueue": "dep-itemcond" //队列消息
     }
   },
+  //国际站连接字符串
+  "GlobalAzure": {
+    "Storage": {
+      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodelstorage;AccountKey=Yq7D4dE6cFuer2d2UZIccTA/i0c3sJ/6ITc8tNOyW+K5f+/lWw9GCos3Mxhj47PyWQgDL8YbVD63B9XcGtrMxQ==;EndpointSuffix=core.chinacloudapi.cn" // 之前未删除
+    },
+    "Cosmos": {
+      "ConnectionString": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" //本地
+    },
+    "Redis": {
+      "ConnectionString": "52.130.252.100:6379,password=habook,ssl=false,abortConnect=False,writeBuffer=10240"
+    },
+    "ServiceBus": {
+      "ConnectionString": "Endpoint=sb://teammodelos.servicebus.chinacloudapi.cn/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=Sy4h4EQ8zP+7w/lOLi1X3tGord/7ShFHimHs1vC50Dc=",
+      "ActiveTask": "dep-active-task",
+      "ItemCondQueue": "dep-itemcond"
+    }
+  },
   "HaBookAuth": {
     "CoreId": {
       "userinfo": "https://api2.teammodel.cn/Oauth2/GetUserInfos"

+ 3 - 1
TEAMModelOS.FunctionV4/CosmosDB/CommonTrigger.cs

@@ -57,7 +57,9 @@ namespace TEAMModelOS.FunctionV4
                         element.TryGetProperty("pk", out JsonElement jsond);
                         if (!string.IsNullOrWhiteSpace($"{jsond}"))
                         {
-                            if ($"{jsond}".Equals("Receiver", StringComparison.OrdinalIgnoreCase) || $"{jsond}".Equals("ExamClassResult", StringComparison.OrdinalIgnoreCase))
+                            if ($"{jsond}".Equals("Receiver", StringComparison.OrdinalIgnoreCase)
+                                ||$"{jsond}".Equals("Notice", StringComparison.OrdinalIgnoreCase)
+                                || $"{jsond}".Equals("ExamClassResult", StringComparison.OrdinalIgnoreCase))
                             {
                                 ///通知接收者的变更
                                 continue;

+ 11 - 5
TEAMModelOS.FunctionV4/HttpTrigger/IESHttpTrigger.cs

@@ -215,7 +215,7 @@ namespace TEAMModelOS.FunctionV4.HttpTrigger
                         {
                             Student student = await cosmosClient.GetContainer("TEAMModelOS", Constant.Student).ReadItemAsync<Student>(id, new PartitionKey($"Base-{school}"));
                             student.loginInfos = new List<LoginInfo>() { new LoginInfo { expire = Expire, ip = ip, time = now } };
-                            await cosmosClient.GetContainer("TEAMModelOS", Constant.Student).ReplaceItemAsync<Student>(student, student.id, new PartitionKey("Base"));
+                            await cosmosClient.GetContainer("TEAMModelOS", Constant.Student).ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"Base-{school}"));
                         }
                         catch { }
                         break;
@@ -344,10 +344,10 @@ namespace TEAMModelOS.FunctionV4.HttpTrigger
                 }
 
                 string tbHourSql = $"PartitionKey eq 'HourLogin' and RowKey le '{delTbHour}'";
-                await table.DeleteStringWhere<BIOptLog>(rowKey: tbHourSql);  //删除168小时前的数据
+                await table.DeleteStringWhere<HourLogin>(rowKey: tbHourSql);  //删除168小时前的数据
 
                 string tbDaySql = $"PartitionKey eq 'DayLogin' and RowKey le '{delTbDay}'";
-                await table.DeleteStringWhere<BIOptLog>(rowKey: tbDaySql);   //删除180天前的数据
+                await table.DeleteStringWhere<DayLogin>(rowKey: tbDaySql);   //删除180天前的数据
 
                 if (!string.IsNullOrWhiteSpace(school))
                 {
@@ -466,10 +466,16 @@ namespace TEAMModelOS.FunctionV4.HttpTrigger
                     }
 
                     string tbScHourSql = $"PartitionKey eq 'HourLogin-{school}' and RowKey le '{delTbHour}'";
-                    await table.DeleteStringWhere<BIOptLog>(rowKey: tbHourSql); //删除学校168小时前的数据
+                    List<HourLogin> scHourLog = await table.QueryWhereString<HourLogin>(tbScHourSql);
+                    if (scHourLog.Count > 0)
+                        //await table.DeleteStringWhere<HourLogin>(tbScHourSql); //删除学校168小时前的数据
+                        await table.DeleteAll(scHourLog);
 
                     string tbScDaySql = $"PartitionKey eq 'DayLogin-{school}' and RowKey le '{delTbDay}'";
-                    await table.DeleteStringWhere<BIOptLog>(rowKey: tbDaySql); //删除学校180天前的数据
+                    List<DayLogin> scDayLog = await table.QueryWhereString<DayLogin>(tbScDaySql);
+                    if (scDayLog.Count > 0)
+                        await table.DeleteAll(scDayLog);
+                        //await table.DeleteStringWhere<DayLogin>(tbScDaySql); //删除学校180天前的数据
                 }
 
                 await response.WriteAsJsonAsync(new { data = json });

+ 5 - 1
TEAMModelOS.FunctionV4/Program.cs

@@ -1,4 +1,6 @@
-using Microsoft.Extensions.Configuration;
+using DinkToPdf;
+using DinkToPdf.Contracts;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection.Extensions;
 using Microsoft.Extensions.Hosting;
@@ -44,6 +46,8 @@ namespace TEAMModelOS.FunctionV4
                services.AddAzureServiceBus(context.Configuration.GetSection("Azure:ServiceBus:ConnectionString").Get<string>());
                services.AddAzureStorage(context.Configuration.GetSection("Azure:Storage:ConnectionString").Get<string>());
                services.AddAzureRedis(context.Configuration.GetSection("Azure:Redis:ConnectionString").Get<string>());
+               services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));
+               services.AddSnowflakeId(Convert.ToInt64(context.Configuration.GetSection("Option:LocationNum").Get<Int32>()), 1);
            })
            .Build();
             await host.RunAsync();

+ 285 - 18
TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs

@@ -26,6 +26,8 @@ using Azure.Storage.Blobs.Models;
 using System.IO;
 using Azure;
 using static TEAMModelOS.SDK.Models.Service.LessonService;
+using DinkToPdf.Contracts;
+using TEAMModelOS.SDK.Helper.Common.DateTimeHelper;
 
 namespace TEAMModelOS.FunctionV4.ServiceBus
 {
@@ -40,7 +42,9 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
         private readonly NotificationService _notificationService;
         private readonly CoreAPIHttpService _coreAPIHttpService;
         private readonly IConfiguration _configuration;
-        public ActiveTaskTopic(CoreAPIHttpService coreAPIHttpService, AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, AzureServiceBusFactory serviceBus, IOptionsSnapshot<Option> option, NotificationService notificationService, IConfiguration configuration)
+        private readonly IConverter _converter;
+        private readonly SnowflakeId _snowflakeId;
+        public ActiveTaskTopic(SnowflakeId snowflakeId,IConverter converter, CoreAPIHttpService coreAPIHttpService, AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis, AzureServiceBusFactory serviceBus, IOptionsSnapshot<Option> option, NotificationService notificationService, IConfiguration configuration)
         {
             _azureCosmos = azureCosmos;
             _dingDing = dingDing;
@@ -51,6 +55,8 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
             _notificationService = notificationService;
             _configuration = configuration;
             _coreAPIHttpService = coreAPIHttpService;
+            _converter = converter;
+            _snowflakeId = snowflakeId;
         }
         [Function("Exam")]
         public async Task ExamFunc([ServiceBusTrigger("%Azure:ServiceBus:ActiveTask%", "exam", Connection = "Azure:ServiceBus:ConnectionString")] string msg)
@@ -532,9 +538,16 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                 await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-GroupChange-GroupChange\n{ex.Message}\n{ex.StackTrace}\n\n{msg}", GroupNames.醍摩豆服務運維群組);
             }
         }
+        //接收死信消息
+        [Function("ItemCondDel")]
+        public async Task ItemCondDelFunc([ServiceBusTrigger("%Azure:ServiceBus:ItemCondQueue%/$DeadLetterQueue", Connection = "Azure:ServiceBus:ConnectionString")] string message) {
+            await _dingDing.SendBotMsg($"{message}", GroupNames.成都开发測試群組);
+
+        }
         [Function("ItemCond")]
         public async Task ItemCondFunc([ServiceBusTrigger("%Azure:ServiceBus:ItemCondQueue%", Connection = "Azure:ServiceBus:ConnectionString")] string msg)
         {
+            
             try
             {
                 var client = _azureCosmos.GetCosmosClient();
@@ -601,7 +614,259 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                 await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,ItemCond()\n{ex.Message}\n{ex.StackTrace}\n\n{msg}", GroupNames.醍摩豆服務運維群組);
             }
         }
+        [Function("GenPdf")]
+        public async Task GenPdfFunc([ServiceBusTrigger("%Azure:ServiceBus:GenPdfQueue%", Connection = "Azure:ServiceBus:ConnectionString")] string msg)
+        {
+            try
+            {
+                var client = _azureCosmos.GetCosmosClient();
+                /*                var jsonMsg = JsonDocument.Parse(msg);
+                                jsonMsg.RootElement.TryGetProperty("id", out JsonElement id);
+                                //jsonMsg.RootElement.TryGetProperty("code", out JsonElement code);*/
+                JsonElement element = msg.ToObject<JsonElement>();
+                element.TryGetProperty("id", out JsonElement ids);
+                List<string> tIds = ids.ToObject<List<string>>();
+                element.TryGetProperty("school", out JsonElement code);
+                string sname = string.Empty;
+                string areaId = string.Empty;
+                var scquery = $"SELECT c.name,c.areaId from c where c.id = '{code}'";
+                await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: scquery, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base") }))
+                {
+
+                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        var accounts = json.RootElement.GetProperty("Documents").EnumerateArray();
+                        while (accounts.MoveNext())
+                        {
+                            JsonElement account = accounts.Current;
+                            sname = account.GetProperty("name").ToString();
+                            areaId = account.GetProperty("areaId").ToString();
+                        }
+                    }
+                }
+                foreach (string id in tIds)
+                {
+                    List<Study> studies = new();
+                    StringBuilder stringBuilder = new StringBuilder();
+                    List<(List<string> tch, List<Dictionary<string, List<string>>> groupLists)> tchInfo = new();
+                    var query = $"SELECT  top 1  c.tchLists,c.groupLists FROM c where array_contains(c.teacIds, '{id}') order by c.createTime desc";
+                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Common").GetItemQueryStreamIterator(queryText: query, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Study-{code}") }))
+                    {
+
+                        using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                        if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                        {
+                            var accounts = json.RootElement.GetProperty("Documents").EnumerateArray();
+                            while (accounts.MoveNext())
+                            {
+                                JsonElement account = accounts.Current;
+                                List<string> tchId = new();
+                                List<Dictionary<string, List<string>>> groupLists = new();
+                                if (account.TryGetProperty("tchLists", out JsonElement tchLists))
+                                {
+                                    tchId = tchLists.ToObject<List<string>>();
+                                };
+                                if (account.TryGetProperty("groupLists", out JsonElement group))
+                                {
+                                    groupLists = group.ToObject<List<Dictionary<string, List<string>>>>();
+                                };
+                                tchInfo.Add((tchId, groupLists));
+                            }
+                        }
+                    }
+                    string cname = string.Empty;
+                    string gname = string.Empty;
+                    foreach (var (tch, groupLists) in tchInfo)
+                    {
+                        List<(string pId, List<string> gid)> ps = new List<(string pId, List<string> gid)>();
+                        if (groupLists.Count > 0)
+                        {
+                            var group = groupLists;
+                            foreach (var gp in group)
+                            {
+                                foreach (KeyValuePair<string, List<string>> pp in gp)
+                                {
+                                    ps.Add((pp.Key, pp.Value));
+                                }
+                            }
+                        }
+                        (List<RMember> tchList, List<RGroupList> classInfos) = await GroupListService.GetStutmdidListids(_coreAPIHttpService, client, _dingDing, tch, code.GetString(), ps);
+                        gname = tchList.Where(c => c.id == id).FirstOrDefault().groupName;
+                        cname = tchList.Where(c => c.id == id).FirstOrDefault().name;
+                    }
+
+                    var queryInfo = $"select value(c) from c where (c.status<>404 or IS_DEFINED(c.status) = false) and array_contains(c.teacIds, '{id}')";
+                    await foreach (var item in client.GetContainer("TEAMModelOS", "Common").GetItemQueryIterator<Study>(queryText: queryInfo, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Study-{code}") }))
+                    {
+                        studies.Add(item);
+
+                    }
+                    List<string> details = new();
+                    foreach (Study study in studies)
+                    {
+                        var sresponse = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync(study.id, new PartitionKey($"StudyRecord-{id}"));
+                        if (sresponse.Status == 200)
+                        {
+                            var json = await JsonDocument.ParseAsync(sresponse.ContentStream);
+                            StudyRecord record = json.ToObject<StudyRecord>();
+                            if (record.status == 1)
+                            {
+                                if (!string.IsNullOrEmpty(study.workId))
+                                {
+                                    var response = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync(study.workId, new PartitionKey($"HomeworkRecord-{id}"));
+                                    if (response.Status == 200)
+                                    {
+                                        var start = DateTimeHelper.FromUnixTimestamp(study.startTime).ToString("yyyy/MM/dd");
+                                        var end = DateTimeHelper.FromUnixTimestamp(study.endTime).ToString("yyyy/MM/dd");
+                                        string tname = string.Empty;
+                                        List<string> setName = new();
+                                        foreach (string setting in study.settings)
+                                        {
+                                            if (setting.Equals("sign"))
+                                            {
+                                                setName.Add("扫码签到");
+                                            }
+                                            else if (setting.Equals("hw"))
+                                            {
+                                                setName.Add("作业提交");
+                                            }
+                                            else if (setting.Equals("survey"))
+                                            {
+                                                setName.Add("问卷反馈");
+                                            }
+                                            else if (setting.Equals("exam"))
+                                            {
+                                                setName.Add("评测活动");
+                                            }
+                                        }
+                                        switch (study.type)
+                                        {
+                                            case 1:
+                                                tname = "信息化教学案例展示与分享";
+                                                break;
+                                            case 2:
+                                                tname = "专家专题培训";
+                                                break;
+                                            case 3:
+                                                tname = "同课同构";
+                                                break;
+                                            case 4:
+                                                tname = "同课异构";
+                                                break;
+                                            case 5:
+                                                tname = "校本2.0培训";
+                                                break;
+                                            case 6:
+                                                tname = "自定义活动";
+                                                break;
+                                        };
+                                        stringBuilder.Append($@"<tr>
+                                                        <td> {study.topic} </td>
+                                                        <td> {tname} </td >
+                                                        <td> {study.hour} </td >
+                                                        <td> {study.hour} </td >
+                                                        <td> {start} 到 {end} </td>
+                                                        <td>{study.desc}</td>
+                                                        <td> {string.Join("、", setName.Select(x => $"{x}\n"))} </td>
+                                                        <td> 已完成 </td >
+                                                    </tr> ");
+                                    }
+                                    else
+                                    {
+                                        continue;
+                                    }
+                                }
+                                else
+                                {
+                                    var start = DateTimeHelper.FromUnixTimestamp(study.startTime).ToString("yyyy/MM/dd");
+                                    var end = DateTimeHelper.FromUnixTimestamp(study.endTime).ToString("yyyy/MM/dd");
+                                    string tname = string.Empty;
+                                    List<string> setName = new();
+                                    foreach (string setting in study.settings)
+                                    {
+                                        if (setting.Equals("sign"))
+                                        {
+                                            setName.Add("扫码签到");
+                                        }
+                                        else if (setting.Equals("hw"))
+                                        {
+                                            setName.Add("作业提交");
+                                        }
+                                        else if (setting.Equals("survey"))
+                                        {
+                                            setName.Add("问卷反馈");
+                                        }
+                                        else if (setting.Equals("exam"))
+                                        {
+                                            setName.Add("评测活动");
+                                        }
+                                    }
+                                    switch (study.type)
+                                    {
+                                        case 1:
+                                            tname = "信息化教学案例展示与分享";
+                                            break;
+                                        case 2:
+                                            tname = "专家专题培训";
+                                            break;
+                                        case 3:
+                                            tname = "同课同构";
+                                            break;
+                                        case 4:
+                                            tname = "同课异构";
+                                            break;
+                                        case 5:
+                                            tname = "校本2.0培训";
+                                            break;
+                                        case 6:
+                                            tname = "自定义活动";
+                                            break;
+                                    };
+                                    stringBuilder.Append($@"<tr>
+                                                        <td> {study.topic} </td>
+                                                        <td> {tname} </td >
+                                                        <td> {study.hour} </td >
+                                                        <td> {study.hour} </td >
+                                                        <td> {start} 到 {end} </td>
+                                                        <td>{study.desc}</td>
+                                                        <td> {string.Join("、", setName.Select(x => $"{x}\n"))} </td>
+                                                        <td> 已完成 </td >
+                                                    </tr> ");
+                                }
+                            }
+                            else {
+                                continue;
+                            }
+                        }
+                        else {
+                            continue;
+                        }                                                                    
+                    }
+                    await StudyService.GenPdf(id, areaId, cname, sname, gname, stringBuilder.ToString(), _converter, _azureStorage,_dingDing);
+                }
+
+
+                /*var query = $"select c.id,c.name,c.type,c.hour,c.startTime,c.endTime,c.presenter,c.topic from c where (c.status<>404 or IS_DEFINED(c.status) = false and array_contains(c.teacIds, '{id}') )";
+                await foreach (var item in client.GetContainer("TEAMModelOS", "Common").GetItemQueryIterator<Study>(queryText: query, requestOptions: new QueryRequestOptions() {PartitionKey = new PartitionKey($"Study-{code}") }))
+                {
+                    studies.Add(item);
+
+                }*/
+                //(List<StuActivity> datas, string continuationToken) = await ActivityStudentService.FindActivity(element, null, null, _azureCosmos, _azureRedis);
+
+                // string blob =  await StudyService.GenPdf(cname,sname,gname,"", _converter);
 
+            }
+            catch (CosmosException ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,GenPdfFunc()\n{ex.Message}\n{ex.StackTrace}\n\n{msg}", GroupNames.醍摩豆服務運維群組);
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,GenPdfFunc()\n{ex.Message}\n{ex.StackTrace}\n\n{msg}", GroupNames.醍摩豆服務運維群組);
+            }
+        }
         //更新學校產品一覽表
         //處理內容:取得所有序號購買紀錄,服務週期、硬體購買紀錄後,更新ProductSum
         [Function("Product")]
@@ -682,7 +947,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                             }
                         }
                     }
-                    
+
                 }
                 ////服務產品特別對應項
                 School school = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{schoolId}", new PartitionKey("Base")); //學校基本資料取得
@@ -874,7 +1139,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
             }
 
             var client = _azureCosmos.GetCosmosClient();
-           
+
             if ($"{scope}".Equals("school") && !string.IsNullOrEmpty($"{school}"))
             {
                 blobname = $"{school}";
@@ -1022,10 +1287,10 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                         //await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS,Constant.Student).UpsertItemAsync<LessonStudentRecord>(lessonStudentRecord, new PartitionKey("LessonStudentRecord"));
                                     }
                                     //有上传 base.josn.
-                                    lessonRecord.upload =1;
+                                    lessonRecord.upload = 1;
                                     // await _dingDing.SendBotMsg($"{_option.Location},课堂id:{_lessonId} 更新完成", GroupNames.醍摩豆服務運維群組);
 
-                                    LessonService.DoAutoDeleteSchoolLessonRecord(lessonRecord, scope, client, school, tmdid,   teacher, _notificationService, _serviceBus, _azureStorage, _configuration);
+                                    LessonService.DoAutoDeleteSchoolLessonRecord(lessonRecord, scope, client, school, tmdid, teacher, _notificationService, _serviceBus, _azureStorage, _configuration);
                                     long? size = await _azureStorage.GetBlobContainerClient(blobname).GetBlobsSize($"records/{_lessonId}");
                                     Bloblog bloblog = new Bloblog
                                     {
@@ -1047,6 +1312,8 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                     await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
                                     //await _dingDing.SendBotMsg($"{_option.Location},课堂id:{_lessonId} blob刷新完成!", GroupNames.醍摩豆服務運維群組);
                                     msgs.Add(update);
+
+                                    DoLessonStudentRecord(_dingDing, _snowflakeId, lessonRecord, scope, client, school, tmdid, teacher, _notificationService, _serviceBus, _azureStorage, _configuration, lessonBase);
                                 }
                                 catch (Exception ex)
                                 {
@@ -1132,7 +1399,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                             case "create":
                                 oldlessonRecord = null;
                                 //处理课堂选用的课程信息
-                              
+
                                 lessonRecord.show = teacher.lessonShow;
                                 lessonRecord.upload = 0;
                                 if (!string.IsNullOrEmpty(lessonRecord.courseId))
@@ -1287,7 +1554,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                                 //var day3= now.AddDays(Constant.private_lesson_expire - 3).ToUnixTimeMilliseconds();
                                                 //result.Add(3, day3);
                                                 //剩余1天的通知
-                                                var day1 = now.AddDays(Constant.private_lesson_expire - (Constant.private_lesson_expire-1)).ToUnixTimeMilliseconds();
+                                                var day1 = now.AddDays(Constant.private_lesson_expire - (Constant.private_lesson_expire - 1)).ToUnixTimeMilliseconds();
                                                 result.Add(1, new ExpireTag { expire = day1, tag = "notification" });
                                                 //到期通知
 
@@ -1299,7 +1566,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                                     addSecond = Constant.private_lesson_expire * 86400 + (24 - now.Hour) * 3600;
                                                     //再加 00到05小时内的 随机秒数
                                                     Random rand = new Random();
-                                                    int randInt= rand.Next(0 , 18000);
+                                                    int randInt = rand.Next(0, 18000);
                                                     addSecond += randInt;
                                                 }
                                                 else
@@ -1315,7 +1582,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                                 {
                                                     hubName = "hita",
                                                     type = "msg",
-                                                    from = $"ies5:{ Environment.GetEnvironmentVariable("Option:Location")}:private",
+                                                    from = $"ies5:{Environment.GetEnvironmentVariable("Option:Location")}:private",
                                                     to = new List<string> { tmdid },
                                                     label = $"{biz}_lessonRecord",
                                                     body = new
@@ -1326,7 +1593,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                                         tmdname = teacher.name,
                                                         sid = lessonRecord.id,
                                                         sname = lessonRecord.name,
-                                                        scope=scope,
+                                                        scope = scope,
                                                         stime = lessonRecord.startTime,
                                                         expire = lessonRecord.expire,
                                                         status = 1,
@@ -1434,7 +1701,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                 jsonMsg.TryGetProperty("tag", out JsonElement _tag);
                 var client = _azureCosmos.GetCosmosClient();
                 //处理到期删除
-                if (_tag.ValueKind.Equals(JsonValueKind.String) && $"{_tag}".Equals("delete") )
+                if (_tag.ValueKind.Equals(JsonValueKind.String) && $"{_tag}".Equals("delete"))
                 {
                     string lessonId = $"{id}";
                     string tbname;
@@ -1567,7 +1834,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                             else
                             {
 
-                                await _dingDing.SendBotMsg($"OS,{ Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                                await _dingDing.SendBotMsg($"OS,{Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
                             }
                         }
                     }
@@ -1604,7 +1871,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                             else
                             {
 
-                                await _dingDing.SendBotMsg($"OS,{ Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                                await _dingDing.SendBotMsg($"OS,{Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
                             }
                         }
                     }
@@ -1648,7 +1915,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                             else
                             {
 
-                                await _dingDing.SendBotMsg($"OS,{ Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                                await _dingDing.SendBotMsg($"OS,{Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
                             }
                         }
                     }
@@ -1686,7 +1953,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                             else
                             {
 
-                                await _dingDing.SendBotMsg($"OS,{ Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                                await _dingDing.SendBotMsg($"OS,{Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
                             }
                         }
                     }
@@ -1748,7 +2015,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                         }
                         catch (CosmosException ex)
                         {
-                            await _dingDing.SendBotMsg($"OS,{ Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                            await _dingDing.SendBotMsg($"OS,{Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
                         }
                     }
                 }
@@ -1808,14 +2075,14 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                         }
                         catch (CosmosException ex)
                         {
-                            await _dingDing.SendBotMsg($"OS,{ Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                            await _dingDing.SendBotMsg($"OS,{Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -CosmosDB异常\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
                         }
                     }
                 }
             }
             catch (Exception ex)
             {
-                await _dingDing.SendBotMsg($"OS,{ Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -Course\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+                await _dingDing.SendBotMsg($"OS,{Environment.GetEnvironmentVariable("Option:Location")},CourseServiceBus -Course\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
             }
         }
     }

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

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

+ 4 - 1
TEAMModelOS.FunctionV4/local.settings.json

@@ -9,11 +9,13 @@
     "Azure:Redis:ConnectionString": "52.130.252.100:6379,password=habook,ssl=false,abortConnect=False,writeBuffer=10240",
     "Azure:ServiceBus:ActiveTask": "dep-active-task",
     "Azure:ServiceBus:ItemCondQueue": "dep-itemcond",
+    "Azure:ServiceBus:GenPdfQueue": "dep-genpdf",
     "Option:Location": "China-Dep",
     "HaBookAuth:CoreService:sendnotification": "https://api2.teammodel.cn/service/sendnotification",
     "HaBookAuth:CoreAPI": "https://api2.teammodel.cn",
     "HaBookAuth:CoreService:clientID": "c7317f88-7cea-4e48-ac57-a16071f7b884",
-    "HaBookAuth:CoreService:clientSecret": "kguxh:V.PLmxBdaI@jnrTrDSth]A3346"
+    "HaBookAuth:CoreService:clientSecret": "kguxh:V.PLmxBdaI@jnrTrDSth]A3346",
+    "Option:LocationNum": 1
   }
 
   //"Values": {
@@ -30,5 +32,6 @@
   //  "HaBookAuth:CoreAPI": "https://api2.teammodel.cn",
   //  "HaBookAuth:CoreService:clientID": "c7317f88-7cea-4e48-ac57-a16071f7b884",
   //  "HaBookAuth:CoreService:clientSecret": "kguxh:V.PLmxBdaI@jnrTrDSth]A3346"
+  //  "Option:LocationNum": 2
   //}
 }

+ 106 - 2
TEAMModelOS.SDK/Context/Constant/Constant.cs

@@ -7,7 +7,7 @@ namespace TEAMModelOS.SDK.DI
     public class Constant
     {
         public static readonly List<string> BlobPrefix = new List<string> { "exam", "vote", "survey", "item", "paper", "syllabus", "records", "doc", "image", "res", "video", "audio", "other", "thum", "train", "temp", "jyzx" };
-        public static readonly List<string> ContentPrefix = new List<string> { "doc", "image", "res", "video", "audio", "other"};
+        public static readonly List<string> ContentPrefix = new List<string> { "doc", "image", "res", "video", "audio", "other" };
         public static readonly string TEAMModelOS = "TEAMModelOS";
         public static readonly string ScopeTeacher = "teacher";
         public static readonly string ScopeTmdUser = "tmduser";
@@ -19,8 +19,112 @@ namespace TEAMModelOS.SDK.DI
         public static readonly string Common = "Common";
         public static readonly string Teacher = "Teacher";
         public static readonly string Student = "Student";
-        public static readonly int private_lesson_limit =50;
+        public static readonly int private_lesson_limit = 50;
         public static readonly int private_lesson_expire = 7;
         public static readonly int school_lesson_expire = 7;
+        public static readonly string html = @"
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset=""UTF-8"">
+    <meta http-equiv=""X-UA-Compatible"" content=""IE=edge"">
+    <meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
+    <title>校本研修活动完成情况</title>
+    <style>
+        body {
+            font-family: ""fangsong"";
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+        }
+
+        .tch-table,
+        .details-table {
+            width: 100%;
+        }
+
+        .tch-table table {
+            width: 100%;
+            margin: 15px 0;
+            border: 0;
+        }
+
+        .tch-table th {
+            background-color: #c5c5c5;
+            color: #222222;
+             
+        }
+
+
+        .tch-table,
+        .tch-table th,
+        .tch-table td {
+            font-size: 0.95em;
+            text-align: center;
+            padding: 4px;
+            border-collapse: collapse;
+            font-weight: bolder;
+        }
+
+        .tch-table th,
+        .tch-table td {
+            border-bottom: 1px solid #918b8c;
+            border-width: 1px 0 1px 0;
+        }
+
+        .tch-table tr {
+            border: 1px solid #ffffff;
+            height: 3.2em;
+        }
+
+        .title {
+            font-weight: bold;
+            align-self: flex-start;
+            margin: 30px 5px;
+        }
+
+        .title::before {
+            content: """";
+            width: 10px;
+            height: 10px;
+            background: #3a3a3a;
+            display: inline-block;
+            margin-right: 10px;
+        }
+    </style>
+</head>
+
+<body>
+    <h1 style=""text-align:center;font-weight: bold;"">校本研修活动完成情况</h1>
+    <p class=""title"">教师信息</p>
+    <table class=""tch-table"" id=""tchTable"">
+        <tr>
+            <th style=""width: 33.3%;"">教师姓名</th>
+            <th style=""width: 33.3%;"">机构</th>
+            <th style=""width: 33.3%;"">教研组</th>
+        </tr>
+        <tr>
+            <td>{c.cname}</td>
+            <td>{c.sname}</td>
+            <td>{c.gname}</td>
+        </tr>
+    </table>
+    <p class=""title"">活动明细</p>
+    <table class=""tch-table"" id=""acTable"">
+        <tr>
+            <th style=""width: 25%;"">活动主题</th>
+            <th style=""width: 15%;"">活动类型</th>
+            <th style=""width: 5%;"">任务学时</th>
+            <th style=""width: 5%;"">完成学时</th>
+            <th style=""width: 20%;"">活动时间</th>
+            <th style=""width: 10%;"">活动内容</th>
+            <th style=""width: 10%;"">活动任务</th>
+            <th style=""width: 10%;"">状态</th>
+        </tr>
+        {c.details}
+    </table>
+</body>
+</html>
+";
     }
 }

+ 3 - 1
TEAMModelOS.SDK/DI/DingDing/DingDing.cs

@@ -111,7 +111,9 @@ namespace TEAMModelOS.SDK.DI
         [Description("1a316ce4edc2db88231d40d80072b00f2751d7d9e2e5871c5dc061885b01c48d,SECff60201ac9b219943b9f8fc397fda1a617d0cbc140850f5ea9cb4f131479d39a")]
         醍摩豆服務運維群組,
         [Description("a83ea4ead63bf1b4e087723b3a7ccdf7f4c96708a22493f489bb928999f50d87,SECf1d22db7d00580dc7c0e597e31112a25ae1025500fc998b5b30961d91e115271")]
-        AI智慧學校申請通知群
+        國際客戶聯繫通知群,
+        [Description("b1293e05c6aaeece746a2e46d69164a4373bab071bfafb5d7b5141f947e493cb,SEC658c7c70204f18976fa5e02f554d4fc72e9892c9bb82c5c6b98ecfd3c4eb0531")]
+        大陸客戶聯繫通知群
 
     }
 

+ 50 - 0
TEAMModelOS.SDK/Models/Cosmos/BI/AreaQuoteRecord.cs

@@ -0,0 +1,50 @@
+using Microsoft.Azure.Cosmos.Table;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Context.Attributes.Azure;
+
+namespace TEAMModelOS.SDK.Models.Cosmos.BI
+{
+    /// <summary>
+    /// 区域引用记录
+    /// </summary>
+    [TableName(Name = "IESLogin")]
+    public class AreaQuoteRecord : TableEntity
+    {
+        public string areaId { get; set; }
+        public string quoteId { get; set; }
+        public string quoteName { get; set; }
+        public string standard { get; set; }
+    }
+
+    public class AppGWRecHour : TableEntity 
+    {
+        /// <summary>
+        /// 小时      yyyyMMddHH
+        /// </summary>
+        public string hour { get; set; }
+        /// <summary>
+        /// 次数
+        /// </summary>
+        public int cnt { get; set; }
+        public string hostName { get; set; }
+    }
+
+    public class AppGWRecDay : TableEntity 
+    {
+        /// <summary>
+        /// 天      yyyyMMdd
+        /// </summary>
+        public int day { get; set; }
+        /// <summary>
+        /// 次数
+        /// </summary>
+        public int cnt { get; set; }
+        public string hostName { get; set; }
+    }
+
+
+}

+ 10 - 0
TEAMModelOS.SDK/Models/Cosmos/Common/Inner/CodeValue.cs

@@ -9,4 +9,14 @@ namespace TEAMModelOS.SDK.Models
         public string code { get; set; }
         public string value { get; set; }
     }
+    public class IdSchool
+    {
+        public string id { get; set; }
+        public string school { get; set; }
+    }
+    public class IdCode
+    {
+        public string id { get; set; }
+        public string code { get; set; }
+    }
 }

+ 6 - 1
TEAMModelOS.SDK/Models/Cosmos/Common/Notice.cs

@@ -10,7 +10,12 @@ namespace TEAMModelOS.SDK.Models
         public string title { get; set; }
         public string content { get; set; }
         public string school { get; set; }
-        
+        /// <summary>
+        /// 发布状态
+        /// </summary>
+        [Required(ErrorMessage = "scope 必须设置")]
+        public string scope { get; set; }
+
         [Required(ErrorMessage = "creatorId 必须设置")]
         public string creatorId { get; set; }
         /// <summary>

+ 31 - 11
TEAMModelOS.SDK/Models/Cosmos/Common/LessonStudentRecord.cs

@@ -7,30 +7,42 @@ using System.Threading.Tasks;
 namespace TEAMModelOS.SDK.Models.Cosmos.Common
 {
     /// <summary>
-    /// 课堂记录简要信息。
+    /// 学生计分记录
     /// </summary>
-    public class LessonStudentRecord : CosmosEntity
+    public class StudentScoreRecord : CosmosEntity
     {
-        // id 学生id、醍摩豆id  ,
-        //code  LessonStudentRecord[-学校编码],
+        // id  雪花id
+        //code  LessonStudentRecord[-学校编码],  LessonStudentRecord
         //其他基础信息
-        public LessonStudentRecord() {
-            pk = "LessonStudentRecord";
+        public StudentScoreRecord()
+        {
+            pk = "StudentScoreRecord";
         }
-        public List<StudentRecord> records { get; set; } = new List<StudentRecord>();
+        public string stuid { get; set; }
+        public string tmdid { get; set; }
+        public string userType { get; set; }
+        public string school { get; set; }
+        public int year { get; set; }
+
+        public List<StudentLessonRecord> lessonRecords { get; set; } = new List<StudentLessonRecord>();
+        //单独记录 组计分,个人积分,互动分,
+        public double gscore { get; set; } //组计分
+        public double pscore { get; set; }  //个人计分
+        public double tscore { get; set; } //互动计分
     }
 
 
-    public class StudentRecord {
+    public class StudentLessonRecord
+    {
         /// <summary>
         ///必填 教师醍摩豆id
         /// </summary>
         public string tmdid { get; set; }
-        public string tmdname { get; set; }
+        // public string tmdname { get; set; }
         /// <summary>
         ///必填 课堂名称
         /// </summary>
-        public string name { get; set; }
+        //public string name { get; set; }
         /// <summary>
         ///必填 scope==school必填 | string | 学校id
         /// </summary>
@@ -56,10 +68,18 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
         ///  不填 科目id,由课程id获取
         /// </summary>
         public string subjectId { get; set; }
-        public ClientSummaryList report { get; set; }
+        public double gscore { get; set; } //组计分
+        public double pscore { get; set; }  //个人计分
+        public double tscore { get; set; } //互动计分
+        /// <summary>
+        /// 课例时间
+        /// </summary>
+        public long time { get; set; }
         /// <summary>
         /// 名单信息
         /// </summary>
         //public List<string> groupIds { get; set; } = new List<string>();
     }
+
+
 }

+ 0 - 1
TEAMModelOS.SDK/Models/Cosmos/School/Course.cs

@@ -53,7 +53,6 @@ namespace TEAMModelOS.SDK.Models
         /// <summary>
         /// 创建者的id 
         /// </summary>
-        [Required(ErrorMessage = "creatorId 必须设置")]
         public string creatorId { get; set; }
         /// <summary>
         /// 学校编码或教师tmdid

+ 1 - 1
TEAMModelOS.SDK/Models/Cosmos/School/School.cs

@@ -88,7 +88,7 @@ namespace TEAMModelOS.SDK.Models
         /// <summary>
         /// 存多个区域
         /// </summary>
-        public List<ManyArea> manyAreas { get; set; }
+        public List<ManyArea> manyAreas { get; set; } = new List<ManyArea>();
     }
     /// <summary>
     /// 课表计划

+ 39 - 7
TEAMModelOS.SDK/Models/Cosmos/School/SchoolProduct.cs

@@ -109,25 +109,57 @@ namespace TEAMModelOS.SDK.Models
         public string model { get; set; }
     }
 
-    //服務各產品資訊購買紀錄(前端用)
+    //服務各產品資訊可用資訊(前端用)
     public class SchoolProductOrder
     {
         public string prodCode { get; set; }
-        public List<SchoolProductOrderList> order { get; set; }
         public int avaliable { get; set; }
         public long avaliableStartDate { get; set; }
         public long avaliableEndDate { get; set; }
     }
-    //產品購買紀錄(前端顯示用)
-    public class SchoolProductOrderList
+
+    //IES5 學校產品購買紀錄 前端顯示用
+    public class SchoolOrder
     {
         public string id { get; set; }
-        public long orderDate { get; set; }
-        public long startDate { get; set; }
-        public long endDate { get; set; }
+        public long date { get; set; }
+        public List<SchoolOrderSerial> serial { get; set; }
+        public List<SchoolOrderService> service { get; set; }
+        public List<SchoolOrderHard> hard { get; set; }
+    }
+    //IES5 學校產品購買紀錄 前端顯示用 序號部分
+    public class SchoolOrderSerial
+    {
+        public string prodCode { get; set; }
+        public string type { get; set; }
+        public string ymwd { get; set; }
+        public long sdate { get; set; }
+        public long edate { get; set; }
+        public int cqty { get; set; }
+        public int device { get; set; }
+        public object aprule { get; set; }
+        public List<string> sn { get; set; }
+    }
+    //IES5 學校產品購買紀錄 前端顯示用 服務部分
+    public class SchoolOrderService
+    {
+        public string prodCode { get; set; }
+        public string type { get; set; }
+        public long sdate { get; set; }
+        public long edate { get; set; }
         public int number { get; set; }
         public string unit { get; set; }
     }
+    //IES5 學校產品購買紀錄 前端顯示用 硬體部分
+    public class SchoolOrderHard
+    {
+        public string prodCode { get; set; }
+        public List<string> sn { get; set; }
+    }
+
+
+
+
 
     //序號資訊(含deviceId、classId、OS等硬體資訊)
     public class SerialInfoBaseWithdeviceBoundExt : SchoolProductSerial

+ 201 - 22
TEAMModelOS.SDK/Models/Service/LessonService.cs

@@ -4,6 +4,7 @@ using HTEXLib.COMM.Helpers;
 using Microsoft.Extensions.Configuration;
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
@@ -16,9 +17,181 @@ namespace TEAMModelOS.SDK.Models.Service
 {
     public class LessonService
     {
+        public static async void DoLessonStudentRecord(DingDing _dingding, SnowflakeId snowflakeId, LessonRecord lessonRecord, string scope, CosmosClient client, string school, string tmdid,
+            Teacher teacher, NotificationService _notificationService, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, IConfiguration _configuration, LessonBase lessonBase)
+        {
 
-        public static async void DoAutoDeleteSchoolLessonRecord(LessonRecord lessonRecord,string  scope ,CosmosClient client,string school,string tmdid,
-            Teacher teacher, NotificationService _notificationService, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, IConfiguration _configuration) {
+            try
+            {
+                int year = DateTimeOffset.UtcNow.Year;
+                var clientSummaryList = lessonBase.report.clientSummaryList.Where(x => x.groupTaskCompleteCount != 0 || x.groupScore != 0 || x.score != 0 || x.tnteractScore != 0 || x.taskCompleteCount != 0);
+                IEnumerable<LessonStudent> students = new List<LessonStudent>();
+                if (clientSummaryList.Any())
+                {
+                    students = lessonBase.student.Where(x => clientSummaryList.Select(x => x.seatID).Contains(x.seatID));
+                }
+                var stuids = students.Where(x => x.type == 2);
+                if (stuids.Any())
+                {
+                    stuids.ToList().ForEach(x => {
+                        x.school = string.IsNullOrWhiteSpace(x.school) ? school : x.school;
+                    });
+                }
+                var groups = stuids.Where(z => !string.IsNullOrWhiteSpace(z.school)).GroupBy(x => x.school).Select(y => new { code = y.Key, list = y.ToList() });
+                List<StudentScoreRecord> lessonStudentRecords = new List<StudentScoreRecord>();
+                foreach (var group in groups)
+                {
+
+                    string stusql = $"select value(c) from c where c.stuid in({string.Join(",", group.list.Select(x => $"'{x.id}'"))}) and  c.school='{group.code}' and c.year={year}";
+                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryIterator<StudentScoreRecord>(queryText: stusql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"StudentScoreRecord") }))
+                    {
+                        lessonStudentRecords.Add(item);
+                    }
+
+                }
+                var tmdids = students.Where(x => x.type == 1);
+                if (tmdids.Any())
+                {
+                    string tmdsql = $"select value(c) from c where c.tmdid in({string.Join(",", tmdids.Select(x => $"'{x}'"))})    and c.year={year}";
+                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, Constant.Student).GetItemQueryIterator<StudentScoreRecord>(queryText: tmdsql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"StudentScoreRecord") }))
+                    {
+                        lessonStudentRecords.Add(item);
+                    }
+                }
+                List<Task<ItemResponse<StudentScoreRecord>>> records = new List<Task<ItemResponse<StudentScoreRecord>>>();
+                stuids.ToList().ForEach(x => {
+                    var record = lessonStudentRecords.Find(l => l.id.Equals(x.id) && l.code.Equals($"StudentScoreRecord") && l.school.Equals(x.school));
+                    ClientSummaryList clientSummaryList = lessonBase.report.clientSummaryList.Find(c => c.seatID == x.seatID);
+                    if (record != null)
+                    {
+                        if (clientSummaryList != null)
+                        {
+                            //排查重复课例的数据。》》》》》》》》》》》》》》》》》》》》》
+                            record.lessonRecords.Add(
+                                 new StudentLessonRecord
+                                 {
+                                     gscore = clientSummaryList.groupScore,
+                                     pscore = clientSummaryList.score,
+                                     tscore = clientSummaryList.tnteractScore,
+                                     tmdid = teacher.id,
+                                     school = school,
+                                     scope = lessonRecord.scope,
+                                     lessonId = lessonRecord.id,
+                                     courseId = lessonRecord.courseId,
+                                     periodId = lessonRecord.periodId,
+                                     subjectId = lessonRecord.subjectId,
+                                     time = lessonRecord.startTime
+                                 }
+                             );
+                        }
+                    }
+                    else
+                    {
+                        //排查重复课例的数据。》》》》》》》》》》》》》》》》》》》》》
+                        record = new StudentScoreRecord
+                        {
+                            userType = Constant.ScopeStudent,
+                            id = $"{snowflakeId.NextId()}",
+                            year = year,
+                            stuid = x.id,
+                            school = x.school,
+                            code = $"StudentScoreRecord",
+                            pk = "StudentScoreRecord",
+                            ttl = -1,
+                            lessonRecords = new List<StudentLessonRecord> {  new StudentLessonRecord
+                             {
+                                 gscore = clientSummaryList.groupScore,
+                                 pscore = clientSummaryList.score,
+                                 tscore = clientSummaryList.tnteractScore,
+                                 tmdid = teacher.id,
+                                 school = school,
+                                 scope = lessonRecord.scope,
+                                 lessonId = lessonRecord.id,
+                                 courseId = lessonRecord.courseId,
+                                 periodId = lessonRecord.periodId,
+                                 subjectId = lessonRecord.subjectId,
+                             }}
+                        };
+
+                    }
+                    record.userType = Constant.ScopeStudent;
+                    record.gscore = record.lessonRecords.Select(x => x.gscore).Sum();
+                    record.pscore = record.lessonRecords.Select(x => x.pscore).Sum();
+                    record.tscore = record.lessonRecords.Select(x => x.tscore).Sum();
+                    records.Add(client.GetContainer(Constant.TEAMModelOS, Constant.Student).UpsertItemAsync(record, partitionKey: new PartitionKey(record.code)));
+                });
+                tmdids.ToList().ForEach(x => {
+                    var record = lessonStudentRecords.Find(l => l.id.Equals(x.id) && l.code.Equals($"StudentScoreRecord"));
+                    ClientSummaryList clientSummaryList = lessonBase.report.clientSummaryList.Find(c => c.seatID == x.seatID);
+                    if (record != null)
+                    {
+                        if (clientSummaryList != null)
+                        {  //排查重复课例的数据。》》》》》》》》》》》》》》》》》》》》》
+                            record.lessonRecords.Add(
+                            new StudentLessonRecord
+                            {
+                                gscore = clientSummaryList.groupScore,
+                                pscore = clientSummaryList.score,
+                                tscore = clientSummaryList.tnteractScore,
+                                tmdid = teacher.id,
+                                school = school,
+                                scope = lessonRecord.scope,
+                                lessonId = lessonRecord.id,
+                                courseId = lessonRecord.courseId,
+                                periodId = lessonRecord.periodId,
+                                subjectId = lessonRecord.subjectId,
+                            });
+                        }
+                    }
+                    else
+                    {  //排查重复课例的数据。》》》》》》》》》》》》》》》》》》》》》
+                        record = new StudentScoreRecord
+                        {
+                            userType = Constant.ScopeTmdUser,
+                            id = $"{snowflakeId.NextId()}",
+                            code = $"StudentScoreRecord",
+                            pk = "StudentScoreRecord",
+                            ttl = -1,
+                            year = year,
+                            tmdid = x.id,
+                            lessonRecords = new List<StudentLessonRecord> 
+                            {
+                                new StudentLessonRecord
+                                {
+                                 gscore = clientSummaryList.groupScore,
+                                 pscore = clientSummaryList.score,
+                                 tscore = clientSummaryList.tnteractScore,
+                                 tmdid = teacher.id,
+                                 school = school,
+                                 scope = lessonRecord.scope,
+                                 lessonId = lessonRecord.id,
+                                 courseId = lessonRecord.courseId,
+                                 periodId = lessonRecord.periodId,
+                                 subjectId = lessonRecord.subjectId,
+                                }
+                            }
+                        };
+                    }
+                    record.userType = Constant.ScopeStudent;
+                    record.gscore = record.lessonRecords.Select(x => x.gscore).Sum();
+                    record.pscore = record.lessonRecords.Select(x => x.pscore).Sum();
+                    record.tscore = record.lessonRecords.Select(x => x.tscore).Sum();
+                    records.Add(client.GetContainer(Constant.TEAMModelOS, Constant.Student).UpsertItemAsync(record, partitionKey: new PartitionKey(record.code)));
+                });
+                if (records.Any())
+                {
+                    await Task.WhenAll(records);
+                }
+            }
+            catch (Exception ex)
+            {
+                await _dingding.SendBotMsg($"学生个人课例统计信息异常,{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
+            }
+        }
+
+        public static async void DoAutoDeleteSchoolLessonRecord(LessonRecord lessonRecord, string scope, CosmosClient client, string school, string tmdid,
+            Teacher teacher, NotificationService _notificationService, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, IConfiguration _configuration)
+        {
             if (lessonRecord.scope.Equals("school"))
             {
                 SchoolSetting setting = null;
@@ -44,7 +217,7 @@ namespace TEAMModelOS.SDK.Models.Service
                 {
                     setting = new SchoolSetting() { lessonSetting = new LessonSetting { openAutoClean = 0, expireDays = Constant.school_lesson_expire } };
                 }
-                int school_lesson_expire =0;
+                int school_lesson_expire = 0;
                 bool save = true;
                 List<string> msg = new List<string>();
                 if (setting.lessonSetting.openAutoClean == 1)
@@ -53,7 +226,8 @@ namespace TEAMModelOS.SDK.Models.Service
                     {
                         save = false;
                     }
-                    else {
+                    else
+                    {
                         school_lesson_expire = setting.lessonSetting.expireDays;
                         foreach (var item in setting.lessonSetting.conds)
                         {
@@ -227,7 +401,8 @@ namespace TEAMModelOS.SDK.Models.Service
                         }
                     }
                 }
-                else {
+                else
+                {
                     save = false;
                     school_lesson_expire = Constant.school_lesson_expire;
                 }
@@ -242,14 +417,14 @@ namespace TEAMModelOS.SDK.Models.Service
                     //result.Add(3, day3);
                     //剩余1天的通知
                     var day1 = now.AddDays(school_lesson_expire - (school_lesson_expire - 1)).ToUnixTimeMilliseconds();
-                    result.Add(1, new ExpireTag { expire= day1 ,tag= "notification" });
+                    result.Add(1, new ExpireTag { expire = day1, tag = "notification" });
                     //到期通知
                     //不到五点上传的课例,七天之后直接删除。
                     int addSecond = 0;
                     if (now.Hour > 5)
                     {
                         // 到凌晨00点还差 (24 - now.Hour) *60 * 60 分钟,再加天数;
-                        addSecond = school_lesson_expire * 86400 + (24 - now.Hour) * 3600-(now.Hour* 3600);
+                        addSecond = school_lesson_expire * 86400 + (24 - now.Hour) * 3600 - (now.Hour * 3600);
                         //再加 00到05小时内的 随机秒数
                         Random rand = new Random();
                         int randInt = rand.Next(0, 18000);
@@ -261,13 +436,13 @@ namespace TEAMModelOS.SDK.Models.Service
                     }
                     lessonRecord.expire = now.AddSeconds(addSecond).ToUnixTimeMilliseconds();
                     result.Add(school_lesson_expire, new ExpireTag { expire = lessonRecord.expire, tag = "delete" });
-                   // result.Add(school_lesson_expire, lessonRecord.expire);
+                    // result.Add(school_lesson_expire, lessonRecord.expire);
                     string biz = "expire";
                     Notification notification = new Notification
                     {
                         hubName = "hita",
                         type = "msg",
-                        from = $"ies5:{ Environment.GetEnvironmentVariable("Option:Location")}:private",
+                        from = $"ies5:{Environment.GetEnvironmentVariable("Option:Location")}:private",
                         to = new List<string> { tmdid },
                         label = $"{biz}_lessonRecord",
                         body = new
@@ -332,7 +507,8 @@ namespace TEAMModelOS.SDK.Models.Service
                         }
                     }
                 }
-                else {
+                else
+                {
 
                     if (lessonRecord.expire > 0)
                     {
@@ -358,7 +534,8 @@ namespace TEAMModelOS.SDK.Models.Service
         }
 
 
-        public record ExpireTag {
+        public record ExpireTag
+        {
             public long expire { get; set; }
             public string tag { get; set; }
         }
@@ -377,9 +554,10 @@ namespace TEAMModelOS.SDK.Models.Service
                 lessonDis.record = 1;
             }
             //删除数据的情况
-            /*else if (oldRecord != null && newRecord == null)
+            //不再对LessonCount进行减
+            else if (oldRecord != null && newRecord == null)
             {
-                lessonDis.record = -1;
+                /*lessonDis.record = -1;
                 //P分数量加减
                 if (oldRecord.pScore >= 70)
                 {
@@ -393,8 +571,8 @@ namespace TEAMModelOS.SDK.Models.Service
                 if (oldRecord.tScore >= 70 && oldRecord.pScore >= 70)
                 {
                     lessonDis.disTCount = -1;
-                }
-            }*/
+                }*/
+            }
             //无效操作
             else if (oldRecord == null && newRecord == null)
             {
@@ -519,11 +697,12 @@ namespace TEAMModelOS.SDK.Models.Service
                         code = $"LessonCount-{data.school}-{year}";
                         tbname = "School";
                     }
-                    else {
+                    else
+                    {
                         code = $"LessonCount-{data.school}-{year}-{data.periodId}";
                         tbname = "School";
                     }
-                    
+
                 }
                 else
                 {
@@ -545,17 +724,17 @@ namespace TEAMModelOS.SDK.Models.Service
                 }
                 else
                 {
-                    LessonCount count = new LessonCount
+                    LessonCount count = new()
                     {
                         id = data.tmdid,
                         code = code,
                         ttl = -1
                     };
                     double[] da = new double[days];
-                    List<double> list = new (da);
-                    List<double> listT = new (da);
-                    List<double> listP = new (da);
-                    List<double> listPT = new (da);
+                    List<double> list = new(da);
+                    List<double> listT = new(da);
+                    List<double> listP = new(da);
+                    List<double> listPT = new(da);
 
                     list[day - 1] += lessonDis.record;
                     listT[day - 1] += lessonDis.disTCount;

+ 101 - 14
TEAMModelOS.SDK/Models/Service/StatisticsService.cs

@@ -158,28 +158,115 @@ namespace TEAMModelOS.SDK
                 school = await client.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>(schoolId, new PartitionKey("Base"));
             }
         }
-        public static async Task<List<(List<TeacherTrain> trains, List<RGroupList> yxtrain)>> StatisticsArea(CoreAPIHttpService _coreAPIHttpService, AreaSetting setting, Area area, CosmosClient client, DingDing _dingDing, HashSet<string> updates)
+        //public static async Task<List<(List<TeacherTrain> trains, List<RGroupList> yxtrain)>> StatisticsArea(CoreAPIHttpService _coreAPIHttpService, AreaSetting setting, Area area, CosmosClient client, DingDing _dingDing, HashSet<string> updates)
+        //{
+        //    List<(List<TeacherTrain> trains, List<RGroupList> yxtrain)> teacherTrains = new List<(List<TeacherTrain> trains, List<RGroupList> yxtrain)>();
+        //    List<School> schools = new List<School>();
+        //    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School")
+        //    .GetItemQueryIterator<School>(queryText: $"select value(c) from c where c.areaId='{area.id}'  ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base") }))
+        //    {
+        //        schools.Add(item);
+        //    }
+        //    await foreach ((List<TeacherTrain> trains, List<RGroupList> yxtrain) tarain in GetStatisticsSchool(_coreAPIHttpService, schools, setting, area, client, _dingDing, updates))
+        //    {
+        //        teacherTrains.Add(tarain);
+        //    }
+        //    return teacherTrains;
+        //}
+        //private static async IAsyncEnumerable<(List<TeacherTrain> trains, List<RGroupList> yxtrain)> GetStatisticsSchool(CoreAPIHttpService _coreAPIHttpService,List<School> schools, AreaSetting setting, Area area, CosmosClient client, DingDing _dingDing, HashSet<string> updates)
+        //{
+        //    foreach (var school in schools)
+        //    {
+        //        yield return await StatisticsSchool(  _coreAPIHttpService,school.id, setting, area, client, _dingDing, updates);
+        //    }
+        //}
+
+        public static async Task<(List<TeacherTrain> trains, List<RGroupList> yxtrain)> StatisticsSchoolQuik(CoreAPIHttpService _coreAPIHttpService, string school, AreaSetting setting, Area area, CosmosClient client, DingDing _dingDing, HashSet<string> updates)
         {
-            List<(List<TeacherTrain> trains, List<RGroupList> yxtrain)> teacherTrains = new List<(List<TeacherTrain> trains, List<RGroupList> yxtrain)>();
-            List<School> schools = new List<School>();
-            await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School")
-            .GetItemQueryIterator<School>(queryText: $"select value(c) from c where c.areaId='{area.id}'  ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base") }))
+            _coreAPIHttpService.check = false;
+
+            List<RGroupList> yxtrain = await GroupListService.GetGroupListMemberByType(_coreAPIHttpService, client, "yxtrain", new List<string> { "school" }, $"{school}", _dingDing);
+            List<TeacherTrain> trains = new List<TeacherTrain>();
+            var members = yxtrain.SelectMany(x => x.members).ToList();
+            if (members.Count <= 0)
             {
-                schools.Add(item);
+                return (trains, yxtrain);
             }
-            await foreach ((List<TeacherTrain> trains, List<RGroupList> yxtrain) tarain in GetStatisticsSchool(_coreAPIHttpService, schools, setting, area, client, _dingDing, updates))
+
+            await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher")
+             .GetItemQueryIterator<TeacherTrain>(queryText: $"select value(c) from c  ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"TeacherTrain-{school}") }))
             {
-                teacherTrains.Add(tarain);
+
+                trains.Add(item);
             }
-            return teacherTrains;
-        }
-        private static async IAsyncEnumerable<(List<TeacherTrain> trains, List<RGroupList> yxtrain)> GetStatisticsSchool(CoreAPIHttpService _coreAPIHttpService,List<School> schools, AreaSetting setting, Area area, CosmosClient client, DingDing _dingDing, HashSet<string> updates)
-        {
-            foreach (var school in schools)
+            if (updates != null)
+            {
+                foreach (var up in updates)
+                {
+                    trains.ForEach(x => x.update.Add(up));
+                }
+            }
+            var update = trains.FindAll(x => x.update.Count() > 0);
+            var noupdate = trains.FindAll(x => x.update.Count() <= 0);
+
+            var unStatistics = members.Select(x => x.id).Except(trains.Select(x => x.id));
+            List<TeacherTrain> teacherTrains = new List<TeacherTrain>();
+            List<TeacherTrain> returnTrains = new List<TeacherTrain>();
+            if (update.IsNotEmpty())
+            {
+                teacherTrains.AddRange(update);
+            }
+            if (unStatistics != null)
+            {
+                foreach (string x in unStatistics)
+                {
+                    var member = members.Find(y => y.id.Equals(x));
+                    teacherTrains.Add(new TeacherTrain
+                    {
+                        pk = "TeacherTrain",
+                        id = x,
+                        code = $"TeacherTrain-{school}",
+                        tmdid = x,
+                        nickname = member.nickname,
+                        name = member.name,
+                        picture = member.picture,
+                        school = school,
+                        update = new HashSet<string> { TeacherAbility, TeacherClass, OfflineRecord }
+                    });
+                }
+            }
+            List<Study> studies = new List<Study>();
+            await foreach (var item in client.GetContainer("TEAMModelOS", "Common")
+               .GetItemQueryIterator<Study>(queryText: $"select value(c) from c    where c.owner<>'area'  ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Study-{school}") }))
             {
-                yield return await StatisticsSchool(  _coreAPIHttpService,school.id, setting, area, client, _dingDing, updates);
+                studies.Add(item);
             }
+            returnTrains = teacherTrains;// await GetStatisticsTeacher(teacherTrains, setting, area, client, studies);
+            //await foreach (var tarain in GetStatisticsTeacher(teacherTrains, setting, area, client))
+            //{
+            //    returnTrains.Add(tarain);
+            //}
+            if (noupdate.IsNotEmpty())
+            {
+                returnTrains.AddRange(noupdate);
+            }
+            //移除不在研修名单的人员
+            returnTrains.RemoveAll(x => !members.Select(y => y.id).Contains(x.id));
+            returnTrains.ForEach(x => {
+                var mbm = members.Find(y => y.id.Equals(x.id));
+                if (mbm != null)
+                {
+                    x.groupName = mbm?.groupName;
+                    x.name = !string.IsNullOrWhiteSpace(x.name) ? x.name : mbm?.name;
+                    x.nickname = mbm?.nickname;
+                    x.picture = mbm?.picture;
+
+                }
+            });
+
+            return (returnTrains, yxtrain);
         }
+
         public static async Task<(List<TeacherTrain> trains, List<RGroupList> yxtrain)> StatisticsSchool(CoreAPIHttpService _coreAPIHttpService,string school, AreaSetting setting, Area area, CosmosClient client, DingDing _dingDing, HashSet<string> updates)
         {
             _coreAPIHttpService.check = false;

+ 90 - 0
TEAMModelOS.SDK/Models/Service/StudyService.cs

@@ -0,0 +1,90 @@
+using DinkToPdf;
+using DinkToPdf.Contracts;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.DI;
+
+namespace TEAMModelOS.SDK.Models.Service
+{
+    public static class StudyService
+    {
+        public static async Task GenPdf(string tId,string areaId, string cname,string sname,string gname,string details, IConverter _converter, AzureStorageFactory _azureStorage,DingDing _dingDing)
+        {
+            //https://article.itxueyuan.com/JAxOnG
+            //http://t.zoukankan.com/hsiang-p-14608694.html
+            //https://github.com/rdvojmoc/DinkToPdf
+            //https://blog.csdn.net/u011966339/article/details/114964016?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-114964016-blog-118609642.pc_relevant_antiscanv2&spm=1001.2101.3001.4242.1&utm_relevant_index=3
+
+            try
+            {
+                var html = Constant.html.Replace("{c.cname}", cname).Replace("{c.sname}", sname).Replace("{c.gname}", gname).Replace("{c.details}", details);
+                List<Task<string>> tasks = new List<Task<string>>();
+                GlobalSettings globalSettings = new GlobalSettings();
+                globalSettings.ColorMode = ColorMode.Color;
+                globalSettings.Orientation = Orientation.Portrait;
+                globalSettings.PaperSize = PaperKind.A4;
+                globalSettings.Margins = new MarginSettings { Top = 25, Bottom = 25 };
+                //globalSettings.Out = @"E:\pdf\test.pdf";
+                ObjectSettings objectSettings = new ObjectSettings();
+                objectSettings.PagesCount = true;
+                objectSettings.HtmlContent = html;
+                WebSettings webSettings = new WebSettings();
+                webSettings.DefaultEncoding = "utf-8";
+                HeaderSettings headerSettings = new HeaderSettings();
+                headerSettings.FontSize = 15;
+                headerSettings.FontName = "fangsong";
+                headerSettings.Right = "";
+                // headerSettings.Line = true;
+                FooterSettings footerSettings = new FooterSettings();
+                footerSettings.FontSize = 12;
+                footerSettings.FontName = "Ariel";
+                footerSettings.Center = "校本研修活动完成情况([page]/[toPage])页";
+                // footerSettings.Line = true;
+                objectSettings.HeaderSettings = headerSettings;
+                objectSettings.FooterSettings = footerSettings;
+                objectSettings.WebSettings = webSettings;
+                HtmlToPdfDocument htmlToPdfDocument = new HtmlToPdfDocument()
+                {
+                    GlobalSettings = globalSettings,
+                    Objects = { objectSettings },
+                };
+                var a = _converter.Convert(htmlToPdfDocument);
+                MemoryStream m = new(a);
+                tasks.Add(_azureStorage.UploadFileByContainer("teammodelos", m, $"{areaId}", $"{tId}/offline-report.pdf", false));
+                await Task.WhenAll(tasks);
+                /* FileStream fs = new FileStream("F:\\1111111111111\\SimplePdf1.pdf", FileMode.Create, FileAccess.Write, FileShare.Read);
+                 m.WriteTo(fs);
+                 m.Close();
+                 fs.Close();*/
+
+                //var doc = new HtmlToPdfDocument()
+                //{
+                //    GlobalSettings = {
+                //        ColorMode = ColorMode.Color,
+                //        Orientation = Orientation.Portrait,
+                //        PaperSize = PaperKind.A4,
+                //        Margins = new MarginSettings() { Top = 10 },
+                //        Out = @"F:\test.pdf",
+                //    },
+                //    Objects = {
+                //        new ObjectSettings()
+                //        {
+                //            Page = "https://zhidao.baidu.com/question/175696719173618004.html",
+                //        },
+                //    }
+                //};
+                //_converter.Convert(doc);
+                // return Ok(File(a, "application/octet-stream", "SimplePdf.pdf"));
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-StudyService,GenPdf()\n{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+            }
+        }
+    }
+}

+ 8 - 0
TEAMModelOS.SDK/Models/Service/Third/Xkw/XkwOAuthModel.cs

@@ -113,5 +113,13 @@ namespace TEAMModelOS.SDK.Models
         /// 0 不同意 ,1 同意
         /// </summary>
         public int agree { get; set; }
+
+
+        /// <summary>
+        /// 题目推送通知用
+        /// openid,paperid
+        /// </summary>
+        public string openid { get; set; }
+        public string paperid { get; set; }
     }
 }

+ 1 - 0
TEAMModelOS.SDK/TEAMModelOS.SDK.csproj

@@ -20,6 +20,7 @@
     <PackageReference Include="Azure.Storage.Blobs.Batch" Version="12.8.0" />
     <PackageReference Include="Azure.Storage.Queues" Version="12.9.0" />
     <PackageReference Include="ClouDASLibx" Version="1.2.7" />
+    <PackageReference Include="DinkToPdf" Version="1.0.8" />
     <PackageReference Include="DocumentFormat.OpenXml" Version="2.15.0" />
     <PackageReference Include="HTEXLib" Version="5.2203.232" />
     <PackageReference Include="HtmlAgilityPack" Version="1.11.42" />

File diff ditekan karena terlalu besar
+ 134 - 67
TEAMModelOS/ClientApp/public/lang/en-US.js


File diff ditekan karena terlalu besar
+ 79 - 12
TEAMModelOS/ClientApp/public/lang/zh-CN.js


+ 108 - 41
TEAMModelOS/ClientApp/public/lang/zh-TW.js

@@ -386,8 +386,8 @@ const LANG_ZH_TW = {
         noDeviceName:'暫無設備名稱',
         noClassId:'未關聯教室',
         rltClass:'關聯教室',
-        unrltClass:'解教室',
-        unbdDeviceCont:'確認解綁當前設備嗎?',
+        unrltClass:'解除關聯教室',
+        unbdDeviceCont:'確認解綁當前序號嗎?',
         unbdOk:'解綁成功',
         unbdErr1:'設備ID錯誤',
         unbdErr2:'序號異常',
@@ -395,16 +395,15 @@ const LANG_ZH_TW = {
         deviceName:'設備名稱:',   
         bandOk:'綁定成功',
         bandErr:'綁定失敗',
-        unbdRoomCt:'確定解當前教室嗎?',
-        unbdOk:'解成功',
-        unbdErr:'解失敗',
+        unbdRoomCt:'確定解除關聯當前教室嗎?',
+        unbdOk:'解成功',
+        unbdErr:'解失敗',
         pdName1:'學情分析模組',
         pdName2:'智慧學校管理服務',
         pdName3:'卷卡合一閱卷系統',
         pdName4:'AClass ONE智慧學伴',
         pdName5:'數據儲存服務空間',
         pdName6:'Haboard醍摩豆智慧大屏',
-        serialLabel:'序號:',
         authDate:'有效期:',
         fuAuth:'功能權限:',
         IRSnumber:'IRS連線數:',
@@ -418,6 +417,34 @@ const LANG_ZH_TW = {
         text2:'設備:',
         text3:'服務類型:',
         text4:'智慧教室:',
+        contactUs:'聯繫我們',
+        learnMore:'了解更多',
+        ctFrom:'聯繫表單',
+        ctFmText1:'您好,歡迎留下聯絡事項,我們將盡快與您聯繫。',
+        ctFmText2:'姓名',
+        ctFmText3:'請輸入姓名',
+        ctFmText4:'性別',
+        ctFmText5:'先生',
+        ctFmText6:'女士',
+        ctFmText7:'聯繫電話',
+        ctFmText8:'請輸入聯繫電話',
+        ctFmText9:'請選擇性別',
+        ctFmText10:'請輸入聯繫電話',
+        ctFmText11:'請完善表單信息',
+        ctFmText12:'所在地區',
+        ctFmText13:'諮詢內容',
+        ctFmText14:'服務存儲空間',
+        ctFmText15:'其他',
+        ctFmText16:'請選擇所在地區',
+        ctFmText17:'請選擇諮詢內容',
+        ctFmText18:'保存成功,稍後我們將與您聯繫',
+        ctFmText19:'保存失敗',
+        ctFmText20:'智慧教室',
+        ctFmText21:'研修中心',
+        ctFmText22:'顧問服務',
+        ctFmText23:'電子郵箱',
+        ctFmText24:'請輸入郵箱',
+        ctFmText25:'郵箱格式不正確',
     },
     // 班级管理
     classMgmt: {
@@ -542,11 +569,12 @@ const LANG_ZH_TW = {
         cancel: '取消',
         ok: '確定',
         generatOk: '生成成功',
-        generateErr: '生成失敗'
+        generateErr: '生成失敗',
+        noStuTip:'未獲取到學生數據',
     },
     // 课堂管理
     courseManage: {
-        addCourse: '新課程',
+        addCourse: '新課程',
         courseList: '課程清單',
         courseList1: '我的課程',
         courseShow: '顯示所有課程',
@@ -626,7 +654,7 @@ const LANG_ZH_TW = {
             studentTableC10: '編制班',
             studentTableC11: '暱稱',
             addClassroomProp1: '選擇預設教室',
-            addClassroomProp2: '新個人教室',
+            addClassroomProp2: '新個人教室',
             chooseClassroom: '請選擇系統的教室',
             searchHolder1: '關鍵字搜尋',
             classroomTableC1: '教室編碼',
@@ -825,6 +853,23 @@ const LANG_ZH_TW = {
         evRcd: '歷次評量',
         gradeErr: '查詢成績數據失敗',
         rcdExpired:'到期',
+        cusTab1:'評量活動',
+        cusTab2:'作業活動',
+        cusTab3:'投票活動',
+        cusTab4:'問卷活動',
+        cusTab5:'班級公告',
+        cusTab6:'評量列表',
+        cusTab7:'成績統計',
+        cusTab8:'新增評量',
+        cusTab9:'新增作業',
+        cusTab10:'新增公告',
+        cusTab11:'新增問卷',
+        cusTab12:'新增投票',
+        fullTips1:'學校空間已滿,無法建立學校課程評量',
+        fullTips2: '個人空間已滿,無法建立個人課程評量',
+        createPrivCus: '新建個人課程',
+        cusSource: '來源:',
+        moreFn: '更多功能',
 
         //ManageClass.vue
         stuMgt: '學生管理',
@@ -892,7 +937,7 @@ const LANG_ZH_TW = {
         saveLabel: '儲存變更',
         addStuList: '新增名單',
         addListType: '方式',
-        addListType1: '新名單',
+        addListType1: '新名單',
         addListType2: '選擇已有名單',
         listLabel: '名單',
         removeList: '移除名單',
@@ -911,7 +956,7 @@ const LANG_ZH_TW = {
         listType2: '選課班',
         confirmAdd: '確認新增',
         cancelAdd: '取消新增',
-        createList: '新選課班',
+        createList: '新選課班',
         name: '名稱:',
         nameHolder: '請輸入名單名稱…',
         nameRepeat: '選課班名稱重複',
@@ -988,6 +1033,7 @@ const LANG_ZH_TW = {
         cusTable: '課程表',
         importLabel: '匯入課表',
         cusMode: '課程模式',
+        rtnRoom:'返回教室管理',
         roomLabel: '教室',
         roomType: '教室類型:',
 
@@ -1074,6 +1120,17 @@ const LANG_ZH_TW = {
             optionCount: '選項分佈',
             tPushLabel: '推送:',
             noAns: '未答',
+            filter1:'所有',
+            filter2:'推送',
+            filter3:'任務',
+            filter4:'互動',
+            filter5:'測驗',
+            evt1:'搶權:',
+            evt2:'課中評量數據',
+            evt3:'挑人:',
+            evt4:'收集:',
+            evt5:'收集的作品',
+            oldHT:'舊版HiTeach上傳數據,無法查看課中評測數據'
         }
     },
     // ElementUI相关
@@ -1159,7 +1216,7 @@ const LANG_ZH_TW = {
         index: {
             item: '試題',
             paper: '試卷',
-            addExercise: '新試題',
+            addExercise: '新試題',
             openAll: '全部展開',
             collapseAll: '全部折疊',
             autoCreate: '智慧組卷',
@@ -1230,8 +1287,8 @@ const LANG_ZH_TW = {
         cancelSuc: '取消成功',
         addSuc: '新增成功',
         newExercise: {
-            newSchoolItem: '新學校題目',
-            newPrivateItem: '新個人題目',
+            newSchoolItem: '新學校題目',
+            newPrivateItem: '新個人題目',
             backToBank: '返回題庫',
             choosePeriod: '選擇學制',
             chooseGrade: '選擇年級',
@@ -2091,7 +2148,7 @@ const LANG_ZH_TW = {
         delete: '刪除',
         moveBlock: '移出知識塊',
         searchBlock: '搜尋知識塊',
-        buildBlock: '新知識塊',
+        buildBlock: '新知識塊',
         pointCount: '知識點數:',
         editBlock: '編輯知識塊',
         addPoint: '新增知識點',
@@ -2130,7 +2187,7 @@ const LANG_ZH_TW = {
     },
     // 活动模块
     learnActivity: {
-        noStartTimeTip:'不設定則默認為立即發布',
+        noStartTimeTip:'不設定則預設為立即發布',
         school: '校',
         area: '區',
         noFound: '未查詢到當前活動!',
@@ -2143,7 +2200,7 @@ const LANG_ZH_TW = {
             listLabel1: '學校評量',
             listLabel2: '個人評量',
             period: '學制:',
-            create: '新',
+            create: '新',
             delete: '刪除',
             edit: '編輯',
             createTime: '施測時間:',
@@ -2208,6 +2265,9 @@ const LANG_ZH_TW = {
             shareText6: '快速登入醍摩豆雲平臺IES學習主頁,進行線上活動',
             shareText7: '或登入醍摩豆雲平臺IES學習主頁,進行線上活動:',
             shareText8: '掃碼進行線上活動',
+            shareText9: '複製評量通知',
+            shareText10: '複製評量網址',
+            shareText11: '為了更好的用戶體驗,請在PC端完成線上活動!',
             copy: '複製',
             copyTitle: '複製評量',
             copyContent: '確認複製當前評量嗎?',
@@ -2640,11 +2700,12 @@ const LANG_ZH_TW = {
     },
     // 课堂记录
     lessonRecord: {
+        noRecordTip:'當前學段下未查詢到課堂記錄!',
         overdue:'到期',
         customSetting:'自定義設定',
         open:'開啟',
         close:'關閉',
-        condTip:'默認課堂記錄僅保留7天(被收藏的課例除外),若開啟自定義設定,則需要您設定保留期限以及保留條件',
+        condTip:'預設課堂記錄僅保留7天(被收藏的課例除外),若開啟自定義設定,則需要您設定保留期限以及保留條件',
         daySetting:'課例保留時間設定',
         expire:'課堂記錄保留期限',
         day:'天',
@@ -3057,8 +3118,8 @@ const LANG_ZH_TW = {
     // 教研中心
     researchCenter: {
         dashboard: {
-            title: '智慧校園大數據',
-            block1: '教學教研',
+            title: '校園大數據',
+            block1: '教學大數據',
             block2: '教室物聯',
             block3: '多元評量',
             block4: '學情分析',
@@ -3155,9 +3216,9 @@ const LANG_ZH_TW = {
         noExam: '暫無考試類型',
         eugenicsLabel: '優生率:',
         eugenicsTips: '優生率計算方式',
-        incomeLable: '進線生:',
-        incomeTips: '根據學校參考人數,設定考試前百分之多少人數為進線,取該範圍內最後一名分數作為進線分數,計算進線生',
-        touchLabel: '踩線生:',
+        incomeLable: '達標率:',
+        incomeTips: '根據學校參考人數,設定考試前百分之多少人數為達標值,取該範圍內最後一名分數作為達標分數,計算達標率',
+        touchLabel: '臨界值:',
         touchTips: '學校預設一個分值,學生成績在合格分數以上且不超過該分值範圍',
         scoreUnit: '分',
         delExamTitle: '刪除考試類型',
@@ -3199,7 +3260,7 @@ const LANG_ZH_TW = {
         periodCountTips: '學段數量根據學校購買進行分配,學校只能進行編輯學段名稱,不能新增和刪除學段。',
         cgCountTips: '學院數量根據學校購買進行分配,學校只能進行編輯學院名稱,不能新增和刪除學院。',
         addSubjectType: '新增方式',
-        addSubjectType1: '新學科',
+        addSubjectType1: '新學科',
         addSubjectType2: '選擇已有學科',
         addSubjectType3: '已有學科',
         addSubjectType4: '請選擇需要新增的學科',
@@ -3284,7 +3345,7 @@ const LANG_ZH_TW = {
         classAttr2: '專科教室(無固定學生)',
         nameWarning: '請輸入名稱',
         typeWarning: '請設定教室内容',
-        gradeWarning: '請設定級',
+        gradeWarning: '請設定級',
         expireLabel1: '已到期',
         expireLabel2: '無限期',
         findStuErr: '査詢班級學生名單失敗!',
@@ -3342,7 +3403,7 @@ const LANG_ZH_TW = {
         updOk: '修改成功',
         updErr: '修改失敗',
         warmTips: '溫馨提示',
-        mobileTips:'為了您更好的使用體驗,建議使用PC端訪問IES5雲平臺!',
+        mobileTips:'為了您更好的使用體驗,建議使用PC端訪問IES 5雲平臺!',
         noStuContent: '學校暫未匯入學生帳號,是否前往學生管理匯入學生帳號?',
         setIrsWarning: '請設定IRS編號',
         setNoWarning: '請設定座號'
@@ -4294,6 +4355,7 @@ const LANG_ZH_TW = {
                 noExam: "暫沒有試題",
                 wrongSub: "錯題",
                 rightSub: "正確題目",
+                shwoPage: "答題卡",
             },
             report: {
                 anwser: '前往作答',
@@ -4379,7 +4441,7 @@ const LANG_ZH_TW = {
                 cIndex: '班級排名',
                 gIndex: '年級排名',
                 aIndex: '區級排名',
-                onLine: '是否進線',
+                onLine: '是否達標',
                 subject: '學科',
                 score: '個人得分',
                 cAverage: '班級平均',
@@ -4536,8 +4598,10 @@ const LANG_ZH_TW = {
             noRoom: "暫無教室",
             courseCode: "課程QR Code",
             me: "我",
+            myWorks: "我的作品",
             notes: "電子筆記",
             mynotes: "我的筆記",
+            noExam: "暫無評量數據",
             type: {
                 all: "全部",
                 msg: "飛訊",
@@ -4744,7 +4808,7 @@ const LANG_ZH_TW = {
         chooseTargetTip:'請先選擇分享對象!',
         chooseShareChapterTip:'請先選擇需要分享的章節!',
         chooseChapterTip: '請先選擇需要收藏的章節!',
-        chooseVolumeTip: '請選擇已新增的冊別或者新冊別!',
+        chooseVolumeTip: '請選擇已新增的冊別或者新冊別!',
         playFailTip: '資源暫不支援播放',
         shareSyllabus: '分享個人課綱',
         chooseShareChapter: '選擇分享的章節',
@@ -4755,7 +4819,7 @@ const LANG_ZH_TW = {
         allSave: '整冊收藏',
         chooseSavePos: '選擇收藏位置',
         pos1: '已有冊別',
-        pos2: '新冊別',
+        pos2: '新冊別',
         chooseVolume: '選擇一個冊別',
         newVolume: '輸入新冊別名稱',
         confirmSave: '確認收藏',
@@ -4779,7 +4843,7 @@ const LANG_ZH_TW = {
         search: '搜尋',
         edit: '編輯',
         delete: '刪除',
-        add: '新冊別',
+        add: '新冊別',
         place1: '輸入冊別名稱…',
         isDel: '已失效',
         deleteDelV: '刪除失效冊別',
@@ -4889,8 +4953,8 @@ const LANG_ZH_TW = {
     },
     // 系统相关
     system: {
-        wechatTip: '關注醍摩豆微信公眾號詢',
-        wechatTipTw: '透過QR Code加入LINE好友',
+        wechatTip: '關注醍摩豆微信公眾號詢',
+        wechatTipTw: '透過QR Code加入Line官方客服',
         preview: '預覽',
         title: '醍摩豆雲平臺',
         hostTitle: {
@@ -5060,7 +5124,7 @@ const LANG_ZH_TW = {
         text65: "播放影片",
         text66: "換頁",
     },
-    // 阅卷任务
+    // 閱卷任務
     task: {
         markTask: '閱卷任務',
         markStatus0: '未開始',
@@ -5292,16 +5356,16 @@ const LANG_ZH_TW = {
             'classroom': '教室管理',
             'classroom-read': '檢視教室資料',
             'classroom-upd': '變更教室設定',
-            'schoolSetting': '學校基礎數據管理',
+            'schoolSetting': '基礎設置',
             'schoolSetting-read': '檢視學校基礎設定',
             'schoolSetting-upd': '變更學校基礎設定',
-            'student': '學生帳號管理',
+            'student': '學生管理',
             'student-read': '檢視學生帳號',
             'student-upd': '變更學生帳號',
-            'teacher': '教師帳號管理',
+            'teacher': '教師管理',
             'teacher-read': '檢視教師帳號',
             'teacher-upd': '變更教師帳號',
-            'course': '課程設定管理',
+            'course': '課程管理',
             'course-read': '查看課程設定資訊',
             'course-upd': '修改課程設定資訊',
             'newCoursePlan': '排課管理',
@@ -5528,7 +5592,7 @@ const LANG_ZH_TW = {
         menuKlg: '依據學科建立對應的校本知識點,於題庫中使用以利學情分析數據確實。教師所建立的線上試題,需遵循此知識點庫所定義的知識點規範。',
         cusClass: '1.學生可運用行動裝置掃碼加入您所建立的課程,參予此課程相關活動。\n2.若有學校指派課程名單,教師亦可選用該名單',
         cusType: '學校課程:學校分配的課程,不允許自行新增、修改、刪除。 \n個人課程:您自己建立的課程,可以根據需要進行修改和調整。',
-        semester: '您可自行設定學校的學期和入學期,如果沒有設定入學期系統會默認設定第一個學期為入學期。',
+        semester: '您可自行設定學校的學期和入學期,如果沒有設定入學期系統會預設第一個學期為入學期。',
         time: '可設定學校作息時間,以利課程課表的產出。 若未設定則無法產出課表運用。',
         stuModel: 'Id:學生學號(4-12位數字)\npw:密碼(若不輸入,預設值等於學生學號)\nname:學生姓名\nstuYear:學生入學年 ex:2020\nclassName:學生班級 ex:一年一班\nclassYear:班級年,通常與學生入學年相同 ex:2020\nclassId:班級編碼 ex:20200101',
         cusInfo: '依據學校定義的學科,建立所屬歸類的課程 Ex:”語文”學科下的 “閱讀理解”',
@@ -5542,9 +5606,9 @@ const LANG_ZH_TW = {
         backToTop:'返回頂部',
         noMore:'沒有更多了',
         reachBottomTip:'我也是有底線的',
-        filterOrigin:'評來源',
-        examName:'評名稱',
-        examNameTip:'請輸入評名稱',
+        filterOrigin:'評來源',
+        examName:'評名稱',
+        examNameTip:'請輸入評名稱',
         totalIndex: {
             title: '學情分析儀表',
             tab1: '統計資料',
@@ -6149,6 +6213,9 @@ const LANG_ZH_TW = {
     },
     // 投票活动
     vote: {
+        copyNotice:'複製活動通知',
+        copyLinkUrl:'複製活動網址',
+        fullTip:'資源空間已滿,無法建立新活動!',
         secret: '匿名',
         allTeacher: '所有老師',
         noFindVote: '未查詢到當前活動!',

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

@@ -7,6 +7,10 @@ export default {
     // 查询课堂记录列表
 	getLessonList: function (data) {
         return post('/common/lesson-record/get-lesson-record', data)
+    },
+    // 查询课堂记录列表(不分个人还是校本课程)
+	getLessonListAll: function (data) {
+        return post('/common/lesson-record/get-lesson-record-schorpvt', data)
     },
 	getDashboardData: function (data) {
         return post('/class/analysis/analysis-recod', data)

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

@@ -10,6 +10,10 @@ export default {
     },
     classroomBand: function (data) {
         return post('/school/classroom/hiteach-link-unlink', data)
+    },
+    //联系表单
+    sendContactInfo: function (data) {
+        return post('/core/apply-service', data)
     }
     
 }

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

@@ -343,4 +343,8 @@ export default {
     getAllGrouplist: function(data) {
         return post("/grouplist/get-student-joined-grouplist", data)
     },
+    // 获取所有公告
+    getAllNotice: function(data) {
+        return post("/school/notice/find-by-student", data)
+    },
 }

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

@@ -54,6 +54,12 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe67c;</span>
+                <div class="name">作品</div>
+                <div class="code-name">&amp;#xe67c;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe6b6;</span>
                 <div class="name">意见反馈、记笔记</div>
@@ -1224,9 +1230,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1650883304079') format('woff2'),
-       url('iconfont.woff?t=1650883304079') format('woff'),
-       url('iconfont.ttf?t=1650883304079') format('truetype');
+  src: url('iconfont.woff2?t=1653386415595') format('woff2'),
+       url('iconfont.woff?t=1653386415595') format('woff'),
+       url('iconfont.ttf?t=1653386415595') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -1252,6 +1258,15 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont icon-zuopin"></span>
+            <div class="name">
+              作品
+            </div>
+            <div class="code-name">.icon-zuopin
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont icon-myNote"></span>
             <div class="name">
@@ -3007,6 +3022,14 @@
       <div class="content symbol">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-zuopin"></use>
+                </svg>
+                <div class="name">作品</div>
+                <div class="code-name">#icon-zuopin</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-myNote"></use>

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

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2000444 */
-  src: url('iconfont.woff2?t=1650883304079') format('woff2'),
-       url('iconfont.woff?t=1650883304079') format('woff'),
-       url('iconfont.ttf?t=1650883304079') format('truetype');
+  src: url('iconfont.woff2?t=1653386415595') format('woff2'),
+       url('iconfont.woff?t=1653386415595') format('woff'),
+       url('iconfont.ttf?t=1653386415595') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,10 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-zuopin:before {
+  content: "\e67c";
+}
+
 .icon-myNote:before {
   content: "\e6b6";
 }

File diff ditekan karena terlalu besar
+ 1 - 1
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.js


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

@@ -5,6 +5,13 @@
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "20000263",
+      "name": "作品",
+      "font_class": "zuopin",
+      "unicode": "e67c",
+      "unicode_decimal": 59004
+    },
     {
       "icon_id": "24591518",
       "name": "意见反馈、记笔记",

TEMPAT SAMPAH
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.ttf


TEMPAT SAMPAH
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff


TEMPAT SAMPAH
TEAMModelOS/ClientApp/src/assets/iconfont/iconfont.woff2


TEMPAT SAMPAH
TEAMModelOS/ClientApp/src/assets/image/qrcode_en.png


TEMPAT SAMPAH
TEAMModelOS/ClientApp/src/assets/image/qrcode_tw.png


+ 8 - 5
TEAMModelOS/ClientApp/src/common/BaseLayout.vue

@@ -487,7 +487,7 @@ export default {
                             tag: '',
                             role: 'admin',
                             permission: 'research-read|research-upd',
-                            menuName: 'courseCenter',
+                            menuName: 'sokrates',
                             isShow: true
                         },
                         {
@@ -695,7 +695,8 @@ export default {
                         menuName: 'discuss',
                         isShow: true
                     },
-                    {
+                    // 研修的投票和问卷隐藏
+                    /* {
                         icon: 'iconfont icon-vote',
                         name: this.$t('system.menu.scVote'),
                         router: '/home/privateVote',
@@ -716,7 +717,7 @@ export default {
                         child: [],
                         menuName: 'privateQuestionnaire',
                         isShow: true
-                    },
+                    }, */
                     ]
                 },
                 // 我的班级
@@ -735,12 +736,14 @@ export default {
                 {
                     icon: 'iconfont icon-course-self',
                     name: this.$t('system.menu.myCus'),
-                    router: '/home/myCourse',
+                    // router: '/home/myCourse',
+                    router: '/home/course',
                     tag: '',
                     role: 'teacher',
                     permission: '',
                     child: [],
-                    menuName: 'myCourse',
+                    menuName: 'course',
+                    // menuName: 'myCourse',
                     isShow: this.IES5Menu,
                     info: this.$t('tip.myCus')
                 },

File diff ditekan karena terlalu besar
+ 515 - 530
TEAMModelOS/ClientApp/src/components/questionnaire/BaseQnForm.vue


+ 114 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Buzr.vue

@@ -0,0 +1,114 @@
+<template>
+    <div class="buzz-wrap">
+        <p class="event-type">{{ $t("talMgmt.text42") }}:</p>
+        <div class="buzz-box" v-for="(item,index) in buzzClients" :key="index">
+            <!-- <Icon custom="iconfont icon-buzz" size="24" /> -->
+            <span>{{getChinese(item.name)}}</span>
+        </div>
+        <StudentClient></StudentClient>
+    </div>
+</template>
+<script>
+import StudentClient from '@/view/classrecord/eventchart/StudentClient.vue'
+export default {
+    props: {
+        buzrData: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    components: {
+        StudentClient
+    },
+    data() {
+        return {
+            // buzzClients: []
+        }
+    },
+    methods: {
+        getChinese(strValue) {
+            if (!strValue) return ''
+            var reg = /[\u4e00-\u9fa5]/g;
+            var names = strValue.match(reg);
+            if (names) {
+                return names.join("").substring(names.length - 2)
+            } else {
+                return strValue.substr(0, 2)
+            }
+        }
+    },
+    computed: {
+        buzzClients() {
+            // 学生端只展示自己的
+            let data = []
+            if (this.buzrData.buzzClients) {
+                let buzzClients = this._.cloneDeep(this.buzrData.buzzClients)
+                if (this.students.length) {
+                    buzzClients.forEach(item => {
+                        let s = this.students.find(stu => stu.seatID == item && stu.id === this.$store.state.userInfo.sub)
+                        if(s) {
+                            data.push({
+                                seatNo: item,
+                                name: s ? s.name : item
+                            })
+                        }
+                    })
+                }/*  else {
+                    data = buzzClients.map(item => {
+                        return {
+                            seatNo: item,
+                            name: item
+                        }
+                    })
+                } */
+                return data
+            }
+            return data
+        }
+    }
+    // watch: {
+    //     buzrData: {
+    //         immediate: true,
+    //         deep: true,
+    //         handler(n, o) {
+    //             if (n.buzzClients) {
+    //                 this.buzzClients = n.buzzClients
+    //             }
+    //         }
+    //     }
+    // }
+}
+</script>
+<style lang="less" scoped>
+.buzz-wrap {
+    display: flex;
+}
+.buzz-box {
+    margin-right: 20px;
+    width: 70px;
+    height: 70px;
+    line-height: 70px;
+    background: #17233d;
+    text-align: center;
+    color: white;
+    border-radius: 50%;
+    background-image: radial-gradient(#5cadff 50%, #2b85e4 30%);
+    span {
+        font-size: 20px;
+        font-weight: 600;
+    }
+}
+.event-type {
+    margin-right: 20px;
+    font-size: 15px;
+    font-weight: 600;
+}
+</style>

+ 7 - 6
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.less

@@ -109,7 +109,7 @@
 
     .message-area {
         position: relative;
-        height: 815px;
+        // height: 815px;
         padding: 10px;
         margin-bottom: 20px;
 
@@ -389,10 +389,11 @@
                     border: 1px dashed transparent;
                     margin-bottom: 15px;
                     padding: 5px 5px;
-                }
-
-                .event-item:hover {
-                    border: 1px dashed #e0e0e0;
+                    overflow-x: scroll;
+                    
+                    &:hover {
+                        border: 1px dashed #e0e0e0;
+                    }
                 }
 
                 .student-event {
@@ -405,7 +406,7 @@
 
     .dec {
         padding: 0px;
-        height: 800px;
+        // height: 800px;
         overflow: hidden;
         position: relative;
 

+ 160 - 105
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ClassRecord.vue

@@ -7,6 +7,7 @@
             </span>
             <span class="testTitleText">{{$t("studentWeb.courseContent.classRecord")}}</span>
         </div>
+        <div class="class-record">
         <vuescroll ref="pagewrap">
             <div style="padding: 1% 3% 3%; margin-top: 44px;">
                 <div class="record-head" v-if="recordInfo">
@@ -120,7 +121,7 @@
                                     <Icon :class="{'select-filter': currentfilterType === 'ShowAnsLoad' || currentfilterType === 'QaWrong' }" custom="iconfont icon-cuowu" :title="$t('studentWeb.courseContent.type.wrong')" size="30" />
                                 </span>
                             </div> -->
-                            <vuescroll ref="datawrap">
+                            <!-- <vuescroll ref="datawrap"> -->
                                 <div style="margin-bottom: 50px;">
                                     <div v-for="(items, index) in pageList" :key="index" :id="'page' + (items.page)">
                                         <div v-if="items.pageData.length" class="message-box">
@@ -153,19 +154,21 @@
                                                         <!-- 推送 -->
                                                         <Push class="event-item" v-if="event.Event === 'FastPgPush' && (currentfilterType === '' || currentfilterType === 'doc')" :pushData="event.data"></Push>
                                                         <!-- 作品收集 -->
-                                                        <StuReceive :nowStuInfo="nowStuInfo" :recordInfo="recordInfo" class="student-event event-item" v-if="event.Event === 'WrkSpaceEnd' && baseData" :rcvData="event.data" :students="baseData.student"></StuReceive>
-                                                        <Receive :recordInfo="recordInfo" class="student-event event-item" v-if="event.Event === 'WrkSpaceEnd' && baseData" :rcvData="event.data" :students="baseData.student"></Receive>
+                                                        <StuReceive class="student-event event-item" v-if="event.Event === 'WrkSpaceEnd' && baseData" :nowStuInfo="nowStuInfo" :rcvData="event.data" :recordInfo="recordInfo" :students="baseData.student"></StuReceive>
+                                                        <!-- 老师收集的所有作品目前不展示,只展示老师回贴的学生作品 -->
+                                                        <!-- <Receive :recordInfo="recordInfo" class="student-event event-item" v-if="event.Event === 'WrkCmp' && baseData" :rcvData="event.data" :students="baseData.student"></Receive> -->
+                                                        <ReceiveBack class="event-item" v-if="event.Event === 'WrkCmp' && event.data" :recordInfo="recordInfo" :rcvData="event.data" :students="baseData.student"></ReceiveBack>
                                                         <!-- 随机挑人 -->
-                                                        <Pick class="event-item student-event" :pickData="event.data" v-if="event.Event === 'PickupResult' && baseData" :students="baseData.student"></Pick>
+                                                        <Pick class="event-item student-event" v-if="event.Event === 'PickupResult' && baseData" :pickData="event.data" :students="baseData.student"></Pick>
                                                         <!-- 课中评测 -->
-                                                        <Exam class="event-item" :pickData="event.data" v-if="event.Event === 'SPQStrt'"></Exam>
+                                                        <Exam class="student-event event-item" :examInfo="event.data" :recordInfo="recordInfo" v-if="event.Event === 'SPQStrt'"></Exam>
                                                     </div>
                                                 </template>
                                             </div>
                                         </div>
                                     </div>
                                 </div>
-                            </vuescroll>
+                            <!-- </vuescroll> -->
                         </div>
                         <div v-else class="no-interaction">
                             {{ $t("studentWeb.hiteachNote.noContent") }}
@@ -174,6 +177,10 @@
                 </div>
             </div>
         </vuescroll>
+        </div>
+        
+        <!--返回顶部-->
+        <BackToTop @on-to-top="handleToTop"></BackToTop>
         <Modal v-model="showNote" :title="$t('studentWeb.courseContent.mynotes')" width="800">
             <!-- <div> -->
                 <img :src="item" alt="" v-for="(item, index) in recordInfo.myNote" :key="index" style="width: 100%">
@@ -194,11 +201,12 @@ import ShowQues from './ShowQues.vue';
 import StuReceive from './StuReceive.vue';
 import PopQues from '@/view/classrecord/eventchart/PopQues.vue';
 import Buzr from '@/view/classrecord/eventchart/Buzr.vue';
-import Pick from '@/view/classrecord/eventchart/Pick.vue';
+import Pick from './Pick.vue';
 import Push from '@/view/classrecord/eventchart/Push.vue';
-import Exam from '@/view/classrecord/eventchart/Exam.vue';
+import Exam from './Exam.vue';
 import Receive from './Receive.vue';
 import DataCount from './DataCount.vue';
+import ReceiveBack from './ReceiveBack.vue';
 
 export default {
     name: "ClassRecord",
@@ -215,6 +223,7 @@ export default {
         Exam,
         Receive,
         DataCount,
+        ReceiveBack,
     },
     data() {
         return {
@@ -380,110 +389,142 @@ export default {
             this.recordInfo.eNote = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Note.pdf?${sas.sas}`
             // 如果只会存在一个视频,文件名是否可以固定?
             this.playerOptions.sources[0].src = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Record/CourseRecord.mp4?${sas.sas}`
-            let url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Sokrates/SokratesRecords.json?${sas.sas}` //后面会根据TimeLine.json处理
-            this.$tools.getFile(url).then(
-                async res => {
-                    this.sokratesRecords = JSON.parse(res)
-                    //获取Push.json、IRS.json、Task.json、Base.json数据
-                    try {
-                        let pushUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/Push.json?${sas.sas}`
-                        this.pushData = JSON.parse(await this.$tools.getFile(pushUrl) || '[]')
-                        this.pushData.forEach(item => {
-                            item.pageUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}${item.pageMeta}?${sas.sas}`
-                        })
-                    } catch (e) {
-                        this.pushData = []
-                    }
-                    try {
-                        let irsUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/IRS.json?${sas.sas}`
-                        this.irsData = JSON.parse(await this.$tools.getFile(irsUrl) || '[]')
-                    } catch (e) {
-                        this.irsData = []
-                    }
-                    try {
-                        let taskUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/Task.json?${sas.sas}`
-                        this.taskData = JSON.parse(await this.$tools.getFile(taskUrl) || '[]')
-                    } catch (e) {
-                        this.taskData = []
+            // let url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Sokrates/SokratesRecords.json?${sas.sas}` //后面会根据TimeLine.json处理
+            // 这里需要兼容原来没有TimeLine.json的课例(优先度读timeLine.json,没有则读SokratesRecords.json)
+            let url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/TimeLine.json?${sas.sas}`
+            let hasTimeLine = true
+            let dataErr = false
+            let pgids = []
+            let pageEvents = []
+            try {
+                let res = await this.$tools.getFile(url)
+                this.sokratesRecords = JSON.parse(res)
+                pgids = this.sokratesRecords.PgIdList || []
+                pageEvents = this.sokratesRecords.events || []
+            } catch (e) {
+                hasTimeLine = false
+            }
+            //读取 timeLine.json 失败,则读取 SokratesRecords.json
+            if (!hasTimeLine) {
+                url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Sokrates/SokratesRecords.json?${sas.sas}`
+                try {
+                    let res = await this.$tools.getFile(url)
+                    let resJson = JSON.parse(res)
+                    // 处理成timeLine数据格式
+                    let pageidEvent = resJson.find(item => item.Event == 'PgidList')
+                    pgids = pageidEvent && pageidEvent.PgIdList ? pageidEvent.PgIdList : []
+                    pageEvents = resJson.filter(item => this.events.includes(item.Event))
+                    this.sokratesRecords = {
+                        events: pageEvents,
+                        PgIdList: pgids
                     }
-                    try {
-                        let baseUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/base.json?${sas.sas}`
-                        this.baseData = JSON.parse(await this.$tools.getFile(baseUrl) || '{}')
-                        this.baseData.student.forEach((item, index) => {
-                            if (item.name === this.$store.state.userInfo.name) {
-                                this.nowStuInfo = item
-                                this.nowStuInfo.index = index
-                            }
-                        })
-                    } catch (e) {
-                        this.baseData = undefined
+                } catch (e) {
+                    //timeLine 和 SokratesRecords都没有找到
+                    dataErr = true
+                }
+            }
+            
+            // 数据异常
+            if (dataErr) {
+                this.$Message.error(this.$t('cusMgt.rcd.dataErr'))
+                return
+            }
+            //获取Push.json、IRS.json、Task.json、Base.json数据
+            try {
+                let pushUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/Push.json?${sas.sas}`
+                this.pushData = JSON.parse(await this.$tools.getFile(pushUrl) || '[]')
+                this.pushData.forEach(item => {
+                    item.pageUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}${item.pageMeta}?${sas.sas}`
+                })
+            } catch (e) {
+                this.pushData = []
+            }
+            try {
+                let irsUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/IRS.json?${sas.sas}`
+                this.irsData = JSON.parse(await this.$tools.getFile(irsUrl) || '[]')
+            } catch (e) {
+                this.irsData = []
+            }
+            try {
+                let taskUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/Task.json?${sas.sas}`
+                this.taskData = JSON.parse(await this.$tools.getFile(taskUrl) || '[]')
+            } catch (e) {
+                this.taskData = []
+            }
+            try {
+                let baseUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/base.json?${sas.sas}`
+                this.baseData = JSON.parse(await this.$tools.getFile(baseUrl) || '{}')
+                this.baseData.student.forEach((item, index) => {
+                    if (item.id === this.$store.state.userInfo.sub) {
+                        this.nowStuInfo = item
+                        this.nowStuInfo.index = index
                     }
-                    let r = this.sokratesRecords.find(item => item.Event === 'PgidList')
-                    let pgids = r ? r.PgIdList : []
+                })
+            } catch (e) {
+                this.baseData = undefined
+            }
 
-                    //这里需要判断录制开始的pageid
-                    let startInfo = this.sokratesRecords.find(item => item.Event === 'EzsStartRecord')
-                    let startId = startInfo ? startInfo.Pgid : ''
-                    let startIndex = 0
-                    if (startId) {
-                        startIndex = pgids.findIndex(item => item === startId)
-                    }
-                    pgids = pgids.slice(startIndex)
+            //这里需要判断录制开始的pageid
+            let startInfo = pageEvents.find(item => item.Event === 'EzsStartRecord')
+            let startId = startInfo ? startInfo.Pgid : ''
+            let startIndex = 0
+            if (startId) {
+                startIndex = pgids.findIndex(item => item === startId)
+            }
+            pgids = pgids.slice(startIndex)
 
-                    let havePage = 0
-                    pgids.forEach((item, index) => {
-                        let page = {}
-                        page.id = item
-                        page.img = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Memo/${item}.jpg?${sas.sas}`
-                        page.page = index + 1
-                        //当前页面对应的sokrates
-                        page.pageData = this.sokratesRecords.filter(record => record.Pgid === item && this.fnEvents.includes(record.Event))
-                        havePage += (page.pageData.length ? 1 : 0)
-                        page.pageData.forEach(e => {
-                            e.pageIndex = index
-                            e.eventName = this.hiTeachEvent[e.Event]?.text
-                            let rlt = this.hiTeachEvent[e.Event]?.relation
-                            e.relation = rlt
-                            switch (rlt) {
-                                case 'irs':
-                                    e.data = this.irsData.find(i => i.pageID == e.Pgid)
-                                    break
-                                case 'push':
-                                    e.data = this.pushData.find(p => p.pageId == e.Pgid)
-                                    break
-                                case 'task':
-                                    e.data = this.taskData.find(t => t.pageID == e.Pgid)
-                                    break
-                                case 'timeline':
-                                    e.data = this._.cloneDeep(e)
-                                    break
-                                default:
-                                    break
-                            }
-                        })
-                        this.pageList.push(page)
-                    })
-                    this.haveInteraction = havePage != 0
-                    let pageEvent = this.sokratesRecords.filter(item => item.Event === 'PopQuesLoad' || item.Event === 'ReAtmpAnsStrt' || item.Event === 'FastPgPush' || item.Event === 'WrkSpaceEnd' || item.Event === 'SPQStrt')
-                    this.markers = pageEvent.map((item, index) => {
-                        return {
-                            time: item.Time,
-                            text: `${this.$t('cusMgt.rcd.di')}${index + 1}${this.$t('cusMgt.rcd.page')}`,
-                            page: index + 1
-                        }
-                    })
-                    this.getVideo()
-                },
-                err => {
-                    this.$Message.error('获取数据失败')
+            let havePage = 0
+            pgids.forEach((item, index) => {
+                let page = {}
+                page.id = item
+                page.img = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Memo/${item}.jpg?${sas.sas}`
+                page.page = index + 1
+                //当前页面对应的sokrates
+                page.pageData = pageEvents.filter(record => record.Pgid === item && this.fnEvents.includes(record.Event))
+                havePage += (page.pageData.length ? 1 : 0)
+                page.pageData.forEach(e => {
+                    e.pageIndex = index
+                    e.eventName = this.hiTeachEvent[e.Event]?.text
+                    let rlt = this.hiTeachEvent[e.Event]?.relation
+                    e.relation = rlt
+                    switch (rlt) {
+                        case 'irs':
+                            e.data = this.irsData.find(i => i.pageID == e.Pgid)
+                            break
+                        case 'push':
+                            e.data = this.pushData.find(p => p.pageId == e.Pgid || p.pageID == e.Pgid)
+                            break
+                        case 'task':
+                            e.data = this.taskData.find(t => t.pageID == e.Pgid)
+                            break
+                        case 'timeline':
+                            e.data = this._.cloneDeep(e)
+                            break
+                        case 'exam':
+                            e.data = this._.cloneDeep(e)
+                            break
+                        default:
+                            break
+                    }
+                })
+                this.pageList.push(page)
+            })
+            this.haveInteraction = havePage != 0
+            let pageEvent = pageEvents.filter(item => item.Event === 'PopQuesLoad' || item.Event === 'ReAtmpAnsStrt' || item.Event === 'FastPgPush' || item.Event === 'WrkSpaceEnd' || item.Event === 'SPQStrt')
+            this.markers = pageEvent.map((item, index) => {
+                return {
+                    time: item.Time,
+                    text: `${this.$t('cusMgt.rcd.di')}${index + 1}${this.$t('cusMgt.rcd.page')}`,
+                    page: index + 1
                 }
-            )
+            })
+            this.getVideo()
         },
         // 点击互动记录页面tag
         toVideo(page, e) {
             this.curPage = page
             //页面滚动
-            let dataLoacation = this.$refs["datawrap"].getPosition()
+            /* let dataLoacation = this.$refs["datawrap"].getPosition()
             let pageLocaltion = this.$refs["pagewrap"].getPosition()
             let y = e.pageY - 770 + pageLocaltion.scrollTop + dataLoacation.scrollTop
             this.$nextTick(() => {
@@ -499,7 +540,7 @@ export default {
                         y: y
                     }
                 )
-            })
+            }) */
             //视频时间定位
             let pageInfo = this.markers.find(item => {
                 return item.page === page
@@ -524,7 +565,7 @@ export default {
                     this.player.pause()
                 }
                 //互动记录滚动
-                this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
+                // this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
             } else {
             }
         },
@@ -532,7 +573,7 @@ export default {
         getCurPage(page) {
             this.curPage = page
             // this.mapJson = require('./data/' + page + '.json')
-            this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
+            // this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
             this.player.play()
         },
         //查看电子笔记
@@ -603,6 +644,16 @@ export default {
         quitRec() {
             this.$router.go(-1)
         },
+        //返回顶部
+        handleToTop() {
+            document.getElementsByClassName("class-record")[0].scrollIntoView()
+            /* this.$refs['pagewrap'].scrollTo(
+                {
+                    y: '0'
+                },
+                300
+            ) */
+        },
     },
     computed: {
         curImg() {
@@ -663,4 +714,8 @@ export default {
     border-radius: 50%;
     background: #de7320;
 }
+
+.record-overflow {
+    overflow-x: scroll;
+}
 </style>

+ 249 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Exam.vue

@@ -0,0 +1,249 @@
+<template>
+    <div class="exam-wrap">
+        <div class="clt-type" v-if="!examDetaiInfo">
+            <!-- <Icon type="md-podium" style="margin-right:5px" />
+            查看评测数据 -->
+            {{ $t("studentWeb.courseContent.noExam") }}
+        </div>
+        <div v-else class="exam-chart-wrap">
+            <ScoreBarChart :total="classTotal" :subjectNames="subjectName"></ScoreBarChart>
+            <ExamQu :quData="correctData[0] ? correctData[0].data : []"></ExamQu>
+            <ExamTable :examDetaiInfo="examDetaiInfo" :examInfo="examInfo" :recordInfo="recordInfo"></ExamTable>
+        </div>
+        <div>
+            <Icon type="ios-person" class="owner-student-client-icon"/>
+        </div>
+    </div>
+</template>
+<script>
+import TeacherClient from '@/view/classrecord/eventchart/TeacherClient.vue'
+import ExamGrade from '@/view/classrecord/eventchart/ExamGrade.vue'
+import ExamQu from './ExamQu.vue'
+import ScoreBarChart from './ScoreBarChart.vue'
+import ExamTable from './ExamTable.vue'
+
+export default {
+    components: {
+        TeacherClient, ExamGrade, ExamQu, ExamTable, ScoreBarChart
+    },
+    props: {
+        examInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+    },
+    data() {
+        return {
+            simpleData: {},
+            examDetaiInfo: undefined,
+            paperQuInfo: [],
+            correctData: [],
+            subjectName: []
+        }
+    },
+    methods: {
+        getExamInfo() {
+            return new Promise((r, j) => {
+                let code = this.recordInfo.scope == 'school' ? this.recordInfo.school : this.recordInfo.tmdid
+                let req = {
+                    id: this.examInfo.ExamId,
+                    studentId: this.$store.state.userInfo.sub,
+                    code,
+                    scode: `Exam-${this.recordInfo.tmdid}`,
+                    cIds: this.recordInfo.groupIds
+                }
+                this.$api.studentWeb.FindStudentPaper(req).then(res => {
+                    this.examDetaiInfo = res
+                    res.subjects.forEach(item => {
+                        this.subjectName.push(item.name)
+                    })
+                    let d = []
+                    let totalNum = 0 //总人数
+                    // 不同科目,多个班级,以科目的数量为准
+                    res.total.forEach((item, index) => {
+                        if((index + 1) % res.subjects.length === 0) {
+                            item.forEach(num => {
+                                totalNum += num
+                            })
+                        }
+                    })
+                    res.wno.forEach((item, index) => {
+                        let cData = item.map((w, i) => {
+                            let correct = totalNum - w
+                            return {
+                                correct: correct, //正确人数
+                                wrong: w,//错误人数
+                                rate: totalNum ? parseInt(correct * 100 / totalNum) : 0,//正确率
+                                quLabel: i + 1,
+                                type: false,
+                            }
+                        })
+                        d.push({
+                            subjectId: res.subjects[index],
+                            data: cData,
+                        })
+                    })
+                    this.correctData = d
+                    r(true)
+                })
+            })
+            
+        },
+        // 设置各科题号信息
+        async setPaperQuInfo() {
+            this.paperQuInfo = []
+            let code = this.recordInfo.scope == 'school' ? this.recordInfo.school : this.recordInfo.tmdid
+            let papers = await this.getStuPaper(code)
+            if (!papers) return
+            let objectiveQu = ['single', 'multiple', 'judge']
+            papers.forEach(paper => {
+                let data = []
+                let realIndex = 0
+                paper.item?.forEach((item, index) => {
+                    if (item.children.length) {
+                        item.children.forEach((childItem, childIndex) => {
+                            let i = realIndex++
+                            data.push({
+                                label: (index + 1) + '-' + (childIndex + 1),
+                                value: i,
+                                disabled: objectiveQu.includes(childItem.type),
+                                quIndex: index,
+                                childIndex: childIndex
+                            })
+                        })
+                    } else {
+                        let i = realIndex++
+                        data.push({
+                            label: (index + 1) + '',
+                            value: i,
+                            disabled: objectiveQu.includes(item.type),
+                            quIndex: index,
+                            childIndex: -1
+                        })
+                    }
+                })
+                this.paperQuInfo.push({
+                    subjectId: paper.subjectId,
+                    subjectName: paper.subjectName,
+                    quNo: data
+                })
+            })
+        },
+        getStuPaper(code) {
+            return new Promise(async(r, j) => {
+                try {
+                    let a = await this.$evTools.getStuPaper(code)
+                    r(a)
+                } catch(e) {
+                    j(undefined)
+                }
+            })
+        },
+    },
+    computed: {
+        examScore() {
+            if (this.examDetaiInfo && this.examDetaiInfo.score) {
+                return this.examDetaiInfo.score
+            } else {
+                return 0
+            }
+        },
+        classTotal() {
+            if (this.examDetaiInfo && this.examDetaiInfo.total) {
+                let total = []
+                this.examDetaiInfo.total.forEach((item, index) => {
+                    total.push({
+                        value: this.examDetaiInfo.subjects[index].name,
+                        data: item,
+                        type: "bar",
+                    })
+                })
+                return total
+            }
+        },
+        // 所有学生总分统计(多科)
+        stuTotalScores() {
+            if (this.simpleData && this.simpleData.averageTotal) {
+                let total = []
+                this.simpleData.averageTotal.forEach((subItem, i) => {
+                    // 累积各科分数
+                    subItem.total.forEach((sItem, j) => {
+                        if (!total[j]) total[j] = 0
+                        total[j] += sItem
+                    })
+                })
+                return total
+            } else {
+                return []
+            }
+        },
+        //总分成绩分布
+        scoreSegment() {
+            if (this.stuTotalScores.length) {
+                let segment = []
+                let unit = this.examScore / 10
+                let startScore = 0
+                for (let i = 0; i < 10; i++) {
+                    let endScore = Math.ceil(unit * (i + 1))
+                    let s = this.stuTotalScores.filter(item => {
+                        return item >= startScore && item <= endScore
+                    })
+                    segment.push({
+                        name: `${startScore}-${endScore}`,
+                        value: s.length
+                    })
+                    startScore = endScore + 1
+                }
+                return segment
+            } else {
+                return []
+            }
+        },
+    },
+    watch:{
+        examInfo: {
+            deep: true,
+            immediate: true,
+            async handler(n, o) {
+                if(n && n.ExamId){
+                    await this.getExamInfo()
+                }
+            }
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.exam-wrap {
+    display: flex;
+}
+.exam-info {
+    width: fit-content;
+    padding: 5px 20px;
+    background: #5cadff;
+    color: white;
+    border: 2px solid #2b85e4;
+    border-radius: 6px;
+    font-size: 14px;
+    user-select: none;
+    cursor: pointer;
+}
+.exam-chart-wrap{
+    display: flex;
+    overflow-x: scroll;
+}
+
+.clt-type {
+    margin-right: 10px;
+    font-size: 15px;
+    font-weight: 600;
+}
+</style>

+ 283 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ExamQu.vue

@@ -0,0 +1,283 @@
+<template>
+    <div class="qu-score">
+        <div class="qu-score-count" :id="`class-score-count-${id}`"></div>
+        <p class="legend-info">
+            <!-- <span>{{ $t('studentWeb.exam.chart.legendSimple.legend1') }}</span> -->
+            <span>{{ $t('studentWeb.exam.chart.legendSimple.legend2') }}
+                <span style="color: #00AD6C;">√</span>
+            </span>
+            <span>{{ $t('studentWeb.exam.chart.legendSimple.legend3') }}
+                <span style="color: #FF5508;">×</span>
+            </span>
+        </p>
+    </div>
+</template>
+<script>
+import elementResizeDetectorMaker from "element-resize-detector"
+export default {
+    props: {
+        quData: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        let _this = this
+        return {
+            isShowRate: true,
+            progressPie: undefined,
+            stuCount: 0,
+            option: {},
+            id: "",
+        }
+    },
+    created() {
+        this.id = this.$jsFn.getBtwRandom(0, 100000000)
+    },
+    mounted() {
+        this.progressPie = this.$echarts.init(document.getElementById(`class-score-count-${this.id}`), 'macarons')
+        this.progressPie.setOption(this.option)
+        let erd11 = elementResizeDetectorMaker()
+        erd11.listenTo(document.getElementById(`class-score-count-${this.id}`), () => {
+            this.$nextTick(() => {
+                //监听到事件后执行的业务逻辑
+                this.progressPie.resize()
+            })
+        })
+    },
+    watch: {
+        quData: {
+            handler(n, o) {
+                console.log(n);
+                let _this = this
+                this.$nextTick(() => {
+                    if (this.quData.length) _this.stuCount = this.quData[0].correct + this.quData[0].wrong
+                    this.option = {
+                        tooltip: {
+                            trigger: 'item',
+                            formatter: '{b} : {c}' + this.$t('unit.text7')
+                        },
+                        title: {
+                            "text": this.$t('learnActivity.simple.quCorrectRate'),
+                            "left": "center",
+                            "top": 0,
+                            "textStyle": {
+                                fontSize: 15,
+                                color: "#333"
+                            }
+                        },
+                        legend: {
+                            /* itemStyle: {
+                                opacity: 0
+                            }, */
+                            /* formatter: (params) => {
+                                return `√表示答对`
+                            }, */
+                            bottom: 0,
+                        },
+                        tooltip: {
+                            formatter: (params) => {
+                                return `${_this.$t('learnActivity.simple.rac')} ${params.data.value.toFixed(0)}%(${_this.quData[params.dataIndex].correct}${_this.$t('unit.text7')})`
+                            }
+                        },
+                        grid: {
+                            // width: "100%",
+                            left: 50,
+                            right: 50
+                        },
+                        xAxis: {
+                            show: true,
+                            // name:'题号',
+                            nameLocation: 'end',
+                            type: 'category',
+                            data: [],
+                            axisLine: {
+                                lineStyle: {
+                                    color: "#AAA"
+                                }
+                            },
+                            axisLabel: {
+                                rotate: 0
+                            }
+                        },
+                        yAxis: {
+                            // name:'正确率',
+                            type: 'value',
+                            axisLine: {
+                                lineStyle: {
+                                    color: "#AAA"
+                                }
+                            },
+                        },
+                        series: [
+                            {
+                                data: [],
+                                type: 'bar',
+                                barMinWidth: 30,
+                                barMaxWidth: 30,
+                                label: {
+                                    show: true,
+                                    position: 'top',
+                                    formatter: (params) => {
+                                        //统计人数的算法
+                                        // if (_this.stuCount > 0) {
+                                        //     return `${(params.data * 100 / _this.stuCount).toFixed(1)}%`
+                                        // } else {
+                                        //     return params.data
+                                        // }
+                                        // console.log(params);
+                                        // 统计正确率的算法
+                                        return _this.isShowRate ? `${params.data.type ? '{typea|√}' : '{typeb|×}'}\n{count|${params.data.value.toFixed(0)}%}` : ''
+                                    },
+                                    rich: {
+                                        typea: {
+                                            color: '#00AD6C',
+                                            fontSize: 20,
+                                            // height: 24,
+                                            // padding: [0, 5, 0, 5],
+                                            align: 'center'
+                                        },
+                                        typeb: {
+                                            color: '#FF5508',
+                                            fontSize: 20,
+                                            // height: 24,
+                                            // padding: [0, 5, 0, 5],
+                                            align: 'center'
+                                        },
+                                        count: {
+                                            color: '#333',
+                                            height: 24,
+                                            // padding: [0, 5, 0, 5],
+                                            // align: 'right'
+                                        },
+                                    },
+                                },
+                                itemStyle: {
+                                    normal: {
+                                        color: function (params) {
+                                            const colorList = ['#ed4014', '#ff9900', '#19be6b']
+                                            //统计人数的算法
+                                            // if (_this.stuCount >= 0) {
+                                            //     let rate = params.data * 100 / _this.stuCount
+                                            //     console.log(params, rate)
+                                            //     if (rate >= 70) {
+                                            //         return colorList[2]
+                                            //     } else if (rate > 50) {
+                                            //         return colorList[1]
+                                            //     } else {
+                                            //         return colorList[0]
+                                            //     }
+                                            // } else {
+                                            //     return colorList[2]
+                                            // }
+                                            // 统计正确率的算法
+                                            if (params.data.value >= 70) {
+                                                return colorList[2]
+                                            } else if (params.data.value > 50) {
+                                                return colorList[1]
+                                            } else {
+                                                return colorList[0]
+                                            }
+
+                                        }
+                                    }
+                                },
+                                stillShowZeroSum: true,
+                            },
+                        ]
+                    }
+                    if (!this.progressPie) {
+                        this.progressPie = this.$echarts.init(document.getElementById(`class-score-count-${this.id}`), 'macarons')
+                    }
+                    this.option.series[0].data = this.quData.map(item => {
+                        return {
+                            value: item.correct * 100 / this.stuCount,
+                            type: item.type
+                        }
+                    })
+                    /* this.option.series[0].data = [
+                        {
+                            value: this.quData.map(item => item.correct * 100 / this.stuCount)
+                        },
+                        {
+                            value: this.quData.map(item => {return item.type})
+                        }
+                    ] */
+                    // this.option.series[0].data = this.quData.map(item => item.correct * 100 / this.stuCount) //计算正确率
+                    this.option.xAxis.data = this.quData.map(item => item.quLabel)
+                    // if (this.quData.length) this.option.yAxis.max = this.quData[0].correct + this.quData[0].wrong
+                    if (this.quData.length) this.option.yAxis.max = 100 //计算比例不算人数
+                    if (this.quData.length > 8) {
+                        this.option.dataZoom = [
+                            {
+                                show: true,
+                                height: 8,
+                                xAxisIndex: [
+                                    0
+                                ],
+                                bottom: 10,
+                                endValue: 30,
+                                startValue: 0,
+                                handleIcon: 'M512 497.821538m-418.264615 0a418.264615 418.264615 0 1 0 836.52923 0 418.264615 418.264615 0 1 0-836.52923 0Z',
+                                handleSize: '100%',
+                                handleStyle: {
+                                    color: '#d3dee5'
+
+                                },
+                                textStyle: {
+                                    color: '#fff'
+                                },
+                                borderRadius: '5px',
+                                maxValue: 30,
+                            },
+                            {
+                                type: 'inside',
+                                show: true,
+                                height: 15,
+                                endValue: 30,
+                                startValue: 0,
+                                maxValue: 30,
+                            }
+                        ]
+                    } else {
+                        this.option.dataZoom = []
+                    }
+                    this.progressPie.setOption(this.option, true)
+                    /* this.progressPie.on('datazoom', (params) => {
+                        let start = params.batch[0]?.start
+                        let end = params.batch[0]?.end
+                        if (end - start > 30) {
+                            this.isShowRate = false
+                        } else {
+                            this.isShowRate = true
+                        }
+                    }) */
+                })
+            },
+            deep: true,
+            immediate: true
+        },
+    }
+}
+</script>
+<style scoped lang="less">
+.qu-score {
+    padding: 15px 40px 0 40px;
+    width: 400px;
+    height: 250px;
+}
+.qu-score-count {
+    width: 100%;
+    height: 100%;
+}
+.legend-info {
+    text-align: center;
+    font-weight: bold;
+
+    & > span:not(:last-child) {
+        margin-right: 10px;
+    }
+}
+</style>

+ 359 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ExamTable.vue

@@ -0,0 +1,359 @@
+<template>
+    <div class="exam-table-wrap">
+        <!-- 班级人数 -->
+        <div class="data-count-item">
+            <p class="data-value">
+                {{ overviewInfo.total }}
+            </p>
+            <p class="data-text">
+                {{ $t('learnActivity.simple.classStuCount') }}
+            </p>
+        </div>
+        <!-- 得分题目数 -->
+        <div class="data-count-item">
+            <p class="data-value">
+                {{ overviewInfo.examNum }}
+            </p>
+            <p class="data-text">
+                {{ $t("studentWeb.exam.report.getScore") }}
+            </p>
+        </div>
+        <!-- 得分 -->
+        <div class="data-count-item">
+            <p class="data-value" style="color:#ed4014">
+                {{ overviewInfo.score }}
+            </p>
+            <p class="data-text">
+                {{ $t("studentWeb.exam.score") }}
+            </p>
+        </div>
+        <!-- 平均分 -->
+        <div class="data-count-item">
+            <p class="data-value">
+                {{ overviewInfo.average }}
+            </p>
+            <p class="data-text">
+                {{ $t("studentWeb.myAchievement.average") }}
+            </p>
+        </div>
+        <!-- 查看更多 -->
+        <div class="data-count-item view-more" @click="toEvDetail" style="width: 100%;">
+            <p class="data-value" style="color:#2d8cf0">
+                <Icon type="ios-more" />
+            </p>
+            <p class="data-text" style="color:#2d8cf0">
+                {{ $t("totalAnalysis.more") }}
+            </p>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    props: {
+        examInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        examDetaiInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+    },
+    data() {
+        return {
+            currentPage: 1,
+            pageSize: 10,
+            pageSizeOpts: [5, 10, 20, 30, 40],
+            viewTableStatus: false,
+            dataLoading: true,
+            overviewInfo: {
+                total: 0,
+                examNum: 0,
+                score: 0,
+                average: 0,
+                noScore: 0
+            },
+            scoreList: [
+                {
+                    title: this.$t('learnActivity.score.column1'),
+                    slot: "name",
+                    fixed: "left",
+                    align: "center",
+                    width: 150,
+                },
+                {
+                    title: this.$t('learnActivity.score.column2'),
+                    slot: "total",
+                    align: "center",
+                    sortable: true,
+                    fixed: "right",
+                    width: 100
+                },
+                // {
+                //     title: this.$t('learnActivity.score.column3'),
+                //     slot: "status",
+                //     align: "center",
+                //     fixed: "right",
+                //     width: 130,
+                // }
+            ],
+            tableColumn: [],
+            studentScore: [],
+            originData: [],
+            students: [],
+            tableData: [],
+            quCount: 0
+        }
+    },
+    methods: {
+        toEvDetail() {
+            this.$router.push({
+                path: "/studentWeb/examView",
+                query: {aId: this.examInfo.ExamId}
+            })
+            /* this.$router.push({
+                path: '/home/evDetail',
+                query: {
+                    examId: this.examInfo.id,
+                    code: `Exam-${this.$store.state.userInfo.TEAMModelId}`
+                }
+            })  */ 
+        },
+        getStatusInfo(answer, score, status) {
+            //评测设置学生可以补考
+            if (status == 2 || status == 3) {
+                return {
+                    status: 3,
+                    statusText: this.$t('learnActivity.score.status5'),
+                    statusColor: '#2db7f5'
+                }
+            }
+
+            //评测结束,学生缺考
+            if (status == 1 && this.examDetaiInfo.progress == 'finish') {
+                return {
+                    status: 1,
+                    statusText: this.$t('learnActivity.score.status4'),
+                    statusColor: '#ed4014'
+                }
+            }
+
+            //评测进行中,未作答
+            if (status == 1 && this.examDetaiInfo.progress == 'going') {
+                return {
+                    status: 0,
+                    statusText: this.$t('learnActivity.score.status1'),
+                    statusColor: '#808695'
+                }
+            }
+            // status:0
+            //已作答,未评分
+            if (score.includes(-1)) {
+                return {
+                    status: 2,
+                    statusText: this.$t('learnActivity.score.status2'),
+                    statusColor: '#ff9900'
+                }
+            }
+            //已作答已评分
+            return {
+                status: 4,
+                statusText: this.$t('learnActivity.score.status3'),
+                statusColor: '#19be6b'
+            }
+        },
+        //分数求和
+        getcount(arr) {
+            return arr.reduce((total, item) => {
+                if (item !== -1) {
+                    return total + item;
+                } else {
+                    return total;
+                }
+            }, 0);
+        },
+        //初始化表单数据
+        setTableData(studentData, studentAns) {
+            console.log(arguments)
+            this.studentScore = []
+            this.tableColumn = [...this.scoreList]
+            this.quCount = studentAns.studentScores[0] ? studentAns.studentScores[0].length : 0
+            for (let i = 0; i < this.quCount; i++) {
+                let data = {
+                    title: "Q" + (i + 1),
+                    slot: "qu" + i,
+                    align: "center",
+                    minWidth: 70
+                }
+                this.tableColumn.push(data);
+            }
+            let ans = []
+            for (let i = 0; i < studentAns.studentIds.length; i++) {
+                for (let k = 0; k < studentData.length; k++) {
+                    let score = {}
+                    if (studentAns.studentIds[i] == studentData[k].id) {
+                        score.name = studentData[k].name
+                        score.type = studentData[k].type
+                        score.id = studentAns.studentIds[i]
+                        score.data = studentAns.studentScores[i]
+                        score.total = this.getcount(score.data)
+                        score.ansBlob = studentAns.studentAnswers[i]
+                        score.mark = studentAns.mark[i]
+                        let { status, statusText, statusColor } = this.getStatusInfo(studentAns.studentAnswers[i], studentAns.studentScores[i], studentAns.status[i])
+                        score.status = status
+                        score.statusText = statusText
+                        score.statusColor = statusColor
+                        this.studentScore.push(score)
+                    }
+                }
+            }
+            this.originData = this._.cloneDeep(this.studentScore)
+            this.students = this._.cloneDeep(this.studentScore)
+            this.pageChange(1)
+            if (ans.length) {
+                for (let k = 0; k < this.paperInfo.papers.item.length; k++) {
+                    this.$set(
+                        this.paperInfo.papers.item[k],
+                        "answerData",
+                        ans[k]
+                    );
+                    this.$set(
+                        this.paperInfo.papers.item[k],
+                        "stuScore",
+                        score[k]
+                    );
+                }
+            }
+        },
+        // 页面size变化
+        pageSizeChange(val) {
+            this.pageSize = val
+            this.pageChange(1)
+        },
+        // 分页页面变化
+        pageChange(page) {
+            let start = this.pageSize * (page - 1)
+            let end = this.pageSize * page
+            this.currentPage = page
+            this.tableData = this.studentScore.slice(start, end)
+        },
+        //获取学生作答详情数据
+        getStudentAnswer() {
+            this.dataLoading = true
+            let requestData = {
+                id: this.examDetaiInfo.id,
+                code: this.recordInfo.scope == 'school' ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId,
+                subjectId: this.recordInfo.subjectId,
+                classId: this.recordInfo.groupIds[0],
+            };
+            this.$api.learnActivity.FindAllStudent(requestData).then(
+                (res) => {
+                    if (res.examClassResults && res.examClassResults.length) {
+                        this.setTableData(res.ufos, res.examClassResults[0])
+                        this.calcOverView(res.examClassResults[0])
+                        this.dataLoading = false
+                        this.tableLoading = false
+                    }
+                    //如果首次没有获取到有效数据(数据暂未生成),则尝试重复活动5次
+                    else {
+                        this.$Message.error({
+                            content: this.$t('learnActivity.score.dataError'),
+                            duration: 3
+                        })
+                        this.dataLoading = false
+                        this.tableLoading = false
+                    }
+                },
+                (err) => {
+                    this.$t('learnActivity.score.dataError')
+                    this.dataLoading = false
+                    this.tableLoading = false
+                }
+            )
+        },
+        //计算总览数据
+        calcOverView(data) {
+            console.log(data);
+            // 班级总人数
+            data.total.forEach(item => {
+                item.forEach(num => {
+                    this.overviewInfo.total += num
+                })
+            })
+            // 得分
+            // 先取第一套试卷的分数
+            data.stuScore[0].forEach((item, index) => {
+                if(item != -1) {
+                    this.overviewInfo.score += item
+                }
+                // 得了满分才算做对
+                this.overviewInfo.examNum += ((item != -1 && item === data.papers[0].point[index]) ? 1 : 0)
+            })
+            this.overviewInfo.average = data.average[0]
+        },
+    },
+    watch: {
+        examDetaiInfo: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                console.log('评测数据', n)
+                if (n && n.status === 200) {
+                    this.calcOverView(n)
+                    // this.getStudentAnswer()
+                }
+            }
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.exam-table-wrap {
+    margin-top: 5px;
+    padding: 10px;
+    width: 250px;
+    height: 270px;
+    box-shadow: 0px 2px 5px #e9e9e9;
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+}
+.data-count-item {
+    text-align: center;
+    width: 50%;
+    min-width: 115px;
+}
+.view-more {
+    cursor: pointer;
+    &:hover {
+        color: #2b85e4;
+    }
+}
+.stu-status-tag {
+    cursor: pointer;
+    font-size: 12px;
+    padding: 3px 8px;
+    font-weight: 800;
+    border-radius: 4px;
+    color: white;
+}
+.page-wrap {
+    float: right;
+    margin-top: 15px;
+}
+</style>
+<style lang="less">
+.exam-table-wrap .ivu-page-options-sizer {
+    margin-right: 0px;
+}
+</style>

+ 116 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Pick.vue

@@ -0,0 +1,116 @@
+<template>
+    <!-- 随机挑人 -->
+    <div class="pick-wrap" v-if="pickRes.length">
+        <p class="event-type">{{ $t("talMgmt.text40") }}:</p>
+        <div class="pick-item" v-for="(item,index) in pickRes" :key="index">
+            <p class="student-no">{{item.seatNo}}</p>
+            <p class="student-name">{{item.name}}</p>
+        </div>
+        <StudentClient></StudentClient>
+    </div>
+</template>
+<script>
+import StudentClient from '@/view/classrecord/eventchart/StudentClient.vue'
+export default {
+    components: {
+        StudentClient
+    },
+    props: {
+        pickData: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        return {
+            // pickRes: []
+        }
+    },
+    methods: {
+        getChinese(strValue) {
+            if (!strValue) return ''
+            var reg = /[\u4e00-\u9fa5]/g;
+            var names = strValue.match(reg);
+            if (names) {
+                return names.join("").substring(names.length - 2)
+            } else {
+                return strValue.substr(0, 2)
+            }
+        }
+    },
+    computed: {
+        pickRes() {
+            let data = []
+            if (this.pickData.PickupMemberId) {
+                let r = JSON.parse(this.pickData.PickupMemberId)
+                if (r) {
+                    r.forEach(item => {
+                        let s = this.students.find(stu => stu.seatID == item && stu.id === this.$store.state.userInfo.sub)
+                        if(s) {
+                            data.push({
+                                seatNo: item,
+                                name: s ? s.name : item
+                            })
+                        }
+                    })
+                }
+            }
+            return data
+        }
+    }
+    // watch: {
+    //     pickData: {
+    //         deep: true,
+    //         immediate: true,
+    //         handler(n, o) {
+    //             console.log('挑人数据', n)
+    //             if (n.PickupMemberId) {
+    //                 this.pickRes = JSON.parse(n.PickupMemberId)
+    //             } else {
+    //                 this.pickRes = []
+    //             }
+    //         }
+    //     }
+    // }
+}
+</script>
+<style scoped lang="less">
+.pick-wrap {
+    display: flex;
+}
+.pick-item {
+    margin-right: 20px;
+    width: 80px;
+    text-align: center;
+    background: rgba(0, 0, 0, 0.3);
+    height: 80px;
+    border: 2px solid #ff9900;
+    border-radius: 8px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    .student-no {
+        font-size: 30px;
+        color: black;
+        font-weight: 600;
+    }
+    .student-name {
+        color: #007cff;
+        font-weight: 800;
+        font-size: 14px;
+    }
+}
+.event-type {
+    margin-right: 20px;
+    font-size: 15px;
+    font-weight: 600;
+}
+</style>

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

@@ -154,7 +154,8 @@ export default {
             if (this.rcvData.clientWorks) {
                 data = this._.cloneDeep(this.rcvData.clientWorks)
             }
-            let sas = await this.$tools.getBlobSas(this.recordInfo.tmdid)
+            let code = this.recordInfo.scope === "school" ? this.recordInfo.school : this.recordInfo.tmdid
+            let sas = await this.$tools.getBlobSas(code)
             data.forEach(stu => {
                 //处理完整文件路径
                 if (stu.blobFiles) {

+ 256 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ReceiveBack.vue

@@ -0,0 +1,256 @@
+<template>
+    <!-- 作品收集 -->
+    <div class="receive-wrap">
+        <TeacherClient></TeacherClient>
+        <div class="receive-wrap">
+            <p class="clt-type">
+                {{ rcvData.eventName }}:
+            </p>
+            <div class="receive-wrap">
+                <div v-for="(item, rIndex) in receiveData" :key="`r${rIndex}`" class="receive-item">
+                    <div v-if="collateType == 0">
+                        收集的作品
+                    </div>
+                    <!-- 图片类型作品 -->
+                    <div v-else-if="collateType == 1">
+                        <img class="receive-img" :src="item.url" alt="" @click="viewImage(item.url)">
+                    </div>
+                    <!-- HTEX作品类型 -->
+                    <div v-else-if="collateType == 2">
+                        收集的HTEX作品
+                    </div>
+                    <!-- 音频 -->
+                    <div v-else-if="collateType == 3">
+                        <div class="audio-box" @click="viewAudio(item.url)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-audio-outline" />
+                        </div>
+                    </div>
+                    <!-- 视频 -->
+                    <div v-else-if="collateType == 4">
+                        <div class="audio-box" @click="viewAudio(item.url)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-video-outline" />
+                        </div>
+                    </div>
+                    <!-- 文字 -->
+                    <div v-else-if="collateType == 5">
+                        收集的文字
+                    </div>
+                    <!-- 附件 -->
+                    <div v-else-if="collateType == 6">
+                        <div class="audio-box" @click="downloadFile(item.url, item.name)">
+                            <Icon class="collate-type-icon" custom="iconfont icon-file" />
+                        </div>
+                    </div>
+                    <p style="text-align: center;">{{ item.name }}</p>
+                </div>
+            </div>
+        </div>
+        <!-- <StudentClient></StudentClient> -->
+        <!--文件预览-->
+        <div v-if="previewStatus" class="image-viewer">
+            <div style="width:fit-content;position:relative;margin:auto;">
+                <Icon type="md-close" class="close-icon" @click="closePreview" />
+                <video v-if="collateType == 4" id="previewVideo" :src="prevUrl" width="870" controls="controls" style="max-height: 800px;">
+                    {{$t('teachContent.tips8')}}
+                </video>
+                <audio v-else-if="collateType == 3" controls>
+                    <source :src="prevUrl">
+                    {{$t('teachContent.notAudio')}}
+                </audio>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import StudentClient from '@/view/classrecord/eventchart/StudentClient.vue'
+import TeacherClient from '@/view/classrecord/eventchart/TeacherClient.vue'
+export default {
+    props: {
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        rcvData: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    components: {
+        StudentClient, TeacherClient
+    },
+    data() {
+        return {
+            prevUrl: '',
+            previewStatus: false,
+            collateType: 0,
+            cltTypeMap: {
+                0: {
+                    type: 'None',
+                    text: ''
+                },
+                1: {
+                    type: 'Image',
+                    text: '图片'
+                },
+                2: {
+                    type: 'Htex',
+                    text: 'HTEX'
+                },
+                3: {
+                    type: 'Audio',
+                    text: '音频'
+                },
+                4: {
+                    type: 'Video',
+                    text: '视频'
+                },
+                5: {
+                    type: 'Text',
+                    text: '文字'
+                },
+                6: {
+                    type: 'File',
+                    text: '附件'
+                },
+            },
+            receiveData: []
+        }
+    },
+    methods: {
+        viewAudio(url) {
+            this.prevUrl = url
+            this.previewStatus = true
+        },
+        closePreview() {
+            this.previewStatus = false
+        },
+        viewImage(url) {
+            this.$hevueImgPreview(url)
+        },
+        downloadFile(url, name) {
+            const downloadRes = async () => {
+                let response = await fetch(url); // 内容转变成blob地址
+                let blob = await response.blob(); // 创建隐藏的可下载链接
+                let objectUrl = window.URL.createObjectURL(blob);
+                let a = document.createElement('a');
+                a.href = objectUrl;
+                let fileName = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('?'))
+                a.download = `${this.rcvData.eventName}-${name}-${fileName}`
+                a.click()
+                a.remove();
+            }
+            downloadRes();
+        },
+        async getSas(data) {
+            // let data = this.rcvData
+            let code = this.recordInfo.scope === "school" ? this.recordInfo.school : this.recordInfo.tmdid
+            let sas = await this.$tools.getBlobSas(code)
+            if(data.Works) {
+                data.Works.forEach(stu => {
+                    //处理完整文件路径
+                    let urls = `${sas.url}/${sas.name}/records/${this.recordInfo.id}${stu}?${sas.sas}`
+                    let stuId = stu.substring(stu.lastIndexOf('/Clients/') + 9, stu.lastIndexOf('/Task'))
+                    let stuInfo = this.students.find(item => {
+                        return item.id === stuId
+                    })
+                    stuInfo.url = urls
+                    this.receiveData.push(stuInfo)
+                })
+            }
+        },
+    },
+    created () {
+    },
+    computed: {
+    },
+    watch: {
+        rcvData: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                this.receiveData = []
+                this.collateType = n.WrkType
+                this.getSas(n)
+            }
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.image-viewer {
+    background-color: rgba(0, 0, 0, 0.8);
+    z-index: 9999;
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    top: 0;
+    left: 0;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    text-align: center;
+    padding: 50px;
+    padding-top: 8%;
+    .close-icon {
+        position: absolute;
+        right: -16px;
+        top: -16px;
+        font-size: 24px;
+        color: black;
+        cursor: pointer;
+        border-radius: 50px;
+        background: white;
+        padding: 2px;
+        z-index: 9999;
+    }
+}
+.receive-wrap {
+    display: flex;
+}
+.receive-item {
+    margin-right: 20px;
+}
+.receive-img {
+    max-width: 150px;
+    max-height: 150px;
+    border: 1px solid #eeeeee;
+    cursor: pointer;
+}
+.audio-box {
+    width: 80px;
+    height: 80px;
+    border: 1px solid #eeeeee;
+    background: #f3f3f3;
+    text-align: center;
+    line-height: 80px;
+    border-radius: 5px;
+    cursor: pointer;
+    &:hover {
+        // background: #f9f9f9;
+        border-color: #2d8cf0;
+    }
+    &:hover .collate-type-icon {
+        color: #2d8cf0;
+    }
+}
+.collate-type-icon {
+    font-size: 30px;
+}
+.clt-type {
+    /* margin-right: 10px;
+    font-size: 15px;
+    font-weight: 600; */
+
+    display: inline-block;
+    vertical-align: top;
+}
+</style>

+ 204 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ScoreBarChart.vue

@@ -0,0 +1,204 @@
+<template>
+    <div class="score-bar-chart">
+        <!-- <h4>{{ $t("studentWeb.exam.chart.scoreDistribution") }}</h4> -->
+        
+        <!-- <div class="no-data">
+            <p class="no-data-title">{{ $t("studentWeb.exam.chart.scoreDistribution") }}</p>
+        </div> -->
+        <!-- <div class="attendance">
+            {{ $t("studentWeb.exam.chart.participant") }}
+            <span class="timeNum">{{ Attendance }}</span>
+            {{ $t("studentWeb.exam.chart.student") }}
+        </div> -->
+        <div class="score-stu" :id="`scoreStu-${id}`"></div>
+    </div>
+</template>
+
+<script>
+export default {
+    name: "ScoreBarChart",
+
+    computed: {
+        Attendance: function () {
+            let att = 0
+            this.total[0].data.map(item => {
+                att += item
+            })
+            return att
+        },
+    },
+    props: {
+        total: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        subjectNames: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+    },
+    data() {
+        return {
+            id: "",
+        }
+    },
+    created () {
+        this.id = this.$jsFn.getBtwRandom(0, 100000000)
+    },
+    methods: {
+        setMap() {
+            // return new Promise((r, j) => {
+                let myChart = this.$echarts.init(document.getElementById(`scoreStu-${this.id}`), 'macarons')
+                let option = {
+                    title: {
+                        text: this.$t("studentWeb.exam.chart.scoreDistribution"),
+                        left: 'center',
+                        "top": 0,
+                        textStyle: {
+                            // fontWeight: "normal",
+                            fontSize: "14",
+                            color: "#484848",
+                        },
+                        // show: false,
+                    },
+                    tooltip: {
+                        trigger: "item",
+                        padding: [4, 12],
+                        // backgroundColor: "white",
+                        textStyle: {
+                            // color: "black",
+                            // fontFamily: "Ariel",
+                            // fontWeight: "bolder",
+                        },
+                        formatter: `{b0}${this.$t('studentWeb.exam.chart.score')}: <span>{c0}</span>${this.$t('studentWeb.exam.chart.student')}`,
+                    },
+                    /* grid: {
+                        top: "20%",
+                        left: "6%",
+                        right: "12%",
+                        bottom: "18%",
+                        containLabel: true,
+                    }, */
+                    xAxis: {
+                        type: "category",
+                        data: ['0-59', '59-70', '70-80', '80-90', '90-100'],
+                        splitLine: {
+                            lineStyle: {
+                                color: "transparent",
+                            },
+                        },
+                        //座標軸線的設置
+                        axisLine: {
+                            lineStyle: {
+                                color: "gray",
+                                width: 1,
+                            },
+                        },
+                        //座標軸的刻度顏色
+                        axisLabel: {
+                            color: "black",
+                        },
+                    },
+                    yAxis: {
+                        type: "value",
+                        show: true,
+                        //座標軸線的設置
+                        axisLine: {
+                            lineStyle: {
+                                color: "transparent",
+                            },
+                        },
+                        //座標軸的刻度顏色
+                        axisLabel: {
+                            color: "black",
+                        },
+                    },
+                    legend: {
+                        data: this.subjectNames,
+                        bottom: 0,
+                    },
+                    barCategoryGap: "1px",
+                    series: this.total/* [
+                        {
+                            data: this.total,
+                            type: "bar",
+                            // itemStyle: { color: "#73a373" }
+                        },
+                    ] */,
+                }
+                console.log(option.series);
+                myChart.setOption(option)
+            // })
+        },
+    },
+    watch: {
+        total: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                
+            },
+        },
+    },
+    mounted () {
+        this.setMap()
+    }
+}
+</script>
+
+<style scoped>
+.score-bar-chart {
+    width: 400px;
+    /* margin: auto; */
+    /* margin-bottom: 50px; */
+    padding: 15px 40px 0px 40px;
+    height: 270px;
+    color: rgba(0, 0, 0, 0.726);
+}
+
+.score-bar-chart .no-data .no-data-title {
+    color: #484848;
+    font-weight: bolder;
+}
+
+.attendance {
+    position: relative;
+    top: 10px;
+    margin-top: -20px;
+    text-align: right;
+    padding-left: 12%;
+}
+.avatar {
+    width: 32px;
+    height: 32px;
+    position: relative;
+    top: -60px;
+    left: 26.5%;
+}
+.attendance .timeNum {
+    color: #575757;
+    font-weight: 500;
+    font-size: 44px;
+}
+
+.score-stu {
+    width: 306px;
+    height: 250px;
+}
+
+@media screen and (max-width: 1365px) {
+    .score-stu {
+        width: 180px;
+    }
+}
+
+@media screen and (max-width: 995px) {
+    .score-stu {
+        width: 306px;
+    }
+}
+</style>

+ 9 - 7
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/StuReceive.vue

@@ -9,31 +9,31 @@
                 收集的作品
             </div>
             <!-- 图片类型作品 -->
-            <div v-else-if="collateType == 1">
+            <div v-else-if="collateType == 'Image'">
                 <img v-for="(blob,index) in item.blobFiles" :key="index" class="receive-img" :src="blob" alt="" @click="viewImage(blob)">
             </div>
             <!-- HTEX作品类型 -->
-            <div v-else-if="collateType == 2">
+            <div v-else-if="collateType == 'Htex'">
                 收集的HTEX作品
             </div>
             <!-- 音频 -->
-            <div v-else-if="collateType == 3">
+            <div v-else-if="collateType == 'Audio'">
                 <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(blob)">
                     <Icon class="collate-type-icon" custom="iconfont icon-audio-outline" />
                 </div>
             </div>
             <!-- 视频 -->
-            <div v-else-if="collateType == 4">
+            <div v-else-if="collateType == 'Video'">
                 <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="viewAudio(blob)">
                     <Icon class="collate-type-icon" custom="iconfont icon-video-outline" />
                 </div>
             </div>
             <!-- 文字 -->
-            <div v-else-if="collateType == 5">
+            <div v-else-if="collateType == 'Text'">
                 收集的文字
             </div>
             <!-- 附件 -->
-            <div v-else-if="collateType == 6">
+            <div v-else-if="collateType == 'File'">
                 <div v-for="(blob,index) in item.blobFiles" :key="index" class="audio-box" @click="downloadFile(blob,item)">
                     <Icon class="collate-type-icon" custom="iconfont icon-file" />
                 </div>
@@ -160,7 +160,8 @@ export default {
                 data = this._.cloneDeep(this.rcvData.clientWorks)
                 data = data.filter(item => this.nowStuInfo.seatID === item.seatID)
             }
-            let sas = await this.$tools.getBlobSas(this.recordInfo.tmdid)
+            let code = this.recordInfo.scope === "school" ? this.recordInfo.school : this.recordInfo.tmdid
+            let sas = await this.$tools.getBlobSas(code)
             data.forEach(stu => {
                 //处理完整文件路径
                 if (stu.blobFiles) {
@@ -254,5 +255,6 @@ export default {
     margin-right: 10px;
     font-size: 15px;
     font-weight: 600;
+    line-height: 40px;
 }
 </style>

+ 231 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/myWorks.vue

@@ -0,0 +1,231 @@
+<template>
+    <!-- 作品收集 -->
+    <div class="receive-wrap">
+        <div v-for="(item, index) in receiveData" :key="index">
+            <p>
+                {{ $t("studentWeb.myAchievement.hwName") }}:{{ item.jobName }}
+            </p>
+            <div v-if="item.collateType === 0">
+                收集的作品
+            </div>
+            <!-- 图片类型作品 -->
+            <div v-else-if="item.collateType === 'Image'">
+                <img v-for="(blob, Bindex) in item.blobFiles" :key="Bindex" class="receive-img" :src="blob">
+            </div>
+            <!-- HTEX作品类型 -->
+            <div v-else-if="item.collateType === 'Htex'">
+                收集的HTEX作品
+            </div>
+            <!-- 音频 -->
+            <div v-else-if="item.collateType === 'Audio'">
+                <div v-for="(blob, Aindex) in item.blobFiles" :key="Aindex" class="audio-box">
+                    <audio controls>
+                        <source :src="blob">
+                        {{$t('teachContent.notAudio')}}
+                    </audio>
+                </div>
+            </div>
+            <!-- 视频 -->
+            <div v-else-if="item.collateType === 'Video'">
+                <div v-for="(blob, Vindex) in item.blobFiles" :key="Vindex" class="audio-box">
+                    <video :src="blob" width="870" controls="controls" style="max-height: 800px;">
+                        {{$t('teachContent.tips8')}}
+                    </video>
+                </div>
+            </div>
+            <!-- 文字 -->
+            <div v-else-if="item.collateType === 'Text'">
+                收集的文字
+            </div>
+            <!-- 附件 -->
+            <div v-else-if="item.collateType === 'File'">
+                <div v-for="(blob, Findex) in item.blobFiles" :key="Findex" class="audio-box" @click="downloadFile(blob,item)">
+                    <Icon class="collate-type-icon" custom="iconfont icon-file" />
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    props: {
+        recordInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        rcvData: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        return {
+            prevUrl: '',
+            previewStatus: false,
+            // collateType: 0,
+            cltTypeMap: {
+                0: {
+                    type: 'None',
+                    text: ''
+                },
+                1: {
+                    type: 'Image',
+                    text: '图片'
+                },
+                2: {
+                    type: 'Htex',
+                    text: 'HTEX'
+                },
+                3: {
+                    type: 'Audio',
+                    text: '音频'
+                },
+                4: {
+                    type: 'Video',
+                    text: '视频'
+                },
+                5: {
+                    type: 'Text',
+                    text: '文字'
+                },
+                6: {
+                    type: 'File',
+                    text: '附件'
+                },
+            },
+            receiveData: []
+        }
+    },
+    methods: {
+        viewAudio(url) {
+            this.prevUrl = url
+            this.previewStatus = true
+        },
+        closePreview() {
+            this.previewStatus = false
+        },
+        viewImage(url) {
+            this.$hevueImgPreview(url)
+        },
+        downloadFile(url, taskInfo) {
+            const downloadRes = async () => {
+                let response = await fetch(url); // 内容转变成blob地址
+                let blob = await response.blob(); // 创建隐藏的可下载链接
+                let objectUrl = window.URL.createObjectURL(blob);
+                let a = document.createElement('a');
+                a.href = objectUrl;
+                let fileName = url.substring(url.lastIndexOf('/' + 1), url.lastIndexOf('?'))
+                a.download = `${this.rcvData.jobName}-${taskInfo.seatID}-${fileName}`
+                a.click()
+                a.remove();
+            }
+            downloadRes();
+        },
+        async getSas() {
+            let data = this._.cloneDeep(this.rcvData)
+            let code = this.recordInfo.scope === "school" ? this.recordInfo.school : this.recordInfo.tmdid
+            let sas = await this.$tools.getBlobSas(code)
+            data.forEach(stu => {
+                //处理完整文件路径
+                if (stu.blobFiles) {
+                    stu.blobFiles = stu.blobFiles.map(f => {
+                        return `${sas.url}/${sas.name}/records/${this.recordInfo.id}${f}?${sas.sas}`
+                    })
+                }
+            })
+            this.receiveData = data
+        },
+    },
+    created () {
+        // this.getSas()
+    },
+    computed: {
+    },
+    watch: {
+        rcvData: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                // this.collateType = n.collateType
+                this.getSas()
+            }
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.image-viewer {
+    background-color: rgba(0, 0, 0, 0.8);
+    z-index: 9999;
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    top: 0;
+    left: 0;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    text-align: center;
+    padding: 50px;
+    padding-top: 8%;
+    .close-icon {
+        position: absolute;
+        right: -16px;
+        top: -16px;
+        font-size: 24px;
+        color: black;
+        cursor: pointer;
+        border-radius: 50px;
+        background: white;
+        padding: 2px;
+        z-index: 9999;
+    }
+}
+.receive-wrap {
+    display: flex;
+}
+.receive-item {
+    margin-right: 20px;
+}
+.receive-img {
+    max-width: 150px;
+    max-height: 150px;
+    border: 1px solid #eeeeee;
+    cursor: pointer;
+}
+.audio-box {
+    width: 80px;
+    height: 80px;
+    border: 1px solid #eeeeee;
+    background: #f3f3f3;
+    text-align: center;
+    line-height: 80px;
+    border-radius: 5px;
+    cursor: pointer;
+    &:hover {
+        // background: #f9f9f9;
+        border-color: #2d8cf0;
+    }
+    &:hover .collate-type-icon {
+        color: #2d8cf0;
+    }
+}
+.collate-type-icon {
+    font-size: 30px;
+}
+.clt-type {
+    margin-right: 10px;
+    font-size: 15px;
+    font-weight: 600;
+    line-height: 40px;
+}
+</style>

+ 529 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/newClassRecord.less

@@ -0,0 +1,529 @@
+
+.courseware-wrap {
+    // width: 50%;
+    position: relative;
+    height: 450px;
+    background-color: #808080;
+    // margin-right: 5px;
+
+    .page-wrap {
+        opacity: 1;
+    }
+
+    .cur-page-tag {
+        opacity: 0;
+    }
+
+    .course-cur-img {
+        height: 100%;
+    }
+}
+
+.cur-page-tag {
+    position: absolute;
+    left: 5px;
+    bottom: 5px;
+    width: 30px;
+    height: 30px;
+    font-size: 18px;
+    line-height: 30px;
+    display: block;
+    background: rgba(255, 153, 0, 0.9);
+    border-radius: 50%;
+    text-align: center;
+    opacity: 1;
+    transition: opacity 0.2s;
+    color: white;
+    box-shadow: 0px 0px 5px rgb(255, 153, 0);
+}
+
+.page-wrap {
+    left: 0px;
+    bottom: 0px;
+    position: absolute;
+    padding: 6px;
+    color: white;
+    width: 100%;
+    height: 36px;
+    background: rgba(43, 51, 63, 0.7);
+    text-align: center;
+    opacity: 0;
+    transition: opacity 1.2s;
+    border-right: 2px solid black;
+}
+
+.record-info {
+    background-color: #fff;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 10;
+
+    .record-head {
+        display: flex;
+        justify-content: space-between;
+
+        &>div>p {
+            margin-bottom: 5px;
+        }
+
+        .attend-type {
+            transform: rotate(338deg);
+            font-size: 17px;
+            font-weight: bold;
+        }
+    }
+
+    .testTitle {
+        background-color: rgb(255, 255, 255);
+        // color: #24b880;
+        font-size: 20px;
+        padding: 5px 15px;
+        position: fixed;
+        width: 100%;
+        height: 44px;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+        z-index: 10;
+
+        .logoutIcon {
+            cursor: pointer;
+            margin-right: 10px;
+        }
+    }
+
+    img {
+        border-radius: 4px;
+        margin-right: 20px;
+        border: 1px solid rgba(173, 173, 173, 0.233);
+        width: 100%;
+    }
+
+    .count-box {
+        justify-content: center;
+        display: flex;
+        position: relative;
+    }
+
+    .video-player-box {
+        // width: 50%;
+        justify-content: center;
+        height: 450px;
+        display: flex;
+        // margin-left: 5px;
+        position: relative;
+        // background-color: #fff;
+    }
+
+    .message-area {
+        position: relative;
+        height: 75vh;
+        padding: 10px;
+        margin-bottom: 20px;
+
+        .filter-type {
+            position: absolute;
+            right: 0;
+            top: 10px;
+            padding-right: 10px;
+            padding-left: 10px;
+            text-align: center;
+            z-index: 9;
+            display: flex;
+            flex-direction: column;
+            background-color: #F1F1F1;
+
+            span {
+                border: none;
+                padding: 0px;
+                font-size: 32px;
+
+                &:hover {
+                    cursor: pointer;
+                    color: #24b880;
+                }
+
+                .select-filter {
+                    color: #24b880;
+                }
+            }
+        }
+
+        .message-box {
+            display: flex;
+            border-bottom: 1px #ccc solid;
+
+            .message-page {
+                // width: 10%;
+                padding: 10px 15px;
+                padding-bottom: 0;
+                // border-right: 1px #ccc dashed;
+
+                img {
+                    margin-top: 5px;
+                    margin-right: 0;
+                    width: 130px;
+                    cursor: pointer;
+                }
+
+                .messagetoPPT-tag {
+                    background-color: rgba(0, 0, 0, 0.1);
+                    white-space: initial;
+                    display: block;
+                    font-size: 14px;
+                    height: auto;
+                    padding: 3px 10px;
+                    border-radius: 4px;
+
+                    &:hover {
+                        cursor: pointer;
+                        color: #ffffff;
+                        background-color: #24b880;
+                    }
+                }
+            }
+
+            .message-record {
+                padding: 10px 15px;
+                padding-bottom: 0;
+                width: calc(100% - 160px);
+                // min-width: 780px;
+
+                .message-item {
+                    white-space: nowrap;
+                    display: block;
+                    position: relative;
+                    padding-top: 20px;
+                    margin-bottom: 50px;
+
+                    .message-avatar {
+                        border-radius: 50%;
+                        float: left;
+                        width: 32px !important;
+                        height: 32px !important;
+                        margin: 0px 15px;
+                        margin-top: -10px;
+                    }
+
+                    .user-name {
+                        white-space: nowrap;
+                        font-weight: bolder;
+                        position: absolute;
+                        top: 0;
+                        left: 55px;
+                        // position: absolute;
+                        // top: -12px;
+                        // margin-left: -8px;
+                    }
+
+                    .message-content {
+                        background: #d4ede1;
+                        padding: 10px;
+                        display: inline-block;
+                        border-radius: 4px;
+                        position: relative;
+                        // width: 85%;
+
+                        &::before {
+                            content: "";
+                            position: absolute;
+                            top: 3px;
+                            // margin-left: -16px;
+                            left: -7px;
+                            width: 0;
+                            height: 0;
+                            border-style: solid;
+                            border-width: 5px 7.7px 5px 0;
+                            border-color: transparent #d4ede1 transparent transparent;
+                        }
+
+                        .message-text {
+                            img {
+                                max-width: 80%;
+                            }
+
+                            .pick-item {
+                                width: 100px;
+                                text-align: center;
+                                background: rgba(0, 0, 0, 0.3);
+                                height: 100px;
+                                border: 2px solid #ff9900;
+                                border-radius: 8px;
+                                display: flex;
+                                flex-direction: column;
+                                justify-content: center;
+
+                                .student-no {
+                                    font-size: 40px;
+                                    color: black;
+                                    font-weight: 600;
+                                }
+
+                                .student-name {
+                                    color: #007cff;
+                                    font-weight: 800;
+                                    font-size: 16px;
+                                }
+                            }
+                        }
+
+                    }
+
+                    .message-time {
+                        white-space: nowrap;
+                        color: gray;
+                        font-size: 10px;
+                        font-weight: 400;
+                        // position: absolute;
+                        // bottom: -20px;
+                        // margin-left: -8px;
+                        position: absolute;
+                        // bottom: 0;
+                        left: 55px;
+                    }
+                }
+
+                .teacher-item {
+                    display: block;
+                    // padding-right: 5%;
+                    text-align: right !important;
+                    position: relative;
+                    margin-bottom: 50px;
+
+                    .message-avatar {
+                        border-radius: 50%;
+                        float: left;
+                        width: 32px !important;
+                        height: 32px !important;
+                        margin: 0px 15px;
+                        margin-top: -10px;
+                    }
+
+                    .user-name {
+                        white-space: nowrap;
+                        font-weight: bolder;
+                        position: absolute;
+                        top: 0;
+                        left: 55px;
+                        // position: absolute;
+                        // top: -12px;
+                        // margin-left: -8px;
+                    }
+
+                    .message-content {
+                        background: #ffecc2;
+                        padding: 10px;
+                        display: inline-block;
+                        border-radius: 4px;
+                        position: relative;
+                        // width: 85%;
+
+                        &::before {
+                            content: "";
+                            position: absolute;
+                            top: 3px;
+                            // margin-left: -16px;
+                            right: -9px;
+                            width: 0;
+                            height: 0;
+                            border-style: solid;
+                            border-width: 5px 0 5px 8.7px;
+                            border-color: transparent transparent transparent #ffecc2;
+                        }
+
+                        .message-text {
+                            img {
+                                max-width: 80%;
+                            }
+
+                            .pick-item {
+                                width: 100px;
+                                text-align: center;
+                                background: rgba(0, 0, 0, 0.3);
+                                height: 100px;
+                                border: 2px solid #ff9900;
+                                border-radius: 8px;
+                                display: flex;
+                                flex-direction: column;
+                                justify-content: center;
+
+                                .student-no {
+                                    font-size: 40px;
+                                    color: black;
+                                    font-weight: 600;
+                                }
+
+                                .student-name {
+                                    color: #007cff;
+                                    font-weight: 800;
+                                    font-size: 16px;
+                                }
+                            }
+                        }
+
+                    }
+
+                    .message-time {
+                        white-space: nowrap;
+                        color: gray;
+                        font-size: 10px;
+                        font-weight: 400;
+                        // position: absolute;
+                        // bottom: -20px;
+                        // margin-left: -8px;
+                        position: absolute;
+                        // bottom: 0;
+                        right: 55px;
+                    }
+                }
+
+                .teacher-client-icon {
+                    font-size: 30px;
+                    padding: 5px;
+                    color: #ffffff;
+                    border-radius: 50%;
+                    background: #19be6b;
+                    margin-right: 15px;
+                    float: left;
+                }
+
+                .student-client-icon {
+                    font-size: 30px;
+                    padding: 5px;
+                    color: #ffffff;
+                    border-radius: 50%;
+                    background: #efbb49;
+                    float: right;
+                    margin-left: 15px;
+                }
+
+                .event-item {
+                    border: 1px dashed transparent;
+                    margin-bottom: 15px;
+                    padding: 5px 5px;
+                    overflow-x: scroll;
+                    
+                    &:hover {
+                        border: 1px dashed #e0e0e0;
+                    }
+                }
+
+                .student-event {
+                    display: flex;
+                    justify-content: end;
+                }
+            }
+        }
+    }
+
+    .dec {
+        padding: 0px;
+        // height: 800px;
+        overflow: hidden;
+        position: relative;
+
+        .no-interaction {
+            text-align: center;
+            font-size: 20px;
+            margin-top: 80px;
+            font-weight: bold;
+        }
+    }
+
+    .title-rect-name>span {
+        background-color: #24B880;
+        font-size: 12px;
+        padding: 3px;
+        border: 1px solid #ccc;
+        border-radius: 4px;
+        color: #fff;
+        cursor: pointer;
+    }
+
+    .e-note-tag {
+        font-size: 16px;
+        padding: 2px 5px;
+        margin-left: 30px;
+        vertical-align: top;
+        cursor: pointer;
+        user-select: none;
+
+        &:hover {
+            color: #24b880;
+        }
+    }
+
+    .record-content {
+        display: flex;
+        justify-content: space-between;
+        
+        .record-left {
+            width: 36%;
+            height: 85vh;
+
+            & > div{
+                margin-top: 10px;
+            }
+        }
+
+        .record-right {
+            width: 63%;
+        }
+    }
+
+}
+
+.image-viewer {
+    background-color: rgba(0, 0, 0, 0.8);
+    z-index: 9999;
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    top: 0;
+    left: 0;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    img {
+        max-width: 50%;
+        margin: 5% auto;
+        border: none;
+    }
+
+    .close-icon {
+        position: absolute;
+        right: 15px;
+        top: 10px;
+        font-size: 20px;
+        color: white;
+        cursor: pointer;
+    }
+}
+
+.no-video-tips {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    color: #ff9900;
+    width: 100%;
+}
+
+@media screen and (max-width: 768px) {
+    .record-info .record-content {
+        display: block;
+
+        .record-left,
+        .record-right {
+            width: 100%;
+        }
+
+        .record-left {
+            margin-bottom: 30px;
+        }
+    }
+}

+ 789 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/newClassRecord.vue

@@ -0,0 +1,789 @@
+<template>
+    <div class="record-info">
+        <Loading v-show="isLoad" bgColor="rgba(0, 0, 0, 0.3)"></Loading>
+        <div class="testTitle">
+            <span class="logoutIcon" @click="quitRec">
+                <svg-icon icon-class="logout" />
+            </span>
+            <span class="testTitleText">{{$t("studentWeb.courseContent.classRecord")}}</span>
+        </div>
+        <div style="margin-top: 44px; padding: 10px 15px;">
+            <h2 class="event-title" v-if="recordInfo">{{ recordInfo.name }}</h2>
+            <div style="text-align: right; margin-bottom: 5px;">
+                <Button type="primary" @click="showWorks = true" :disabled="!myWorks.length">
+                    <Icon custom="iconfont icon-zuopin" size="16" />
+                    {{ $t('studentWeb.courseContent.myWorks') }}
+                </Button>
+                <Button type="primary" @click="showNote = true" v-if="recordInfo.myNote.length">
+                    <Icon custom="iconfont icon-myNote" size="17" />
+                    {{ $t('studentWeb.courseContent.mynotes') }}
+                </Button>
+                <Button type="primary" @click="viewENote">
+                    <Icon custom="iconfont icon-activityT" />
+                    {{ $t('studentWeb.courseContent.notes') }}
+                </Button>
+                <Button type="primary" @click="isShowVd = !isShowVd" v-show="hasVideo">
+                    <Icon :type="isShowVd ? 'md-podium' : 'logo-youtube'" />
+                    {{ isShowVd ? $t('cusMgt.rcd.dataCount') : $t('cusMgt.rcd.videoData') }}
+                </Button>
+            </div>
+            <div class="record-content">
+                <div class="record-left">
+                    <vuescroll>
+                        <div style="padding-right: 10px;">
+                            <div class="record-head" v-if="recordInfo">
+                                <div>
+                                    <p style="margin-left: 20px;">
+                                        <Icon type="ios-contact-outline" style="font-weight: bold;" class="base-info-icon" />{{ $t('studentWeb.baseInfo.teacher') }}
+                                        <span class="base-info-text">{{ recordInfo.tmdname }}</span>
+                                    </p>
+                                    <template v-if="courseNow">
+                                        <p style="margin-left: 20px;">
+                                            <svg-icon class="base-info-icon" icon-class="course" />{{ $t('studentWeb.baseInfo.subjectName') }}
+                                            <span class="base-info-text">{{ courseNow.name }}</span>
+                                        </p>
+                                        <p style="margin-left: 20px;">
+                                            <Icon custom="iconfont icon-mingdan" class="base-info-icon" />{{ $t('studentWeb.baseInfo.stuList') }}
+                                            <template v-if="courseNow.className.length">
+                                                <span class="base-info-text" v-for="(item, index) in courseNow.className" :key="index" style="margin-right: 10px;">{{ item.name }}</span>
+                                            </template>
+                                        </p>
+                                    </template>
+                                    <p style="margin-left: 20px;">
+                                        <Icon type="md-timer" class="base-info-icon" />{{ $t('studentWeb.baseInfo.duration') }}:
+                                        <span class="base-info-text">{{ recordInfo.time }}</span>
+                                    </p>
+                                    <p style="margin-left: 20px;">
+                                        <svg-icon icon-class="time" class="base-info-icon" />{{$t('studentWeb.baseInfo.classTime')}}:
+                                        <span class="base-info-text">{{ recordInfo.startTime }}</span>
+                                    </p>
+                                </div>
+                                <div>
+                                    <i-circle :percent="100" :size="100" :stroke-width="10" :stroke-color="attentColor[attendType]">
+                                        <p class="attend-type">{{ $t(`studentWeb.hiteachNote.dataCount.attendTypeList[${attendType}]`) }}</p>
+                                    </i-circle>
+                                </div>
+                            </div>
+                            <!-- <div class="count-box">
+                                <Alert v-show="!hasVideo" class="no-video-tips" type="warning" show-icon>
+                                    {{$t('cusMgt.rcd.noVideo')}}
+                                </Alert>
+                                <DataCount :nowStuInfo="nowStuInfo" :rcdInfo="baseData" v-if="baseData"></DataCount>
+                            </div> -->
+                            
+                            <div v-if="hasVideo" v-show="isShowVd" class="video-player-box">
+                                <video style="width: 100%;" id="recordVideo" class="video-js vjs-default-skin" type="video/mp4"></video>
+                            </div>
+                            <div v-show="!isShowVd" class="video-player-box" style="padding:25px 0px">
+                                <Alert v-show="!hasVideo" class="no-video-tips" type="warning" show-icon>
+                                    {{$t('cusMgt.rcd.noVideo')}}
+                                </Alert>
+                                <DataCount :nowStuInfo="nowStuInfo" :rcdInfo="baseData" v-if="baseData"></DataCount>
+                            </div>
+                            <div class="courseware-wrap">
+                                <!-- <DrawHTEX :mapJson="mapJson"></DrawHTEX> -->
+                                <img :src="curImg" alt="" class="course-cur-img">
+                                <div class="page-wrap">
+                                    <Page :total="pageList.length" :current="curPage" :page-size="1" size="small" @on-change="getCurHTEX" />
+                                    <!-- <Icon v-if="pageList.length" type="md-qr-scanner" class="full-screen-icon" @click="viewHtex" /> -->
+                                </div>
+                                <!-- <span class="cur-page-tag">{{ curPage }}</span> -->
+                            </div>
+                        </div>
+                    </vuescroll>
+                </div>
+                <div class="record-right">
+                    <div>
+                        <h2 class="title-rect-name">
+                            {{ $t("studentWeb.hiteachNote.classInteractionRecord") }}
+                        </h2>
+                        <div style="width: 100%; display: flex; justify-content: space-between;">
+                            <div>
+                                <!-- <Button type="primary">时间</Button>
+                                <Button type="primary">页次</Button> -->
+                            </div>
+                            <div>
+                                <Button type="warning" @click="filterFn('all')">{{ $t("cusMgt.rcd.filter1") }}</Button>
+                                <Button type="warning" :disabled="!filtertype.push" @click="filterFn('push')">{{ $t("cusMgt.rcd.filter2") }}({{ filtertype.push }})</Button>
+                                <Button type="warning" :disabled="!filtertype.task" @click="filterFn('task')">{{ $t("cusMgt.rcd.filter3") }}({{ filtertype.task }})</Button>
+                                <Button type="warning" :disabled="!filtertype.irs" @click="filterFn('irs')">{{ $t("cusMgt.rcd.filter4") }}({{ filtertype.irs }})</Button>
+                                <Button type="warning" :disabled="!filtertype.exam" @click="filterFn('exam')">{{ $t("cusMgt.rcd.filter5") }}({{ filtertype.exam }})</Button>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="dec">
+                        <div class="message-area" v-if="haveInteraction">
+                            <vuescroll ref="datawrap">
+                                <div style="margin-bottom: 50px;">
+                                    <div v-for="(items, index) in showPageList" :key="index" :id="'page' + (items.page)">
+                                        <div v-if="items.pageData.length" class="message-box">
+                                            <div class="message-page">
+                                                <div @click="toVideo(index + 1, $event)">
+                                                    <img :src="items.img" @click="openViewer(items.img)">
+                                                </div>
+                                            </div>
+                                            <div class="message-record">
+                                                <template v-if="items.pageData.length">
+                                                    <div v-for="event in items.pageData" :key="event.Time">
+                                                        <!-- 即问即答 -->
+                                                        <div v-if="currentfilterType === '' || currentfilterType === 'ShowAnsLoad'">
+                                                            <ShowQues class="event-item student-event" v-if="event.Event === 'PopQuesLoad' || event.Event === 'ReAtmpAnsStrt'" :nowStuInfo="nowStuInfo" :evtType="event.Event" :irsData="event.data"></ShowQues>
+                                                            <PopQues class="event-item" v-if="event.Event === 'PopQuesLoad' || event.Event === 'ReAtmpAnsStrt'" :evtType="event.Event" :irsData="event.data"></PopQues>
+                                                        </div>
+                                                        <!-- 抢权 -->
+                                                        <Buzr class="event-item student-event" v-if="event.Event === 'BuzrAns' && baseData" :buzrData="event.data" :students="baseData.student"></Buzr>
+                                                        <!-- 推送 -->
+                                                        <Push class="event-item" v-if="event.Event === 'FastPgPush' && (currentfilterType === '' || currentfilterType === 'doc')" :pushData="event.data"></Push>
+                                                        <!-- 作品收集 -->
+                                                        <StuReceive class="student-event event-item" v-if="event.Event === 'WrkSpaceEnd' && baseData" :nowStuInfo="nowStuInfo" :rcvData="event.data" :recordInfo="recordInfo" :students="baseData.student"></StuReceive>
+                                                        <!-- 老师收集的所有作品目前不展示,只展示老师回贴的学生作品 -->
+                                                        <!-- <Receive :recordInfo="recordInfo" class="student-event event-item" v-if="event.Event === 'WrkCmp' && baseData" :rcvData="event.data" :students="baseData.student"></Receive> -->
+                                                        <ReceiveBack class="event-item" v-if="event.Event === 'WrkCmp' && event.data && baseData" :recordInfo="recordInfo" :rcvData="event.data" :students="baseData.student"></ReceiveBack>
+                                                        <!-- 随机挑人 -->
+                                                        <Pick class="event-item student-event" v-if="event.Event === 'PickupResult' && baseData" :pickData="event.data" :students="baseData.student"></Pick>
+                                                        <!-- 课中评测 -->
+                                                        <Exam class="student-event event-item" :examInfo="event.data" :recordInfo="recordInfo" v-if="event.Event === 'SPQStrt'"></Exam>
+                                                    </div>
+                                                </template>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </vuescroll>
+                        </div>
+                        <div v-else class="no-interaction">
+                            {{ $t("studentWeb.hiteachNote.noContent") }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <Modal v-model="showNote" :title="$t('studentWeb.courseContent.mynotes')" width="800">
+            <img :src="item" alt="" v-for="(item, index) in recordInfo.myNote" :key="index" style="width: 100%">
+        </Modal>
+        <Modal v-model="showWorks" :title="$t('studentWeb.courseContent.mynotes')" width="800">
+            <myWorks :recordInfo="recordInfo" :rcvData="myWorks" />
+        </Modal>
+        
+    </div>
+</template>
+
+<script>
+import videojs from "video.js";
+import "videojs-markers";
+
+import Loading from '@/common/Loading.vue';
+import DataCount from './newDataCount.vue';
+import ShowQues from './ShowQues.vue';
+import PopQues from '@/view/classrecord/eventchart/PopQues.vue';
+import Buzr from './Buzr.vue';
+import Push from '@/view/classrecord/eventchart/Push.vue';
+import StuReceive from './StuReceive.vue';
+import ReceiveBack from './ReceiveBack.vue';
+import Pick from './Pick.vue';
+import Exam from './Exam.vue';
+import myWorks from './myWorks.vue';
+
+export default {
+    name: "ClassRecord",
+    components: {
+        Loading,
+        DataCount, ShowQues, PopQues, Buzr, Push,
+        StuReceive, ReceiveBack, Pick, Exam, myWorks,
+    },
+    data() {
+        return {
+            isLoad: false,
+            player: undefined,
+            pageList: [], //课件
+            markers: [], //打点
+            sokratesRecords: {}, //原始数据
+            curPage: 1, //当前课件页码
+            currentfilterType: "",
+            openHtexViewer: false,
+            playerOptions: {
+                height: "450px",
+                controlBar: {
+                    children: [// 写在这里,会在播放条上显示出来,并且是按照写的顺序显示位置。
+                        { name: "playToggle" }, //播放暂停按钮
+                        { name: "currentTimeDisplay" }, //当前播放时间
+                        { name: "progressControl" }, //播放进度条
+                        { name: "durationDisplay" }, //总时间
+                        {
+                            name: "playbackRateMenuButton",
+                            playbackRates: [0.5, 1, 1.5, 2, 2.5]
+                        }, //播放速率
+                        {
+                            name: "volumePanel", //音量控制
+                            inline: false, //不使用水平方式
+                        },
+                        { name: "FullscreenToggle" }, //全屏
+                        { name: "DashBoardEchart" }
+                    ],
+                },
+                /* html5: {
+                    nativeControlsForTouch: true,
+                }, */
+                // inactivityTimeout: 1,
+                // nativeVideoTracks: false,
+                // playbackRates: [0.5, 1.0, 1.25, 2.0], //播放速度
+                // autoplay: false, //如果true,浏览器准备好时开始回放。
+                controls: true, //控制条
+                preload: 'auto', //视频预加载
+                muted: false, //默认情况下将会消除任何音频。
+                // loop: false, //导致视频一结束就重新开始。
+                // language: 'zh-CN',
+                // aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
+                //fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
+                sources: [{
+                    src: ""
+                }],
+                // notSupportedMessage: '此视频暂无法播放,请稍后再试' //允许覆盖Video.js无法播放媒体源时显示的默认信息。
+            },
+            recordInfo: {},
+            baseData: undefined, //base.json
+            pushData: [], //push.json
+            irsData: [], //irs.json
+            taskData: [], //task.json
+            fnEvents: [], //功能事件
+            events: [], //事件ID
+            hiTeachEvent: [], //需要解析的事件信息
+            isShowVd: true,
+            hasVideo: true,
+            nowStuInfo: undefined, //当前学生的信息
+            haveInteraction: true,
+            courseNow: undefined,
+            showNote: false,
+            filtertype: {
+                push: 0, //推送
+                task: 0, //任务
+                irs: 0, //互动
+                exam: 0, //测验
+            },
+            showPageList: [],
+            myWorks: [],
+            showWorks: false,
+            attendType: 2,
+            attentColor: ['', '#19be6b', '#EB3941', '#EB3941', '#EB3941', '#EB3941'],
+        }
+    },
+    created() {
+        this.hiTeachEvent = this.$GLOBAL.HI_TEACH_EVENT()
+        this.events = Object.keys(this.hiTeachEvent)
+        this.fnEvents = this.events.filter(key => this.hiTeachEvent[key].type === 'fn')
+        this.recordInfo = this.$route.params.record
+        this.courseNow = this.$route.params.courseNow
+        if (!this.recordInfo) {
+            this.$router.go(-1)
+        } else {
+            this.recordInfo.startTime = this.dateFormat(this.recordInfo.startTime)
+            let sec = this.recordInfo.duration % 60
+            let min = parseInt(this.recordInfo.duration / 60)
+            let mins = min >= 60 ? min % 60 : min
+            let hour = parseInt(min / 60)
+            this.recordInfo.time = `${hour < 10 ? ('0' + hour) : hour}:${mins < 10 ? ('0' + mins) : mins}:${sec < 10 ? ('0' + sec) : sec}`
+            this.getPageList()
+        }
+    },
+    mounted() {
+        // this.getVideo()
+    },
+    methods: {
+        getVideo() {
+            var that = this
+            this.player = videojs(document.getElementById("recordVideo"), this.playerOptions, function () {
+                this.on('error', (e) => {
+                    that.hasVideo = false
+                    that.isShowVd = false
+                })
+            })
+            //时间切片
+            this.player.markers({
+                markerStyle: {
+                    width: "16px",
+                    height: "16px",
+                    top: "-22px",
+                    "display": "inline-block",
+                    "border-radius": "50%",
+                    "font-size": "12px",
+                    "line-height": "16px",
+                    "background-color": "orange"
+                },
+                breakOverlay: {
+                    display: false,
+                    displayTime: 4,
+                    style: {
+                        "z-index": "6",
+                        width: "100%",
+                        height: "10%",
+                        "background-color": "rgba(200,250,10,0.6)",
+                        color: "white",
+                        "font-size": "16px",
+                    },
+                    text: function (marker) {
+                        return that.$t('system.compt.cusWare') + marker.text;
+                    },
+                },
+                markerTip: {
+                    display: false,
+                    text: function (marker) {
+                        return marker.text;
+                    },
+                },
+                markers: that.markers,
+
+                //标记点击事件
+                onMarkerClick: function (marker) {
+
+                },
+                //视频播放到标记点触发的时间
+                onMarkerReached: function (marker) {
+                    let mkDoms = document.getElementsByClassName('vjs-marker ')
+                    for (let index in mkDoms) {
+                        if (mkDoms[index].dataset) {
+                            if (parseInt(mkDoms[index].dataset.markerTime) <= marker.time) {
+                                mkDoms[index].style.backgroundColor = '#1CC0F3'
+                                mkDoms[index].classList.add('vjs-marker-active')
+                            } else {
+                                mkDoms[index].style.backgroundColor = 'orange'
+                                mkDoms[index].classList.remove('vjs-marker-active')
+                            }
+                            // mkDoms[index].innerHTML = this.markers[index].page
+                        }
+                    }
+                    that.getCurPage(marker.page)
+                },
+            });
+
+            this.isLoad = false
+        },
+        // 根据SokratesRecords.json处理page数据
+        async getPageList() {
+            this.isLoad = true
+            this.pageList = []
+            this.showPageList = []
+            this.markers = []
+            let sas = await this.$tools.getBlobSas(this.recordInfo.scope === 'school' ? this.recordInfo.school : this.recordInfo.tmdid)
+            this.recordInfo.eNote = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Note.pdf?${sas.sas}`
+            // 如果只会存在一个视频,文件名是否可以固定?
+            this.playerOptions.sources[0].src = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Record/CourseRecord.mp4?${sas.sas}`
+            // let url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Sokrates/SokratesRecords.json?${sas.sas}` //后面会根据TimeLine.json处理
+            // 这里需要兼容原来没有TimeLine.json的课例(优先度读timeLine.json,没有则读SokratesRecords.json)
+            let url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/TimeLine.json?${sas.sas}`
+            let hasTimeLine = true
+            let dataErr = false
+            let pgids = []
+            let pageEvents = []
+            try {
+                let res = await this.$tools.getFile(url)
+                this.sokratesRecords = JSON.parse(res)
+                pgids = this.sokratesRecords.PgIdList || []
+                pageEvents = this.sokratesRecords.events || []
+            } catch (e) {
+                hasTimeLine = false
+            }
+            //读取 timeLine.json 失败,则读取 SokratesRecords.json
+            if (!hasTimeLine) {
+                url = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Sokrates/SokratesRecords.json?${sas.sas}`
+                try {
+                    let res = await this.$tools.getFile(url)
+                    let resJson = JSON.parse(res)
+                    // 处理成timeLine数据格式
+                    let pageidEvent = resJson.find(item => item.Event == 'PgidList')
+                    pgids = pageidEvent && pageidEvent.PgIdList ? pageidEvent.PgIdList : []
+                    pageEvents = resJson.filter(item => this.events.includes(item.Event))
+                    this.sokratesRecords = {
+                        events: pageEvents,
+                        PgIdList: pgids
+                    }
+                } catch (e) {
+                    //timeLine 和 SokratesRecords都没有找到
+                    dataErr = true
+                }
+            }
+            
+            // 数据异常
+            if (dataErr) {
+                this.$Message.error(this.$t('cusMgt.rcd.dataErr'))
+                return
+            }
+            //获取Push.json、IRS.json、Task.json、Base.json数据
+            try {
+                let pushUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/Push.json?${sas.sas}`
+                this.pushData = JSON.parse(await this.$tools.getFile(pushUrl) || '[]')
+                this.pushData.forEach(item => {
+                    item.pageUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}${item.pageMeta}?${sas.sas}`
+                })
+            } catch (e) {
+                this.pushData = []
+            }
+            try {
+                let irsUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/IRS.json?${sas.sas}`
+                this.irsData = JSON.parse(await this.$tools.getFile(irsUrl) || '[]')
+            } catch (e) {
+                this.irsData = []
+            }
+            try {
+                let taskUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/Task.json?${sas.sas}`
+                this.taskData = JSON.parse(await this.$tools.getFile(taskUrl) || '[]')
+            } catch (e) {
+                this.taskData = []
+            }
+            try {
+                let baseUrl = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/IES/base.json?${sas.sas}`
+                this.baseData = JSON.parse(await this.$tools.getFile(baseUrl) || '{}')
+                this.baseData.student.forEach((item, index) => {
+                    if (item.id === this.$store.state.userInfo.sub) {
+                        this.nowStuInfo = item
+                        this.nowStuInfo.index = index
+                    }
+                })
+                if(this.nowStuInfo) {
+                    let nowClient = this.baseData.report.clientSummaryList.find(item => {
+                        return this.nowStuInfo.seatID === item.seatID
+                    })
+                    this.attendType = nowClient ? nowClient.attendState : 2
+                }
+            } catch (e) {
+                this.baseData = undefined
+            }
+
+            //这里需要判断录制开始的pageid
+            let startInfo = pageEvents.find(item => item.Event === 'EzsStartRecord')
+            let startId = startInfo ? startInfo.Pgid : ''
+            let startIndex = 0
+            if (startId) {
+                startIndex = pgids.findIndex(item => item === startId)
+            }
+            pgids = pgids.slice(startIndex)
+
+            let havePage = 0
+            let myTask = []
+            pgids.forEach((item, index) => {
+                let page = {}
+                page.id = item
+                page.img = `${sas.url}/${sas.name}/records/${this.recordInfo.id}/Memo/${item}.jpg?${sas.sas}`
+                page.page = index + 1
+                //当前页面对应的sokrates
+                page.pageData = pageEvents.filter(record => record.Pgid === item && this.fnEvents.includes(record.Event))
+                havePage += (page.pageData.length ? 1 : 0)
+                page.pageData.forEach(e => {
+                    e.pageIndex = index
+                    e.eventName = this.hiTeachEvent[e.Event]?.text
+                    let rlt = this.hiTeachEvent[e.Event]?.relation
+                    e.relation = rlt
+                    switch (rlt) {
+                        case 'irs':
+                            this.filtertype.irs += 1
+                            e.data = this.irsData.find(i => i.pageID == e.Pgid)
+                            break
+                        case 'push':
+                            this.filtertype.push += 1
+                            e.data = this.pushData.find(p => p.pageId == e.Pgid || p.pageID == e.Pgid)
+                            break
+                        case 'task':
+                            this.filtertype.task += 1
+                            e.data = this.taskData.find(t => t.pageID == e.Pgid)
+                            myTask.push(e.data)
+                            break
+                        case 'timeline':
+                            e.data = this._.cloneDeep(e)
+                            break
+                        case 'exam':
+                            this.filtertype.exam += 1
+                            e.data = this._.cloneDeep(e)
+                            break
+                        default:
+                            break
+                    }
+                })
+                this.pageList.push(page)
+            })
+            if(this.baseData) {
+                myTask.forEach(item => {
+                    if(item.clientWorks.length) {
+                        item.clientWorks.forEach(works => {
+                            let owner = this.baseData.student.find(stu => stu.seatID == works.seatID && stu.id === this.$store.state.userInfo.sub)
+                            if(owner) {
+                                this.myWorks.push({
+                                    jobName: item.jobName,
+                                    collateType: item.collateType,
+                                    blobFiles: works.blobFiles
+                                })
+                            }
+                        })
+                    }
+                })
+            }
+            this.showPageList = [...this.pageList]
+            this.haveInteraction = havePage != 0
+            let pageEvent = pageEvents.filter(item => item.Event === 'PopQuesLoad' || item.Event === 'ReAtmpAnsStrt' || item.Event === 'FastPgPush' || item.Event === 'WrkSpaceEnd' || item.Event === 'SPQStrt')
+            this.markers = pageEvent.map((item, index) => {
+                return {
+                    time: item.Time,
+                    text: `${this.$t('cusMgt.rcd.di')}${index + 1}${this.$t('cusMgt.rcd.page')}`,
+                    page: index + 1
+                }
+            })
+            this.getVideo()
+        },
+        // 点击互动记录页面tag
+        toVideo(page, e) {
+            this.curPage = page
+            //页面滚动
+            /* let dataLoacation = this.$refs["datawrap"].getPosition()
+            let pageLocaltion = this.$refs["pagewrap"].getPosition()
+            let y = e.pageY - 770 + pageLocaltion.scrollTop + dataLoacation.scrollTop
+            this.$nextTick(() => {
+                this.$refs["pagewrap"].scrollTo(
+                    {
+                        x: 0,
+                        y: 0
+                    }
+                )
+                this.$refs["datawrap"].scrollTo(
+                    {
+                        x: 0,
+                        y: y
+                    }
+                )
+            }) */
+            //视频时间定位
+            let pageInfo = this.markers.find(item => {
+                return item.page === page
+            })
+            if (pageInfo) {
+                this.player.currentTime(pageInfo.time)
+            }
+        },
+        // 点击课件page
+        getCurHTEX(page) {
+            this.curPage = page
+            // this.mapJson = require('./data/' + page + '.json')
+            //视频时间定位
+            let pageInfo = this.markers.find(item => {
+                return item.page === page
+            })
+            if (pageInfo) {
+                this.player.currentTime(pageInfo.time)
+                if (!this.openHtexViewer) {
+                    this.player.play()
+                } else {
+                    this.player.pause()
+                }
+                //互动记录滚动
+                // this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
+            } else {
+            }
+        },
+        // 点击视频切片
+        getCurPage(page) {
+            this.curPage = page
+            // this.mapJson = require('./data/' + page + '.json')
+            // this.$refs["datawrap"].scrollIntoView('#page' + page, 500)
+            this.player.play()
+        },
+        //查看电子笔记
+        async viewENote() {
+            let eNote
+            if (this.recordInfo.eNote) {
+                eNote = this.recordInfo.eNote
+            } else {
+                // let sasInfo = {}
+                // let blobInfo = this.recordInfo.scope === 'school' ? this.$store.state.user.schoolProfile : this.$store.state.user.userProfile
+                // sasInfo.sas = '?' + blobInfo.blob_sas
+                // sasInfo.name = this.recordInfo.scope ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId
+                // sasInfo.url = blobInfo.blob_uri.slice(0, blobInfo.blob_uri.lastIndexOf(sasInfo.name) - 1)
+                let sasInfo = await this.$tools.getBlobSas(this.recordInfo.scope === 'school' ? this.recordInfo.school : this.recordInfo.tmdid)
+                eNote = `${sasInfo.url}/${sasInfo.name}/records/${this.recordInfo.id}/Note.pdf?${sasInfo.sas}`
+            }
+            window.open('/web/viewer.html?file=' + encodeURIComponent(eNote))
+            // if (this.recordInfo.eNote) {
+            //     window.open('/web/viewer.html?file=' + encodeURIComponent(this.recordInfo.eNote))
+            // } else {
+            //     this.$Message.warning(this.$t('cusMgt.rcd.noNote'))
+            // }
+        },
+        //下载电子笔记
+        async loadNote() {
+            if (this.recordInfo.eNote) {
+                // 已经有授权,在查看文件时不需要再次获取授权
+                let blobData = this.recordInfo.eNote
+                const downloadRes = async () => {
+                    let response = await fetch(blobData); // 内容转变成blob地址
+                    let blob = await response.blob();  // 创建隐藏的可下载链接
+                    let objectUrl = window.URL.createObjectURL(blob);
+                    let a = document.createElement('a');
+                    a.href = objectUrl;
+                    a.download = "电子笔记.pdf";
+                    a.click()
+                    a.remove();
+                }
+                downloadRes();
+            } else {
+                this.$Message.warning("暂无电子笔记")
+            }
+        },
+        openViewer(item) {
+            this.$hevueImgPreview(item)
+        },
+        // 筛选
+        showFile(type) {
+            this.currentfilterType = (type === "all" ? "" : type)
+        },
+        getTime(time) {
+            let sec = parseInt(time % 60)
+            let min = parseInt(time / 60)
+            let mins = min >= 60 ? min % 60 : min
+            let hour = parseInt(min / 60)
+            return `${hour < 10 ? ('0' + hour) : hour}:${mins < 10 ? ('0' + mins) : mins}:${sec < 10 ? ('0' + sec) : sec}`
+        },
+        dateFormat(timestamp) {
+            var date = new Date(timestamp)
+            var Y = date.getFullYear() + '-'
+            var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
+            var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' '
+            var H = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ":"
+            var Min = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes())
+            var S = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()) + " "
+            return Y + M + D + H + Min;
+        },
+        quitRec() {
+            this.$router.go(-1)
+        },
+        //返回顶部
+        handleToTop() {
+            document.getElementsByClassName("class-record")[0].scrollIntoView()
+            /* this.$refs['pagewrap'].scrollTo(
+                {
+                    y: '0'
+                },
+                300
+            ) */
+        },
+        filterFn(type) {
+            this.showPageList = []
+            if(type === 'all') {
+                this.showPageList = [...this.pageList]
+            } else {
+                this.isLoad = true
+                this.pageList.forEach(item => {
+                    if(item.pageData.length) {
+                        let filterArr = item.pageData.filter(data => {
+                            return data.relation === type
+                        })
+                        if(filterArr.length) {
+                            this.showPageList.push({
+                                id: item.id,
+                                img: item.img,
+                                page: item.page,
+                                pageData: filterArr
+                            })
+                        }
+                    }
+                })
+                this.isLoad = false
+            }
+        },
+        downloadFile(url, taskInfo) {
+            const downloadRes = async () => {
+                let response = await fetch(url); // 内容转变成blob地址
+                let blob = await response.blob(); // 创建隐藏的可下载链接
+                let objectUrl = window.URL.createObjectURL(blob);
+                let a = document.createElement('a');
+                a.href = objectUrl;
+                let fileName = url.substring(url.lastIndexOf('/' + 1), url.lastIndexOf('?'))
+                a.download = `${taskInfo.jobName}-${taskInfo.seatID}-${fileName}`
+                a.click()
+                a.remove();
+            }
+            downloadRes();
+        },
+    },
+    computed: {
+        curImg() {
+            if (this.pageList[this.curPage - 1]) {
+                return this.pageList[this.curPage - 1].img
+            } else {
+                return ""
+            }
+        }
+    },
+}
+</script>
+
+<style lang="less" scoped>
+@import "./newClassRecord.less";
+</style>
+<style lang="less">
+.record-left {
+    .video-js .vjs-big-play-button {
+        top: 50%;
+        left: 50%;
+        margin-left: -20px;
+        margin-top: -20px;
+        display: none;
+    }
+
+    .video-js .vjs-control-bar {
+        display: flex;
+    }
+
+    .vjs-marker::after {
+        content: "";
+        height: 0px;
+        width: 0px;
+        border: 3px transparent solid;
+        display: block;
+        position: absolute;
+        bottom: -10px;
+        z-index: -1;
+        border-right: 8px solid transparent;
+        border-top: 15px solid orange;
+        border-left: 8px solid transparent;
+    }
+    .vjs-marker-active::after {
+        border-right: 8px solid transparent !important;
+        border-top: 15px solid #1cc0f3 !important;
+        border-left: 8px solid transparent !important;
+    }
+
+    .vjs-marker:hover {
+        z-index: 101;
+    }
+}
+
+.record-info {
+    .ivu-btn-primary {
+        background-color: #24b880;
+        border-color: #24b880;
+
+        &:not(:last-child) {
+            margin-right: 15px;
+        }
+    }
+
+    .ivu-btn-warning {
+        color: #515a6e;
+        background-color: #FEE49E;
+        border-color: #FEE49E;
+
+        &:not(:last-child) {
+            margin-right: 15px;
+        }
+    }
+
+    .ivu-btn-primary[disabled],
+    .ivu-btn-primary[disabled]:hover {
+        background-color: #24b880;
+        border-color: #24b880;
+    }
+
+    .ivu-btn-warning[disabled],
+    .ivu-btn-warning[disabled]:hover {
+        color: #c5c8ce;
+        background-color: #FEE49E;
+        border-color: #FEE49E;
+    }
+}
+</style>

+ 184 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/newDataCount.vue

@@ -0,0 +1,184 @@
+<template>
+    <div>
+        <!-- <p class="attend-type" :style="{'color': attentColor[attendType]}">
+            {{ $t(`studentWeb.hiteachNote.dataCount.attendTypeList[${attendType}]`) }}
+        </p> -->
+        <div class="data-count-container">
+            <div v-for="(item,index) in dataList" :key="'1' + index" class="data-item">
+                <p class="data-value" :style="{borderColor: attentColor[attendType]}">{{item.value}}</p>
+                <p class="data-label">{{item.label}}</p>
+                <p class="data-info">{{item.info}}</p>
+            </div>
+            <div v-for="(item,index) in dataList2" :key="'2' + index" class="data-item">
+                <p class="data-value" :style="{borderColor: attentColor[attendType]}">{{item.value}}</p>
+                <p class="data-label">{{item.label}}</p>
+                <p class="data-info">{{item.info}}</p>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import CountTo from 'vue-count-to'
+export default {
+    props: {
+        rcdInfo: {
+            type: Object,
+            default: () => {
+                return undefined
+            }
+        },
+        nowStuInfo: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        }
+    },
+    components: {
+        CountTo
+    },
+    data() {
+        return {
+            attentColor: ['', '#19be6b', '#EB3941', '#EB3941', '#EB3941', '#EB3941'],
+            colorList: ['#19be6b', '#2d8cf0', '#2db7f5', '#2db7f5', '#2db7f5', '#19be6b', '#2db7f5', '#2db7f5', '#2db7f5', '#ed4014'],
+            attendType: 2, //新加入的会没有该学生的信息,即nowStuInfo = undefined
+            dataList: [
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.getCount"),
+                    value: 0,
+                    info: ''
+                },
+
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.intCount"),
+                    value: 0,
+                    info: ''
+                },
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.taskNum"),
+                    value: 0,
+                    info: ''
+                },
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.colctNum"),
+                    value: 0,
+                    info: ''
+                },
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.pushNum"),
+                    value: 0,
+                    info: ''
+                },
+            ],
+            dataList2: [
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.examNum"),
+                    value: 0,
+                    info: ''
+                },
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.intExamNum"),
+                    value: 0,
+                    info: ''
+                },
+                {
+                    label: this.$t("studentWeb.hiteachNote.dataCount.examCount"),
+                    value: 0,
+                    info: ''
+                },
+            ]
+        }
+    },
+    created() {
+        if (this.rcdInfo) {
+            console.log(this.rcdInfo);
+            const summary = this.rcdInfo
+            let nowClient = summary.report.clientSummaryList.find(item => {
+                return this.nowStuInfo.seatID === item.seatID
+            })
+            if(nowClient) {
+                /* 
+                    EAttendState
+                    {
+                        Uncall,0(未點名)
+                        Attended,1(出席)
+                        Absent,2(缺席)
+                        DayOff,3(請假)
+                        Absent_Sick,4(病假)
+                        Absent_Personal,5(事假)
+                        Absent_Official,6(公假)
+                    }
+                */
+                this.attendType = nowClient.attendState
+                // this.dataList[0].value = nowClient.attendState
+                // 记分
+                this.dataList[0].value = nowClient.score
+                // 互动分
+                this.dataList[1].value = nowClient.interactScore
+                // 任务数
+                this.dataList[4].value = nowClient.taskCompleteCount
+                nowClient.examScoreList.map(item => {
+                    this.dataList2[2].value += item
+                })
+            }
+            // 出席
+            // this.dataList[0].value = summary.attendCount
+            // 總計分
+            // this.dataList[1].value = summary.totalPoint
+            // 總互動分
+            // this.dataList[2].value = summary.totalInteractPoint
+            // 任务总数(作品收集任務數)
+            // this.dataList[3].value = summary.collateTaskCount
+            // 作品总数
+            this.dataList[3].value = summary.report.collateCount
+            // 推送總數(頁面+資源+訊息+差異化)
+            this.dataList[4].value = summary.report.pushCount
+            // 测验总题数
+            this.dataList2[0].value = summary.report.examQuizCount
+            // 互动题数
+            this.dataList2[1].value = summary.report.interactionCount
+            // 测验得分率
+            // this.dataList[8].value = summary.examPointRate
+            this.$forceUpdate()
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.attend-type {
+    font-size: 20px;
+    font-weight: 600;
+    margin-right: 50px;
+    margin-top: 10px;
+    float: right;
+}
+.data-count-container {
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    padding: 20px 0px;
+}
+.data-item {
+    width: 150px;
+    padding: 10px;
+    text-align: center;
+    margin-bottom: 30px;
+}
+.data-value {
+    font-size: 40px;
+    font-weight: 600;
+    color: #17233d;
+    width: fit-content;
+    min-width: 70px;
+    margin: auto;
+    margin-bottom: 5px;
+    border-bottom: 2px solid;
+}
+.data-label {
+    font-size: 16px;
+    // color: #2d8cf0;
+}
+.data-info {
+    font-size: 12px;
+}
+</style>

+ 30 - 25
TEAMModelOS/ClientApp/src/components/student-web/HomeView/ChartHome/HomeworkPoint.vue

@@ -154,31 +154,7 @@ export default {
                     // data: ["罗老师", "陈老师", "辅导员"],
                     bottom: 0,
                 },
-                dataZoom: [
-                    {
-						'show': true,
-						'height': 3,
-						'xAxisIndex': [0],
-						bottom: 25,
-						'start': 0,
-						'end': 100,
-						handleIcon: 'M512 497.821538m-418.264615 0a418.264615 418.264615 0 1 0 836.52923 0 418.264615 418.264615 0 1 0-836.52923 0Z',
-						handleSize: '160%',
-						handleStyle: {
-							color: '#d3dee5'
-						},
-						textStyle: {
-							color: '#717171'
-						},
-						// borderColor: '#90979c'
-					}, {
-						'type': 'inside',
-						'show': true,
-						'height': 15,
-						'start': 0,
-						'end': 100
-					}
-                ],
+                
                 xAxis: {
                     show: true,
                     // name:'作业次数',
@@ -240,6 +216,35 @@ export default {
                     },
                 ] */,
             }
+            if (maxNum > 8) {
+                option.dataZoom = [
+                    {
+						'show': true,
+						'height': 3,
+						'xAxisIndex': [0],
+						bottom: 25,
+						'start': 0,
+						'end': 100,
+						handleIcon: 'M512 497.821538m-418.264615 0a418.264615 418.264615 0 1 0 836.52923 0 418.264615 418.264615 0 1 0-836.52923 0Z',
+						handleSize: '160%',
+						handleStyle: {
+							color: '#d3dee5'
+						},
+						textStyle: {
+							color: '#717171'
+						},
+						// borderColor: '#90979c'
+					}, {
+						'type': 'inside',
+						'show': true,
+						'height': 15,
+						'start': 0,
+						'end': 100
+					}
+                ]
+            } else {
+                option.dataZoom = []
+            }
             myChart.setOption(option)
         },
         getTeacherName(id) {

+ 70 - 46
TEAMModelOS/ClientApp/src/components/student-web/HomeView/HomeViewnnnnew.vue

@@ -3,13 +3,13 @@
         <Loading v-show="isLoading" bgColor="rgba(0, 0, 0, 0.3)"></Loading>
         <div class="home-head home-card">
             <Row :gutter="30">
-                <Col :xs="24" :sm="12" :md="12" :lg="8">
+                <Col :xs="24" :sm="24" :md="24" :lg="8">
                     <ExamPerform />
                 </Col>
-                <Col :xs="24" :sm="12" :md="12" :lg="8">
+                <Col :xs="24" :sm="24" :md="24" :lg="8">
                     <HomeworkPoint />
                 </Col>
-                <Col :xs="24" :sm="12" :md="12" :lg="8">
+                <Col :xs="24" :sm="24" :md="24" :lg="8">
                     <ClassPoint />
                 </Col>
             </Row>
@@ -129,7 +129,7 @@
                                 <div class="list-new-type">
                                     <span style="font-size: 12px; text-align: center;">{{ activity.tempsub }}</span>
                                     <div class="list-new-unDone isAllowRetry">
-                                        <span>去查看</span>
+                                        <span>{{ $t("studentWeb.home.toLook") }}</span>
                                     </div>
                                 </div>
                             </div>
@@ -145,18 +145,18 @@
                 <div class="home-card">
                     <div>
                         <p class="home-title-name">{{ $t("studentWeb.home.title.notice1") }}</p>
-                        <div v-if="classNotice.length" style="height: 200px; margin-bottom: 10px;">
+                        <div v-if="classNotice.length" style="height: 175px; margin-bottom: 10px;">
                             <vuescroll>
-                                <div v-for="(cNotice, nIndex) in classNotice" :key="nIndex" class="list-new">
+                                <div v-for="(cNotice, nIndex) in classNotice" :key="nIndex" class="list-new" @click="getNoticeInfo(cNotice)">
                                     <div class="list-new-icon">
                                         <Icon custom="iconfont icon-notify" size="30" />
                                     </div>
                                     <div class="list-new-test">
                                         <p class="list-item-title">
-                                            <span>{{ cNotice.name }}</span>
+                                            <span>{{ cNotice.title }}</span>
                                         </p>
                                         <p class="list-item-time">
-                                            {{ cNotice.time }}
+                                            {{ cNotice.start }} ~ {{ cNotice.end }}
                                         </p>
                                     </div>
                                 </div>
@@ -169,30 +169,32 @@
                     </div>
                     <div>
                         <p class="home-title-name">{{ $t("studentWeb.home.title.notice2") }}</p>
-                        <div v-if="courseNotice.length" style="height: 200px; margin-bottom: 10px;">
+                        <div v-if="courseNotice.length" style="height: 230px; margin-bottom: 10px;">
                             <vuescroll>
-                                <div v-for="(couNotice, nIndex) in courseNotice" :key="nIndex" class="list-new">
+                                <div v-for="(couNotice, nIndex) in courseNotice" :key="nIndex" class="list-new" @click="getNoticeInfo(couNotice)">
                                     <div class="list-new-icon">
                                         <Icon custom="iconfont icon-notify" size="30" />
                                     </div>
                                     <div class="list-new-test">
                                         <p class="list-item-title">
-                                            <span>{{ couNotice.name }}</span>
+                                            <span>{{ couNotice.title }}</span>
                                         </p>
                                         <p style="font-size:12px; margin-top: 5px; word-break: keep-all;">
-                                            <span v-if="couNotice.className">
-                                                <span class="tag-style" style="border-color: #499c8d; color: #499c8d;">
-                                                    {{ couNotice.className }}
+                                            <span v-if="couNotice.className.length">
+                                                <span class="tag-style" style="border-color: #499c8d; color: #499c8d;"
+                                                        v-for="(cName, cIndex) in couNotice.className" :key="cIndex"
+                                                >
+                                                    {{ cName }}
                                                 </span>
                                             </span>
-                                            <span v-if="couNotice.teaName">
+                                            <span v-if="couNotice.teacherName">
                                                 <span class="tag-style" style="border-color: #8f8787; color: #8f8787;">
-                                                    {{ couNotice.teaName }}
+                                                    {{ couNotice.teacherName }}
                                                 </span>
                                             </span>
                                         </p>
                                         <p class="list-item-time">
-                                            {{ couNotice.time }}
+                                            {{ couNotice.start }} ~ {{ couNotice.end }}
                                         </p>
                                     </div>
                                 </div>
@@ -206,6 +208,11 @@
                 </div>
             </Col>
         </Row>
+        <Modal v-model="showNotice" :title="nowNotice ? nowNotice.title : ''" width="800">
+            <template v-if="nowNotice">
+                <p>{{ nowNotice.content }}</p>
+            </template>
+        </Modal>
     </div>
 </template>
 
@@ -247,45 +254,54 @@ export default {
                 private: [],
             },
             sasInfo: undefined,
+            classIds: [],
+            showNotice: false,
+            nowNotice: undefined,
         }
     },
     mounted () {
         this.MyName = this.$t("studentWeb.homeView-title")
         this.$emit("onNavNo", this.MyNo)
         this.$emit("onNavName", this.MyName)
-        this.getNotice()
         this.getGroupList()
         this.getActivity()
     },
     methods: {
-        getNotice() {
-            this.courseNotice = [
-                /* {
-                    name: "测试",
-                    time: "2021-05-05 ~ 2021-05-06",
-                    content: "测试内容",
-                    className: "数学",
-                    teaName: "罗老师",
-                },{
-                    name: "测试",
-                    time: "2021-05-05 ~ 2021-05-06",
-                    content: "测试内容",
-                    className: "数学",
-                    teaName: "罗老师",
-                },{
-                    name: "测试",
-                    time: "2021-05-05 ~ 2021-05-06",
-                    content: "测试内容",
-                    className: "数学",
-                    teaName: "罗老师",
-                },{
-                    name: "测试",
-                    time: "2021-05-05 ~ 2021-05-06",
-                    content: "测试内容",
-                    className: "数学",
-                    teaName: "罗老师",
-                }, */
-            ]
+        getNotice(classes) {
+            this.classNotice = []
+            this.courseNotice = []
+            this.$api.studentWeb.getAllNotice({classes}).then(async res => {
+                // type:class(班级公告)  course(课程公告)
+                if(res.notices.length) {
+                    let teaIds = []
+                    res.notices.forEach(item => {
+                        teaIds.push(item.creatorId)
+                    })
+                    let teaidNames = await this.getTeacherName(teaIds)
+                    res.notices.forEach(item => {
+                        item.start = this.dateFormat(item.startTime)
+                        item.end = this.dateFormat(item.endTime)
+                        item.className = []
+                        item.classes.forEach(classes => {
+                            let names = this.classIds.find(ids => {
+                                return ids.id === classes
+                            })
+                            if(names) {
+                                item.className.push(names.name)
+                            }
+                        })
+                        let nameIds = teaidNames.find(names => {
+                            return names.id === item.creatorId
+                        })
+                        item.teacherName = !nameIds ? undefined : nameIds.name
+                        if(item.type === "class") {
+                            this.classNotice.push(item)
+                        } else {
+                            this.courseNotice.push(item)
+                        }
+                    })
+                }
+            })
         },
         getGroupList() {
             // return new Promise(resolve => {
@@ -298,14 +314,18 @@ export default {
                 // this.getSas(this.userInfo.azp)
                 this.$api.studentWeb.getAllGrouplist({}).then(res => {
                     if(res.groups.length) {
+                        let groupIds = []
+                        this.classIds = [...res.groups]
                         res.groups.forEach(item => {
                             if(item.scope === 'school') {
                                 this.groups.school.push(item.id)
                             } else if(item.scope === 'private') {
                                 this.groups.private.push(item.id)
                             }
+                            groupIds.push(item.id)
                         })
                         this.getSchool("school", this.groups.school)
+                        this.getNotice(groupIds)
                     }
                 }).finally(() => {
                     // resolve(groups)
@@ -616,6 +636,10 @@ export default {
                 }
             })
         },
+        getNoticeInfo(info) {
+            this.showNotice = true
+            this.nowNotice = info
+        },
     },
     computed: {
         ...mapState({

File diff ditekan karena terlalu besar
+ 729 - 747
TEAMModelOS/ClientApp/src/components/vote/BaseVoteForm.vue


+ 2 - 2
TEAMModelOS/ClientApp/src/css/common-style.less

@@ -188,10 +188,10 @@
         padding: 5px 0;
         cursor: pointer;
         vertical-align: middle;
-        padding: 5px 0px;
+        padding: 9px 0px;
         &.active{
             font-weight: bold;
-            color: var(--tabs-text-color);
+            color: var(--tabs-bottom-color);
             border-bottom: 2px var(--tabs-bottom-color) solid;
         }
     }

+ 14 - 4
TEAMModelOS/ClientApp/src/css/site.css

@@ -120,10 +120,6 @@ html[white] {
 	background: #94998a;
 }
 
-.scrollstyle::-webkit-scrollbar-thumb:hover {
-	/* background: #555; */
-}
-
 .scrollstyle::-webkit-scrollbar-button {
 	display: none;
 }
@@ -340,6 +336,20 @@ audio::-internal-media-controls-overflow-button {
 	white-space: nowrap;
 	overflow: hidden;
 }
+
+.ac-share-tooltip .ivu-icon {
+	margin-right: 6px;
+}
+
+.ac-share-tooltip .link-item {
+	padding: 6px;
+	cursor: pointer;
+}
+
+.ac-share-tooltip .link-item:hover {
+	color: #70b1e7;
+}
+
 dot{
 	position: relative;
 }

+ 63 - 12
TEAMModelOS/ClientApp/src/router/routes.js

@@ -260,7 +260,7 @@ export const routes = [{
 	{
 		path: 'ByStu',
 		name: 'ByStu',
-		component: resolve => require(['@/view/task/mark/ByStu.vue'], resolve),
+		component: resolve => require(['@/view/task/marking/mark/ByStu.vue'], resolve),
 		meta: {
 			activeName: 'taskList'
 		}
@@ -269,7 +269,7 @@ export const routes = [{
 	{
 		path: 'ByQu',
 		name: 'ByQu',
-		component: resolve => require(['@/view/task/mark/ByQu.vue'], resolve),
+		component: resolve => require(['@/view/task/marking/mark/ByQu.vue'], resolve),
 		meta: {
 			activeName: 'taskList'
 		}
@@ -278,7 +278,7 @@ export const routes = [{
 	{
 		path: 'ByQu2',
 		name: 'ByQu2',
-		component: resolve => require(['@/view/task/mark/ByQu2.vue'], resolve),
+		component: resolve => require(['@/view/task/marking/mark/ByQu2.vue'], resolve),
 		meta: {
 			activeName: 'taskList'
 		}
@@ -287,7 +287,7 @@ export const routes = [{
 	{
 		path: 'ArbView',
 		name: 'ArbView',
-		component: resolve => require(['@/view/task/arb/ArbView.vue'], resolve),
+		component: resolve => require(['@/view/task/marking/arb/ArbView.vue'], resolve),
 		meta: {
 			activeName: 'taskList'
 		}
@@ -520,14 +520,55 @@ export const routes = [{
 			isKeep: true
 		}
 	},
+	//新版我的课程页面
+	{
+		path: 'course',
+		name: 'course',
+		component: resolve => require(['@/view/mycourse/MyCourse.vue'], resolve),
+		meta: {
+			activeName: 'course',
+			isKeep: true
+		}
+	},
+	//任务列表
 	{
 		path: 'taskList',
 		name: 'taskList',
 		component: resolve => require(['@/view/task/index.vue'], resolve),
+		redirect: '/home/taskList/marking',
 		meta: {
 			activeName: 'taskList',
-			// isKeep: true
-		}
+		},
+		children: [
+			//阅卷任务
+			{
+				path: 'marking',
+				name: 'marking',
+				component: resolve => require(['@/view/task/marking/index.vue'],
+					resolve),
+				meta: {
+					activeName: 'taskList'
+				},
+			},
+			//投票任务
+			{
+				path: 'vote',
+				name: 'vote',
+				component: resolve => require(['@/view/jyzx/Vote.vue'], resolve),
+				meta: {
+					activeName: 'taskList'
+				}
+			},
+			//问卷任务
+			{
+				path: 'survey',
+				name: 'survey',
+				component: resolve => require(['@/view/jyzx/Question.vue'], resolve),
+				meta: {
+					activeName: 'taskList'
+				}
+			},
+		]
 	},
 
 	//我的课程查看评测详细数据
@@ -554,7 +595,7 @@ export const routes = [{
 		name: 'CoursePlan',
 		component: resolve => require(['@/view/newcourse/CoursePlan.vue'], resolve),
 		meta: {
-			activeName: 'NewCusMgt'
+			activeName: ''
 		}
 	},
 	// 新课纲管理
@@ -612,7 +653,7 @@ export const routes = [{
 			isKeep: true
 		}
 	},
-	// 创建个人评测
+	// 创建个人评测(待废弃)
 	{
 		path: 'createPrivEva',
 		name: 'createPrivEva',
@@ -622,6 +663,16 @@ export const routes = [{
 			isKeep: true
 		}
 	},
+	// 创建个人评测
+	{
+		path: 'createPrivExam',
+		name: 'createPrivExam',
+		component: resolve => require(['@/view/mycourse/exam/CreatePrivExam.vue'], resolve),
+		meta: {
+			activeName: 'privateEvaluation',
+			isKeep: true
+		}
+	},
 	//创建学习单元
 	{
 		path: 'createLearnUnit',
@@ -1002,7 +1053,7 @@ export const routes = [{
 		}
 	},
 	//投票
-	{
+	/* {
 		path: 'privateVote',
 		name: 'privateVote',
 		component: resolve => require(['@/view/jyzx/Vote.vue'], resolve),
@@ -1018,7 +1069,7 @@ export const routes = [{
 		meta: {
 			activeName: 'privateQuestionnaire'
 		}
-	},
+	}, */
 	//学校数据看板
 	{
 		path: 'Dashboard',
@@ -1135,7 +1186,7 @@ export const routes = [{
 	{
 		path: 'classRecord',
 		name: 'classRecord',
-		component: resolve => require(['@/view/classrecord/ClassRecord.vue'], resolve),
+		component: resolve => require(['@/view/classrecord/ClassRecordNew.vue'], resolve),
 		meta: {
 			activeName: 'myCourse'
 		}
@@ -1311,7 +1362,7 @@ export const routes = [{
 		// 课堂记录
 		name: "stuClassRec",
 		path: "stuClassRec",
-		component: resolve => require(['@/components/student-web/ClassRecord/ClassRecord'], resolve),
+		component: resolve => require(['@/components/student-web/ClassRecord/newClassRecord'], resolve),
 	},
 	{
 		// 错题本

+ 4 - 4
TEAMModelOS/ClientApp/src/static/Global.js

@@ -364,10 +364,10 @@ const HI_TEACH_EVENT = () => {
 			type: 'fn',
 			relation: 'task'
 		},
-		WrkCmp: {
-			text: '作品贴上',
-			type: 'tag',
-			relation: ''
+		WrkCmp:{
+			text:'作品贴上',
+			type:'fn',
+			relation:'timeline'
 		},
 		BuzrAns: {
 			text: '抢权',

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

@@ -630,9 +630,6 @@ export default class BlobTool {
                 console.log(scope)
                 console.log(sizeRes)
                 if (sizeRes) {
-                    console.log(sizeRes.total)
-                    console.log(this.blobSpace * 1024 * 1024 * 1024)
-
                     r(sizeRes.total > this.blobSpace * 1024 * 1024 * 1024)
                 } else {
                     j('容器空间判断失败!')

+ 8 - 8
TEAMModelOS/ClientApp/src/utils/editorLangTw.js

@@ -1,9 +1,9 @@
 let editor_tw_config = {
     wangEditor: {
-        重置: '重',
+        重置: '重',
         插入: '插入',
-        默认: '默認',
-        创建: '建',
+        默认: '預設',
+        创建: '建',
         修改: '修改',
         如: '連結URL,如',
         请输入正文: '請輸入正文',
@@ -30,7 +30,7 @@ let editor_tw_config = {
                 表格: '表格',
                 代码: '程式碼',
                 分割线: '分割線',
-                恢复: '復',
+                恢复: '復',
                 撤销: '撤銷',
                 全屏: '全屏',
                 代办事项: '代辦事項',
@@ -69,7 +69,7 @@ let editor_tw_config = {
 					插入公式:'插入公式',
 				},
                 emoticon: {
-                    默认: '默認',
+                    默认: '預設',
                     新浪: '新浪',
                     emoji: 'emoji',
                     手势: '手勢',
@@ -77,14 +77,14 @@ let editor_tw_config = {
                 image: {
                     图片链接: '圖片連結',
                     上传图片: '上傳圖片',
-                    网络图片: '網圖片',
+                    网络图片: '網圖片',
                 },
                 link: {
                     链接: '連結',
                     链接文字: '連結描述',
                     删除链接: '删除連結',
                     查看链接: '查看連結',
-					取消链接: '取消鏈接',
+					取消链接: '取消連結',
                 },
                 video: {
                     插入视频: '插入視頻',
@@ -126,7 +126,7 @@ let editor_tw_config = {
             服务器返回状态: '服務器返回狀態',
             上传图片返回结果错误: '上傳圖片返回結果錯誤',
             请替换为支持的图片类型: '請替換為支持的圖片類型',
-            您插入的网络图片无法识别: '您插入的網圖片無法識別',
+            您插入的网络图片无法识别: '您插入的網圖片無法識別',
             您刚才插入的图片链接未通过编辑器校验: '您剛才插入的圖片連結未通過編輯器校驗',
         },
     }

+ 40 - 1
TEAMModelOS/ClientApp/src/utils/public.js

@@ -8,6 +8,7 @@ import JsPDF from 'jspdf'
 import 'jspdf-autotable'
 import domtoimage from '@/utils/dom_to_image';
 import excel from '@/utils/excel.js'
+import BlobTool from '@/utils/blobTool.js'
 import i18n from '@/locale'
 // 引入iView多语言包
 import zhLocale from 'view-design/src/locale/lang/zh-CN'
@@ -188,6 +189,44 @@ export default {
 		'justify', // 对齐方式
 		'table', // 表格
 	],
+	/* 查询指定容器是否空间已满 */
+	isBlobContainerFull(scope) {
+		return new Promise(async (resolve, reject) => {
+			try {
+				let sas = scope == 'school' ? store.state?.user?.schoolProfile?.blob_sas : store.state?.user?.userProfile?.blob_sas
+				let blobUrl = scope == 'school' ? JSON.parse(decodeURIComponent(localStorage.school_profile, "utf-8"))?.blob_uri : JSON.parse(decodeURIComponent(localStorage.user_profile, "utf-8"))?.blob_uri
+				let host = blobUrl?.substring(0, blobUrl?.lastIndexOf('/'))
+				let cont = blobUrl?.substring(blobUrl?.lastIndexOf('/') + 1)
+				let blobClient = new BlobTool(host, cont, '?' + sas, scope)
+				let isFull = await blobClient.isContainerFull(scope)
+				console.log(isFull);
+				resolve(isFull)
+			} catch (e) {
+				reject(e)
+			}
+		})
+
+	},
+	/* 复制活动链接 */
+	async doCopyAcLink(type, acInfo) {
+		let shareContent = ''
+		let shareUrl = `https://${window.location.host}/studentWeb/examView?aId=${acInfo.id}`
+		let shortUrl = await $api.getShortUrl(encodeURI(shareUrl))
+		let acName = acInfo.name
+		if (type === 'notice') {
+			shareContent = `${app.$t('learnActivity.mgtScEv.shareText1')}\n${app.$t('cusMgt.inviteInfo8')}${app.$store.state.userInfo.name}\n${app.$t('learnActivity.mgtScEv.shareText2')}${acName}\n\n${app.$t('learnActivity.mgtScEv.shareText6')}\n${shortUrl.result || encodeURI(shareUrl)}\n\n${app.$t('learnActivity.mgtScEv.shareText11')}`
+		} else {
+			shareContent = shortUrl.result || encodeURI(shareUrl)
+		}
+		app.$copyText(shareContent).then(
+			ok => {
+				Message.success(app.$t("settings.copyModal1"))
+			},
+			fail => {
+				Message.error(app.$t("settings.copyModal2"))
+			}
+		)
+	},
 	/* 切换语系 */
 	changeLang(lang) {
 		// 如果已经加载过语言包 则直接设置 否则加载语言包
@@ -902,7 +941,7 @@ export default {
 	batchQrcodes(arr, pdfName) {
 		return new Promise((resolve, reject) => {
 			if (!arr.length) {
-				app.$Message.warning('未获取到学生数据')
+				app.$Message.warning(app.$t('common.noStuTip'))
 				resolve(200)
 				return
 			}

+ 18 - 2
TEAMModelOS/ClientApp/src/view/Home.vue

@@ -17,7 +17,7 @@
                 <BaseUserPoptip @logout="basicMenu('quit')"></BaseUserPoptip>
                 <div class="overlay-qrcode" v-show="isShowQrCode">
                     <img src="../assets/image/qrcode.png" width="150px" v-if="!inGlobalSite">
-                    <img src="../assets/image/qrcode_tw.png" width="150px" v-if="inGlobalSite">
+                    <img :src="globalQrCodeImg" width="150px" v-if="inGlobalSite">
                     <p style="margin-top: 10px;">{{ inGlobalSite ? $t('system.wechatTipTw') : $t('system.wechatTip') }}</p>
                 </div>
             </div>
@@ -79,6 +79,7 @@ export default {
     },
     data() {
         return {
+            globalQrCodeImg:'',
             isShowQrCode: false,
             updStatus: false,
             isRouterAlive: true,
@@ -315,10 +316,19 @@ export default {
                     path: '/login'
                 })
             }
+        },
+        getQrImgByLang(){
+            let curLang = localStorage.getItem('local') || 'zh-cn'
+            let imgMap = {
+                'zh-cn':require('../assets/image/qrcode.png'),
+                'zh-tw':require('../assets/image/qrcode_tw.png'),
+                'en-us':require('../assets/image/qrcode_en.png'),
+            }
+            this.globalQrCodeImg = imgMap[curLang]
         }
     },
     mounted() {
-
+        this.getQrImgByLang()
         if (localStorage.getItem('noSave') && JSON.parse(localStorage.getItem('noSave')).length) {
             this.$tools.deleteNoSave(JSON.parse(localStorage.getItem('noSave')))
         } else {
@@ -357,6 +367,9 @@ export default {
         isMobile() {
             return navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)
         },
+        curLang(){
+            return localStorage.getItem('local') || 'zh-cn'
+        }
     },
     watch: {
         $route: {
@@ -376,6 +389,9 @@ export default {
             deep: true,
             //立即执行
             immediate: true
+        },
+        '$i18n.locale'(n, o) {
+            this.getQrImgByLang()
         }
     },
 }

+ 2 - 2
TEAMModelOS/ClientApp/src/view/ability/Review.vue

@@ -312,7 +312,7 @@ export default {
       let isPdf = suffix === 'pdf'
       let isVideo = suffix === 'mp4' || suffix === 'webm'
       let isAudio = ['mp3', 'wav'].includes(suffix)
-      let type = isImg ? 'image' : isVideo ? 'video' : isAudio ? 'audio' : 'doc'
+      let type = isImg ? 'image' : isVideo ? 'video' : isAudio ? 'audio' : (isDoc ? 'doc' : '')
       switch (type) {
         case 'doc':
           if (isPdf) {
@@ -493,7 +493,7 @@ export default {
     /* 根据类型换取ICON图标 */
     getTypeIcon(file) {
       let suffix = file.name.split('.')[file.name.split('.').length - 1].toLowerCase()
-      if (['jpg', 'png', 'gif'].includes(suffix)) {
+      if (['jpg', 'png', 'gif','webp'].includes(suffix)) {
         return require('../../assets/icon/pic50.png')
       }
       if (['doc', 'docx'].includes(suffix)) {

+ 212 - 37
TEAMModelOS/ClientApp/src/view/auth/Product.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
     <div class="product-container">
         <Loading v-show="isLoading"></Loading>
         <!-- 模组列表 -->
@@ -17,29 +17,30 @@
                         </p>
                         <p class="product-content">
                             {{item.content}}
+                            <a :href="item[lang]" target="_black" style="text-decoration: underline;">
+                                {{$t('auth.learnMore')}}
+                            </a>
                         </p>
                         <span :class="['product-status-tag', item.isPay ? 'has-pay' : '']">
                             {{item.isPay ? $t('auth.hasBuy') : $t('auth.noBuy')}}
                         </span>
                         <div style="display: flex;align-items: center;margin-top: 20px;">
                             <Button :type="item.isPay ? 'success' : 'primary'" :icon="item.isPay ? 'md-checkmark' : 'md-cart'" class="buy-btn" @click="toContact(item.isPay)">
-                                {{item.isPay ? $t('auth.using') :  $t('auth.buy')}}
+                                {{item.isPay ? $t('auth.using') :  $t('auth.contactUs')}}
+                                <!-- {{item.isPay ? $t('auth.using') :  $t('auth.buy')}} -->
                             </Button>
                             <p class="product-status" style="margin-top:5px" v-show="item.isPay">
                                 <!-- 时间 -->
                                 <span>
                                     {{$t('auth.expired')}}
                                     <span class="expire-time">
-                                        {{item.time}}
+                                        {{$jsFn.dateFormat(item.start)}}
+                                    </span>
+                                    -
+                                    <span class="expire-time">
+                                        {{$jsFn.dateFormat(item.end)}}
                                     </span>
                                 </span>
-                                <!-- 数量 -->
-                                <!-- <span v-show="item.count">
-                                        数量:
-                                        <span class="expire-time">
-                                            {{item.count}}人
-                                        </span>
-                                    </span> -->
                             </p>
                         </div>
                     </div>
@@ -60,7 +61,7 @@
                     </span>
                 </div>
                 <Alert show-icon v-if="spaceStatus == 2" type="warning">
-                    <p style="font-size:12px">
+                    <p s tyle="font-size:12px">
                         {{$t('auth.spaceWarning')}}
                     </p>
                 </Alert>
@@ -121,25 +122,123 @@
                 <EmptyData :top="80"></EmptyData> -->
             </vuescroll>
         </div>
+        <!-- 联系表单 -->
+        <Modal v-model="formStatus" className="ed-name-modal" :width="600" footer-hide>
+            <div slot="header" class="modal-header">
+                {{$t('auth.ctFrom')}}
+            </div>
+            <div class="edit-name-content">
+                <p>
+                    {{$store.state.userInfo.name}} {{$t('auth.ctFmText1')}}
+                </p>
+                <Form ref="contact" :model="contact" :label-width="80" :rules="ruleValidate" label-position="left" style="margin-top:30px">
+                    <FormItem :label="$t('auth.ctFmText2')" prop="name">
+                        <Input v-model="contact.name" :placeholder="$t('auth.ctFmText3')"></Input>
+                    </FormItem>
+                    <FormItem :label="$t('auth.ctFmText4')" prop="sex">
+                        <RadioGroup v-model="contact.sex">
+                            <Radio :label="$t('auth.ctFmText5')"></Radio>
+                            <Radio :label="$t('auth.ctFmText6')"></Radio>
+                        </RadioGroup>
+                    </FormItem>
+                    <FormItem :label="$t('auth.ctFmText7')" prop="mobile">
+                        <Input v-model="contact.mobile" type="number" :placeholder="$t('auth.ctFmText8')"></Input>
+                    </FormItem>
+                    <FormItem :label="$t('auth.ctFmText23')" prop="email">
+                        <Input v-model="contact.email" :placeholder="$t('auth.ctFmText24')"></Input>
+                    </FormItem>
+                    <!-- 不需要地区 -->
+                    <!-- <FormItem :label="$t('auth.ctFmText12')" prop="address">
+                        <BaseAreaPicker ref="areaPicker" v-if="isChinaSite"></BaseAreaPicker>
+                        <div class="country-select" v-if="!isChinaSite">
+                            <Select v-model="contact.address" filterable :placeholder="$t('settings.applyForm.place6')">
+                                <Option v-for="(country,index) in countryArr" :value="country.cn" :key="index">{{ country.cn }}</Option>
+                            </Select>
+                        </div>
+                    </FormItem> -->
+                    <FormItem :label="$t('auth.ctFmText13')" prop="content">
+                        <Select v-model="contact.content" multiple>
+                            <Option v-for="item in productList" :value="item.name" :key="item.prodCode">{{ item.name }}</Option>
+                            <Option :value="$t('auth.ctFmText14')">
+                                {{$t('auth.ctFmText14')}}
+                            </Option>
+                            <Option :value="$t('auth.ctFmText20')">
+                                {{$t('auth.ctFmText20')}}
+                            </Option>
+                            <Option :value="$t('auth.ctFmText21')">
+                                {{$t('auth.ctFmText21')}}
+                            </Option>
+                            <Option :value="$t('auth.ctFmText22')">
+                                {{$t('auth.ctFmText22')}}
+                            </Option>
+                            <Option :value="$t('auth.ctFmText15')">
+                                {{$t('auth.ctFmText15')}}
+                            </Option>
+                        </Select>
+                    </FormItem>
+
+                </Form>
+                <Button :loading="btnLoading" @click="sendInfo" long type="primary" class="confirm-btn">{{ $t('syllabus.confirm') }}</Button>
+            </div>
+        </Modal>
     </div>
 </template>
 <script>
+import countries from '@/static/countries.js'
+import enCountries from '@/static/countryCodeData.js'
+import twCitys from '@/static/twJson.js'
+import twTCitys from '@/static/twJsonT.js'
 import { mapGetters } from 'vuex'
 import SpaceInfo from './SpaceInfo.vue'
 export default {
     components: {
         SpaceInfo
     },
-    props:{
-        service:{
-            type:Array,
-            default:()=>{
+    props: {
+        service: {
+            type: Array,
+            default: () => {
                 return []
             }
         }
     },
     data() {
         return {
+            isShowTw: false,
+            curCountry: '',
+            curTwCity: [],
+            countryArr: [],
+            twCityArr: [],
+            ruleValidate: {
+                name: [
+                    { required: true, message: this.$t('auth.ctFmText3'), trigger: 'blur' }
+                ],
+                sex: [
+                    { required: true, message: this.$t('auth.ctFmText9'), trigger: 'change' }
+                ],
+                address: [
+                    { required: true, message: this.$t('auth.ctFmText16'), trigger: 'change' }
+                ],
+                content: [
+                    { required: true, type: 'array', message: this.$t('auth.ctFmText17'), trigger: 'change' }
+                ],
+                mobile: [
+                    { required: true, message: this.$t('auth.ctFmText10'), trigger: 'change' }
+                ],
+                email: [
+                    { required: true, message: this.$t('auth.ctFmText24'), trigger: 'blur' },
+                    { type: 'email', message: this.$t('auth.ctFmText25'), trigger: 'blur' }
+                ],
+            },
+            contact: {
+                name: this.$store.state.userInfo.name,
+                sex: '',
+                mobile: '',
+                content: [],
+                address: ''
+            },
+            btnLoading: false,
+            formStatus: false,
             isLoading: false,
             scaleCount: 0, //学校规模授权数
             spaceStatus: 1,
@@ -147,27 +246,39 @@ export default {
             productList: [
                 {
                     name: this.$t('auth.module1'),
-                    time: '--',
+                    start: 0,
+                    end: 0,
                     image: require("@/assets/image/module2.jpg"),
                     content: this.$t('auth.content1'),
                     isPay: 0,
                     prodCode: 'YMPCVCIM',
+                    'zh-cn': 'https://www.habook.com.cn/product.php?act=view&id=28',
+                    'zh-tw': 'https://www.habook.com/zh-tw/cloud.php?act=view&id=9',
+                    'en-us': 'https://www.habook.com/en/cloud.php?act=view&id=9'
                 },
                 {
                     name: this.$t('auth.module2'),
-                    time: '--',
+                    start: 0,
+                    end: 0,
                     image: require("@/assets/image/module1.jpg"),
                     content: this.$t('auth.content2'),
                     isPay: 0,
                     prodCode: 'VABAJ6NV',
+                    'zh-cn': 'https://www.habook.com.cn/product.php?act=view&id=30',
+                    'zh-tw': 'https://www.habook.com/zh-tw/cloud.php?act=view&id=8',
+                    'en-us': 'https://www.habook.com/en/cloud.php?act=view&id=8'
                 },
                 {
                     name: this.$t('auth.module3'),
-                    time: '--',
+                    start: 0,
+                    end: 0,
                     image: require("@/assets/image/module3.jpg"),
                     content: this.$t('auth.content3'),
                     isPay: 0,
                     prodCode: 'Sokrates',
+                    'zh-cn': 'https://www.habook.com.cn/cloud.php?act=view&id=22',
+                    'zh-tw': 'https://www.habook.com/zh-tw/product.php?act=view&id=37',
+                    'en-us': 'https://www.habook.com/en/product.php?act=view&id=37'
                 }
             ]
         }
@@ -176,29 +287,68 @@ export default {
         ...mapGetters({
             schoolBase: 'user/getSchoolBase',
         }),
+        isChinaSite() {
+            return this.$store.state.config.srvAdr === 'China'
+        },
         periodCount() {
             if (this.schoolBase && this.schoolBase.period) {
                 return this.schoolBase.period.length
             } else {
                 return 0
             }
+        },
+        lang() {
+            let curLang = localStorage.getItem('local') || 'zh-cn'
+            return curLang
         }
     },
     methods: {
-        toContact(isPay) {
-            if (!isPay) {
-                let urls = {
-                    en: 'https://www.habook.com/en/contact_us.php',
-                    tw: 'https://www.habook.com/zh-tw/contact_us.php',
-                    cn: 'https://www.habook.com.cn/contact_us.php'
-                }
-                let lang = localStorage.getItem('local') || 'zh-cn'
-                for (let key in urls) {
-                    if (lang.includes(key)) {
-                        window.open(/^(http:|https:)/i.test(urls[key]) ? urls[key] : "http://" + urls[key])
-                        break
+        /* 国家选择 */
+        onCountrySelect(val) {
+            if (val === '中国台湾' || val === '臺灣') {
+                this.isShowTw = true
+                this.curTwCity = this.twCityArr[0]
+            } else {
+                this.isShowTw = false
+            }
+        },
+        sendInfo() {
+            // if (this.isChinaSite) {
+            //     let pickResult = this.$refs.areaPicker.pickResult
+            //     let hasFullAddress = pickResult.province && pickResult.city && pickResult.area
+            //     this.contact.address = hasFullAddress ? (pickResult.province + pickResult.city + pickResult.area) : ''
+            // }
+            this.$refs['contact'].validate((valid) => {
+                if (valid) {
+                    let params = {
+                        // area: this.contact.address,
+                        name: this.$store.state.user.schoolProfile?.school_base?.name,
+                        tmdname: this.$store.state.userInfo.name,
+                        tmdid: this.$store.state.userInfo.TEAMModelId,
+                        gender: this.contact.sex,
+                        email: this.contact.email,
+                        cellphone: this.contact.mobile,
+                        content: this.contact.content.join(',')
                     }
+                    console.log(params)
+                    this.$api.serviceDriveAuth.sendContactInfo(params).then(
+                        res => {
+                            this.$Message.success(this.$t('auth.ctFmText18'))
+                            this.formStatus = false
+                        },
+                        err => {
+                            this.$Message.error(this.$t('auth.ctFmText19'))
+                        }
+                    )
+                } else {
+                    this.$Message.error(this.$t('auth.ctFmText11'));
                 }
+            })
+
+        },
+        toContact(isPay) {
+            if (!isPay) {
+                this.formStatus = true
             }
         },
         toTeacherMgt() {
@@ -218,22 +368,47 @@ export default {
                     item.time = svc.time //目前暂定取time字段,后续确认返回格式
                 }
             })
-
             //获取学校规模授权数量
             let scaleInfo = services.find(item => item.prodCode === '3CLYJ6NP')
             if (scaleInfo) this.scaleCount = scaleInfo.avaliable || 0
         }
     },
-    watch:{
-        service:{
-            deep:true,
-            immediate:true,
-            handler(n,o){
-                if(n){
+    watch: {
+        service: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                if (n) {
                     this.formatData()
                 }
             }
         }
+    },
+    created() {
+        let curLocal = localStorage.getItem('local')
+        let attr = 'CountryEn'
+        if (curLocal.includes('cn') || curLocal.includes('CN')) {
+            this.countryArr = countries
+            this.twCityArr = twCitys
+        } else if (curLocal.includes('tw') || curLocal.includes('TW')) {
+            this.twCityArr = twTCitys
+            for (const key in enCountries) {
+                this.countryArr.push(
+                    {
+                        cn: enCountries[key].CountryTw,
+                    }
+                )
+            }
+        } else {
+            this.twCityArr = twTCitys
+            for (const key in enCountries) {
+                this.countryArr.push(
+                    {
+                        cn: enCountries[key].CountryEn,
+                    }
+                )
+            }
+        }
     }
 }
 </script>

+ 7 - 0
TEAMModelOS/ClientApp/src/view/auth/Serial.less

@@ -183,6 +183,9 @@
     .room-name {
         color: white;
     }
+    .device-ip{
+        color: white;
+    }
 
     .band-serial {
         color: #fff;
@@ -290,4 +293,8 @@
     }
 
     margin-top: 20px;
+}
+.device-ip{
+    font-size: 12px;
+    text-align: center;
 }

+ 6 - 3
TEAMModelOS/ClientApp/src/view/auth/Serial.vue

@@ -137,6 +137,9 @@
                                 <p class="room-name">
                                     {{item.pcname || $t('auth.noDeviceName')}}
                                 </p>
+                                <p class="device-ip">
+                                    IP: {{item.ip || '- . - . - . -'}}
+                                </p>
                                 <p class="room-type-tag" :style="{background:item.classId ? '#2d8cf0' : '#c5c8ce'}">
                                     {{ item.classId ? getClassName(item.classId) : $t('auth.noClassId')}}
                                 </p>
@@ -159,7 +162,7 @@
                     </vuescroll>
                 </div>
             </Split>
-            <!-- 修改课堂记录名称 -->
+            <!-- 绑定教室 -->
             <Modal v-model="rltRoomStatus" className="ed-name-modal" footer-hide>
                 <div slot="header" class="modal-header">
                     {{$t('auth.rltClass')}}
@@ -329,7 +332,7 @@ export default {
         // 解绑教室
         unbandRoom(data) {
             this.$Modal.confirm({
-                title: this.$t('auth.bandErr'),
+                title: this.$t('auth.unrltClass'),
                 content: this.$t('auth.unbdRoomCt'),
                 onOk: () => {
                     let serialInfo = this.serialList.find(item => item.serial == this.deviceInfo.serial)
@@ -451,7 +454,7 @@ export default {
             }
         },
         formatData() {
-            let data = this.serial
+            let data = this._.cloneDeep(this.serial)
             let timestamp = Date.now()
             console.log(timestamp)
             data.forEach(item => {

+ 1 - 0
TEAMModelOS/ClientApp/src/view/classmgt/ClassNotice.less

@@ -82,4 +82,5 @@
     margin-bottom: 20px;
     text-indent: 2em;
     color: #515a6e;
+    
 }

+ 3 - 2
TEAMModelOS/ClientApp/src/view/classmgt/ClassNotice.vue

@@ -109,7 +109,8 @@ export default {
                 admin: "1",
                 code: this.$store.state.userInfo.schoolCode,
                 type: "class",
-                classes: this.classList.map(item => { return item.id })
+                classes: this.classList.map(item => { return item.id }),
+                scope:'school'
             }
             this.$api.notice.FindNotice(params).then(
                 res => {
@@ -142,7 +143,7 @@ export default {
                 onOk: () => {
                     let params = {
                         id: this.notifyListShow[index].id,
-                        code: this.notifyListShow[index].code.replace('Notice-', '')
+                        code: this.notifyListShow[index].code
                     }
                     this.$api.notice.DelNotice(params).then(
                         res => {

+ 0 - 0
TEAMModelOS/ClientApp/src/view/classmgt/CreateNotice.vue


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini