Procházet zdrojové kódy

Merge branch 'develop' into develop-rc

HiITEdenX před 2 roky
rodič
revize
7ea53f833d
57 změnil soubory, kde provedl 3044 přidání a 166 odebrání
  1. 9 1
      TEAMModelBI/ClientApp/src/api/index.js
  2. 1 1
      TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue
  3. 25 1
      TEAMModelBI/ClientApp/src/view/schoolServe/school.vue
  4. 1 1
      TEAMModelBI/ClientApp/src/view/schoolServe/setSchooladmin.vue
  5. 40 19
      TEAMModelBI/ClientApp/src/view/systemConfig/correlation.vue
  6. 436 0
      TEAMModelBI/ClientApp/src/view/systemConfig/manageschool.vue
  7. 7 2
      TEAMModelBI/ClientApp/src/view/teachermanage/manage.vue
  8. 1 1
      TEAMModelBI/Controllers/BINormal/AreaRelevantController.cs
  9. 8 1
      TEAMModelBI/Controllers/BINormal/BatchAreaController.cs
  10. 84 6
      TEAMModelBI/Controllers/BISchool/BatchSchoolController.cs
  11. 131 15
      TEAMModelBI/Controllers/BISchool/SchoolController.cs
  12. 11 11
      TEAMModelBI/Controllers/BITable/TableDingDingInfoController.cs
  13. 11 0
      TEAMModelBI/Controllers/BITest/TestController.cs
  14. 4 2
      TEAMModelBI/Lang/en-us.json
  15. 4 2
      TEAMModelBI/Lang/zh-cn.json
  16. 4 2
      TEAMModelBI/Lang/zh-tw.json
  17. 3 0
      TEAMModelBI/LogLang/zh-cn.json
  18. 6 0
      TEAMModelBI/Models/AssistSchool.cs
  19. 3 0
      TEAMModelBI/TEAMModelBI.csproj
  20. 4 2
      TEAMModelOS.FunctionV4/Lang/en-us.json
  21. 4 2
      TEAMModelOS.FunctionV4/Lang/zh-cn.json
  22. 4 2
      TEAMModelOS.FunctionV4/Lang/zh-tw.json
  23. 31 1
      TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs
  24. 3 3
      TEAMModelOS.FunctionV4/TEAMModelOS.FunctionV4.csproj
  25. 1 1
      TEAMModelOS.SDK/Models/Cosmos/BI/CreateSchoolInfo.cs
  26. 1 1
      TEAMModelOS.SDK/Models/Cosmos/BI/DingDingUserInfo.cs
  27. 18 4
      TEAMModelOS.SDK/Models/Cosmos/Common/ArtEvaluation.cs
  28. 1 1
      TEAMModelOS.SDK/Models/Cosmos/School/ExamInfo.cs
  29. 34 4
      TEAMModelOS.SDK/Models/Service/LessonService.cs
  30. 12 0
      TEAMModelOS/ClientApp/src/api/areaArt.js
  31. 24 0
      TEAMModelOS/ClientApp/src/common/BaseLayout.vue
  32. 31 3
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Exam.vue
  33. 27 3
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ExamQu.vue
  34. 171 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/PopQues.vue
  35. 32 0
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/RecordView.less
  36. 24 6
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/RecordView.vue
  37. 4 4
      TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ScoreBarChart.vue
  38. 20 0
      TEAMModelOS/ClientApp/src/router/routes.js
  39. 835 0
      TEAMModelOS/ClientApp/src/view/artexam/Create.vue
  40. 307 0
      TEAMModelOS/ClientApp/src/view/artexam/ExamSetting.vue
  41. 63 0
      TEAMModelOS/ClientApp/src/view/artexam/ExamSubject.vue
  42. 139 0
      TEAMModelOS/ClientApp/src/view/artexam/Mgt.vue
  43. 179 0
      TEAMModelOS/ClientApp/src/view/artexam/QuoTree.vue
  44. 66 0
      TEAMModelOS/ClientApp/src/view/artexam/WorkSetting.vue
  45. 61 0
      TEAMModelOS/ClientApp/src/view/artexam/WorkSubject.vue
  46. 0 4
      TEAMModelOS/ClientApp/src/view/learnactivity/ManualPaper.vue
  47. 4 1
      TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkData.vue
  48. 6 1
      TEAMModelOS/ClientApp/src/view/mycourse/MyCourse.vue
  49. 1 2
      TEAMModelOS/ClientApp/src/view/student-account/import/StuImport.vue
  50. 16 1
      TEAMModelOS/ClientApp/src/view/train/TrainDetail.vue
  51. 44 4
      TEAMModelOS/Controllers/Common/ArtController.cs
  52. 64 33
      TEAMModelOS/Controllers/Common/ExamController.cs
  53. 4 2
      TEAMModelOS/Lang/en-us.json
  54. 4 2
      TEAMModelOS/Lang/zh-cn.json
  55. 4 2
      TEAMModelOS/Lang/zh-tw.json
  56. 3 3
      TEAMModelOS/TEAMModelOS.csproj
  57. 9 9
      TEAMModelOS/appsettings.Development.json

+ 9 - 1
TEAMModelBI/ClientApp/src/api/index.js

@@ -132,6 +132,11 @@ export default {
     adminRelevanceschool(data){
         return post('/schoolcheck/get-managescs',data)
     },
+    //添加管理员所管理的学校(人员管理-管理学校)
+    adminAddschool(data){
+        return post('/schoolcheck/set-batchmageage',data)
+    },
+
 
     //学校管理
     //获取所有学校或 单独某个学校信息
@@ -318,7 +323,10 @@ export default {
     getApiInfo(data) {
         return post('/analyse/get-dayapi', data)
     },
-
+    //查询当前人员管理的学校列表
+    adminMangeSchool(data){
+        return post('/schoolcheck/get-managescs',data)
+    },
 
 
 

+ 1 - 1
TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue

@@ -980,7 +980,7 @@ export default {
         type: 'error',
         center: true,
       }).then(() => {
-        let data = { tmdId:adminSchoold.value.nowid, scId:value.id }
+        let data = { tmdId:adminSchoold.value.nowid, scIds:[value.id] }
         proxy.$api
           .deleteSchooladmin(data)
           .then((res) => {

+ 25 - 1
TEAMModelBI/ClientApp/src/view/schoolServe/school.vue

@@ -875,7 +875,31 @@ export default {
       }
       proxy.$api.getSchooldata(data).then((res) => {
         console.log(res, '查询的返回')
-        res.state === 200 ? tableData.value = res.schoolAssists : ''
+        // res.state === 200 ? tableData.value = res.schoolAssists : ''
+        if(res.state === 200){
+          if(res.schoolAssists.length !==0){
+            for (let i in res.schoolAssists) {
+            res.schoolAssists[i].serviceData = []
+            if (res.schoolAssists[i].assists) {
+              res.schoolAssists[i].assisName = ''
+              res.schoolAssists[i].location = res.schoolAssists[i].dist !== null ? res.schoolAssists[i].province + res.schoolAssists[i].city + res.schoolAssists[i].dist : res.schoolAssists[i].province + res.schoolAssists[i].city
+              let datas = res.schoolAssists[i].assists
+              for (let y in datas) {
+                res.schoolAssists[i].assisName = res.schoolAssists[i].assisName + datas[y].tmdName + ','
+              }
+            }
+            if (res.schoolAssists[i].service.length > 0) {
+              res.schoolAssists[i].service.forEach((x) => {
+                for (let m in patternIcon.value) {
+                  patternIcon.value[m].key === x ? res.schoolAssists[i].serviceData.push(patternIcon.value[m]) : ''
+                }
+              })
+            }
+          }
+          }
+          tableData.value = res.schoolAssists
+          tableData.value.forEach((item) => { item.areaName = ''; areaSelect.value.data.forEach((itema) => { item.areaId === itema.id ? item.areaName = itema.name : '' }) })
+        }
       }).catch((err) => {
         ElMessage.error('查询学校API异常,请重试')
       })

+ 1 - 1
TEAMModelBI/ClientApp/src/view/schoolServe/setSchooladmin.vue

@@ -129,7 +129,7 @@ export default {
         type: 'error',
         center: true,
       }).then(() => {
-        let data = { tmdId: value.id, scId: schoolValue.value.id }
+        let data = { tmdId: value.id, scIds: [schoolValue.value.id] }
         proxy.$api
           .deleteSchooladmin(data)
           .then((res) => {

+ 40 - 19
TEAMModelBI/ClientApp/src/view/systemConfig/correlation.vue

@@ -12,17 +12,18 @@
     </div>
     <p class="correlationbox-title">目前用户所关联学校:</p>
     <div class="possessbox" v-if="nowUsers.handleSchools.length >0">
-      <el-table :data="nowUsers.handleSchools" style="width: 100%" height="20vh" size="small">
-        <el-table-column :label="$t(`personnelManagement.personnelTable.headportrait`)" align="center">
+      <el-table :data="nowUsers.handleSchools" style="width: 100%" height="20vh" size="small" v-loading="loadingData.possess" element-loading-text="数据加载中...">
+        <el-table-column label="校徽" align="center">
           <template #default="scope">
-            <el-image style="width: 40px; height: 40px;" :src="scope.row.picture" fit="fill"></el-image>
+            <el-image style="width: 40px; height: 40px;" :src="scope.row.picture" fit="fill" v-if="scope.row.picture"></el-image>
+            <div class="notimage" v-else>暂无<br/>图片</div>
           </template>
         </el-table-column>
-        <el-table-column fixed prop="name" label="名称" />
-        <el-table-column prop="id" label="学校简码" />
+        <el-table-column prop="name" label="名称" align="center"/>
+        <el-table-column prop="id" label="学校简码" align="center"/>
         <!-- <el-table-column prop="name" label="版本" />
                 <el-table-column prop="name" label="目前顾问" /> -->
-        <el-table-column fixed="right" label="操作" width='80' v-if="PowerShow">
+        <el-table-column fixed="right" label="操作" width='80' align="center" v-if="PowerShow">
           <template #default="scope">
             <el-button type="text" size="small" @click="removeSchool(scope.row,scope.$index)">取消关联</el-button>
           </template>
@@ -44,18 +45,19 @@
         </div>
       </div>
       <div class="listbox">
-        <el-table :data="tableData" id="schoolList" style="width: 100%" height="45vh" size="small" @selection-change="checkSchool" empty-text='暂无相关搜索数据'>
+        <el-table :data="tableData" id="schoolList" style="width: 100%" height="45vh" size="small" @selection-change="checkSchool" empty-text='暂无相关搜索数据' v-loading="loadingData.list" element-loading-text="数据加载中...">
           <el-table-column type="selection" v-if="PowerShow" />
-          <el-table-column :label="$t(`personnelManagement.personnelTable.headportrait`)" align="center">
+          <el-table-column label="校徽" align="center">
             <template #default="scope">
-              <el-image style="width: 40px; height: 40px;" :src="scope.row.picture" fit="fill"></el-image>
+              <el-image style="width: 40px; height: 40px;" :src="scope.row.picture" fit="fill" v-if="scope.row.picture"></el-image>
+              <div class="notimage" v-else>暂无<br/>图片</div>
             </template>
           </el-table-column>
-          <el-table-column fixed prop="name" label="名称" />
-          <el-table-column prop="id" label="学校简码" />
+          <el-table-column prop="name" label="名称" align="center"/>
+          <el-table-column prop="id" label="学校简码" align="center"/>
           <!-- <el-table-column prop="name" label="版本" /> -->
-          <el-table-column prop="assisName" label="目前顾问" />
-          <el-table-column fixed="right" label="操作" width='80' v-if="PowerShow">
+          <el-table-column prop="assisName" label="目前顾问" align="center"/>
+          <el-table-column fixed="right" label="操作" width='80' align="center" v-if="PowerShow">
             <template #default="scope">
               <el-button type="text" size="small" @click="correlation(scope.row,tableData)">关联学校</el-button>
             </template>
@@ -91,6 +93,10 @@ export default {
     let timer = ref('')
     let nextpageToken = ref('')
     let scrollHeight = ref('init')
+    let loadingData=ref({
+      possess:true,
+      list:false,
+    })
     onMounted(() => {
       //监听表格滚动事件
       // let table = mutipleTable.value._value.layout.table.refs.bodyWrapper;
@@ -111,6 +117,7 @@ export default {
         ElMessage.success('已经到最底了')
         return
       }
+      loadingData.value.list=true
       let data = value ? { contToken: value } : {}
       proxy.$api
         .getSchooldata(data)
@@ -130,10 +137,10 @@ export default {
             nextpageToken.value = res.continuationToken
             if (!value) {
               tableData.value = res.schoolAssists;
-              original.value = res.schoolAssists;
+              original.value = JSON.parse(JSON.stringify(res.schoolAssists));
             } else {
               tableData.value.push(...res.schoolAssists)
-              original.value = tableData.value
+              // original.value = tableData.value
               scrollHeight.value = 'init'
             }
             processingSchool()
@@ -231,8 +238,10 @@ export default {
           })
       })
     }
-    function processingSchool () {
-      let allSchool = tableData.value
+    function processingSchool (state) {
+      console.log(tableData.value,original.value,'原始数据和变更数据')
+      state ? tableData.value=original.value:''
+      let allSchool = JSON.parse(JSON.stringify(tableData.value))
       let handleSchools = nowUsers.value.handleSchools
       for (let i in handleSchools) {
         let schoolInfo = handleSchools[i].id
@@ -241,6 +250,7 @@ export default {
         }
       }
       tableData.value = allSchool
+      loadingData.value.list=false
     }
     function debounce (fn, wait) {
       if (timer.value !== null) {
@@ -280,7 +290,7 @@ export default {
     watch(
       props,
       (newuser) => {
-        newuser ? (nowUsers.value = newuser.userdata) : ''
+        newuser ? (nowUsers.value = newuser.userdata,loadingData.value.possess=false,processingSchool(true)) : ''
         console.log(nowUsers.value, '触发监听')
       },
       { immediate: true, deep: true }
@@ -309,7 +319,8 @@ export default {
       timer,
       personnelSearch,
       scrollHeight,
-      loadData
+      loadData,
+      loadingData
     }
   },
 }
@@ -388,6 +399,16 @@ export default {
   width: 30%;
   text-align: right;
 }
+.notimage{
+  width: 40px;
+  height: 40px;
+  line-height: 20px;
+  text-align: center;
+  background-color: #bdc3c7;
+  font-size: 6px;
+  color: #ecf0f1;
+  margin: 0 auto;
+}
 </style>
 <style>
 .school-list-header .el-button--small {

+ 436 - 0
TEAMModelBI/ClientApp/src/view/systemConfig/manageschool.vue

@@ -0,0 +1,436 @@
+<template>
+    <div class="correlationbox">
+      <div class="nowuser">
+        <p class="correlationbox-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: 50px; height: 50px;border-radius:50%" :src="nowUsers.picture" fit="fill" v-else></el-image>
+          </div>
+          <div class="userlist-name">{{nowUsers.name}}({{nowUsers.mobile}})</div>
+        </div>
+      </div>
+      <p class="correlationbox-title">目前用户管理学校:</p>
+      <div class="possessbox" v-if="adminlist.length >0">
+        <el-table :data="adminlist" style="width: 100%" height="20vh" size="small"  v-loading="loadingData.possess" element-loading-text="数据加载中...">
+          <el-table-column label="校徽" align="center">
+            <template #default="scope">
+              <el-image style="width: 40px; height: 40px;" :src="scope.row.picture" fit="fill" v-if="scope.row.picture"></el-image>
+              <div class="notimage" v-else>暂无<br/>图片</div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="name" label="名称" align="center"/>
+          <el-table-column prop="id" label="学校简码" align="center"/>
+          <!-- <el-table-column prop="name" label="版本" />
+                  <el-table-column prop="name" label="目前顾问" /> -->
+          <el-table-column fixed="right" label="操作" align="center" v-if="PowerShow">
+            <template #default="scope">
+              <el-button type="text" size="small" @click="removeSchool(scope.row,scope.$index)">移除管理</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="nodata" v-else-if="adminlist.length ===0">
+        <div>暂无管理学校</div>
+      </div>
+      <div class="school-list">
+        <div class="school-list-header">
+          <div class="correlationbox-titles">学校列表:</div>
+          <div class="correlationbox-search">
+            <el-input v-model="schoolSearch" placeholder="输入学校名称/简码 搜索" :prefix-icon="Search" size="small" clearable />
+          </div>
+          <div class="correlationbox-btn">
+            <el-button type="primary" size="small" v-if="multipleSchool.length >0 && PowerShow" @click="multipleCorrelation">添加管理员选中学校</el-button>
+            <el-button type="primary" size="small" v-else-if="multipleSchool.length ===0 && PowerShow" disabled>添加管理员选中学校</el-button>
+          </div>
+        </div>
+        <div class="listbox">
+          <el-table :data="tableData" id="schoolList" style="width: 100%" height="45vh" size="small" @selection-change="checkSchool" empty-text='暂无相关搜索数据' v-loading="loadingData.list" element-loading-text="数据加载中...">
+            <el-table-column type="selection" v-if="PowerShow"  align="center"/>
+            <el-table-column label="校徽" align="center">
+              <template #default="scope">
+                <el-image style="width: 40px; height: 40px;" :src="scope.row.picture" fit="fill" v-if="scope.row.picture"></el-image>
+                <div class="notimage" v-else>暂无<br/>图片</div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="name" label="名称" />
+            <el-table-column prop="id" label="学校简码" />
+            <!-- <el-table-column prop="name" label="版本" /> -->
+            <el-table-column prop="assisName" label="目前管理员" >
+            <template #default="scope">
+                <div class="adminnamedata" :title="scope.row.assisName">{{scope.row.assisName}}</div>
+            </template>
+            </el-table-column>
+            <el-table-column fixed="right" label="操作"  v-if="PowerShow">
+              <template #default="scope">
+                <el-button type="text" size="small" @click="correlation(scope.row,tableData)">添加管理学校</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </div>
+  </template>
+  <script>
+  import { ref, getCurrentInstance, watch, onMounted } from 'vue'
+  import { ElMessage, ElLoading, ElMessageBox } from 'element-plus'
+  import { Search } from '@element-plus/icons'
+  export default {
+    props: {
+      userdata: {
+        type: Object,
+        default: () => { },
+      },
+    },
+    components: {
+      Search
+    },
+    setup (props) {
+      let { proxy } = getCurrentInstance()
+      let PowerShow = proxy.$access.identifyPosition(JSON.parse(localStorage.getItem('id_token')))
+      const tableDatas = ref([])
+      let tableData = ref([])
+      let nowUsers = ref()
+      let adminlist=ref([])
+      let multipleSchool = ref([])
+      let schoolSearch = ref('')
+      let original = ref([])
+      let timer = ref('')
+      let nextpageToken = ref('')
+      let scrollHeight = ref('init')
+      let loadingData=ref({
+      possess:false,
+      list:false,
+    })
+      onMounted(() => {
+        //监听表格滚动事件
+        // let table = mutipleTable.value._value.layout.table.refs.bodyWrapper;
+        let table = document.getElementById('schoolList')
+        console.log(table, '查看是否获取到')
+        table.addEventListener("scroll", (res) => { loadmore(res) }, true);
+      })
+      const loadmore = (res) => {
+        // console.log(res, res.target.scrollHeight, res.target.scrollTop + res.target.clientHeight, '实际高度')
+        if (res.target.scrollTop && ((res.target.scrollHeight - 10) <= (res.target.scrollTop + res.target.clientHeight))) {
+          scrollHeight.value = (res.target.scrollHeight - 10) - (res.target.scrollTop + res.target.clientHeight)
+          console.log(scrollHeight.value, '值')
+        }
+      }
+      function getSchoolList (value) {
+        console.log(value, '触发下一页')
+        if (nextpageToken.value == null) {
+          ElMessage.success('已经到最底了')
+          return
+        }
+        loadingData.value.list=true
+        let data = value ? { contToken: value } : {}
+        proxy.$api
+          .getSchooldata(data)
+          .then((res) => {
+            console.log(res, '学校的返回列表')
+            // res.state === 200 ? (tableData.value = res.schoolAssists) : ''
+            if (res.state === 200) {
+              for (let i in res.schoolAssists) {
+                if (res.schoolAssists[i].assists) {
+                  res.schoolAssists[i].assisName = ''
+                  let datas = res.schoolAssists[i].scAdmin
+                  for (let y in datas) {
+                    datas.length ===1 ? res.schoolAssists[i].assisName= datas[y].tmdName:res.schoolAssists[i].assisName = res.schoolAssists[i].assisName + datas[y].tmdName + ','
+                    // res.schoolAssists[i].assisName = res.schoolAssists[i].assisName + datas[y].tmdName + ','
+                  }
+                }
+              }
+              nextpageToken.value = res.continuationToken
+              if (!value) {
+                tableData.value = res.schoolAssists;
+                original.value = res.schoolAssists;
+              } else {
+                tableData.value.push(...res.schoolAssists)
+                 original.value = tableData.value
+                scrollHeight.value = 'init'
+              }
+              processingSchool()
+            }
+          })
+          .catch((err) => {
+            ElMessage.error('获取学校列表失败')
+          })
+      }
+      function correlation (value, data) {
+        value ? multipleSchool.value.push(value) :''
+        multipleCorrelation()
+        // console.log(value, data)
+        // ElMessageBox.confirm(`请问您确定将 ${nowUsers.value.name} 设为 ${value.name} 管理员吗 ?`, '添加管理学校', {
+        //   confirmButtonText: proxy.$t(`commonMsg.confirm`),
+        //   cancelButtonText: proxy.$t(`commonMsg.closes`),
+        //   type: 'success',
+        //   center: true,
+        // }).then(() => { 
+        // })
+      }
+      function removeSchool (value, index) {
+        console.log(value, nowUsers)
+            ElMessageBox.confirm(`请问您确定从当前管理列表中移除  ${value.name}  吗?`, '移除管理学校', {
+            confirmButtonText: proxy.$t(`commonMsg.confirm`),
+            cancelButtonText: proxy.$t(`commonMsg.closes`),
+            type: 'error',
+            center: true,
+        }).then(() => {
+            let data = { tmdId:nowUsers.value.tmdId, scIds:[value.id] }
+            proxy.$api
+            .deleteSchooladmin(data)
+            .then((res) => {
+                res.state === 200 ?
+                (ElMessage.success('操作成功'), adminManegeSc(nowUsers.value.tmdId)) :
+                res.state === 403 ?
+                    ElMessage.error('管理员至少存在一位,无法移除当前人员') :
+                    res.state === 1 ?
+                    ElMessage.error('无法删除自己,操作失败') :
+                    ''
+            })
+            .catch((error) => {
+                ElMessage.error('删除失败')
+            })
+        })
+      }
+      function checkSchool (value, row) {
+        multipleSchool.value = value
+        console.log(multipleSchool.value, '111')
+      }
+      function multipleCorrelation () {
+        let schoolData = multipleSchool.value
+        let schoolName = ''
+        if (schoolData.length < 5) {
+          schoolData.forEach((item) => {
+            schoolName = schoolName + ',' + item.name
+          })
+        } else {
+          for (let i = 0; i < 5; i++) {
+            schoolName = schoolName + ',' + schoolData[i].name
+          }
+          schoolName = schoolName + '等...'
+        }
+        ElMessageBox.confirm(`请问您确定将 ${nowUsers.value.name} 设为 ${schoolName} 管理员吗 ?`, '添加管理学校', {
+          confirmButtonText: proxy.$t(`commonMsg.confirm`),
+          cancelButtonText: proxy.$t(`commonMsg.closes`),
+          type: 'success',
+          center: true,
+        }).then(() => {
+          let schoolArr = []
+          for (let i in schoolData) {
+            schoolArr.push({id:schoolData[i].id,name:schoolData[i].name,picture:schoolData[i].picture,areaId:schoolData[i].areaId})
+          }
+          let data = {tmdId: nowUsers.value.tmdId, tmdName: nowUsers.value.tmdName, tmdPic: nowUsers.value.picture, scSimplles:schoolArr }
+          console.log(data,'提交数据')
+          proxy.$api
+            .adminAddschool(data)
+            .then((res) => {
+              console.log(res, '成功的返回')
+              res.state === 200
+                ? ((adminlist.value =  adminlist.value.concat(schoolData)), getSchoolList(), processingSchool(), ElMessage.success('操作成功'))
+                : res.state === 201
+                  ? (ElMessage.success('已关联,请勿重复操作'), getSchoolList())
+                  : ''
+            })
+            .catch((error) => {
+              ElMessage.error('关联失败,API异常')
+            })
+        })
+        multipleSchool.value=[]
+      }
+      //处理数据 已是管理员不显示在下面列表
+      function processingSchool (state) {
+        state ? tableData.value=original.value:''
+        let allSchool = JSON.parse(JSON.stringify(tableData.value))
+        let manageSchoolList= adminlist.value
+        for (let i in manageSchoolList) {
+          let schoolInfo = manageSchoolList[i].id
+          for (let s in allSchool) {
+            allSchool[s].id === schoolInfo ? allSchool.splice(s, 1) : ''
+          }
+        }
+        tableData.value = allSchool
+        loadingData.value.list=false
+      }
+      function debounce (fn, wait) {
+        if (timer.value !== null) {
+          clearTimeout(timer.value)
+        }
+        timer.value = setTimeout(fn, wait)
+      }
+      function personnelSearch () {
+        let names = schoolSearch.value
+        let reg = new RegExp("[\\u4E00-\\u9FFF]+", "g")
+        let data = reg.test(names) ? { name: names } : { scId: names }
+        proxy.$api.getSchooldata(data).then((res) => {
+          if (res.state === 200) {
+            for (let i in res.schoolAssists) {
+              if (res.schoolAssists[i].assists) {
+                res.schoolAssists[i].assisName = ''
+                let datas = res.schoolAssists[i].assists
+                for (let y in datas) {
+                  res.schoolAssists[i].assisName = res.schoolAssists[i].assisName + datas[y].tmdName + ','
+                }
+              }
+            }
+            tableData.value = res.schoolAssists
+            processingSchool()
+          }
+        }).catch((error) => {
+          ElMessage.error('搜索学校失败,API异常')
+        })
+      }
+      //当前管理员 管理的学校
+      function adminManegeSc(id){
+        loadingData.value.possess=true
+        let data={tmdId:id}
+        proxy.$api.adminMangeSchool(data).then((res)=>{
+            res.state === 200 ? (adminlist.value=res.mScInfos,processingSchool(true)):''
+        }).catch((error)=>{
+            ElMessage.error('API异常,查询当前用户管理学校列表失败')
+        })
+        loadingData.value.possess=false
+      }
+      watch(scrollHeight, (newdata, olddata) => {
+        console.log(newdata, olddata, '监听的数据')
+        olddata === 'init' && newdata <= 0 ? getSchoolList(nextpageToken.value) : ''
+      })
+      watch(
+        props,
+        (newuser) => {
+        console.log(newuser, '触发监听Manage')
+          newuser ? (nowUsers.value = newuser.userdata,adminManegeSc(newuser.userdata.tmdId)) : ''
+        },
+        { immediate: true, deep: true }
+      )
+      watch(schoolSearch, (newdata) => {
+        if (newdata.trim().length !== 0) {
+          debounce(personnelSearch, 500)
+        } else {
+          tableData.value = original.value
+        }
+      })
+      getSchoolList()
+      return {
+        tableData,
+        tableDatas,
+        correlation,
+        removeSchool,
+        nowUsers,
+        getSchoolList,
+        checkSchool,
+        multipleSchool,
+        multipleCorrelation,
+        processingSchool,
+        PowerShow,
+        schoolSearch,
+        timer,
+        personnelSearch,
+        scrollHeight,
+        adminManegeSc,
+        adminlist,
+        loadingData
+      }
+    },
+  }
+  </script>
+  <style scoped>
+  .correlationbox {
+    width: 100%;
+  }
+  .correlationbox-title {
+    font-size: 14px;
+    color: #b2bec3;
+    text-align: left;
+  }
+  .possessbox {
+    width: 100%;
+    overflow: hidden;
+    height: 20vh;
+    /* border: 1px solid #ccc; */
+  }
+  .school-list {
+    width: 100%;
+    margin-top: 7%;
+  }
+  .nodata {
+    width: 100%;
+    height: 20vh;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  .nodata {
+    font-size: 18px;
+    color: #bdc3c7;
+    font-weight: 700;
+  }
+  .userlist {
+    width: 100%;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  .userlist-name {
+    font-size: 14px;
+    color: #7f8c8d;
+  }
+  .photobox {
+    display: inline-block;
+    width: 20%;
+    vertical-align: top;
+  }
+  .userlist-name {
+    text-align: left;
+    display: inline-block;
+    width: 60%;
+    vertical-align: top;
+    line-height: 40px;
+    margin-left: 0%;
+    font-size: 18px;
+  }
+  .school-list-header {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+  }
+  .correlationbox-titles {
+    width: 20%;
+    font-size: 14px;
+    color: #b2bec3;
+    text-align: left;
+  }
+  .correlationbox-search {
+    width: 45%;
+  }
+  .correlationbox-btn {
+    width: 30%;
+    text-align: right;
+  }
+  .notimage {
+  width: 40px;
+  height: 40px;
+  line-height: 20px;
+  text-align: center;
+  background-color: #bdc3c7;
+  font-size: 6px;
+  color: #ecf0f1;
+  margin: 0 auto;
+}
+.adminnamedata{
+-webkit-box-orient: vertical;
+display: -webkit-box;
+overflow-wrap: break-word;
+overflow: hidden;
+text-overflow: ellipsis;
+-webkit-line-clamp:2;
+}
+  </style>
+  <style>
+  .school-list-header .el-button--small {
+    min-height: 25px;
+    padding: 7px 10px;
+  }
+  </style>
+  

+ 7 - 2
TEAMModelBI/ClientApp/src/view/teachermanage/manage.vue

@@ -63,7 +63,7 @@
           <div v-else>无</div>
         </template>
       </el-table-column>
-      <el-table-column label="管理学校数" align="center" sortable :sort-method="schoolSort">
+      <el-table-column label="关联学校数" align="center" sortable :sort-method="schoolSort">
         <template #default="scope">
           <div v-if="scope.row.handleSchools.length !==0">
             <svg class="qxmages" aria-hidden="true">
@@ -98,6 +98,9 @@
         <el-tab-pane label="关联学校" name="second" v-if="nowUser.handleRoles.includes('assist') || nowUser.handleRoles.includes('admin')">
           <Correlation :userdata="nowUser"></Correlation>
         </el-tab-pane>
+        <el-tab-pane label="管理学校" name="manegeschool" v-if="nowUser.handleRoles.includes('admin')">
+          <Manageschool :userdata="nowUser"></Manageschool>
+        </el-tab-pane>
       </el-tabs>
       <div class="changeBtn" v-show="activeName === 'first'">
         <el-button type="primary" size="small" v-if="drawerChange" @click="changeSubmit">保存变更</el-button>
@@ -121,7 +124,7 @@
             <div :class="[items.state ?'contentbox-title':'switchoff']">{{items.name}}</div>
             <el-switch v-model="items.state" class="manatips-kg" @change="changeManage(items.rowKey,items.state)" />
           </div>
-          <span class="identification" data-v-ee6fa170="">{{item.text}}</span>
+          <span class="identification">{{item.text}}</span>
         </div>
         <div class="admintips-box" v-show="isAdmin">
           <div class="manatips-name">{{userisAdmin.name}}</div>
@@ -138,10 +141,12 @@ import { ElMessage, ElLoading } from 'element-plus'
 import jwt_decode from 'jwt-decode'
 import Operates from '@/view/systemConfig/operate.vue'
 import Correlation from '@/view/systemConfig/correlation.vue'
+import Manageschool from '@/view/systemConfig/manageschool.vue'
 export default {
   components: {
     Operates,
     Correlation,
+    Manageschool
   },
   setup () {
     let { proxy } = getCurrentInstance()

+ 1 - 1
TEAMModelBI/Controllers/BINormal/AreaRelevantController.cs

@@ -424,7 +424,7 @@ namespace TEAMModelBI.Controllers.BINormal
         /// <returns></returns>
         [ProducesDefaultResponseType]
         [HttpPost("get-schools")]
-        public async Task<IActionResult> GetSchools(JsonElement jsonElement) 
+        public async Task<IActionResult> GetSchools(JsonElement jsonElement)
         {
             jsonElement.TryGetProperty("areaId", out JsonElement areaId);
             //jsonElement.TryGetProperty("site", out JsonElement site);//分开部署,就不需要,一站多用时,取消注释

+ 8 - 1
TEAMModelBI/Controllers/BINormal/BatchAreaController.cs

@@ -30,6 +30,8 @@ using TEAMModelOS.SDK.Context.BI;
 using TEAMModelOS.SDK.DI.CoreAPI;
 using System.Text;
 using DocumentFormat.OpenXml.Bibliography;
+using Microsoft.Extensions.Hosting;
+using Microsoft.AspNetCore.Hosting;
 
 namespace TEAMModelBI.Controllers.BINormal
 {
@@ -46,8 +48,9 @@ namespace TEAMModelBI.Controllers.BINormal
         private readonly AzureServiceBusFactory _serviceBus;
         private readonly IHttpClientFactory _http;
         private readonly CoreAPIHttpService _coreAPIHttpService;
+        private readonly IWebHostEnvironment _environment; //读取文件
 
-        public BatchAreaController(AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, IOptionsSnapshot<Option> option, IConfiguration configuration, NotificationService notificationService, AzureServiceBusFactory serviceBus, IHttpClientFactory http, CoreAPIHttpService coreAPIHttpService) 
+        public BatchAreaController(AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, IOptionsSnapshot<Option> option, IConfiguration configuration, NotificationService notificationService, AzureServiceBusFactory serviceBus, IHttpClientFactory http, CoreAPIHttpService coreAPIHttpService, IWebHostEnvironment hostingEnvironment) 
         {
             _azureCosmos = azureCosmos;
             _dingDing = dingDing;
@@ -58,6 +61,7 @@ namespace TEAMModelBI.Controllers.BINormal
             _serviceBus = serviceBus;
             _http = http;
             _coreAPIHttpService = coreAPIHttpService;
+            _environment = hostingEnvironment;
         }
 
         /// <summary>
@@ -825,6 +829,9 @@ namespace TEAMModelBI.Controllers.BINormal
 
                 //发送消息分区键
                 string partitionCode = "DelBeforeCopyAbility-mark";
+                //v2通知
+                Teacher targetTeacher = await cosmosClient.GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>($"1", new PartitionKey($"Base"));
+                _coreAPIHttpService.PushNotify(new List<IdNameCode> { new IdNameCode { id = targetTeacher.id, name = targetTeacher.name, code = targetTeacher.lang } }, "transfer-admin_school", Constant.NotifyType_IES5_Management, new Dictionary<string, object> { { "tmdname", "账号名称" }, { "schooName", "学校名称" }, { "schoolId", $"学校id" }, { "tmdid", "账号id" } }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
 
                 //执行复制操作
                 BatchCopyFile batchCopyFile = new();

+ 84 - 6
TEAMModelBI/Controllers/BISchool/BatchSchoolController.cs

@@ -32,9 +32,8 @@ using TEAMModelOS.SDK.Context.Constant;
 using Pipelines.Sockets.Unofficial.Arenas;
 using Microsoft.Identity.Client;
 using TEAMModelOS.SDK.Models.Dtos;
-using static TEAMModelBI.Controllers.BITest.TestController;
-using static TEAMModelBI.Controllers.BISchool.BatchSchoolController;
 using DocumentFormat.OpenXml.Spreadsheet;
+using DocumentFormat.OpenXml.Wordprocessing;
 
 namespace TEAMModelBI.Controllers.BISchool
 {
@@ -84,6 +83,40 @@ namespace TEAMModelBI.Controllers.BISchool
             return Ok(new { authorityBIList });
         }
 
+        /// <summary>
+        /// 生成学校Code信息
+        /// </summary>
+        /// <param name="jsonElement"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [AuthToken(Roles = "admin,rdc")]
+        [HttpPost("get-schoolcode")]
+        public async Task<IActionResult> GetSchoolCode(JsonElement jsonElement)
+        {
+            if (!jsonElement.TryGetProperty("schools", out JsonElement schools)) return BadRequest();
+            var cosmosClient = _azureCosmos.GetCosmosClient();
+            List<CreateSchoolInfo> cSchools = schools.ToObject<List<CreateSchoolInfo>>();
+
+            List<CreateSchoolInfo> createScInfo = new();  //生成好的学校Codd信息
+
+            foreach (var item in cSchools)
+            {
+                CreateSchoolInfo createSchoolInfo = item;
+                //生成学校ID
+                bool tempStaus = true;
+                do
+                {
+                    createSchoolInfo = await SchoolCode.GenerateSchoolCode(createSchoolInfo, _dingDing, _environment);
+                    var schoolStatu = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync($"{createSchoolInfo.id}", new PartitionKey($"Base"));
+                    if (schoolStatu.Status != 200) tempStaus = false;
+                    else createSchoolInfo.createCount = createSchoolInfo.createCount >= 3 ? createSchoolInfo.createCount = 3 : createSchoolInfo.createCount += 1;
+                } while (tempStaus);
+                createScInfo.Add(createSchoolInfo);
+            }
+
+            return Ok(new { state = RespondCode.Ok, createScInfo });
+        }
+
         /// <summary>
         /// 批量创校   //已对接
         /// </summary>
@@ -433,6 +466,10 @@ namespace TEAMModelBI.Controllers.BISchool
                 else if(upSc.Count == 1)
                     await _dingDing.SendBotMsg($"BI,{_option.Location} \n 单个建校信息:{noticeDD}", GroupNames.成都开发測試群組);
 
+                //v2通知
+                Teacher targetTeacher = await cosmosClient.GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>($"1", new PartitionKey($"Base"));
+                _coreAPIHttpService.PushNotify(new List<IdNameCode> { new IdNameCode { id = targetTeacher.id, name = targetTeacher.name, code = targetTeacher.lang } }, "transfer-admin_school", Constant.NotifyType_IES5_Management, new Dictionary<string, object> { { "tmdname", "账号名称" }, { "schooName", "学校名称" }, { "schoolId", $"学校id" }, { "tmdid", "账号id" } }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
+
                 //保存操作记录
                 await AzureStorageBlobExtensions.SaveBILog(blobClient, tableClient, "school-batchAdd", stringBuilder?.ToString(), _dingDing, httpContext: HttpContext);
                 if (schools.Count == foundSchools.biSchools.Count || userScs.Count == foundSchools.biSchools.Count)
@@ -576,6 +613,7 @@ namespace TEAMModelBI.Controllers.BISchool
                     }
 
                     item.assists = await CommonFind.FindSchoolRoles(cosmosClient, item.id, "assist");
+                    item.scAdmin = await CommonFind.FindSchoolRoles(cosmosClient, item.id, "admin");                    
                     item.lessonCount = await CommonFind.GetSqlValueCount(cosmosClient, "School", $"select value(count(c.id)) from c ", $"LessonRecord-{item.id}");
                 }
 
@@ -1042,8 +1080,13 @@ namespace TEAMModelBI.Controllers.BISchool
             if (!jsonElement.TryGetProperty("scNames", out JsonElement _scNames)) return BadRequest();
             if (!jsonElement.TryGetProperty("accounts", out JsonElement _accounts)) return BadRequest();
             if (!jsonElement.TryGetProperty("areaIds", out JsonElement _areaIds)) return BadRequest();
-
+            jsonElement.TryGetProperty("schools", out JsonElement schools);
+            
             var cosmosClient = _azureCosmos.GetCosmosClient();
+            List<CreateSchoolInfo> cSchools = new();
+            if (!string.IsNullOrEmpty($"{schools}"))
+                cSchools = schools.ToObject<List<CreateSchoolInfo>>();
+
 
             List<string> scNames = _scNames.ToObject<List<string>>();
             List<string> accounts = _accounts.ToObject<List<string>>();
@@ -1053,6 +1096,7 @@ namespace TEAMModelBI.Controllers.BISchool
             List<string> noAccounts = new();    // 不存在的账户
             List<string> noAreaIds = new();     //不存在的学区
             List<string> synPro = new();      //已同步省平台
+            List<CreateSchoolInfo> createScInfo = new();  //学校简码信息
 
             if (scNames.Count > 0)
             {
@@ -1116,10 +1160,25 @@ namespace TEAMModelBI.Controllers.BISchool
                 }
             }
 
+            foreach (var item in cSchools)
+            {
+                CreateSchoolInfo createSchoolInfo = item;
+                //生成学校ID
+                bool tempStaus = true;
+                do
+                {
+                    createSchoolInfo = await SchoolCode.GenerateSchoolCode(createSchoolInfo, _dingDing, _environment);
+                    var schoolStatu = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync($"{createSchoolInfo.id}", new PartitionKey($"Base"));
+                    if (schoolStatu.Status != 200) tempStaus = false;
+                    else createSchoolInfo.createCount = createSchoolInfo.createCount >= 3 ? createSchoolInfo.createCount = 3 : createSchoolInfo.createCount += 1;
+                } while (tempStaus);
+                createScInfo.Add(createSchoolInfo);
+            }
+
             if (existScNames.Count > 0 || noAccounts.Count > 0 || noAreaIds.Count > 0 || synPro.Count > 0)
-                return Ok(new { state = RespondCode.Created, existScNames, noAccounts, noAreaIds, synPro });
+                return Ok(new { state = RespondCode.Created, existScNames, noAccounts, noAreaIds, synPro, createScInfo });
             else
-                return Ok(new { state = RespondCode.Ok });
+                return Ok(new { state = RespondCode.Ok , createScInfo });
         }
 
         /// <summary>
@@ -1127,6 +1186,7 @@ namespace TEAMModelBI.Controllers.BISchool
         /// </summary>
         /// <param name="jsonElement"></param>
         /// <returns></returns>
+        [ProducesDefaultResponseType]
         [HttpPost("set-batchmanage")]
         public async Task<IActionResult> SetBatchManage(JsonElement jsonElement)
         {
@@ -1232,6 +1292,21 @@ namespace TEAMModelBI.Controllers.BISchool
                 return Ok(new { state = RespondCode.Ok });
         }
 
+        /// <summary>
+        /// 批量设置学校集合中学校多个管理员
+        /// </summary>
+        /// <param name="jsonElement"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("batch-scmanage")]
+        public async Task<IActionResult> BatchScManage(JsonElement jsonElement) 
+        {
+
+
+
+            return Ok(new { state = RespondCode.Ok });
+        }
+
         #region   预设学校基础信息 多语言
 
         /// <summary>
@@ -1443,11 +1518,14 @@ namespace TEAMModelBI.Controllers.BISchool
             public string standard { get; set; }
         }
 
-
+        /// <summary>
+        /// 存在的学校
+        /// </summary>
         public record ExistScManage
         {
             public string scId{ get; set; }
             public string tmdId { get; set; }
         }
+
     }
 }

+ 131 - 15
TEAMModelBI/Controllers/BISchool/SchoolController.cs

@@ -1,4 +1,5 @@
 using Azure.Cosmos;
+using Azure.Storage.Blobs;
 using HTEXLib.COMM.Helpers;
 using MathNet.Numerics.LinearAlgebra;
 using MathNet.Numerics.LinearAlgebra.Double;
@@ -34,6 +35,7 @@ using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Models.Cosmos.BI;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using TEAMModelOS.SDK.Models.Service.BI;
+using static TEAMModelBI.Controllers.BISchool.SchoolController;
 
 namespace TEAMModelBI.Controllers.BISchool
 {
@@ -256,7 +258,7 @@ namespace TEAMModelBI.Controllers.BISchool
                             foreach (var item in teachers)
                             {
                                 msg.Append($"{item.name}[{item.id}]");
-                                var tchSchool = item.schools.Find(f => f.schoolId.Equals($"{school.id}") && f.areaId.Equals($"{_areaId}"));
+                                var tchSchool = item.schools.Find(f => f.schoolId.Equals($"{school.id}"));
                                 if (tchSchool == null)
                                 {
                                     Teacher.TeacherSchool teacherSchool = new() { schoolId = school.id, name = school.name, status = "join", time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), picture = school.picture, areaId = school.areaId };
@@ -698,8 +700,8 @@ namespace TEAMModelBI.Controllers.BISchool
                 }
                 else { areaId = $"{scAreaId}"; }
 
-                Teacher teacher = null;
-                SchoolTeacher schoolTeacher = null;
+                Teacher teacher = new();
+                SchoolTeacher schoolTeacher = new();
                 var resTeacher = await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync($"{tmdId}", new PartitionKey($"Base"));
                 if (resTeacher.Status == 200)
                 {
@@ -788,31 +790,145 @@ namespace TEAMModelBI.Controllers.BISchool
         public async Task<IActionResult> DelSchoolManage(JsonElement jsonElement)
         {
             if (!jsonElement.TryGetProperty("tmdId", out JsonElement tmdId)) return BadRequest();
-            if (!jsonElement.TryGetProperty("scId", out JsonElement scId)) return BadRequest();
+            if (!jsonElement.TryGetProperty("scIds", out JsonElement _scIds)) return BadRequest();
             //jsonElement.TryGetProperty("site", out JsonElement site);//分开部署,就不需要,一站多用时,取消注释
+
+            List<string> scIds = _scIds.ToObject<List<string>>();
             var cosmosClient = _azureCosmos.GetCosmosClient();
             ////分开部署,就不需要,一站多用时,取消注释
             //if ($"{site}".Equals(BIConst.Global))
             //    cosmosClient = _azureCosmos.GetCosmosClient(name: BIConst.Global);
+            List<string> existScId = new();
 
             SchoolTeacher scTeacher = null;
-            var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{tmdId}", new PartitionKey($"Teacher-{scId}"));
-            if (response.Status == 200)
+            foreach (var scId in scIds)
             {
-                using var json = await JsonDocument.ParseAsync(response.ContentStream);
-                scTeacher = json.ToObject<SchoolTeacher>();
-                if (scTeacher.roles.Contains("admin"))
+                var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{tmdId}", new PartitionKey($"Teacher-{scId}"));
+                if (response.Status == 200)
                 {
-                    scTeacher.roles.Remove("admin");
-                    scTeacher = await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolTeacher>(scTeacher, scTeacher.id, new PartitionKey($"Teacher-{scId}"));
+                    using var json = await JsonDocument.ParseAsync(response.ContentStream);
+                    scTeacher = json.ToObject<SchoolTeacher>();
+                    if (scTeacher.roles.Contains("admin"))
+                    {
+                        scTeacher.roles.Remove("admin");
+                        scTeacher = await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolTeacher>(scTeacher, scTeacher.id, new PartitionKey($"Teacher-{scId}"));
+                    }
+                    else
+                        existScId.Add(scId);
                 }
                 else
-                    return Ok(new { state = RespondCode.NotFound, msg = "该账户不是管理员" });
+                    existScId.Add(scId);
             }
-            else
-                return Ok(new { state = RespondCode.NotFound, msg = "未找到醍摩豆管理员信息" });
 
-            return Ok(new { state = RespondCode.Ok, scTeacher });
+            return Ok(new { state = RespondCode.Ok, existScId });
+        }
+
+        /// <summary>
+        /// 一个账户设置多个学校的管理员
+        /// </summary>
+        /// <param name="jsonElement"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [AuthToken(Roles = "admin,rdc")]
+        [HttpPost("set-batchmageage")]
+        public async Task<IActionResult> SetBatchScManage(JsonElement jsonElement)
+        {
+            try
+            {
+                var (_tmdId, _tmdName, _, _, _, _) = HttpJwtAnalysis.JwtXAuthBI(HttpContext.GetXAuth("AuthToken"), _option);
+
+                if (!jsonElement.TryGetProperty("tmdId", out JsonElement tmdId)) return BadRequest();
+                if (!jsonElement.TryGetProperty("tmdName", out JsonElement tmdName)) return BadRequest();
+                jsonElement.TryGetProperty("tmdPic", out JsonElement picture);
+                if (!jsonElement.TryGetProperty("scSimplles", out JsonElement scSimplles)) return BadRequest();
+                List<BatchScManage> schools = scSimplles.ToObject<List<BatchScManage>>();
+                List<BatchScManage> existManageSc = new();
+                Teacher teacher = new();
+                StringBuilder msg = new($"{_tmdName}[{_tmdId}]设置账号:");
+
+                var cosmosClient = _azureCosmos.GetCosmosClient();
+                var tableClient = _azureStorage.GetCloudTableClient();
+                var blobClient = _azureStorage.GetBlobContainerClient(containerName: "0-public");
+
+                if (schools.Count > 0)
+                {
+                    var resTeacher = await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync($"{tmdId}", new PartitionKey($"Base"));
+                    if (resTeacher.Status == 200)
+                    {
+                        using var tchJson = await JsonDocument.ParseAsync(resTeacher.ContentStream);
+                        teacher = tchJson.ToObject<Teacher>();
+                    }
+                    else
+                    {
+                        teacher.id = $"{tmdId}";
+                        teacher.name = $"{tmdName}";
+                        teacher.picture = string.IsNullOrEmpty($"{picture}") ? "" : $"{picture}";
+                        teacher.pk = "Base";
+                        teacher.code = "Base";
+                        teacher.size = 1;
+                        teacher.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                    }
+                    msg.Append($"{teacher.name}[{teacher.id}]设置学校管理员;学校信息:");
+
+                    foreach (var school in schools)
+                    {
+                        SchoolTeacher schoolTeacher = null;
+                        var existArea = teacher.schools.Find(f => f.schoolId.Equals($"{school.id}"));
+                        if (existArea == null)
+                        {
+                            teacher.schools.Add(new Teacher.TeacherSchool { schoolId = $"{school.id}", name = $"{school.id}", status = "join", time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), picture = string.IsNullOrEmpty($"{school.picture}") ? "" : $"{school.picture}", areaId = string.IsNullOrEmpty($"{school.areaId}") ? "" : $"{school.areaId}" });
+                        }
+
+                        var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync($"{tmdId}", new PartitionKey($"Teacher-{school.id}"));
+                        if (response.Status == 200)
+                        {
+                            using var json = await JsonDocument.ParseAsync(response.ContentStream);
+                            schoolTeacher = json.ToObject<SchoolTeacher>();
+                            if (!schoolTeacher.roles.Contains("admin"))
+                            {
+                                schoolTeacher.roles.Add("admin");
+                                schoolTeacher = await cosmosClient.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<SchoolTeacher>(schoolTeacher, schoolTeacher.id, new PartitionKey($"Teacher-{school.id}"));
+                            }
+                            else
+                                existManageSc.Add(school);
+                        }
+                        else
+                        {
+                            schoolTeacher = new()
+                            {
+                                id = $"{tmdId}",
+                                code = $"Teacher-{school.id}",
+                                roles = new List<string> { "admin", "teacher" },
+                                job = "管理员",
+                                name = $"{tmdName}",
+                                picture = string.IsNullOrEmpty($"{picture}") ? "" : $"{picture}",
+                                status = "join",
+                                createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
+                                pk = "Teacher",
+                                ttl = -1
+                            };
+
+                            schoolTeacher = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").CreateItemAsync<SchoolTeacher>(schoolTeacher, new PartitionKey($"Teacher-{school.id}"));
+                        }
+                        msg.Append($"{school.name}[{school.id}]");
+                    }
+
+                    if(resTeacher.Status == 200)
+                        teacher = await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, teacher.id, new PartitionKey("Base"));
+                    else
+                        teacher = await cosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").CreateItemAsync<Teacher>(teacher, new PartitionKey("Base"));
+                }
+
+                //保存操作记录
+                await AzureStorageBlobExtensions.SaveBILog(blobClient, tableClient, "schoolTeacher-add", msg.ToString(), _dingDing, httpContext: HttpContext);
+
+                return Ok(new { state = RespondCode.Ok, teacher });
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"BI,{_option.Location}  /schoolcheck/set-batchmageage \n {ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+                return BadRequest();
+            }
         }
 
         /// <summary>

+ 11 - 11
TEAMModelBI/Controllers/BITable/TableDingDingInfoController.cs

@@ -1299,7 +1299,7 @@ namespace TEAMModelBI.Controllers.BITable
 
             //获取部门人员信息
             IDingTalkClient userListClient = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/list");
-            long joinDate =  DateTimeOffset.UtcNow.AddDays(-91).ToUnixTimeMilliseconds();
+            //long joinDate =  DateTimeOffset.UtcNow.AddDays(-91).ToUnixTimeMilliseconds();
 
             //获取部门用户
             OapiV2UserListRequest reqUserList = new()
@@ -1317,14 +1317,14 @@ namespace TEAMModelBI.Controllers.BITable
             {
                 foreach (var itemUser in rspV2UserList.Result.List)
                 {
-                    long isDisble = 0;
-                    if (!string.IsNullOrEmpty($"{itemUser.HiredDate}"))
-                    {
-                        if (itemUser.HiredDate < joinDate)
-                            isDisble = 1;
-                        else
-                            isDisble = 0;
-                    }
+                    //long isDisble = 0;
+                    //if (!string.IsNullOrEmpty($"{itemUser.HiredDate}"))
+                    //{
+                    //    if (itemUser.HiredDate < joinDate)
+                    //        isDisble = 1;
+                    //    else
+                    //        isDisble = 0;
+                    //}
 
                     var tempInfo = ddUserInfos.Find(x => x.RowKey.Equals(itemUser.Unionid));
                     if (string.IsNullOrEmpty($"{tempInfo}"))
@@ -1362,7 +1362,7 @@ namespace TEAMModelBI.Controllers.BITable
                                     joinTime = user.joinTime,
                                     permissions = user.permissions,
                                     schoolIds = user.schoolIds,
-                                    isDisable = isDisble,
+                                    isDisable = 1,
                                 };
 
                                 ddUserInfos.Add(ddUserInfo);
@@ -1397,7 +1397,7 @@ namespace TEAMModelBI.Controllers.BITable
                                 joinTime = 0,
                                 permissions = "areadata-read,areadata-upd,schooldata-read,schooldata-upd",
                                 schoolIds = "",
-                                isDisable = isDisble,
+                                isDisable = 0,
                             };
 
                             ddUserInfos.Add(ddUserInfo);

+ 11 - 0
TEAMModelBI/Controllers/BITest/TestController.cs

@@ -1460,6 +1460,17 @@ namespace TEAMModelBI.Controllers.BITest
 
         }
 
+        [HttpPost("get-noticev2")]
+        public async Task<IActionResult> SetNoticeV2()
+        {
+            var cosmosClient = _azureCosmos.GetCosmosClient();
+            //v2通知
+            Teacher targetTeacher = await cosmosClient.GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>("1636016499", new PartitionKey("Base"));
+            _coreAPIHttpService.PushNotify(new List<IdNameCode> { new IdNameCode { id = targetTeacher.id, name = targetTeacher.name, code = targetTeacher.lang } }, "create-school", Constant.NotifyType_IES5_Management, new Dictionary<string, object> { { "tmdname", $"{targetTeacher.name}" }, { "schooName", "商务智能学校(BI)" }, { "schoolId", "cswznb" }, { "tmdid", $"{targetTeacher.id}" } }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
+
+
+            return Ok(new { state = RespondCode.Ok });
+        }
 
         public class linqTest
         {

+ 4 - 2
TEAMModelBI/Lang/en-us.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "Assign exam paper grading task", "{tmdname} of {schoolName} has assign you an exam paper grading task." ],
   "scan-join_groupList": [ "Join course notice", "{tmdname} join the {groupListName} course via QRcode scanning" ],
   "scan-join_school": [ "Join school notice", "{tmdname} join school, {schoolName}, via QRcode scanning" ],
-  "submitanswer-school_homework": [ "Homework submission notice", "{tmdname} of {schoolName} has submitted a homework({homeworkName})" ],
+  "submitanswer-school_homework": [ "Homework submission notice", "{tmdname} of {schoolName} has submitted a homework:{homeworkName}" ],
   "submitanswer-private_homework": [ "Homework submission notice", "{tmdname} has submitted a homework({homeworkName})" ],
   "expire-school_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName}, on {schoolName} will expire at {expireTime}" ],
-  "expire-private_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName} will expire at {expireTime}" ]
+  "expire-private_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName} will expire at {expireTime}" ],
+  "create-school": [ "Create schools in batches", "{tmdname}You successfully created schools in batch with Bi" ],
+  "copy-file_area": [ "Batch copy file start", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 4 - 2
TEAMModelBI/Lang/zh-cn.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "普通阅卷任务通知", "{schoolName}的{tmdname}向您发送了普通卷阅卷任务。" ],
   "scan-join_groupList": [ "扫码加入名单通知", "{tmdname}扫码加入名单,名单:{groupListName}" ],
   "scan-join_school": [ "扫码加入学校通知", "{tmdname}扫码加入学校,学校名称:{schoolName}" ],
-  "submitanswer-school_homework": [ "作业提交通知", "{schoolName}的{tmdname}已提交作业,作业名称({homeworkName})"],
+  "submitanswer-school_homework": [ "作业提交通知", "{schoolName}的{tmdname}已提交作业,作业名称:{homeworkName}" ],
   "submitanswer-private_homework": [ "作业提交通知", "{tmdname}已提交作业,作业名称({homeworkName})" ],
   "expire-school_lessonRecord": [ "课例到期通知", "您在{schoolName}的课例将在{expireTime}到期,课例名称:{lessonName}" ],
-  "expire-private_lessonRecord": [ "课例到期通知", "您的课例将在{expireTime}到期,课例名称:{lessonName}" ]
+  "expire-private_lessonRecord": [ "课例到期通知", "您的课例将在{expireTime}到期,课例名称:{lessonName}" ],
+  "create-school": [ "批量创建学校", "{tmdname}您用BI批量创建学校成功" ],
+  "copy-file_area": [ "批复制文件开始", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 4 - 2
TEAMModelBI/Lang/zh-tw.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "普通閱卷任務通知", "{schoolName}的{tmdname}向您發送了普通卷閱卷任務。" ],
   "scan-join_groupList": [ "掃碼加入名單通知", "{tmdname}掃碼加入名單,名單:{groupListName}" ],
   "scan-join_school": [ "掃碼加入學校通知", "{tmdname}掃碼加入學校,學校名稱:{schoolName}" ],
-  "submitanswer-school_homework": [ "作業提交通知", "{schoolName}的{tmdname}已提交作業,作業名稱({homeworkName})" ],
+  "submitanswer-school_homework": [ "作業提交通知", "{schoolName}的{tmdname}已提交作業,作業名稱:{homeworkName}" ],
   "submitanswer-private_homework": [ "作業提交通知", "{tmdname}已提交作業,作業名稱({homeworkName})" ],
   "expire-school_lessonRecord": [ "課例到期通知", "您在{schoolName}的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
-  "expire-private_lessonRecord": [ "課例到期通知", "您的課例將在{expireTime}到期,課例名稱:{lessonName}" ]
+  "expire-private_lessonRecord": [ "課例到期通知", "您的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
+  "create-school": [ "批量創建學校", "{tmdname}您用BI批量創建學校成功" ],
+  "copy-file_area": [ "批復制文件開始", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 3 - 0
TEAMModelBI/LogLang/zh-cn.json

@@ -0,0 +1,3 @@
+{
+  "update-log-date": ["修改"]
+}

+ 6 - 0
TEAMModelBI/Models/AssistSchool.cs

@@ -38,6 +38,7 @@ namespace TEAMModelBI.Models
         /// </summary>
         public List<Period> period { get; set; }
         public List<SchoolTeacherRoles> assists { get; set; } = new List<SchoolTeacherRoles>();
+        public List<SchoolTeacherRoles> scAdmin { get; set; } = new List<SchoolTeacherRoles>();
         //public int serial { get; set; } //软体
         //public int service { get; set; } //服务
         //public int hard { get; set; } //硬体
@@ -125,4 +126,9 @@ namespace TEAMModelBI.Models
         public string areaId { get; set; }
     }
 
+    public class BatchScManage : SimpleInfo 
+    {
+        public string areaId { get; set; }
+    }
+
 }

+ 3 - 0
TEAMModelBI/TEAMModelBI.csproj

@@ -45,6 +45,9 @@
 	    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
 	    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
 	  </Content>
+	  <Content Update="LogLang\zh-cn.json">
+	    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+	  </Content>
 	</ItemGroup>
 	<PropertyGroup>
 		<SpaRoot>ClientApp\</SpaRoot>

+ 4 - 2
TEAMModelOS.FunctionV4/Lang/en-us.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "Assign exam paper grading task", "{tmdname} of {schoolName} has assign you an exam paper grading task." ],
   "scan-join_groupList": [ "Join course notice", "{tmdname} join the {groupListName} course via QRcode scanning" ],
   "scan-join_school": [ "Join school notice", "{tmdname} join school, {schoolName}, via QRcode scanning" ],
-  "submitanswer-school_homework": [ "Homework submission notice", "{tmdname} of {schoolName} has submitted a homework({homeworkName})" ],
+  "submitanswer-school_homework": [ "Homework submission notice", "{tmdname} of {schoolName} has submitted a homework:{homeworkName}" ],
   "submitanswer-private_homework": [ "Homework submission notice", "{tmdname} has submitted a homework({homeworkName})" ],
   "expire-school_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName}, on {schoolName} will expire at {expireTime}" ],
-  "expire-private_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName} will expire at {expireTime}" ]
+  "expire-private_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName} will expire at {expireTime}" ],
+  "create-school": [ "Create schools in batches", "{tmdname}You successfully created schools in batch with Bi" ],
+  "copy-file_area": [ "Batch copy file start", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 4 - 2
TEAMModelOS.FunctionV4/Lang/zh-cn.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "普通阅卷任务通知", "{schoolName}的{tmdname}向您发送了普通卷阅卷任务。" ],
   "scan-join_groupList": [ "扫码加入名单通知", "{tmdname}扫码加入名单,名单:{groupListName}" ],
   "scan-join_school": [ "扫码加入学校通知", "{tmdname}扫码加入学校,学校名称:{schoolName}" ],
-  "submitanswer-school_homework": [ "作业提交通知", "{schoolName}的{tmdname}已提交作业,作业名称({homeworkName})"],
+  "submitanswer-school_homework": [ "作业提交通知", "{schoolName}的{tmdname}已提交作业,作业名称:{homeworkName}" ],
   "submitanswer-private_homework": [ "作业提交通知", "{tmdname}已提交作业,作业名称({homeworkName})" ],
   "expire-school_lessonRecord": [ "课例到期通知", "您在{schoolName}的课例将在{expireTime}到期,课例名称:{lessonName}" ],
-  "expire-private_lessonRecord": [ "课例到期通知", "您的课例将在{expireTime}到期,课例名称:{lessonName}" ]
+  "expire-private_lessonRecord": [ "课例到期通知", "您的课例将在{expireTime}到期,课例名称:{lessonName}" ],
+  "create-school": [ "批量创建学校", "{tmdname}您用BI批量创建学校成功" ],
+  "copy-file_area": [ "批复制文件开始", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 4 - 2
TEAMModelOS.FunctionV4/Lang/zh-tw.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "普通閱卷任務通知", "{schoolName}的{tmdname}向您發送了普通卷閱卷任務。" ],
   "scan-join_groupList": [ "掃碼加入名單通知", "{tmdname}掃碼加入名單,名單:{groupListName}" ],
   "scan-join_school": [ "掃碼加入學校通知", "{tmdname}掃碼加入學校,學校名稱:{schoolName}" ],
-  "submitanswer-school_homework": [ "作業提交通知", "{schoolName}的{tmdname}已提交作業,作業名稱({homeworkName})" ],
+  "submitanswer-school_homework": [ "作業提交通知", "{schoolName}的{tmdname}已提交作業,作業名稱:{homeworkName}" ],
   "submitanswer-private_homework": [ "作業提交通知", "{tmdname}已提交作業,作業名稱({homeworkName})" ],
   "expire-school_lessonRecord": [ "課例到期通知", "您在{schoolName}的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
-  "expire-private_lessonRecord": [ "課例到期通知", "您的課例將在{expireTime}到期,課例名稱:{lessonName}" ]
+  "expire-private_lessonRecord": [ "課例到期通知", "您的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
+  "create-school": [ "批量創建學校", "{tmdname}您用BI批量創建學校成功" ],
+  "copy-file_area": [ "批復制文件開始", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 31 - 1
TEAMModelOS.FunctionV4/ServiceBus/ActiveTaskTopic.cs

@@ -209,6 +209,34 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
             }
 
         }
+
+
+        [Function("Art")]
+        public async Task ArtFunc([ServiceBusTrigger("%Azure:ServiceBus:ActiveTask%", "art", Connection = "Azure:ServiceBus:ConnectionString")] string msg)
+        {
+            string activityId = string.Empty;
+            try
+            {
+                var jsonMsg = JsonDocument.Parse(msg);
+                jsonMsg.RootElement.TryGetProperty("id", out JsonElement id);
+                jsonMsg.RootElement.TryGetProperty("progress", out JsonElement progress);
+                jsonMsg.RootElement.TryGetProperty("code", out JsonElement code);
+                var client = _azureCosmos.GetCosmosClient();
+                ArtEvaluation art = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<ArtEvaluation>(id.ToString(), new PartitionKey($"{code}"));
+                art.progress = progress.ToString();
+                activityId = id.ToString();
+                await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(art, id.ToString(), new PartitionKey($"{code}"));
+            }
+            catch (CosmosException e)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,Art()-CosmosDB异常{e.Message}\n{e.StackTrace}\n{e.Status}\n{msg}", GroupNames.醍摩豆服務運維群組);
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,Art()\n{ex.Message}\n{ex.StackTrace}\n\n{msg}", GroupNames.醍摩豆服務運維群組);
+            }
+
+        }
         [Function("ExamLite")]
         public async Task ExamLiteFunc([ServiceBusTrigger("%Azure:ServiceBus:ActiveTask%", "examlite", Connection = "Azure:ServiceBus:ConnectionString")] string msg)
         {
@@ -595,6 +623,8 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                 await ActivityService.FixActivity(client, _dingDing, groupChange, "ExamLite");
                 //名单变动修改学生作业活动信息
                 await ActivityService.FixActivity(client, _dingDing, groupChange, "Homework");
+                //名单变动修改学生艺术评价活动信息
+                await ActivityService.FixActivity(client, _dingDing, groupChange, "Art");
                 //TODO学习活动
                 //await FixActivity(client, stuListChange, "Learn");
 
@@ -1338,7 +1368,7 @@ namespace TEAMModelOS.FunctionV4.ServiceBus
                                     lessonRecord.upload = 1;
                                     // await _dingDing.SendBotMsg($"{_option.Location},课堂id:{_lessonId} 更新完成", GroupNames.醍摩豆服務運維群組);
 
-                                    LessonService.DoAutoDeleteSchoolLessonRecord(lessonRecord, scope, client, school, tmdid, teacher, _notificationService, _serviceBus, _azureStorage, _configuration,_coreAPIHttpService,_dingDing);
+                                    LessonService.DoAutoDeleteSchoolLessonRecord(lessonRecord, scope, client, school, tmdid, teacher, _notificationService, _serviceBus, _azureStorage, _configuration,_coreAPIHttpService,_dingDing, _azureRedis);
                                     long? size = await _azureStorage.GetBlobContainerClient(blobname).GetBlobsSize($"records/{_lessonId}");
                                     Bloblog bloblog = new Bloblog
                                     {

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

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

+ 1 - 1
TEAMModelOS.SDK/Models/Cosmos/BI/CreateSchoolInfo.cs

@@ -11,7 +11,7 @@ namespace TEAMModelOS.SDK.Models.Cosmos.BI
     {
         public string province { get; set; }
         public string id { get; set; }
-        public int createCount { get; set; }
+        public int createCount { get; set; } = 0;
         public string name { get; set; }
         public string city { get; set; }
         public string aname { get; set; }

+ 1 - 1
TEAMModelOS.SDK/Models/Cosmos/BI/DingDingUserInfo.cs

@@ -127,6 +127,6 @@ namespace TEAMModelOS.SDK.Models.Cosmos.BI
         /// <summary>
         /// 是否禁用   0 禁用   1启用
         /// </summary>
-        public long isDisable { get; set; } = 1;
+        public long isDisable { get; set; } = 0;
     }
 }

+ 18 - 4
TEAMModelOS.SDK/Models/Cosmos/Common/ArtEvaluation.cs

@@ -1,4 +1,5 @@
-using System;
+using DocumentFormat.OpenXml.Office2021.DocumentTasks;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -17,7 +18,7 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
         public string name { get; set; }
         public string school { get; set; }
         //高级设置
-        public List<JsonElement> settings { get; set; } = new List<JsonElement>();
+        public List<Tasks> settings { get; set; } = new List<Tasks>();
         public string creatorId { get; set; }
         public long createTime { get; set; }
         public long updateTime { get; set; }
@@ -31,6 +32,7 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
         /// 学生名单(包含自定义个人学生名单,学校教学班)
         /// </summary>
         public List<string> stuLists { get; set; } = new List<string>();
+        public List<string> tchLists { get; set; } = new List<string>();
 
         public List<string> sIds { get; set; } = new List<string>();
         /// <summary>
@@ -56,10 +58,22 @@ namespace TEAMModelOS.SDK.Models.Cosmos.Common
         //培训内容
         public string desc { get; set; }
         public string img { get; set; }
-        public string workId { get; set; }
+/*        public string workId { get; set; }
         public string surveyId { get; set; }
-        public string examId { get; set; }
+        public string examId { get; set; }*/
         //发布层级 0校级,1区级
         public int? publish { get; set; } = 0;
     }
+    public class Tasks {
+        public string id { get; set; }
+        public List<Acs> task { get; set; } = new List<Acs>();
+    }
+    public class Acs {
+        public string acId { get; set; }
+        public string subject { get; set; }
+        public int? isOrder { get; set; } = 0;
+        public string type { get; set; }
+        public string workDesc { get; set; }
+        public long workEnd { get; set; }
+    }
 }

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

@@ -67,7 +67,7 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public string source { get; set; }
         /// <summary>
-        /// 書面問答類型 0:書面問答 1:紙本測驗
+        /// 書面問答類型 0:書面問答 1:紙本測驗 2:艺术评测
         /// </summary>
         public int qamode { get; set; }
         

+ 34 - 4
TEAMModelOS.SDK/Models/Service/LessonService.cs

@@ -1,9 +1,11 @@
-using Azure.Cosmos;
+using Azure.Core;
+using Azure.Cosmos;
 using Azure.Messaging.ServiceBus;
 using HTEXLib.COMM.Helpers;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Hosting;
 using OpenXmlPowerTools;
+using StackExchange.Redis;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -357,7 +359,7 @@ namespace TEAMModelOS.SDK.Models.Service
         }
 
         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, CoreAPIHttpService _coreAPIHttpService,DingDing _dingDing)
+            Teacher teacher, NotificationService _notificationService, AzureServiceBusFactory _serviceBus, AzureStorageFactory _azureStorage, IConfiguration _configuration, CoreAPIHttpService _coreAPIHttpService,DingDing _dingDing,AzureRedisFactory _azureRedis)
         {
             if (lessonRecord.scope.Equals("school"))
             {
@@ -570,8 +572,36 @@ namespace TEAMModelOS.SDK.Models.Service
                 }
                 else
                 {
-                    save = false;
-                    school_lesson_expire = Constant.school_lesson_expire;
+                    ///未设置openAutoClean=1时,则检查学校使用空间是否充足。
+                    var sbm = new List<ServiceBusMessage>();
+                    double usize = 0;
+                    int  tsize = schoolBase.tsize;
+                    //schoolBase.tsize
+                    //計算學校或個人的使用空間
+                    RedisValue redisValue = _azureRedis.GetRedisClient(8).HashGet($"Blob:Record", $"{schoolBase.id}");
+                    if (redisValue.HasValue && long.TryParse(redisValue.ToString(), out var bsize))
+                    {
+                        usize = Math.Round(bsize / 1073741824.0 - (tsize), 2, MidpointRounding.AwayFromZero); //1073741824  1G
+                    }
+                    else //如果檢測不到緩存,觸發刷新計算空間
+                    {
+                        var messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", name = $"{schoolBase.id}" }.ToJsonString()); ;
+                        messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                        sbm.Add(messageBlob);
+                        await _serviceBus.GetServiceBusClient().SendBatchMessageAsync(_configuration.GetValue<string>("Azure:ServiceBus:ActiveTask"), sbm);
+
+                    }
+                    ///空间充足的情况保存。
+                    if (schoolBase.size - usize > 0)
+                    {
+                        save = true;
+                    }
+                    else {
+                        save = false;
+                        school_lesson_expire = Constant.school_lesson_expire;
+                    }
+
+                   
                 }
                 if (!save && school_lesson_expire > 0)
                 {

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

@@ -6,4 +6,16 @@ export default {
     findArtSetting: function (data) {
         return post('/area/art-setting/find-id', data)
     },
+    findArtPaperList: function (data) {
+        return post('/area/art-setting/find-art-papers', data)
+    },
+    saveArt: function (data) {
+        return post('/common/art/save', data)
+    },
+    findArtList: function (data) {
+        return post('/common/art/find', data)
+    },
+    findArtSummary: function (data) {
+        return post('/common/art/find-summary', data)
+    },
 }

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

@@ -597,6 +597,30 @@ export default {
             isShow: this.$store.state.config.srvAdr == 'China' && this.$store.state.userInfo.hasSchool
           }]
         },
+        //艺术评测
+        {
+          icon: 'iconfont icon-yishu',
+          name: this.$t('area.base.artData'),
+          router: '',
+          tag: '',
+          role: 'admin',
+          permission: '',
+          subName: 'artExam',
+          menuName: 'artExam',
+          child: [
+            {
+              icon: 'iconfont icon-test',
+              name: '评测活动',
+              router: '/home/mgtArtExam',
+              tag: '',
+              role: 'admin',
+              permission: '',
+              menuName: 'mgtArtExam',
+              isShow: this.$store.state.config.srvAdrType === 'test'
+            },
+          ],
+          isShow: this.$store.state.config.srvAdrType === 'test'
+        },
         // 学情分析
         {
           icon: 'iconfont icon-xueqing',

+ 31 - 3
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/Exam.vue

@@ -6,9 +6,18 @@
             {{ $t("studentWeb.courseContent.noExam") }}
         </div>
         <div v-else class="exam-chart-wrap">
-            <ExamTable :examDetaiInfo="examDetaiInfo" :examInfo="examInfo" :recordInfo="recordInfo"></ExamTable>
             <ScoreBarChart :total="classTotal" :subjectNames="subjectName"></ScoreBarChart>
-            <ExamQu :quData="correctData[0] ? correctData[0].data : []"></ExamQu>
+            <!-- <ExamTable :examDetaiInfo="examDetaiInfo" :examInfo="examInfo" :recordInfo="recordInfo"></ExamTable> -->
+            <!-- <ExamQu :quData="correctData[0] ? correctData[0].data : []"></ExamQu>s -->
+        </div>
+        <div style="width: 105px; text-align: center; margin-right: 5px;">
+            <p style="font-size: 18px; font-weight: bold; margin-top: 10px;">{{ getScore }}</p>
+            <!-- 查看更多 -->
+            <div class="data-count-item view-more" @click="toEvDetail" style="width: 100%;">
+                <p class="data-text" style="color:#2d8cf0">
+                    {{ $t("totalAnalysis.more") }}
+                </p>
+            </div>
         </div>
         <div>
             <Icon type="ios-person" class="owner-student-client-icon"/>
@@ -46,7 +55,8 @@ export default {
             examDetaiInfo: undefined,
             paperQuInfo: [],
             correctData: [],
-            subjectName: []
+            subjectName: [],
+            getScore: 0,
         }
     },
     methods: {
@@ -62,6 +72,11 @@ export default {
                 }
                 this.$api.studentWeb.FindStudentPaper(req).then(res => {
                     this.examDetaiInfo = res
+                    res.stuScore[0].forEach((item, index) => {
+                        if(item != -1) {
+                            this.getScore += item
+                        }
+                    })
                     res.subjects.forEach(item => {
                         this.subjectName.push(item.name)
                     })
@@ -147,6 +162,19 @@ export default {
                 }
             })
         },
+        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}`
+                }
+            })  */ 
+        },
     },
     computed: {
         examScore() {

+ 27 - 3
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/ExamQu.vue

@@ -99,7 +99,32 @@ export default {
                                 }
                             },
                             axisLabel: {
-                                rotate: 0
+                                rotate: 0,
+                                /* formatter: (params) => {
+                                    return  `${params}\n${_this.quData[params-1].type ? '{typea|√}' : '{typeb|×}'}`
+                                },
+                                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'
+                                    },
+                                }, */
                             }
                         },
                         yAxis: {
@@ -127,7 +152,6 @@ export default {
                                         // } else {
                                         //     return params.data
                                         // }
-                                        // console.log(params);
                                         // 统计正确率的算法
                                         return _this.isShowRate ? `${params.data.type ? '{typea|√}' : '{typeb|×}'}\n{count|${params.data.value.toFixed(0)}%}` : ''
                                     },
@@ -265,7 +289,7 @@ export default {
 <style scoped lang="less">
 .qu-score {
     padding: 15px 20px 0 20px;
-    width: 500px;
+    width: 440px;
     height: 250px;
 }
 .qu-score-count {

+ 171 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/PopQues.vue

@@ -0,0 +1,171 @@
+<template>
+    <div class="pop-ques-wrap">
+        <div v-if="quType != 'complete'">
+            <TeacherClient></TeacherClient>
+            <p class="event-type">
+                {{evtType == 'PopQuesLoad' ? $t('studentWeb.hiteachNote.qA') : $t('studentWeb.hiteachNote.qaAgain')}}
+                <Tooltip :content="$t('talMgmt.text47')">
+                <v-icon class="qu-flip-icon" :style="{'color':isOverview ? '':'#2d8cf0'}" :iconClass="imgSrc" v-show="quType === 'single' || quType === 'multiple' || quType === 'judge'" @click.native="isOverview = !isOverview" />
+                </Tooltip>
+            </p>
+            <!-- 统计数据 -->
+            <template v-if="isOverview">
+                <!-- 单选、多选、判断选项分布 -->
+                <OptionCount v-if="quType === 'single' || quType === 'multiple' || quType === 'judge'" :optionCount="optionData" :answer="answer"></OptionCount>
+                <!-- 填空题(文字题) -->
+                <!-- <CompleteAns v-else-if="quType === 'complete'" :answer="answerData" :students="students"></CompleteAns> -->
+                <!-- 如果有设置正确答案的正确率统计 -->
+                <CorrectRate :correctData="correctData" v-if="hasAnswer"></CorrectRate>
+            </template>
+            <div v-else>
+                <p v-for="key in Object.keys(optionData)" :key="key">
+                    <Tag :color="key == 'noAns' ? 'warning' : 'primary'">
+                        {{key == 'noAns' ? $t('cusMgt.rcd.noAns') : key}}
+                    </Tag>
+                    <Tag color="default" v-for="s in optionData[key]" :key="s">
+                        {{s}}
+                    </Tag>
+                </p>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import TeacherClient from '@/view/classrecord/eventchart/TeacherClient.vue'
+import CorrectRate from '@/view/classrecord/eventchart/CorrectRate.vue'
+import OptionCount from '@/view/classrecord/eventchart/OptionCount.vue'
+import CompleteAns from '@/view/classrecord/eventchart/CompleteAns.vue'
+export default {
+    props: {
+        irsData: {
+            type: Object,
+            default: () => {
+                return {}
+            }
+        },
+        evtType: {
+            type: String,
+            default: 'PopQuesLoad'
+        },
+        students: {
+            type: Array,
+            default: () => {
+                return []
+            }
+        }
+    },
+    data() {
+        require('@/icons/svg/flop.svg')
+        return {
+            imgSrc: 'flop',
+            isOverview: true,
+            answerData: [],
+            hasAnswer: false,
+            quType: '',
+            optionData: {},
+            correctData: {},
+            answer: [],//正确答案
+            optionMap: {
+                0: [],
+                1: ['A'],
+                2: ['A', 'B'],
+                3: ['A', 'B', 'C'],
+                4: ['A', 'B', 'C', 'D'],
+                5: ['A', 'B', 'C', 'D', 'E'],
+                6: ['A', 'B', 'C', 'D', 'E', 'F'],
+                7: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
+                9: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
+                9: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
+            }
+        }
+    },
+    components: {
+        OptionCount, CorrectRate, TeacherClient, CompleteAns
+    },
+    watch: {
+        irsData: {
+            deep: true,
+            immediate: true,
+            handler(n, o) {
+                console.log(n)
+                if (n.clientAnswers && n.question) {
+                    this.hasAnswer = !!n.question.exercise?.answer?.length
+                    // let noAns = 0 //未作答
+                    let noAns = [] //未作答
+                    // let opts = n.question?.exercise?.opts || 0 //opts不准确,需要实际去item
+                    let opts = n.question?.item[0]?.option?.length || 0
+                    this.answer = n.question?.exercise?.answer || []
+                    this.quType = n.question?.exercise?.type || ''
+                    this.optionData = {}
+                    this.correctData = {
+                        wrong: 0,
+                        right: 0
+                    }
+                    this.optionMap[opts].forEach(o => {
+                        // this.optionData[o] = 0
+                        this.optionData[o] = []
+                    })
+                    this.answerData = []
+                    // 首次作答
+                    if (this.evtType === 'PopQuesLoad') {
+                        this.answerData = this._.cloneDeep(n.clientAnswers[0])
+                    }
+                    //二次作答
+                    else if (this.evtType === 'ReAtmpAnsStrt') {
+                        this.answerData = this._.cloneDeep(n.clientAnswers[1])
+                    }
+                    //客观题
+                    if (this.quType === 'single' || this.quType === 'multiple' || this.quType === 'judge') {
+                        if (this.answerData.length) {
+                            this.answerData.forEach((stu, index) => {
+                                //选项分布
+                                if (stu.length) {
+                                    stu.forEach(a => {
+                                        // if (!this.optionData[a]) this.optionData[a] = 0
+                                        // this.optionData[a]++
+                                        if (!this.optionData[a]) this.optionData[a] = []
+                                        this.optionData[a].push(this.students[index]?.name)
+                                    })
+                                } else {
+                                    // noAns++
+                                    noAns.push(this.students[index]?.name)
+                                }
+
+                                // 正确率
+                                let stuCopy = JSON.parse(JSON.stringify(stu))
+                                let ansCopy = JSON.parse(JSON.stringify(this.answer)) //处理触发无限更新
+                                if (stuCopy.sort().toString() == ansCopy.sort().toString()) {
+                                    this.correctData.right++
+                                } else {
+                                    this.correctData.wrong++
+                                }
+
+                            })
+                        }
+                    }
+                    // if (noAns) this.optionData['noAns'] = noAns
+                    if (noAns.length) this.optionData['noAns'] = noAns
+                    console.log('选项数据', this.optionData)
+                }
+            }
+        }
+    }
+}
+</script>
+<style lang="less" scoped>
+.qu-flip-icon {
+    cursor: pointer;
+    width: 25px;
+    height: 25px;
+    transform: scale(0.7);
+    vertical-align: middle;
+}
+.pop-ques-wrap {
+    display: flex;
+}
+.event-type {
+    margin-right: 20px;
+    font-size: 15px;
+    font-weight: 600;
+}
+</style>

+ 32 - 0
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/RecordView.less

@@ -168,6 +168,7 @@
                         margin-right: 0;
                         width: 130px;
                         cursor: pointer;
+                        border: 2px solid #cecece;
                     }
     
                     .messagetoPPT-tag {
@@ -450,6 +451,36 @@
             .course-cur-img {
                 height: 100%;
             }
+            
+            .custom-page-change {
+                position: absolute;
+                top: 50%;
+                margin-top: -20px;
+                background: rgba(60, 60, 60, 0.6);
+                color: white;
+                width: 35px;
+                height: 35px;
+                text-align: center;
+                line-height: 35px;
+                font-size: 20px;
+                cursor: pointer;
+                border-radius: 50%;
+                display: none;
+            }
+
+            &:hover {
+                .custom-page-change {
+                    display: block;
+                }
+            }
+
+            .custom-prev {
+                left: 10px;
+            }
+
+            .custom-next {
+                right: 10px;
+            }
         }
 
         .page-wrap {
@@ -492,6 +523,7 @@
                     overflow: hidden;
                     position: relative;
                     margin-top: 10px;
+                    background: #f9f9f9;
             
                     .no-interaction {
                         height: 100%;

+ 24 - 6
TEAMModelOS/ClientApp/src/components/student-web/ClassRecord/RecordView.vue

@@ -101,6 +101,12 @@
                                 </div>
                                 <div class="courseware-wrap">
                                     <!-- <DrawHTEX :mapJson="mapJson"></DrawHTEX> -->
+                                    <div class="custom-page-change custom-prev" @click="changePage('prev')">
+                                        <Icon type="ios-arrow-back" />
+                                    </div>
+                                    <div class="custom-page-change custom-next" @click="changePage('next')">
+                                        <Icon type="ios-arrow-forward" />
+                                    </div>
                                     <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" />
@@ -123,10 +129,10 @@
                                 </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>
+                                    <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>
@@ -203,7 +209,7 @@ import RcdPoster from '../../../view/homepage/RcdPoster.vue';
 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 PopQues from './PopQues.vue';
 import Buzr from './Buzr.vue';
 import Push from '@/view/classrecord/eventchart/Push.vue';
 import StuReceive from './StuReceive.vue';
@@ -332,7 +338,7 @@ export default {
                 doubleGreen: false,
                 quality: false,
                 DESC: "startTime",
-                pageCount: 10, //返回六条数据(分页)
+                pageCount: 100, //返回六条数据(分页)
                 today: false,
                 continuationToken: this.continuationToken, //返回的有值的话,下次查询就要用这个值
                 groupIds: [this.courseNow.list],
@@ -735,6 +741,14 @@ export default {
                 this.player.currentTime(pageInfo.time)
             }
         },
+        // 自定义换页功能
+        changePage(type) {
+            if (type == 'prev') {
+                if (this.curPage > 1) this.curPage--
+            } else if (type == 'next') {
+                if (this.curPage < this.pageList.length) this.curPage++
+            }
+        },
         // 点击课件page
         getCurHTEX(page) {
             this.curPage = page
@@ -897,6 +911,10 @@ export default {
         background-color: #FEE49E;
         border-color: #FEE49E;
     }
+
+    .tea-push-img {
+        border: 2px solid #cecece;
+    }
 }
 
 .owner-student-client-icon {

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

@@ -152,10 +152,10 @@ export default {
 
 <style scoped>
 .score-bar-chart {
-    width: 300px;
+    width: 440px;
     /* margin: auto; */
     /* margin-bottom: 50px; */
-    padding: 15px 40px 0px 40px;
+    padding: 15px 20px 0 20px;
     height: 270px;
     color: rgba(0, 0, 0, 0.726);
 }
@@ -186,8 +186,8 @@ export default {
 }
 
 .score-stu {
-    width: 306px;
-    height: 250px;
+    width: 100%;
+    height: 100%;
 }
 
 @media screen and (max-width: 1365px) {

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

@@ -1330,6 +1330,26 @@ export const routes = [{
             activeName: 'auth',
             middleware: ['login', 'ability:admin,auth-read|auth-upd'],
         }
+    },
+    //管理艺术评测
+    {
+        path: 'mgtArtExam',
+        name: 'mgtArtExam',
+        component: resolve => require(['@/view/artexam/Mgt.vue'], resolve),
+        meta: {
+            activeName: 'artExam',
+            middleware: ['login', 'ability:admin'],
+        }
+    },
+    //创建艺术评测
+    {
+        path: 'createArtExam',
+        name: 'createArtExam',
+        component: resolve => require(['@/view/artexam/Create.vue'], resolve),
+        meta: {
+            activeName: 'artExam',
+            middleware: ['login', 'ability:admin'],
+        }
     }
     ]
 },

+ 835 - 0
TEAMModelOS/ClientApp/src/view/artexam/Create.vue

@@ -0,0 +1,835 @@
+<template>
+	<div class="create-art-container">
+		<vuescroll>
+			<div class="create-form-wrap">
+				<!-- 上一步 -->
+				<div
+					v-show="step > 0"
+					class="step-handler last-step"
+					@click="lastStep()"
+				>
+					<span class="step-icon-wrap">
+						<Icon type="ios-arrow-back" size="24" color="white" />
+					</span>
+					<p style="margin-top: 2px">
+						{{ $t("train.create.lastStep") }}
+					</p>
+				</div>
+				<!-- 下一步 -->
+				<div
+					v-show="step < 3"
+					class="step-handler next-step"
+					@click="nextStep()"
+				>
+					<span class="step-icon-wrap">
+						<Icon
+							type="ios-arrow-forward"
+							size="24"
+							color="white"
+						/>
+					</span>
+					<p style="margin-top: 2px">
+						{{ $t("train.create.nextStep") }}
+					</p>
+				</div>
+				<Steps :current="step" class="step-wrap">
+					<Step
+						:title="$t('train.create.baseInfo')"
+						@click.native="step = 0"
+					></Step>
+					<!-- <Step :title="$t('train.create.detailInfo')"></Step> -->
+					<Step :title="$t('train.create.advancedTitle')"></Step>
+					<Step :title="$t('train.create.publishTitle')"></Step>
+				</Steps>
+				<!-- 基础设置 -->
+				<Form
+					ref="baseInfo"
+					:model="artInfo"
+					:rules="baseRule"
+					:label-width="100"
+					class="art-form"
+					label-colon
+					v-show="step == 0"
+				>
+					<h1>{{ $t("train.create.baseInfo") }}</h1>
+					<FormItem label="名称" prop="name" style="margin-top: 80px">
+						<Input
+							v-special-char
+							v-model="artInfo.name"
+							placeholder="请输入艺术评测活动名称"
+						></Input>
+					</FormItem>
+					<FormItem
+						label="班级"
+						prop="targets"
+						style="margin-top: 30px"
+					>
+						<el-cascader
+							ref="evtarget"
+							size="small"
+							:show-all-levels="false"
+							clearable
+							:options="csOptions"
+							:props="props"
+							@change="treeChange"
+							style="width: 100%"
+						>
+						</el-cascader>
+					</FormItem>
+				</Form>
+				<!-- 高级设置 -->
+				<Form
+					ref="seniorInfo"
+					:model="artInfo"
+					:rules="trainRule"
+					:label-width="100"
+					class="art-form"
+					label-colon
+					v-show="step == 1"
+				>
+					<h1>{{ $t("train.create.advancedTitle") }}</h1>
+					<!-- 一级指标 -->
+					<CheckboxGroup
+						v-model="artInfo.setting"
+						@on-change="initStatus"
+					>
+						<Checkbox
+							v-for="item in quotas"
+							:key="item.id"
+							:label="item.id"
+							class="check-item"
+						>
+							<span>{{ item.name }}</span>
+						</Checkbox>
+					</CheckboxGroup>
+					<Tabs v-model="tabName">
+						<TabPane
+							v-for="(item, index) in tabListShow"
+							:key="index"
+							:label="item.label"
+							:name="item.name"
+						>
+							<QuoTree
+								:quoid="item.name"
+								:treeData="item.children"
+								@on-check-change="handleCheckChange"
+								@on-setting-change="handleSettingChange"
+								@on-type-change="handleTypeChange"
+							></QuoTree>
+						</TabPane>
+					</Tabs>
+				</Form>
+				<!-- 发布活动 -->
+				<Form
+					class="art-form"
+					label-colon
+					v-show="step == 2  && !published"
+					label-position="left"
+				>
+					<h1>{{ $t("train.create.publishTitle") }}</h1>
+					<FormItem label="名称" style="margin-top: 80px">
+						<span>{{ artInfo.name }}</span>
+					</FormItem>
+					<FormItem label="班级">
+						<span>{{ classNames.join(", ") }}</span>
+					</FormItem>
+					<FormItem label="评价指标">
+						<span>{{ settingNames.join(", ") }}</span>
+					</FormItem>
+					<Button
+						type="primary"
+						class="save-btn"
+						:loading="saveLoading"
+						style="
+							margin: auto;
+							margin-top: 20px;
+							width: 240px;
+							display: block;
+						"
+						@click="publish"
+					>
+						{{ $t("train.create.publishBtn") }}
+					</Button>
+				</Form>
+                <!-- 发布成功 -->
+                <div v-show="step == 2  && published">
+                    <Icon type="md-checkmark-circle-outline" class="published-icon" />
+                    <p class="success-text">
+                        {{$t('train.create.publishOk')}}
+                    </p>
+                    <p class="link-text" @click="toArtMgt">
+                        {{$t('train.create.viewInfo')}}
+                    </p>
+                </div>
+			</div>
+		</vuescroll>
+	</div>
+</template>
+
+<script>
+import QuoTree from "./QuoTree.vue"
+import BlobTool from "@/utils/blobTool.js"
+import { mapGetters } from "vuex"
+export default {
+	components: {
+		QuoTree
+	},
+	data() {
+		return {
+            published:false,
+			saveLoading: false,
+			tabListShow: [],
+			tabList: [],
+			tabName: "",
+			props: {
+				multiple: true,
+				value: "id",
+				label: "name"
+			},
+			step: 0,
+			artInfo: {
+				name: "",
+				targets: [],
+				setting: []
+			},
+			settingDetail: {},
+			baseRule: {
+				name: [
+					{
+						required: true,
+						message: "请输入艺术评测活动名称",
+						trigger: "change"
+					}
+				],
+				targets: [
+					{
+						required: true,
+						type: "array",
+						message: this.$t("train.create.targetErr"),
+						trigger: "blur"
+					}
+				]
+			},
+			// 学业指标相关设置
+			studySetting: {
+				hasKn: false,
+				hasSkill: false,
+				baseKn: {},
+				baseSkill: {}
+			},
+			trainRule: {},
+			allList: [],
+			quotas: []
+		}
+	},
+	computed: {
+		...mapGetters({
+			curPeriod: "user/getCurPeriod"
+		}),
+		//级联选择年级班级
+		csOptions() {
+			let data = []
+			//填充行政班数据
+			if (this.curPeriod.id) {
+				//计算学级逻辑
+				let date = new Date()
+				let curYear = date.getFullYear()
+				let month = date.getMonth() + 1
+				let start = this.curPeriod.semesters.find((item) => {
+					return item.start == 1
+				})
+				// 根据入学月份确定当前年级和学级的关系
+				if (start && month < start.month) {
+					curYear--
+				}
+				this.curGrades.forEach((item, index) => {
+					let dataItem = {
+						id: index, //年级使用index
+						name: `${item}(${curYear - index}${this.$t(
+							"unit.gradeYear"
+						)})`,
+						year: curYear - index,
+						children: []
+					}
+					let child = this.allList.filter((classItem) => {
+						return (
+							classItem.year == curYear - index &&
+							classItem.type == "class"
+						)
+					})
+					dataItem.children = child.length ? child : undefined
+					if (child.length) data.push(dataItem)
+				})
+			}
+			return data
+		},
+		//计算当前学段下面的年级信息
+		curGrades() {
+			if (this.curPeriod.grades) {
+				return this.curPeriod.grades
+			} else {
+				return []
+			}
+		},
+		classNames() {
+			let names = this.artInfo.targets.map((item) => {
+				let c = this.allList.find((c) => c.id === item)
+				return c.name
+			})
+			return names
+		},
+		settingNames() {
+			let settingMap = {}
+            this.quotas.forEach((item) => {
+				settingMap[item.id] = item.name
+			})
+			let names = this.artInfo.setting.map((item) => {
+				return settingMap[item]
+			})
+			return names
+		}
+	},
+	watch: {
+		"$store.state.user.curPeriod": {
+			deep: true,
+			immediate: true,
+			handler(n, o) {
+				if (n) {
+					this.getTargetList()
+				}
+			}
+		}
+	},
+	methods: {
+        toArtMgt(){
+            this.$router.push({
+                name:'mgtArtExam'
+            })
+        },
+		handleCheckChange(data) {
+			if (!this.settingDetail[data.quoid])
+				this.settingDetail[data.quoid] = {}
+			this.settingDetail[data.quoid]["checked"] = data.data
+		},
+		handleTypeChange(data) {
+			if (!this.settingDetail[data.quoid])
+				this.settingDetail[data.quoid] = {}
+			this.settingDetail[data.quoid]["type"] = data.data
+		},
+		handleSettingChange(data) {
+			if (!this.settingDetail[data.quoid])
+				this.settingDetail[data.quoid] = {}
+			this.settingDetail[data.quoid]["setting"] = data.data
+		},
+		/* 获取当前区级设置数据 */
+		getAreaSetting() {
+			this.$api.areaArt
+				.findArtSetting({
+					areaId: sessionStorage.getItem("areaId")
+				})
+				.then((res) => {
+					if (res.setting) {
+						console.log(res)
+						// this.artDimensions = res.setting.dimensions
+						this.quotas = res.setting.quotas
+						this.tabList = this.quotas.map((q) => {
+							return {
+								name: q.id,
+								label: q.name,
+								children: q.children
+							}
+						})
+					} else {
+						this.artDimensions = []
+					}
+				})
+		},
+		async formatData() {
+			return new Promise(async (r, j) => {
+				try {
+					let baseKeys = Object.keys(this.settingDetail)
+					let settings = []
+					for (const key of baseKeys) {
+						let curSet = this.settingDetail[key]
+						if (!curSet.checked?.checkedKeys) continue
+						let lastIds = curSet.checked.checkedKeys.filter(
+							(item) => {
+								let isLast = curSet.checked.checkedNodes.find(
+									(n) => n.id === item && !n.children.length
+								)
+								return !!isLast
+							}
+						)
+						console.log("laseId", lastIds)
+						for (const id of lastIds) {
+							let s = {
+								id,
+								task: []
+							}
+							let type = curSet.type[id]
+							if (type == "score") {
+								s.task.push({ type })
+							} else if (type == "exam") {
+								let quoSetting = curSet.setting[id]
+								let examRes = await this.toSaveExamInfo(
+									quoSetting
+								)
+								examRes.forEach((res, index) => {
+									s.task.push({
+										type,
+										acId: res.exam.id,
+										subject: res.exam.subjects[0].id,
+										unorder:
+											quoSetting.settings[index].unorder
+									})
+								})
+							} else if (type == "work") {
+								let quoSetting = curSet.setting[id]
+								quoSetting.settings.forEach((w) => {
+									s.task.push({
+										type,
+										workDesc: w.desc,
+										workEnd: w.endTime,
+										subject: w.subject
+									})
+								})
+							}
+							settings.push(s)
+						}
+					}
+					console.log("所有设置", settings)
+					r(settings)
+				} catch (e) {
+					j(e)
+				}
+			})
+		},
+		async publish() {
+			console.log(this.settingDetail)
+			try {
+				let settings = await this.formatData()
+				let params = {
+					name: this.artInfo.name,
+					target: this.artInfo.targets,
+					owner: "school",
+					scope: "school",
+					school: this.$store.state.userInfo.schoolCode,
+					classes: this.artInfo.targets,
+					settings: settings
+				}
+				this.$api.areaArt
+					.saveArt({
+						art: params
+					})
+					.then(
+						(res) => {
+							this.$Message.success("艺术评测发布成功")
+                            this.published = true
+						},
+						(err) => {
+							this.$Message.error("艺术评测发布失败")
+						}
+					)
+			} catch (e) {
+				console.error(e)
+			}
+		},
+		// 保存评测相关数据
+		async toSaveExamInfo(quoSetting) {
+			return new Promise(async (r, j) => {
+				let baseKn = quoSetting
+				let promises = []
+				let resourcePaper = []
+				for (const setting of baseKn.settings) {
+					let apiPapers = await this.getPaperInfo(setting.paper)
+					resourcePaper.push(setting.paper)
+					let requestData = {
+						id: this.$jsFn.uuid(),
+						code: this.$store.state.userInfo.schoolCode,
+						school: this.$store.state.userInfo.schoolCode,
+						name: `${this.artInfo.name}-${setting.subject}`,
+						creatorId: this.$store.state.userInfo.TEAMModelId,
+						// type: this.evaluationInfo.type,
+						period: {
+							id: this.curPeriod.id,
+							name: this.curPeriod.name
+						},
+						grades: [],
+						subjects: [
+							{
+								id: setting.subject,
+								name:
+									setting.subject == "music" ? "音乐" : "美术"
+							}
+						],
+						papers: [apiPapers],
+						// examType: this.evaluationInfo.examType,
+						year: new Date().getFullYear(),
+						// range: this.mode,
+						source: "0",
+						classes: this.artInfo.targets,
+						stuLists: [],
+						targets: [],
+						startTime: setting.startTime || -1, //立即发布由后端获取时间
+						endTime: setting.endTime,
+						scope: "school",
+						createTime: Math.round(new Date()),
+						owner: "school" //后面新增字段
+						// income: this.evaluationInfo.income,
+						// touch: this.evaluationInfo.touch
+					}
+					promises.push(
+						this.$api.learnActivity.SaveExamInfo(requestData)
+					)
+				}
+				Promise.all(promises).then(
+					async (reses) => {
+						console.log("评测保存成功", reses)
+						// 评测保存成功还需要复制试卷
+						let schoolSas = {
+							sas:
+								"?" +
+								this.$store.state.user.schoolProfile.blob_sas,
+							url: this.$store.state.user.schoolProfile.blob_uri.slice(
+								0,
+								this.$store.state.user.schoolProfile.blob_uri.lastIndexOf(
+									this.$store.state.userInfo.schoolCode
+								) - 1
+							),
+							name: this.$store.state.userInfo.schoolCode
+						}
+						let schoolBlob = new BlobTool(
+							schoolSas.url,
+							schoolSas.name,
+							schoolSas.sas,
+							"school"
+						)
+						let paperSas = await this.$tools.getBlobSas("hbcn")
+						let paperBlob = new BlobTool(
+							paperSas.url,
+							paperSas.name,
+							"?" + paperSas.sas,
+							"school"
+						)
+						reses.forEach((res, index) => {
+							let targetFolder = "exam/" + res.exam.id + "/paper/"
+							//这里评测都是单科处理
+							schoolBlob
+								.copyFolder(
+									targetFolder +
+										res.exam.subjects[0].id +
+										"/",
+									resourcePaper[index].blob.substring(1),
+									paperBlob
+								)
+								.then(
+									(res) => {
+									},
+									(err) => {
+										this.$Message.error("试卷复制失败")
+									}
+								)
+						})
+						r(reses)
+					},
+					(err) => {
+						console.error("评测保存失败")
+						j(err)
+					}
+				)
+			})
+		},
+		//拼接API需要的paper数据
+		getPaperInfo(simplePaper) {
+			return new Promise(async (r, j) => {
+				try {
+					simplePaper.scope = "school"
+					let fullPaper = await this.$evTools.getFullPaper(
+						simplePaper
+					)
+					if (fullPaper) {
+						let apiPaper = {}
+						apiPaper.id = fullPaper.id
+						apiPaper.code = fullPaper.code
+						apiPaper.name = fullPaper.name
+						apiPaper.blob = simplePaper.blob
+						apiPaper.scope = fullPaper.scope
+						apiPaper.sheet = fullPaper.sheet //答题卡 //202108021讨论: 创建评测不需要传答题卡id,由更新答题卡id去做关联。
+						apiPaper.multipleRule = fullPaper.multipleRule
+						apiPaper.answers = []
+						apiPaper.point = []
+						apiPaper.knowledge = []
+						apiPaper.field = []
+						apiPaper.time = fullPaper.time
+						apiPaper.type = [] //后面新增字段, 保存每个题目类型
+						for (let k = 0; k < fullPaper.slides.length; k++) {
+							if (fullPaper.slides[k].type !== "compose") {
+								apiPaper.answers.push(
+									fullPaper.slides[k].scoring.ans
+										? fullPaper.slides[k].scoring.ans
+										: []
+								)
+								apiPaper.point.push(
+									fullPaper.slides[k].scoring.score
+								)
+								apiPaper.knowledge.push(
+									fullPaper.slides[k].scoring.knowledge
+										? fullPaper.slides[k].scoring.knowledge
+										: []
+								)
+								apiPaper.field.push(
+									fullPaper.slides[k].scoring.field
+										? fullPaper.slides[k].scoring.field
+										: []
+								)
+								apiPaper.type.push(fullPaper.slides[k].type) //后面新增字段, 保存每个题目类型
+							}
+						}
+						r(apiPaper)
+					} else {
+						j("paperErr")
+					}
+				} catch (e) {
+					j("paperErr")
+				}
+			})
+		},
+		//保存学业指标作业相关数据
+		toSaveWorkInfo() {},
+		handleSetKn(data) {
+			this.studySetting.baseKn = this._.cloneDeep(data)
+			console.log(this.studySetting)
+		},
+		initStatus(data) {
+			this.tabListShow = this.tabList.filter((item) => {
+				return data.includes(item.name)
+			})
+			this.tabName =
+				this.artInfo.setting[data.length - 1] == "sign" &&
+				this.artInfo.setting.length > 1
+					? this.artInfo.setting[data.length - 2]
+					: this.artInfo.setting[data.length - 1]
+		},
+		lastStep() {
+			this.step--
+		},
+		nextStep() {
+			switch (this.step) {
+				case 0:
+					this.checkBaseInfo()
+					break
+				case 1:
+					let isErr = this.checkSettingInfo()
+					if (!isErr) this.step++
+					break
+				default:
+					this.step++
+					break
+			}
+		},
+		checkBaseInfo() {
+			this.$refs["baseInfo"].validate((valid) => {
+				if (valid) {
+					this.step++
+				} else {
+					this.$Message.error(this.$t("train.create.fullInfo"))
+				}
+			})
+		},
+		checkSettingInfo() {
+			let isErr = false
+			let settings = this.artInfo.setting
+			if (!settings.length) {
+				return this.$Message.error("请勾选艺术评价指标")
+			}
+			for (let setting of settings) {
+				switch (setting) {
+					case "study":
+						isErr = this.checkStudyInfo()
+						break
+					default:
+						break
+				}
+			}
+			return isErr
+		},
+		checkStudyInfo() {
+			let isErr = false
+			if (!this.studySetting.hasKn && !this.studySetting.hasSkill) {
+				this.$Message.error("请完成学业指标相关设置")
+				isErr = true
+				return isErr
+			}
+			// 检查基础知识相关设置
+			if (this.studySetting.hasKn) {
+				isErr = this.checkBaseKn()
+			}
+			return isErr
+		},
+		checkBaseKn() {
+			let baseKn = this.studySetting.baseKn
+			console.log(baseKn)
+			let isErr = false
+			if (!baseKn.subjects?.length) {
+				this.$Message.error("请设置基础知识评测科目")
+				isErr = true
+				return isErr
+			}
+			//检查对应学科是否有对应的配置
+			for (const item of baseKn.subjects) {
+				let set = baseKn.settings.find((s) => s.subject === item)
+				if (!set) {
+					this.$Message.error(
+						`请完成基础知识指标${
+							item == "music" ? "音乐" : "美术"
+						}学科的相关设置`
+					)
+					isErr = true
+					break
+				}
+			}
+			if (isErr) return isErr
+			// 检查每个学科的设置是否完整
+			for (const setting of baseKn.settings) {
+				let { startTime, endTime, paper } = setting
+				if (!startTime || !endTime || !paper) {
+					this.$Message.error(
+						`请完成${
+							setting.subject == "music" ? "音乐" : "美术"
+						}学科的相关设置`
+					)
+					isErr = true
+					break
+				}
+			}
+			return isErr
+		},
+		getTargetList() {
+			if (!this.curPeriod?.id) return
+			let params = {
+				tmdid: this.$store.state.userInfo.TEAMModelId,
+				schoolId: this.$store.state.userInfo.schoolCode,
+				periodId: this.curPeriod.id,
+				opt: "manage"
+			}
+			this.$api.common.getActivityTarget(params).then(
+				(res) => {
+					this.allList = res.groupLists
+				},
+				(err) => {
+					this.$Messag.error("查询发布对象失败")
+				}
+			)
+		},
+		treeChange(data) {
+			console.log(data)
+			this.artInfo.targets = data.map((item) => item[1])
+		}
+	},
+	created() {
+		this.getAreaSetting()
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.link-text{
+    text-align: center;
+    text-decoration: underline;
+    font-weight: 400;
+    font-size: 14px;
+    color: #909090;
+    margin-top: 30px;
+    cursor: pointer;
+}
+.success-text{
+    text-align: center;
+    margin-top: 10px;
+    color: #19be6b;
+    font-size: 20px;
+    font-weight: 600;
+}
+.published-icon{
+    color: #19be6b;
+    font-size: 99px;
+    display: block;
+    margin-top: 150px;
+}
+.subject-name {
+	margin-right: 15px;
+	font-weight: 800;
+}
+.content-block {
+	background: #f0f0f0;
+	padding: 20px 10px;
+	border-radius: 4px;
+	margin-bottom: 20px;
+	margin-top: 10px;
+}
+.check-item {
+	user-select: none;
+	display: block;
+	margin-bottom: 20px;
+}
+.art-form {
+	width: 800px;
+	margin: auto;
+}
+.create-art-container {
+	width: 100%;
+	height: 100%;
+	padding: 10px;
+}
+.create-form-wrap {
+	background: white;
+	width: 90%;
+	min-height: 800px;
+	margin: auto;
+	margin-top: 10px;
+	padding: 20px;
+	box-shadow: 0px 0px 10px #ccc;
+	border-radius: 5px;
+	position: relative;
+	h1 {
+		font-size: 22px;
+		text-align: center;
+		margin-top: 50px;
+		margin-bottom: 10px;
+	}
+}
+.step-handler {
+	position: absolute;
+	z-index: 999;
+	cursor: pointer;
+	text-align: center;
+	padding: 5px 10px;
+	box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.2);
+	border-radius: 5px;
+	color: #1cc0f3;
+	font-size: 12px;
+	&:hover {
+		background: #f0f0f0;
+	}
+}
+.step-icon-wrap {
+	background: #1cc0f3;
+	color: white;
+	border-radius: 50%;
+	box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.2);
+	display: inline-block;
+}
+.last-step {
+	left: 40px;
+	top: 350px;
+	.step-icon-wrap {
+		padding: 6px 7px 6px 5px;
+	}
+}
+.next-step {
+	right: 40px;
+	top: 350px;
+	.step-icon-wrap {
+		padding: 6px 5px 6px 7px;
+	}
+}
+</style>

+ 307 - 0
TEAMModelOS/ClientApp/src/view/artexam/ExamSetting.vue

@@ -0,0 +1,307 @@
+<template>
+	<div class="subject-content">
+		<!-- 评测试卷 -->
+		<div class="attr-item">
+			<span>评测试卷:</span>
+			<Tag v-if="subjectSetting.paper" color="blue">
+				{{ subjectSetting.paper.name }}
+			</Tag>
+			<span class="choose-paper" @click="selectPaper()">
+				{{ subjectSetting.paper ? "重选" : "请挑选评测试卷" }}
+			</span>
+		</div>
+		<!-- 作答方式 -->
+		<div class="attr-item">
+			<span>作答方式:</span>
+            <RadioGroup v-model="subjectSetting.unorder">
+                <Radio :label="0">
+                    <span>默认排序</span>
+                </Radio>
+                <Radio :label="1">
+                    <span>乱序作答</span>
+                </Radio>
+            </RadioGroup>
+		</div>
+		<!-- 评测时间 -->
+		<div class="attr-item">
+			<span>评测时间:</span>
+			<DatePicker
+                transfer
+				type="datetimerange"
+				placement="bottom-start"
+				placeholder="请设置评测时间"
+                @on-change="handleSetTime"
+                style="width:400px"
+			></DatePicker>
+		</div>
+        <!-- 挑选试卷 -->
+        <Modal
+            v-model="sltPaperStatus"
+            title="挑选艺术评测试卷"
+            :width="1000"
+            footer-hide>
+            <template v-if="!isPreview">
+                <p v-if="$store.state.userInfo.schoolCode != 'hbcn'">
+                    <span style="vertical-align: bottom;" >
+                        试卷来源:
+                    </span>
+                    <RadioGroup v-model="paperResource">
+                        <Radio label="hbcn">
+                            醍摩豆
+                        </Radio>
+                        <Radio :label="$store.state.userInfo.schoolCode">
+                            本校
+                        </Radio>
+                    </RadioGroup>
+                </p>
+                <div class="papaer-list-wrap">
+                    <vuescroll>
+                        <div class="paper-item" v-for="(item,index) in paperListShow" :key="index">
+                            <div class="paper-item-name">
+                                <span>{{(index + 1) + '.  ' + item.name}}</span>
+                            </div>
+                            <div class="paper-item-info">
+                                <span class="info-item">
+                                    {{$t('learnActivity.manual.fitSubject')}}
+                                    <span class="info-bold">{{item.subjectName}}</span>
+                                </span>
+                                <span class="info-item">
+                                    {{$t('learnActivity.manual.quCount')}}
+                                    <span class="info-bold">{{ item.count }}</span>
+                                </span>
+                                <span class="info-item">
+                                    <Icon type="md-time" style="margin-right:8px" size="14" />
+                                    <span class="info-bold">{{$jsFn.dateFormat(item.createTime)}}</span>
+                                </span>
+                                <span class="info-item">{{$t('evaluation.paperList.sortType')}}:
+                                    <span class="info-bold">
+                                        {{ item.itemSort === 1 ? $t('evaluation.paperList.sortByOrder') : $t('evaluation.paperList.sortByType') }}
+                                    </span>
+                                </span>
+                            </div>
+                            <span v-if="item.id == selectedId" style="margin-left:20px;display:block;" class="paper-item-tools">
+                                <Icon custom="iconfont icon-choose" style="margin-right:5px;" :title="$t('learnActivity.manual.stdPaper')" />
+                            </span>
+                            <div v-else class="paper-item-tools">
+                                <span @click.stop="choosePaper(index)">
+                                    <Icon custom="iconfont icon-choose" style="margin-right:5px;" />
+                                    {{$t('learnActivity.manual.stPaper')}}
+                                </span>
+                                <span @click.stop="previewTestPaper(index)" style="margin-left:20px;">
+                                    <Icon type="md-eye" style="margin-right:5px;" />
+                                    {{$t('learnActivity.manual.previewPaper')}}
+                                </span>
+                            </div>
+                        </div>
+                        <EmptyData v-show="!paperListShow.length"></EmptyData>
+                    </vuescroll>
+                </div>
+            </template>
+            <template v-else>
+                <p class="back-to-list" @click="isPreview = false"><Icon type="md-arrow-back" />返回试卷列表</p>
+                <TestPaper :paper="previewPaper" isExamPaper hideSheet></TestPaper>
+            </template>
+        </Modal>
+	</div>
+</template>
+
+<script>
+import TestPaper from '@/view/evaluation/index/TestPaper.vue'
+export default {
+     components: {
+        TestPaper
+    },
+    props:{
+        subject:{
+            type:String,
+            default:'',
+            required:true
+        }
+    },
+	data() {
+		return {
+            subjectSetting:{
+                paper:undefined,
+                unorder:0,
+                startTime:0,
+                endTime:0
+            },
+            isPreview:false,
+            sltPaperStatus:false,
+            paperResource:'hbcn',
+            previewPaper:undefined,
+            selectedId:'',
+            paperListShow:[],
+            paperList:{
+                tmd:[],
+                school:[]
+            },
+        }
+	},
+    methods:{
+        handleSetTime(value){
+            let start = value[0]
+            let end = value[1]
+            this.subjectSetting.startTime = start ? new Date(start).getTime() : 0
+            this.subjectSetting.endTime = end ? new Date(end).getTime() : 0
+            console.log(value,this.subjectSetting)
+        },
+        async previewTestPaper(index){
+            this.previewPaper = {}
+            try {
+                this.paperListShow[index].scope = 'school'
+                this.previewPaper = await this.$evTools.getFullPaper(this.paperListShow[index])
+                setTimeout(() => {
+                    this.isPreview = true
+                }, 400)
+            } catch (e) {
+            }
+        },
+        choosePaper(index){
+            this.subjectSetting.paper = this.paperListShow[index]
+            this.sltPaperStatus = false
+        },
+        selectPaper(){
+            this.sltPaperStatus = true 
+            this.findPaperList()
+        },
+        findPaperList(){
+            if(!this.paperResource){
+                this.$Messag.error('参数异常')
+            }
+            if(this.paperResource == 'hbcn' && this.paperList.tmd.length){
+                return this.paperListShow = this.paperList.tmd
+            }
+            if(this.paperList.school.length){
+                return this.paperListShow = this.paperList.school
+            }
+            let params = {
+                schoolCode:this.paperResource
+            }
+            this.$api.areaArt.findArtPaperList(params).then(
+                res=>{
+                    if(this.paperResource === 'hbcn'){
+                        this.paperList.tmd = res.papers
+                    }else{
+                        this.paperList.school = res.papers
+                    }
+                    this.paperListShow = res.papers
+                }
+            )
+        },
+    },
+    watch:{
+        subjectSetting:{
+            deep:true,
+            handler(n,o){
+                this.$emit('on-exam-change',{
+                    data:this.subjectSetting,
+                    subject: this.subject
+                })
+            }
+        }
+    }
+    // mounted(){
+    //     setTimeout(() => {
+    //         this.subjectSetting.unorder = true
+    //     }, 2000);
+    // }
+
+}
+</script>
+
+<style lang="less" scoped>
+.attr-item{
+    margin-bottom: 10px;
+}
+.subject-content{
+    display: inline-block;
+    width: calc(100% - 80px);
+    vertical-align: top;
+}
+.back-to-list{
+    color: #2d8cf0;
+    cursor: pointer;
+    user-select: none;
+}
+.paper-item {
+    padding: 10px 15px;
+    position: relative;
+    // margin-left: 15px;
+    margin-right: 15px;
+    margin-bottom: 5px;
+    // background: #505050;
+    align-items: start;
+    border-radius: 2px;
+    position: relative;
+
+    &:hover {
+        background: #E1EFF6;
+        // box-shadow: 0px 4px 5px #191919;
+    }
+
+    &-name {
+        font-size: 20px;
+        font-weight: bold;
+    }
+
+    &-tag {
+        font-size: 12px;
+        padding: 1px 10px;
+        border-radius: 5px;
+        color: #fff;
+        background: #15a06c;
+        margin: 0 10px;
+    }
+
+    &-info {
+        margin-top: 15px;
+
+        .info-item {
+            font-size: 12px;
+            padding: 0 10px;
+            color: #757575;
+
+            .info-bold {
+                font-weight: bold;
+                color: #70B1E7;
+            }
+        }
+
+        .info-item:not(:last-child) {
+            border-right: 2px solid #f3f3f3;
+        }
+    }
+
+    &-tools {
+        position: absolute;
+        height: 100%;
+        right: 50px;
+        top: 35px;
+        font-size: 14px;
+        display: none;
+
+        span {
+            cursor: pointer;
+            font-weight: bold;
+            color: #1CC0F3;
+           /*  &:hover{
+                color:#2d8cf0;
+            }*/
+        }
+    }
+
+    &:hover {
+        .paper-item-tools {
+            display: flex;
+        }
+    }
+}
+.papaer-list-wrap{
+    height: 600px;
+}
+.choose-paper{
+    color: #2d8cf0;
+    cursor: pointer;
+}
+</style>

+ 63 - 0
TEAMModelOS/ClientApp/src/view/artexam/ExamSubject.vue

@@ -0,0 +1,63 @@
+<template>
+	<div class="content-block">
+		<CheckboxGroup v-model="baseKn.subjects"  @on-change="handleSetKn">
+			<div class="check-item">
+				<Checkbox label="paint">
+					<span class="subject-name">美术</span>
+				</Checkbox>
+				<ExamSetting v-if="baseKn.subjects.includes('paint')" :subject="'paint'" @on-exam-change="handleExamInfo"></ExamSetting>
+			</div>
+			<div>
+				<Checkbox label="music">
+					<span class="subject-name">音乐</span>
+				</Checkbox>
+				<ExamSetting v-if="baseKn.subjects.includes('music')" :subject="'music'" @on-exam-change="handleExamInfo"></ExamSetting>
+			</div>
+		</CheckboxGroup>
+	</div>
+</template>
+
+<script>
+import ExamSetting from "./ExamSetting.vue"
+export default {
+    components:{
+        ExamSetting
+    },
+	data() {
+		return {
+			unorder1:0,
+			unorder2:0,
+            baseKn:{
+				subjects:[],
+				settings:[]
+			}
+        }
+	},
+	methods:{
+		handleSetKn(){
+			this.$emit('on-set-exam',this.baseKn)
+		},
+		handleExamInfo(data){
+			data = this._.cloneDeep(data)
+			let index = this.baseKn.settings.findIndex(item=>item.subject == data.subject)
+			if(index > -1){
+				this.baseKn.settings.splice(index,1,Object.assign({subject:data.subject},data.data))
+			}else{
+				this.baseKn.settings.push(Object.assign({subject:data.subject},data.data))
+			}
+			this.handleSetKn()
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.content-block{
+	background: #f0f0f0;
+	padding: 8px 5px;
+	margin: 10px 0px;
+}
+.check-item{
+    margin-bottom: 10px;
+}
+</style>

+ 139 - 0
TEAMModelOS/ClientApp/src/view/artexam/Mgt.vue

@@ -0,0 +1,139 @@
+<template>
+	<div class="art-exam-container">
+		<Split v-model="split">
+			<div class="demo-split-pane" slot="left">
+				<div class="art-mgt-top light-iview-select light-iview-input">
+					<span>艺术评价活动</span>
+					<span class="to-create-art" @click="toCreate">
+						<Icon type="md-add" />
+						创建艺术评测
+					</span>
+				</div>
+				<vuescroll>
+					<div :class="['art-item', curIndex == index ? 'art-item-active' : '']" v-for="(item,index) in artList" :key="item.id" @click="selectArt(index)">
+						<p>{{item.name}}</p>
+					</div>
+				</vuescroll>
+				<EmptyData v-show="!artList.length" :top="100"></EmptyData>
+			</div>
+			<div class="art-content" slot="right">
+				<vuescroll>
+					<div class="train-block-box">
+						{{artInfo.name}}
+					</div>
+				</vuescroll>
+			</div>
+        </Split>
+		
+		
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			split:0.25,
+			artList: [],
+			artInfo:{},
+			curIndex:0,
+			quotas:[],
+		}
+	},
+	methods: {
+		selectArt(index){
+			this.curIndex = index
+			this.findArtSummary()
+		},
+		/* 获取当前区级设置数据 */
+		getAreaSetting() {
+			this.$api.areaArt.findArtSetting({
+				areaId: sessionStorage.getItem("areaId")
+			}).then((res) => {
+				if (res.setting) {
+					console.log(res)
+					// this.artDimensions = res.setting.dimensions
+					this.quotas = res.setting.quotas
+					this.tabList = this.quotas.map((q) => {
+						return {
+							name: q.id,
+							label: q.name,
+							children: q.children
+						}
+					})
+				} else {
+					this.artDimensions = []
+				}
+			})
+		},
+		toCreate() {
+			this.$router.push({
+				name: "createArtExam"
+			})
+		},
+		toDetailPage(index) {},
+		findArtList() {
+			let params = {
+				code: this.$store.state.userInfo.schoolCode
+			}
+			this.$api.areaArt.findArtList(params).then(
+				(res) => {
+					this.artList = res.arts
+					if(this.artList.length) this.findArtSummary()
+				},
+				(err) => {}
+			)
+		},
+		findArtSummary(){
+			let params = {
+				id:this.artList[this.curIndex].id,
+				code: this.$store.state.userInfo.schoolCode
+			}
+			this.$api.areaArt.findArtSummary(params).then(
+				(res) => {
+					this.artInfo = res.art
+				},
+				(err) => {}
+			)
+		}
+	},
+	created(){
+		this.findArtList()
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.art-content{
+	padding-left: 10px;
+}
+.art-item-active{
+	background: var(--hover-text-color);
+}
+.art-item{
+	padding: 15px 15px;
+	cursor: pointer;
+	&:hover{
+		background: var(--hover-text-color);
+	}
+}
+.art-exam-container {
+	width: 100%;
+	height: 100%;
+	background: white;
+}
+.art-mgt-top {
+	height: 45px;
+	box-shadow: 0px 2px 5px #e9e9e9;
+	padding-left: 15px;
+	padding-right: 20px;
+	margin-bottom: 5px;
+	line-height: 45px;
+}
+.to-create-art {
+	float: right;
+	user-select: none;
+	cursor: pointer;
+	color: #40a8f0;
+}
+</style>

+ 179 - 0
TEAMModelOS/ClientApp/src/view/artexam/QuoTree.vue

@@ -0,0 +1,179 @@
+<template>
+	<el-tree
+		show-checkbox
+		:data="treeData"
+		:props="defaultProps"
+		class="quo-tree"
+		node-key="id"
+		ref="tree"
+		:render-content="renderContent"
+        @check="hanldeCheckChange"
+		:render-after-expand="false"
+	></el-tree>
+</template>
+
+<script>
+import ExamSubject from "./ExamSubject.vue"
+import WorkSubject from "./WorkSubject.vue"
+export default {
+	components: {
+		ExamSubject,
+		WorkSubject
+	},
+	props: {
+		quoid: {
+			type: String,
+			default: ""
+		},
+		treeData: {
+			type: Array,
+			default: () => {
+				return []
+			}
+		}
+	},
+	data() {
+		return {
+			defaultProps: {
+				children: "children",
+				label: "name"
+			},
+			typeMap: {},
+			settingMap: {}
+		}
+	},
+	methods: {
+		renderContent(h, { node, data, store }) {
+			console.log(arguments)
+			let _this = this
+			// 不是最后一个节点则直接渲染label
+			if (node.childNodes.length) {
+				return h("span", {}, node.label)
+			} else {
+				if (!_this.typeMap[node.data.id])
+					_this.$set(_this.typeMap, node.data.id, "score")
+				return h(
+					"div",
+					{
+						style: {
+							width: "100%"
+						}
+					},
+					[
+						h("span", node.label),
+						h(
+							"RadioGroup",
+							{
+								class: "type-setting",
+								props: {
+									value: _this.typeMap[node.data.id]
+								},
+								on: {
+									input: (event) => {
+										_this.typeMap[node.data.id] = event
+										// _this.$set(_this.typeMap,node.data.id,event)
+										console.log(_this.typeMap, node.data.id)
+										// console.log(event)
+										// _this.$emit("input", event)
+									}
+								},
+								style: {
+									display: node.checked ? undefined : "none"
+								}
+							},
+							[
+								h(
+									"Radio",
+									{
+										props: {
+											label: "score"
+										}
+									},
+									"评分"
+								),
+								h(
+									"Radio",
+									{
+										props: {
+											label: "exam"
+										}
+									},
+									"评测"
+								),
+								h(
+									"Radio",
+									{
+										props: {
+											label: "work"
+										}
+									},
+									"作业"
+								)
+							]
+						),
+						node.checked && _this.typeMap[node.data.id] === "exam"
+							? h(ExamSubject, {
+									on: {
+										"on-set-exam": (data) => {
+											console.log("****", data)
+											_this.$set(_this.settingMap,node.data.id,data)
+										}
+									}
+							  })
+							: undefined,
+						node.checked && _this.typeMap[node.data.id] === "work"
+							? h(WorkSubject, {
+									on:{
+										"on-set-work": (data) => {
+											console.log(data)
+											_this.$set(_this.settingMap,node.data.id,data)
+										}
+									}
+							  })
+							: undefined
+					]
+				)
+			}
+		},
+        hanldeCheckChange(data,status){
+            this.$emit('on-check-change',{
+                quoid:this.quoid,
+                data:status
+            })
+        }
+	},
+    watch:{
+        typeMap:{
+            deep:true,
+            handler(n,o){
+                this.$emit('on-type-change',{
+                    quoid:this.quoid,
+                    data:n
+                })
+            }
+        },
+        settingMap:{
+            deep:true,
+            handler(n,o){
+                this.$emit('on-setting-change',{
+                    quoid:this.quoid,
+                    data:n
+                })
+            }
+        },
+    }
+
+}
+</script>
+
+<style lang="less" scoped>
+</style>
+<style lang="less">
+.type-setting {
+	float: right;
+}
+.quo-tree .el-tree-node__content {
+	height: fit-content;
+	align-items: baseline;
+}
+</style>

+ 66 - 0
TEAMModelOS/ClientApp/src/view/artexam/WorkSetting.vue

@@ -0,0 +1,66 @@
+<template>
+    <div class="work-setting-container">
+        <!-- 作业描述 -->
+		<div class="attr-item">
+			<span style="vertical-align: top;">作业描述:</span>
+            <Input @on-change="handleSetWork" v-model="workSetting.desc" type="textarea" maxlength="100" :rows="2" style="width:500px" placeholder="请输入作业描述..." />
+		</div>
+        <!-- 截止时间 -->
+		<div class="attr-item">
+			<span>截止时间:</span>
+			<DatePicker
+                transfer
+				type="datetime"
+				placement="bottom-start"
+				placeholder="请设置作业截止时间"
+                @on-change="handleSetTime"
+                style="width:500px"
+			></DatePicker>
+		</div>
+    </div>
+</template>
+
+<script>
+    export default {
+        props:{
+            subject:{
+                type:String,
+                default:'',
+                required:true
+            }
+        },
+        data(){
+            return{
+                workSetting:{
+                    startTime:0,
+                    endTime:0,
+                    desc:''
+                }
+            }
+        },
+        methods:{
+            handleSetWork(){
+                this.$emit('on-set-work',{
+                    subject: this.subject,
+                    data:this.workSetting
+                })
+            },
+            handleSetTime(value){
+                this.workSetting.endTime = value ? new Date(value).getTime() : 0
+                console.log(value,this.workSetting)
+                this.handleSetWork()
+            },
+        }
+    }
+</script>
+
+<style lang="less" scoped>
+.work-setting-container{
+    display: inline-block;
+    width: calc(100% - 80px);
+    vertical-align: top;
+}
+.attr-item{
+    margin-bottom: 10px;
+}
+</style>

+ 61 - 0
TEAMModelOS/ClientApp/src/view/artexam/WorkSubject.vue

@@ -0,0 +1,61 @@
+<template>
+	<div class="content-block">
+		<CheckboxGroup v-model="baseWork.subjects"  @on-change="handleSetWork">
+			<div class="check-item">
+				<Checkbox label="paint">
+					<span class="subject-name">美术</span>
+				</Checkbox>
+				<WorkSetting v-if="baseWork.subjects.includes('paint')" subject="paint" @on-set-work="handleWorkInfo"></WorkSetting>
+			</div>
+			<div>
+				<Checkbox label="music">
+					<span class="subject-name">音乐</span>
+				</Checkbox>
+				<WorkSetting v-if="baseWork.subjects.includes('music')" subject="music" @on-set-work="handleWorkInfo"></WorkSetting>
+			</div>
+		</CheckboxGroup>
+	</div>
+</template>
+
+<script>
+import WorkSetting from "./WorkSetting.vue"
+export default {
+	components:{
+		WorkSetting
+	},
+	data(){
+		return{
+			baseWork:{
+				subjects:[],
+				settings:[]
+			}
+		}
+	},
+	methods:{
+		handleSetWork(){
+			this.$emit('on-set-work',this.baseWork)
+		},
+		handleWorkInfo(data){
+			data = this._.cloneDeep(data)
+			let index = this.baseWork.settings.findIndex(item=>item.subject == data.subject)
+			if(index > -1){
+				this.baseWork.settings.splice(index,1,Object.assign({subject:data.subject},data.data))
+			}else{
+				this.baseWork.settings.push(Object.assign({subject:data.subject},data.data))
+			}
+			this.handleSetWork()
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.check-item{
+    margin-bottom: 10px;
+}
+.content-block{
+	background: #f0f0f0;
+	padding: 8px 5px;
+	margin: 10px 0px;
+}
+</style>

+ 0 - 4
TEAMModelOS/ClientApp/src/view/learnactivity/ManualPaper.vue

@@ -45,10 +45,6 @@
                         <span>{{(index + 1) + '.  ' + item.name}}</span>
                     </div>
                     <div class="paper-item-info">
-                        <!-- <span class="info-item" v-show="scope == $store.state.userInfo.schoolCode">
-                            {{$t('learnActivity.manual.fitPd')}}
-                            <span class="info-bold">{{$jsFn.getPeriod(schoolBase.period,item.periodId).name}}</span>
-                        </span> -->
                         <span class="info-item" v-show="scope == $store.state.userInfo.schoolCode">
                             {{$t('learnActivity.manual.fitSubject')}}
                             <span class="info-bold">{{item.subjectName}}</span>

+ 4 - 1
TEAMModelOS/ClientApp/src/view/learnactivity/markpaper/MarkData.vue

@@ -256,7 +256,7 @@ export default {
                         })
                         // 数据生成中,重新请求,最多请求5次
                         if (isUncomplete) {
-                            if(this.reqCount < this.maxReq){
+                            if(this.reqCount++ < this.maxReq){
                                 this.timer = setTimeout(()=>{
                                     this.findMarkProgress()
                                 },1500)
@@ -319,6 +319,9 @@ export default {
             },
             immediate: true
         }
+    },
+    beforeDestroy(){
+        clearTimeout(this.timer)
     }
 }
 </script>

+ 6 - 1
TEAMModelOS/ClientApp/src/view/mycourse/MyCourse.vue

@@ -56,6 +56,9 @@
                                     <span class="attr-label" v-show="listType === 'school'">{{item.classId ? $t('cusMgt.listType1') : $t('cusMgt.listType2')}}: </span>
                                     <span class="attr-label" v-show="listType === 'private'">{{$t('cusMgt.nameList')}}:</span>
                                     <span class="class-name">{{item.classId ? item.classInfo.name: item.listName || '--'}}</span>
+                                    <Tag v-if="item.graduate === 1" color="gold">
+                                        {{$t('cusMgt.hasGraduate')}}
+                                    </Tag>
                                     <span class="relative-tag" v-if="item.stulistRel > 1">{{$t('cusMgt.reuse')}}</span>
                                 </p>
                                 <p class="class-attr-item">
@@ -1014,7 +1017,8 @@ export default {
                                                 name: classInfo ? classInfo.name : this.$t('cusMgt.hasDelClass'),
                                                 year: classInfo ? classInfo.year : 0 //方便计算年级
                                             }
-                                            item.joinLock = classInfo.joinLock
+                                            item.joinLock = classInfo?.joinLock
+                                            item.graduate = classInfo?.graduate
                                         }
                                         //补充教学班信息
                                         if (item.stulist) {
@@ -1024,6 +1028,7 @@ export default {
                                             item.listName = listInfo ? listInfo.name : this.$t('cusMgt.hasDelClass')
                                             item.listSchool = listInfo ? listInfo.school : undefined
                                             item.joinLock = listInfo?.joinLock
+                                            item.graduate = listInfo?.graduate
                                         }
                                         //统一数据格式
                                         item.classId = item.classId || undefined

+ 1 - 2
TEAMModelOS/ClientApp/src/view/student-account/import/StuImport.vue

@@ -515,7 +515,7 @@ export default {
         },
         checkData() {
             //整理TtableData
-            this.tableData.map((item, index) => {
+            this.tableData.forEach((item, index) => {
                 if (!item.pw) item.pw = item.id
                 item['classroom'] = {}
                 item.classroom.classId = item.classId
@@ -523,7 +523,6 @@ export default {
                 item.year = item.stuYear
                 item.no = item.no ? item.no.toString() : ''
                 item.periodId = this.periodId
-                return item
             })
 
             //座号重复检查逻辑

+ 16 - 1
TEAMModelOS/ClientApp/src/view/train/TrainDetail.vue

@@ -526,6 +526,7 @@ export default {
     },
     data() {
         return {
+            repeatCount:0,
             updHeader: {},
             hwPreviewFile: {},
             modalLoading: false,
@@ -1690,7 +1691,21 @@ export default {
                                     })
                                     // 现在研修会记录发布活动时的名单,不需要单独活动名单(解决名单调整问题)
                                     // this.getTeachers()
-                                    this.handleData(res.ufos)
+                                    //后端数据生成可能有延迟,需要循环拉取数据
+                                    debugger
+                                    if(!res.ufos?.length){
+                                        if(this.repeatCount < 5){
+                                            this.repeatCount++
+                                            setTimeout(() => {
+                                                this.getTrainInfo(id)
+                                            }, 1000)
+                                        }else{
+                                            this.$Message.warning('活动暂无名单数据,或者数据生成中,请稍后重试')
+                                            this.repeatCount = 0
+                                        }
+                                    }else{
+                                        this.handleData(res.ufos)
+                                    }
                                 }
                             }
                         }

+ 44 - 4
TEAMModelOS/Controllers/Common/ArtController.cs

@@ -152,7 +152,47 @@ namespace TEAMModelOS.Controllers.Common
                     {
 
                         art.status = 404;
-                        if (!string.IsNullOrEmpty(art.examId))
+                        foreach (var info in art.settings) {
+                            /* if (info.TryGetProperty("examId", out JsonElement eId)) { 
+
+                             }*/
+                            foreach (var acs in info.task) {
+                                if (!string.IsNullOrEmpty(acs.acId)) {
+                                    if (acs.type.Equals("exam"))
+                                    {
+                                        Azure.Response response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(acs.acId, new PartitionKey($"Exam-{code}"));
+                                        if (response.Status == 200)
+                                        {
+                                            ExamInfo data = JsonDocument.Parse(response.Content).RootElement.Deserialize<ExamInfo>();
+                                            data.status = 404;
+                                            await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(data, data.id, new PartitionKey($"Exam-{code}"));
+                                        }
+                                    }
+                                    if (acs.type.Equals("survey"))
+                                    {
+                                        Azure.Response response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(acs.acId, new PartitionKey($"Survey-{code}"));
+                                        if (response.Status == 200)
+                                        {
+                                            Survey data = JsonDocument.Parse(response.Content).RootElement.Deserialize<Survey>();
+                                            data.status = 404;
+                                            await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(data, data.id, new PartitionKey($"Survey-{code}"));
+                                        }
+                                    }
+                                    if (acs.type.Equals("homework"))
+                                    {
+                                        Azure.Response response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(acs.acId, new PartitionKey($"Homework-{code}"));
+                                        if (response.Status == 200)
+                                        {
+                                            Homework data = JsonDocument.Parse(response.Content).RootElement.Deserialize<Homework>();
+                                            data.status = 404;
+                                            await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(data, data.id, new PartitionKey($"Homework-{code}"));
+                                        }
+                                    }
+                                }
+                                
+                            }
+                        }
+                        /*if (!string.IsNullOrEmpty(art.examId))
                         {
                             Azure.Response response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(art.examId, new PartitionKey($"Exam-{code}"));
                             if (response.Status == 200)
@@ -181,7 +221,7 @@ namespace TEAMModelOS.Controllers.Common
                                 data.status = 404;
                                 await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(data, data.id, new PartitionKey($"Homework-{code}"));
                             }
-                        }
+                        }*/
                         await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(art, art.id, new PartitionKey($"{art.code}"));
                     }
 
@@ -208,7 +248,7 @@ namespace TEAMModelOS.Controllers.Common
             {
                 if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
                 var client = _azureCosmos.GetCosmosClient();
-                var query = $"select c.id,c.img,c.name,c.type,c.startTime,c.endTime,c.presenter,c.topic,c.address,c.owner,c.progress,c.groupLists from c where (c.status<>404 or IS_DEFINED(c.status) = false )    ";
+                var query = $"select c.id,c.img,c.name,c.type,c.startTime,c.endTime,c.presenter,c.topic,c.address,c.owner,c.progress from c where (c.status<>404 or IS_DEFINED(c.status) = false )";
                 string continuationToken = string.Empty;
                 string token = default;
                 //是否需要进行分页查询,默认不分页
@@ -259,7 +299,7 @@ namespace TEAMModelOS.Controllers.Common
 
         [ProducesDefaultResponseType]
         [Authorize(Roles = "IES")]
-        [AuthToken(Roles = "teacher,admin")]
+        [AuthToken(Roles = "teacher,admin,student")]
         [HttpPost("find-summary")]
         public async Task<IActionResult> FindSummary(JsonElement request)
         {

+ 64 - 33
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -4,9 +4,6 @@ using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using System;
 using System.Collections.Generic;
-using System.Dynamic;
-using System.IdentityModel.Tokens.Jwt;
-using System.IO;
 using System.Linq;
 using System.Text;
 using System.Text.Json;
@@ -15,9 +12,7 @@ using TEAMModelOS.Models;
 using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.DI;
-using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
 using TEAMModelOS.SDK.Extension;
-using TEAMModelOS.SDK.Helper.Common.StringHelper;
 using TEAMModelOS.SDK.Models.Cosmos.Common;
 using TEAMModelOS.SDK.Models.Table;
 using Azure.Messaging.ServiceBus;
@@ -29,8 +24,6 @@ using HTEXLib.COMM.Helpers;
 using Microsoft.AspNetCore.Authorization;
 using System.Net.Http;
 using TEAMModelOS.SDK.DI.CoreAPI;
-using TEAMModelOS.SDK.Models.Dtos;
-using Microsoft.Extensions.Hosting;
 using Microsoft.AspNetCore.Hosting;
 using static TEAMModelOS.SDK.SchoolService;
 
@@ -210,39 +203,77 @@ namespace TEAMModelOS.Controllers
                 }
                 else
                 {
-                    ExamInfo info = await client.GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<ExamInfo>(request.id, new PartitionKey($"{request.code}"));
-                    //info.name = request.name;
-                    if (info.progress.Equals("going"))
+                    var response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(request.id, new PartitionKey($"{request.code}"));
+                    if (response.Status == 200)
                     {
-                        //exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(request, request.id, new PartitionKey($"{request.code}"));
-                        return Ok(new { v = "活动正在进行中,无法修改", code = 200 });
-                    }
-                    var messageBlob = new ServiceBusMessage();
-                    if (request.scope.Equals("school"))
-                    {
-                        request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"exam/{request.id}");
-                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"exam/{request.id}", name = request.school }.ToJsonString());
+                        using var json = await JsonDocument.ParseAsync(response.ContentStream);
+                        ExamInfo info = json.ToObject<ExamInfo>();
+                        if (info.progress.Equals("going"))
+                        {
+                            //exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(request, request.id, new PartitionKey($"{request.code}"));
+                            return Ok(new { v = "活动正在进行中,无法修改", code = 200 });
+                        }
+                        var messageBlob = new ServiceBusMessage();
+                        if (request.scope.Equals("school"))
+                        {
+                            request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"exam/{request.id}");
+                            messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"exam/{request.id}", name = request.school }.ToJsonString());
 
-                    }
-                    else
-                    {
-                        request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"exam/{request.id}");
-                        messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"exam/{request.id}", name = request.creatorId }.ToJsonString());
+                        }
+                        else
+                        {
+                            request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"exam/{request.id}");
+                            messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "update", root = $"exam/{request.id}", name = request.creatorId }.ToJsonString());
 
+                        }
+                        messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                        var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                        await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
+                        request.progress = info.progress;
+                        int n = 0;
+                        foreach (PaperSimple simple in request.papers)
+                        {
+                            simple.blob = "/exam/" + request.id + "/paper/" + request.subjects[n].id;
+                            n++;
+                        }
+                        exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(request, request.id, new PartitionKey($"{request.code}"));
                     }
-                    messageBlob.ApplicationProperties.Add("name", "BlobRoot");
-                    var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
-                    await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
-                    request.progress = info.progress;
-                    int n = 0;
-                    foreach (PaperSimple simple in request.papers)
-                    {
-                        simple.blob = "/exam/" + request.id + "/paper/" + request.subjects[n].id;
-                        n++;
+                    else {
+                        if (request.startTime > now)
+                        {
+                            request.progress = "pending";
+                        }
+                        else
+                        {
+                            request.progress = "going";
+                        }
+                        var messageBlob = new ServiceBusMessage();
+                        if (request.scope.Equals("school"))
+                        {
+                            request.size = await _azureStorage.GetBlobContainerClient(request.school).GetBlobsSize($"exam/{request.id}");
+                            messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"exam/{request.id}", name = request.school }.ToJsonString());
+                        }
+                        else
+                        {
+                            request.size = await _azureStorage.GetBlobContainerClient(request.creatorId).GetBlobsSize($"exam/{request.id}");
+                            messageBlob = new ServiceBusMessage(new { id = Guid.NewGuid().ToString(), progress = "insert", root = $"exam/{request.id}", name = request.creatorId }.ToJsonString());
+                        }
+                        messageBlob.ApplicationProperties.Add("name", "BlobRoot");
+                        var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
+                        await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
+                        int n = 0;
+                        List<string> sheetIds = new List<string>();
+                        foreach (PaperSimple simple in request.papers)
+                        {
+                            simple.blob = $"/exam/{request.id}/paper/{request.subjects[n].id}";
+                            n++;
+                            simple.sheet = null;
+                        }
+                        exam = await client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
                     }
-                    exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(request, request.id, new PartitionKey($"{request.code}"));
                 }
                 return Ok(new { exam });
+
             }
             catch (Exception ex)
             {

+ 4 - 2
TEAMModelOS/Lang/en-us.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "Assign exam paper grading task", "{tmdname} of {schoolName} has assign you an exam paper grading task." ],
   "scan-join_groupList": [ "Join course notice", "{tmdname} join the {groupListName} course via QRcode scanning" ],
   "scan-join_school": [ "Join school notice", "{tmdname} join school, {schoolName}, via QRcode scanning" ],
-  "submitanswer-school_homework": [ "Homework submission notice", "{tmdname} of {schoolName} has submitted a homework({homeworkName})" ],
+  "submitanswer-school_homework": [ "Homework submission notice", "{tmdname} of {schoolName} has submitted a homework:{homeworkName}" ],
   "submitanswer-private_homework": [ "Homework submission notice", "{tmdname} has submitted a homework({homeworkName})" ],
   "expire-school_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName}, on {schoolName} will expire at {expireTime}" ],
-  "expire-private_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName} will expire at {expireTime}" ]
+  "expire-private_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName} will expire at {expireTime}" ],
+  "create-school": [ "Create schools in batches", "{tmdname}You successfully created schools in batch with Bi" ],
+  "copy-file_area": [ "Batch copy file start", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 4 - 2
TEAMModelOS/Lang/zh-cn.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "普通阅卷任务通知", "{schoolName}的{tmdname}向您发送了普通卷阅卷任务。" ],
   "scan-join_groupList": [ "扫码加入名单通知", "{tmdname}扫码加入名单,名单:{groupListName}" ],
   "scan-join_school": [ "扫码加入学校通知", "{tmdname}扫码加入学校,学校名称:{schoolName}" ],
-  "submitanswer-school_homework": [ "作业提交通知", "{schoolName}的{tmdname}已提交作业,作业名称({homeworkName})"],
+  "submitanswer-school_homework": [ "作业提交通知", "{schoolName}的{tmdname}已提交作业,作业名称:{homeworkName}" ],
   "submitanswer-private_homework": [ "作业提交通知", "{tmdname}已提交作业,作业名称({homeworkName})" ],
   "expire-school_lessonRecord": [ "课例到期通知", "您在{schoolName}的课例将在{expireTime}到期,课例名称:{lessonName}" ],
-  "expire-private_lessonRecord": [ "课例到期通知", "您的课例将在{expireTime}到期,课例名称:{lessonName}" ]
+  "expire-private_lessonRecord": [ "课例到期通知", "您的课例将在{expireTime}到期,课例名称:{lessonName}" ],
+  "create-school": [ "批量创建学校", "{tmdname}您用BI批量创建学校成功" ],
+  "copy-file_area": [ "批复制文件开始", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 4 - 2
TEAMModelOS/Lang/zh-tw.json

@@ -13,8 +13,10 @@
   "scoring-mark_school": [ "普通閱卷任務通知", "{schoolName}的{tmdname}向您發送了普通卷閱卷任務。" ],
   "scan-join_groupList": [ "掃碼加入名單通知", "{tmdname}掃碼加入名單,名單:{groupListName}" ],
   "scan-join_school": [ "掃碼加入學校通知", "{tmdname}掃碼加入學校,學校名稱:{schoolName}" ],
-  "submitanswer-school_homework": [ "作業提交通知", "{schoolName}的{tmdname}已提交作業,作業名稱({homeworkName})" ],
+  "submitanswer-school_homework": [ "作業提交通知", "{schoolName}的{tmdname}已提交作業,作業名稱:{homeworkName}" ],
   "submitanswer-private_homework": [ "作業提交通知", "{tmdname}已提交作業,作業名稱({homeworkName})" ],
   "expire-school_lessonRecord": [ "課例到期通知", "您在{schoolName}的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
-  "expire-private_lessonRecord": [ "課例到期通知", "您的課例將在{expireTime}到期,課例名稱:{lessonName}" ]
+  "expire-private_lessonRecord": [ "課例到期通知", "您的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
+  "create-school": [ "批量創建學校", "{tmdname}您用BI批量創建學校成功" ],
+  "copy-file_area": [ "批復制文件開始", "{tmdname}您用BI创区成功开始复制区域文件" ]
 }

+ 3 - 3
TEAMModelOS/TEAMModelOS.csproj

@@ -60,9 +60,9 @@
     <SpaRoot>ClientApp\</SpaRoot>
     <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
     <UserSecretsId>078b5d89-7d90-4f6a-88fc-7d96025990a8</UserSecretsId>
-    <Version>5.2208.31</Version>
-    <AssemblyVersion>5.2208.31.1</AssemblyVersion>
-    <FileVersion>5.2208.31.1</FileVersion>
+    <Version>5.2209.7</Version>
+    <AssemblyVersion>5.2209.7.1</AssemblyVersion>
+    <FileVersion>5.2209.7.1</FileVersion>
     <Description>TEAMModelOS(IES5)</Description>
     <PackageReleaseNotes>IES版本说明版本切换标记202200701</PackageReleaseNotes>
     <PackageId>TEAMModelOS</PackageId>

+ 9 - 9
TEAMModelOS/appsettings.Development.json

@@ -1,4 +1,4 @@
-{
+{
   "Logging": {
     "LogLevel": {
       "Default": "Debug",
@@ -21,22 +21,22 @@
   },
   "Azure": {
     "Storage": {
-      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodelos;AccountKey=Dl04mfZ9hE9cdPVO1UtqTUQYN/kz/dD/p1nGvSq4tUu/4WhiKcNRVdY9tbe8620nPXo/RaXxs+1F9sVrWRo0bg==;EndpointSuffix=core.chinacloudapi.cn"
+      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodeltest;AccountKey=O2W2vadCqexDxWO+px+QK7y1sHwsYj8f/WwKLdOdG5RwHgW/Dupz9dDUb4c1gi6ojzQaRpFUeAAmOu4N9E+37A==;EndpointSuffix=core.chinacloudapi.cn"
     },
     "Cosmos": {
-      "ConnectionString": "AccountEndpoint=https://teammodelos.documents.azure.cn:443/;AccountKey=clF73GwPECfP1lKZTCvs8gLMMyCZig1HODFbhDUsarsAURO7TcOjVz6ZFfPqr1HzYrfjCXpMuVD5TlEG5bFGGg==;"
+      "ConnectionString": "AccountEndpoint=https://cdhabookdep-free.documents.azure.cn:443/;AccountKey=JTUVk92Gjsx17L0xqxn0X4wX2thDPMKiw4daeTyV1HzPb6JmBeHdtFY1MF1jdctW1ofgzqkDMFOtcqS46by31A==;"
     },
     "Redis": {
-      "ConnectionString": "CoreRedisCN.redis.cache.chinacloudapi.cn:6380,password=LyJWP1ORJdv+poXWofAF97lhCEQPg1wXWqvtzXGXQuE=,ssl=True,abortConnect=False"
+      "ConnectionString": "52.130.252.100:6379,password=habook,ssl=false,abortConnect=False,writeBuffer=10240"
     },
     "ServiceBus": {
-      "ConnectionString": "Endpoint=sb://coreiotservicebuscnpro.servicebus.chinacloudapi.cn/;SharedAccessKeyName=TEAMModelOS;SharedAccessKey=llRPBMDJG9w1Nnifj+pGhV0g4H2REcq0PjvX2qqpcOg=",
-      "ActiveTask": "active-task",
-      "ItemCondQueue": "itemcond",
-      "GenPdfQueue": "genpdf"
+      "ConnectionString": "Endpoint=sb://teammodelos.servicebus.chinacloudapi.cn/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=Sy4h4EQ8zP+7w/lOLi1X3tGord/7ShFHimHs1vC50Dc=",
+      "ActiveTask": "dep-active-task",
+      "ItemCondQueue": "dep-itemcond",
+      "GenPdfQueue": "dep-genpdf"
     },
     "SignalR": {
-      "ConnectionString": "Endpoint=https://channel.signalr.azure.cn;AccessKey=AtcB7JYFNUbUXb1rGxa3PVksQ2X5YSv3JOHZR9J88tw=;Version=1.0;"
+      "ConnectionString": "Endpoint=https://channel.service.signalr.net;AccessKey=KrblW06tuA4a/GyqRPDU0ynFFmAWxbAvyJihHclSXbQ=;Version=1.0;"
     }
   },
   "HaBookAuth": {