浏览代码

Merge branch 'develop' into Jeff/develop

jeff 2 年之前
父节点
当前提交
22dc27ad83
共有 55 个文件被更改,包括 2913 次插入954 次删除
  1. 1 1
      TEAMModelBI/ClientApp/public/index.html
  2. 23 9
      TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue
  3. 309 113
      TEAMModelBI/ClientApp/src/view/created/created.vue
  4. 5 6
      TEAMModelBI/ClientApp/src/view/product/index.vue
  5. 22 4
      TEAMModelBI/ClientApp/src/view/schoolServe/school.vue
  6. 1 0
      TEAMModelBI/ClientApp/src/view/schoolmanage/schoolManege.vue
  7. 38 6
      TEAMModelBI/Controllers/BINormal/AreaRelevantController.cs
  8. 139 95
      TEAMModelBI/Controllers/BISchool/BatchSchoolController.cs
  9. 123 74
      TEAMModelBI/Controllers/BISchool/SchoolController.cs
  10. 36 10
      TEAMModelBI/Controllers/Census/SchoolController.cs
  11. 3 3
      TEAMModelBI/TEAMModelBI.csproj
  12. 4 4
      TEAMModelOS.FunctionV4/CosmosDB/TriggerArt.cs
  13. 1 4
      TEAMModelOS.SDK/DI/AzureCosmos/AzureCosmosFactory.cs
  14. 1 9
      TEAMModelOS.SDK/DI/AzureStorage/AzureStorageFactory.cs
  15. 146 0
      TEAMModelOS.SDK/Extension/HttpContextExtensions.cs
  16. 8 0
      TEAMModelOS.SDK/Models/Cosmos/School/School.cs
  17. 2 2
      TEAMModelOS.SDK/Models/Service/BI/BIProdAnalysis.cs
  18. 44 38
      TEAMModelOS.SDK/Models/Service/BI/BISchoolService.cs
  19. 62 59
      TEAMModelOS/ClientApp/public/lang/en-US.js
  20. 7 4
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  21. 8 5
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  22. 3 0
      TEAMModelOS/ClientApp/src/api/areaArt.js
  23. 7 0
      TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.less
  24. 313 133
      TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.vue
  25. 二进制
      TEAMModelOS/ClientApp/src/components/evaluation/cloudas_title.png
  26. 8 2
      TEAMModelOS/ClientApp/src/components/student-analysis/total/BaseSingleStuScatter.vue
  27. 3 0
      TEAMModelOS/ClientApp/src/components/student-web/EventBasicInfo.vue
  28. 42 3
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/LessonTestReportCharts/LessonTestReportCharts.vue
  29. 2 2
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/LessonTestReport.less
  30. 4 2
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/LessonTestReport.vue
  31. 208 1
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperView.vue
  32. 二进制
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/cloudas_title.png
  33. 13 10
      TEAMModelOS/ClientApp/src/view/areaMgmt/AreaIndex.vue
  34. 32 32
      TEAMModelOS/ClientApp/src/view/areaMgmt/AreaLayout.vue
  35. 4 4
      TEAMModelOS/ClientApp/src/view/areaMgmt/AreaSchool.vue
  36. 330 146
      TEAMModelOS/ClientApp/src/view/art/AreaArt.vue
  37. 8 8
      TEAMModelOS/ClientApp/src/view/learnactivity/StuReport.vue
  38. 80 25
      TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/ScatterAnalysis/ScatterAnalysis.vue
  39. 二进制
      TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/ScatterAnalysis/cloudas_title.png
  40. 74 32
      TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/TestAnalysis/QuestionList.vue
  41. 2 2
      TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/TestAnalysis/TestAnalysis.vue
  42. 3 2
      TEAMModelOS/ClientApp/src/view/student-web/AppNew.vue
  43. 8 7
      TEAMModelOS/Controllers/Analysis/ArtAnalysisController.cs
  44. 477 49
      TEAMModelOS/Controllers/Common/AreaController.cs
  45. 2 2
      TEAMModelOS/Controllers/Common/ExamController.cs
  46. 50 0
      TEAMModelOS/Controllers/Teacher/InitController.cs
  47. 16 12
      TEAMModelOS/Controllers/XTest/FixDataController.cs
  48. 32 0
      TEAMModelOS/Filter/AspNetCoreBuilderServiceCollectionExtensions.cs
  49. 79 0
      TEAMModelOS/Filter/BlobLoggerProvider.cs
  50. 82 0
      TEAMModelOS/Filter/RequestAuditFilter.cs
  51. 9 0
      TEAMModelOS/Program.cs
  52. 5 0
      TEAMModelOS/Startup.cs
  53. 4 4
      TEAMModelOS/TEAMModelOS.csproj
  54. 29 29
      TEAMModelOS/appsettings.Development.json
  55. 1 1
      TEAMModelOS/appsettings.json

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

@@ -12,7 +12,7 @@
     </title>
 </head>
 <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
-<script src="https://at.alicdn.com/t/c/font_2934132_fog7jyuq5b4.js"></script>
+<script src="https://at.alicdn.com/t/c/font_2934132_3s3ctw0yifz.js"></script>
 <script src="../src/access/iconfont.js"></script>
 
 <body>

+ 23 - 9
TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue

@@ -173,6 +173,10 @@
                         </template>
                       </el-popconfirm>
                     </div>
+                    <div class="list-school-type">
+                      <el-button type="primary" round size="small" v-show="item.code ==='BIRel'">实体</el-button>
+                      <el-button type="success" round size="small" v-show="item.code ==='VirtualBase'">虚拟</el-button>
+                    </div>
                   </li>
                   <div class="noData-notjoin" v-show="notjoinSchool.length ===0">
                     <div>{{ $t(`commonMsg.nodataTable`) }}</div>
@@ -801,13 +805,17 @@ export default {
     async function getNotjoin () {
       loadingSchoolList.value = true
       console.log(currentlySelect.value.id)
+      let notaredSchool=[]
       await proxy.$api
-        .getNotjoinSchool({})
+        .getSchool({})
         .then((res) => {
           console.log(res, '获取未加入区域的学校')
-          res.state === 200
-            ? (notjoinSchool.value = [], notjoinSchool.value = res.notAreaSchools, notjoinPrimitive.value = res.notAreaSchools, loadingSchoolList.value = false)
-            : ''
+          // res.state === 200
+          //   ? (notjoinSchool.value = [], notjoinSchool.value = res.notAreaSchools, notjoinPrimitive.value = res.notAreaSchools, loadingSchoolList.value = false)
+          //   : ''
+          res.state === 200 ? 
+          (notjoinSchool.value = [],res.scInfos.forEach((item)=>{!item.areaId ? notaredSchool.push(item):''}),notjoinSchool.value=notaredSchool,notjoinPrimitive.value=notaredSchool,loadingSchoolList.value = false):
+          ''
         })
     }
     //区域内添加学校
@@ -819,8 +827,10 @@ export default {
         schoolCode: [addSchoolitem.value.id],
         standard: currentlySelect.value.standard,
         areaId: currentlySelect.value.id,
+        //code: [addSchoolitem.value.code],
         // schoolCode: [schoolcode],
       }
+      console.log(data,'准备提交的内容')
       proxy.$api.areaAddSchool(data).then(async (res) => {
         console.log(res, '区域加入学校返回')
         res.state === 200
@@ -940,6 +950,7 @@ export default {
             let data = {
               areaId: currentlySelect.value.id,
               schoolId: datas.id,
+              code: datas.code,
               isDefault: states,
             }
             proxy.$api.areaDeleSchool(data).then(async (res) => {
@@ -1291,9 +1302,10 @@ export default {
     }
     //学校显示(虚拟、实体)
     function handleCommand (value) {
-      value === 'all' ? dropdownValue.value = '显示所有学校' : ''
-      value === 'virtual' ? dropdownValue.value = '只看虚拟学校' : ''
-      value === 'entity' ? dropdownValue.value = '只看实体学校' : ''
+      let nowTables=notjoinPrimitive.value
+      value === 'all' ? (dropdownValue.value = '显示所有学校',notjoinSchool.value=notjoinPrimitive.value) : ''
+      value === 'virtual' ? (dropdownValue.value = '只看虚拟学校',notjoinSchool.value=nowTables.filter((item)=>{return item.code ==='VirtualBase'})) : ''
+      value === 'entity' ? (dropdownValue.value = '只看实体学校',notjoinSchool.value=nowTables.filter((item)=>{return item.code ==='BIRel'})) : ''
     }
     watch(abilityModel, (newdata) => {
       console.log(newdata)
@@ -1501,11 +1513,13 @@ export default {
 }
 
 .list-school-name {
-  width: 65%;
+  width: 55%;
   /* padding-left: 1%;
   margin-top: 1.5%; */
 }
-
+.list-school-type{
+  width:15%;
+}
 .list-school-name div {
   font-size: 14px;
   color: #909399;

+ 309 - 113
TEAMModelBI/ClientApp/src/view/created/created.vue

@@ -133,120 +133,214 @@
   <div class="schoolbox" v-else-if="model == 'schollC'">
     <div class="batchCreation">
       <!-- <el-upload class="upload-demo" :before-upload="handleBeforeUpload" :on-exceed="handleExceed"> -->
-      <el-button type="primary" @click="batchDialogState = true">
-        {{ $t(`schoolManages.createSchools.batch`) }}</el-button>
+      <el-button type="primary" @click="batchDialogState = true" v-show="selectModels==='created' && selectSchoolstate ==='entity'">{{ $t(`schoolManages.createSchools.batch`) }}</el-button>
       <!-- </el-upload> -->
       <!-- <button class="created-btninfo">批量创校</button> -->
     </div>
-    <div class="backs">
+    <div class="backs" v-if="selectModels==='select'">
       <el-button @click="closeandreturn('close', 'school')">返回</el-button>
     </div>
+    <div class="backs-details" v-else>
+      <el-button @click="closeandreturn('close', 'school')" type="primary">返回上一级</el-button>
+    </div>
     <div class="schoolListbox" v-loading="loadingSchool" :element-loading-text="loadingTexts">
-      <div class="school-libox" v-for="(item, index) in schoolForm">
-        <p class="aera-title school-headertitle">
-        <div class="created-icon">
-          <svg class="createdsicon" aria-hidden="true">
+      <div class="selectbox" v-show="selectModels ==='select'">
+        <div class="select-block" @click="selectSchoolstate='entity',selectModels='created'">
+          <svg class="select-imgs" aria-hidden="true">
             <use xlink:href="#icon-a-xuexiaofangzijianzhu"></use>
           </svg>
+          <div class="select-text">创建<span class="entity-text">实体</span>学校</div>
         </div>
-        <div class="created-title"> {{ $t(`schoolManages.createSchools.title`) }}</div>
-        <div class="created-title-hint"><span>创建学校,对即将创建的学校的基础信息进行设定,以及管家的预设等。</span></div>
-        </p>
-        <el-form ref="createdSchoolforms" :label-position="labelPosition" :model="item" label-width="120px" :rules="schoolRules">
-          <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.name`)" class="school-name" prop="name">
-            <el-input v-model="item.name" :placeholder="$t(`schoolManages.createSchools.schoolinfo.name`)">
-            </el-input>
-            <p class="repetition-hint" v-if="repetition.name">该学校名称已存在,请检查学校名称</p>
-          </el-form-item>
-          <!-- <el-form-item label="学校编码:" class="school-code">
+        <i class="dividerbox"></i>
+        <div class="select-block two-block" @click="selectSchoolstate='virtual',selectModels='created'">
+          <svg class="select-imgs" aria-hidden="true">
+            <use xlink:href="#icon-xuexiao10"></use>
+          </svg>
+          <div class="select-text">创建<span class="virtual-text">虚拟</span>学校</div>
+        </div>
+      </div>
+      <!--创建实体学校表单-->
+      <div v-if="selectModels==='created' && selectSchoolstate ==='entity'">
+        <div class="school-libox" v-for="(item, index) in schoolForm" :key="index">
+          <p class="aera-title school-headertitle">
+          <div class="created-icon">
+            <svg class="createdsicon" aria-hidden="true">
+              <use xlink:href="#icon-a-xuexiaofangzijianzhu"></use>
+            </svg>
+          </div>
+          <div class="created-title"> {{ $t(`schoolManages.createSchools.title`) }}</div>
+          <div class="created-title-hint"><span>创建学校,对即将创建的学校的基础信息进行设定,以及管家的预设等。</span></div>
+          </p>
+          <el-form ref="createdSchoolforms" :label-position="labelPosition" :model="item" label-width="120px" :rules="schoolRules">
+            <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.name`)" class="school-name" prop="name">
+              <el-input v-model="item.name" :placeholder="$t(`schoolManages.createSchools.schoolinfo.name`)">
+              </el-input>
+              <p class="repetition-hint" v-if="repetition.name">该学校名称已存在,请检查学校名称</p>
+            </el-form-item>
+            <!-- <el-form-item label="学校编码:" class="school-code">
                         <el-input v-model="item.code" maxlength="6" placeholder='6位字符简码'></el-input>
                     </el-form-item> -->
-          <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.type`)" class="school-form-grading" prop="type">
-            <el-radio v-model="item.type" label="1" size="large">{{ $t(`schoolManages.basicSet.ordinary`) }}
-            </el-radio>
-            <el-radio v-model="item.type" label="2" size="large">
-              {{ $t(`schoolManages.basicSet.higherEducation`) }}</el-radio>
-          </el-form-item>
-          <el-form-item label="学校语系" class="school-form-grading" prop="type">
-            <el-radio v-model="item.lang" label="1" size="large">简体</el-radio>
-            <el-radio v-model="item.lang" label="2" size="large">繁体</el-radio>
-            <el-radio v-model="item.lang" label="3" size="large">英语</el-radio>
-          </el-form-item>
-          <el-form-item label="学段类型" prop="radio1" class="radios-check">
-            <el-radio-group v-model="item.radio1">
-              <el-radio :label="item.name" v-for="item in phaselist" :key="item.value"></el-radio>
-              <!-- <el-checkbox label="初中"></el-checkbox>
+            <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.type`)" class="school-form-grading" prop="type">
+              <el-radio v-model="item.type" label="1" size="large">{{ $t(`schoolManages.basicSet.ordinary`) }}
+              </el-radio>
+              <el-radio v-model="item.type" label="2" size="large">
+                {{ $t(`schoolManages.basicSet.higherEducation`) }}</el-radio>
+            </el-form-item>
+            <el-form-item label="学校语系" class="school-form-grading" prop="type">
+              <el-radio v-model="item.lang" label="1" size="large">简体</el-radio>
+              <el-radio v-model="item.lang" label="2" size="large">繁体</el-radio>
+              <el-radio v-model="item.lang" label="3" size="large">英语</el-radio>
+            </el-form-item>
+            <el-form-item label="学段类型" prop="radio1" class="radios-check">
+              <el-radio-group v-model="item.radio1">
+                <el-radio :label="item.name" v-for="item in phaselist" :key="item.value"></el-radio>
+                <!-- <el-checkbox label="初中"></el-checkbox>
               <el-checkbox label="高中"></el-checkbox>
               <el-checkbox label="职高"></el-checkbox>
               <el-checkbox label="大学"></el-checkbox> -->
-            </el-radio-group>
-          </el-form-item>
-          <el-form-item label="预设管理员" class="scholl-admin" prop="presupposeAdmin">
-            <el-input v-model="item.presupposeAdmin" placeholder="输入醍摩豆账号或手机号,预设学校管理员" />
-            <p class="repetition-hint" v-if="repetition.admin">该管理员用户不存在,请检查</p>
-          </el-form-item>
-          <el-form-item label="归属学区ID" class="scholl-admin belong">
-            <!-- <el-input v-model="item.areaIdcreated" placeholder="" /> -->
-            <el-select v-model="item.areaIdcreated" filterable placeholder="选择归属学区">
-              <el-option v-for="item in createdAreds" :key="item.id" :label="item.id ? item.name+ '——' + item.institution:item.name" :value="item.id">
-              </el-option>
-            </el-select>
-            <p class="repetition-hint" v-if="repetition.area">该学区数据异常,请检查</p>
-          </el-form-item>
-          <el-form-item label="所属位置" class="school-location">
-            <!-- <elui-china-area-dht :leave="3" @change="onChange(item.value,item.num)" placeholder="请选择地区" v-model="item.value"></elui-china-area-dht> -->
-            <el-cascader v-model="provinceOptions.provinceValue" :options="provinceOptions.optionInfo" :props="schoolregionParams" placeholder="选择学校所在地" @change="cascaderSchool" />
-          </el-form-item>
-          <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.address`)" class="school-detailedly">
-            <el-input v-model="item.address" :placeholder="$t(`schoolManages.createSchools.schoolinfo.address`)" />
-          </el-form-item>
-          <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.spacesize`)" class="sizecontrol">
-            <!-- <el-input-number v-model="item.pitchSpace" :min='1' :max='50' /><span
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="预设管理员" class="scholl-admin" prop="presupposeAdmin">
+              <el-input v-model="item.presupposeAdmin" placeholder="输入醍摩豆账号或手机号,预设学校管理员" />
+              <p class="repetition-hint" v-if="repetition.admin">该管理员用户不存在,请检查</p>
+            </el-form-item>
+            <el-form-item label="归属学区ID" class="scholl-admin belong">
+              <!-- <el-input v-model="item.areaIdcreated" placeholder="" /> -->
+              <el-select v-model="item.areaIdcreated" filterable placeholder="选择归属学区">
+                <el-option v-for="item in createdAreds" :key="item.id" :label="item.id ? item.name+ '——' + item.institution:item.name" :value="item.id">
+                </el-option>
+              </el-select>
+              <p class="repetition-hint" v-if="repetition.area">该学区数据异常,请检查</p>
+            </el-form-item>
+            <el-form-item label="所属位置" class="school-location">
+              <!-- <elui-china-area-dht :leave="3" @change="onChange(item.value,item.num)" placeholder="请选择地区" v-model="item.value"></elui-china-area-dht> -->
+              <el-cascader v-model="provinceOptions.provinceValue" :options="provinceOptions.optionInfo" :props="schoolregionParams" placeholder="选择学校所在地" @change="cascaderSchool" />
+            </el-form-item>
+            <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.address`)" class="school-detailedly">
+              <el-input v-model="item.address" :placeholder="$t(`schoolManages.createSchools.schoolinfo.address`)" />
+            </el-form-item>
+            <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.spacesize`)" class="sizecontrol">
+              <!-- <el-input-number v-model="item.pitchSpace" :min='1' :max='50' /><span
                             class="spacestitle">G</span> -->
-            <div class="sizebox">
-              <el-slider v-model="item.pitchSpace" :step="500" show-stops show-input :min='0' :max='10300' />
-            </div>
-            <div class="siznum">GB</div>
-            <div class="selectsize">
-              <span>快捷选定:</span>
-              <el-button size="small" @click="item.pitchSpace =100">100</el-button>
-              <!-- <el-button size="small" @click="item.pitchSpace =100" disabled v-else>100</el-button> -->
-              <el-button size="small" @click="item.pitchSpace =300">300</el-button>
-              <!-- <el-button size="small" @click="item.pitchSpace =300" disabled v-else>300</el-button> -->
-              <el-button size="small" @click="item.pitchSpace =500">500</el-button>
-              <!-- <el-button size="small" @click="item.pitchSpace =500" disabled v-else>500</el-button> -->
-            </div>
-          </el-form-item>
-          <el-form-item prop="code" class="scholl-admin school-codes" label-width="120px">
-            <template #label>
-              <span class="span-box">
-                <svg class="lockicon" aria-hidden="true">
-                  <use xlink:href="#icon-xingxing"></use>
-                </svg>
-                <span>学校简码</span>
-              </span>
-            </template>
-            <template #default>
-              <div class="school-codes-input">
-                <el-input v-model="item.code" placeholder="填写完成学校信息后自动生成,无需手动填写" disabled />
+              <div class="sizebox">
+                <el-slider v-model="item.pitchSpace" :step="500" show-stops show-input :min='0' :max='10300' />
               </div>
-              <div class="school-name-btn">
-                <el-button @click="getCodes()" v-if="item.name !=='' && provinceOptions.provinceValue" type="success">生成简码</el-button>
-                <el-button disabled v-else>生成简码</el-button>
+              <div class="siznum">GB</div>
+              <div class="selectsize">
+                <span>快捷选定:</span>
+                <el-button size="small" @click="item.pitchSpace =100">100</el-button>
+                <!-- <el-button size="small" @click="item.pitchSpace =100" disabled v-else>100</el-button> -->
+                <el-button size="small" @click="item.pitchSpace =300">300</el-button>
+                <!-- <el-button size="small" @click="item.pitchSpace =300" disabled v-else>300</el-button> -->
+                <el-button size="small" @click="item.pitchSpace =500">500</el-button>
+                <!-- <el-button size="small" @click="item.pitchSpace =500" disabled v-else>500</el-button> -->
               </div>
-              <p class="repetition-hint" v-if="repetition.code">该学校简码已存在,请检查学校信息</p>
-            </template>
-          </el-form-item>
-        </el-form>
-        <div class="confirmarea">
-          <el-button type="primary" @click="verifyBase()" v-if="verifyForstate.state ===false">数据核验</el-button>
-          <el-button type="success" @click="createdSchool()" :loading="createdSchoolLoading" v-else-if="verifyForstate.state===true && verifyForstate.pass===true ">{{ $t(`schoolManages.createSchools.submit`) }}</el-button>
-          <!-- <el-button @click="closeandreturn('close', 'school')">重置</el-button> -->
-          <el-button @click="resetForm()">重置</el-button>
+            </el-form-item>
+            <el-form-item prop="code" class="scholl-admin school-codes" label-width="120px">
+              <template #label>
+                <span class="span-box">
+                  <svg class="lockicon" aria-hidden="true">
+                    <use xlink:href="#icon-xingxing"></use>
+                  </svg>
+                  <span>学校简码</span>
+                </span>
+              </template>
+              <template #default>
+                <div class="school-codes-input">
+                  <el-input v-model="item.code" placeholder="填写完成学校信息后自动生成,无需手动填写" disabled />
+                </div>
+                <div class="school-name-btn">
+                  <el-button @click="getCodes()" v-if="item.name !=='' && provinceOptions.provinceValue" type="success">生成简码</el-button>
+                  <el-button disabled v-else>生成简码</el-button>
+                </div>
+                <p class="repetition-hint" v-if="repetition.code">该学校简码已存在,请检查学校信息</p>
+              </template>
+            </el-form-item>
+          </el-form>
+          <div class="confirmarea">
+            <el-button type="primary" @click="verifyBase()" v-if="verifyForstate.state ===false">数据核验</el-button>
+            <el-button type="success" @click="createdSchool()" :loading="createdSchoolLoading" v-else-if="verifyForstate.state===true && verifyForstate.pass===true ">{{ $t(`schoolManages.createSchools.submit`) }}</el-button>
+            <!-- <el-button @click="closeandreturn('close', 'school')">重置</el-button> -->
+            <el-button @click="resetForm()">重置</el-button>
+          </div>
+        </div>
+      </div>
+      <!--创建实体学校表单end-->
+      <!--创建虚拟学校表单-->
+      <div v-else-if="selectModels==='created' && selectSchoolstate ==='virtual'">
+        <div class="school-libox">
+          <p class="aera-title school-headertitle">
+          <div class="created-icon">
+            <svg class="createdsicon" aria-hidden="true">
+              <use xlink:href="#icon-xuexiao10"></use>
+            </svg>
+          </div>
+          <div class="created-title"> {{ $t(`schoolManages.createSchools.title`) }}</div>
+          <div class="created-title-hint"><span>创建虚拟(名单)学校,填写相应基础数据即可创建。</span></div>
+          </p>
+          <el-form :model="virtualSchoolform" label-width="120px">
+            <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.name`)" class="school-name virtual-unify" prop="name">
+              <el-input v-model="virtualSchoolform.name" :placeholder="$t(`schoolManages.createSchools.schoolinfo.name`)">
+              </el-input>
+              <!-- <p class="repetition-hint" v-if="repetition.name">该学校名称已存在,请检查学校名称</p> -->
+            </el-form-item>
+            <el-form-item label="别名" class="school-name virtual-unify" prop="name">
+              <el-input v-model="virtualSchoolform.alias" placeholder="学校名称别名">
+              </el-input>
+            </el-form-item>
+            <el-form-item label="学校语系" class="school-form-grading virtual-unify" prop="type">
+              <el-radio v-model="virtualSchoolform.lang" label="1" size="large">简体</el-radio>
+              <el-radio v-model="virtualSchoolform.lang" label="2" size="large">繁体</el-radio>
+              <el-radio v-model="virtualSchoolform.lang" label="3" size="large">英语</el-radio>
+            </el-form-item>
+            <el-form-item label="学段类型" prop="radio1" class="radios-check virtual-unify">
+              <el-radio-group v-model="virtualSchoolform.phasetype">
+                <el-radio :label="item.name" v-for="item in phaselist" :key="item.value"></el-radio>
+                <!-- <el-checkbox label="初中"></el-checkbox>
+              <el-checkbox label="高中"></el-checkbox>
+              <el-checkbox label="职高"></el-checkbox>
+              <el-checkbox label="大学"></el-checkbox> -->
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="所属位置" class="school-location virtual-unify">
+              <!-- <elui-china-area-dht :leave="3" @change="onChange(item.value,item.num)" placeholder="请选择地区" v-model="item.value"></elui-china-area-dht> -->
+              <el-cascader v-model="provinceOptions.provinceValue" :options="provinceOptions.optionInfo" :props="schoolregionParams" placeholder="选择学校所在地" @change="cascaderSchool" />
+            </el-form-item>
+            <el-form-item :label="$t(`schoolManages.createSchools.schoolinfo.address`)" class="school-detailedly virtual-unify">
+              <el-input v-model="virtualSchoolform.address" :placeholder="$t(`schoolManages.createSchools.schoolinfo.address`)" />
+            </el-form-item>
+            <el-form-item prop="code" class="scholl-admin school-codes virtual-unify" label-width="120px">
+              <template #label>
+                <span class="span-box">
+                  <svg class="lockicon" aria-hidden="true">
+                    <use xlink:href="#icon-xingxing"></use>
+                  </svg>
+                  <span>学校简码</span>
+                </span>
+              </template>
+              <template #default>
+                <div class="school-codes-input virtual-unify">
+                  <el-input v-model="virtualSchoolform.code" placeholder="填写完成学校信息后自动生成,无需手动填写" disabled />
+                </div>
+                <div class="school-name-btn">
+                  <el-button @click="getCodes()" v-if="virtualSchoolform.name !=='' && provinceOptions.provinceValue" type="success">生成简码</el-button>
+                  <el-button disabled v-else>生成简码</el-button>
+                </div>
+                <!-- <p class="repetition-hint" v-if="repetition.code">该学校简码已存在,请检查学校信息</p> -->
+              </template>
+            </el-form-item>
+            <div class="confirmarea virtualsubmitbtn">
+              <el-button type="primary" v-if="verifyForstate.state ===false">数据核验</el-button>
+              <el-button type="success" @click="createdSchool()" :loading="createdSchoolLoading" v-else-if="verifyForstate.state===true && verifyForstate.pass===true ">{{ $t(`schoolManages.createSchools.submit`) }}</el-button>
+              <!-- <el-button @click="closeandreturn('close', 'school')">重置</el-button> -->
+              <el-button @click="resetForm()">重置</el-button>
+            </div>
+          </el-form>
         </div>
       </div>
+      <!--创建虚拟学校表单end-->
       <!--查询BB数据-->
-      <div class="searchbox">
+      <div class="searchbox" v-show="selectSchoolstate ==='entity'">
         <el-input v-model="searchSchoolvalue" placeholder="搜索学校名称/简码 检验是否重复" @focus="searchboxState=true">
           <template #prefix>
             <el-icon class="el-input__icon">
@@ -506,6 +600,7 @@ const areaData = {
 }
 const siteValue = window.location.host === 'localhost:5001' ? 'cn' : window.location.host === 'bi.teammodel.cn' ? 'cn' : window.location.host === 'bitest.teammodel.cn' ? 'cn' : 'international'
 const optionsData = siteValue === 'cn' ? option_cn : option_gl
+//创建学校相关数据
 const schoolAssets = {
   num: 1,
   name: '',
@@ -530,6 +625,27 @@ const schoolAssets = {
   address: '',
   areaId: '',
   lang: "1",
+  schoolLocation: {
+    region: '',
+    regionvalue: '',
+    province: '',
+    provincevalue: '',
+    city: '',
+    cityvalue: '',
+    area: '',
+    areaIdcreated: '',
+    state: false,
+  },
+
+}
+//创建虚拟学校相关数据
+const virtualSchooldata = {
+  name: '',
+  alias: '',
+  lang: '1',
+  phasetype: '',
+  address: '',
+  code: '',
 }
 export default {
   components: { EluiChinaAreaDht, Search, StarFilled },
@@ -727,10 +843,17 @@ export default {
     })
     let timer = ref('')
     let regionInfoList = []
+    //新增关于创建虚拟/实体 学校 状态值
+    let selectModels = ref('select')
+    let selectSchoolstate = ref('')
+    //创建虚拟学校内容
+    let virtualSchoolform = ref({})
     onMounted(() => {
       formArea.value = JSON.parse(JSON.stringify(areaData))
       console.log(formArea, '初步的数据')
       let schoolData = schoolForm.value.push(schoolAssets)
+      virtualSchoolform.value = virtualSchooldata
+      console.log(virtualSchoolform.value, '虚拟学校')
       model.value = route.query.model
       //製作region列表
       if (siteValue === 'cn') {
@@ -1048,16 +1171,22 @@ export default {
     }
     //处理取消和确认并返回
     function closeandreturn (val, name) {
-      console.log(val, name, '内容')
-      val === 'close' && name == 'area'
-        ? (router.push({ path: '/home/areamanage' }), (formArea.value = areaData))
-        : (router.push({ path: '/home/schoolmanage' }), (schoolForm.value = []), schoolForm.value.push(schoolAssets))
-      provinceOptions.value.optionInfo = []
-      provinceOptions.value.provinceValue = ''
-      cityOptions.value.cityInfo = []
-      cityOptions.value.cityValue = ''
-      distOptions.value.distInfo = []
-      distOptions.value.distValue = ''
+      let showState = selectModels.value
+      if (showState === 'created') {
+        selectModels.value = 'select'
+        selectSchoolstate.value = ''
+      } else {
+        console.log(val, name, '内容')
+        val === 'close' && name == 'area'
+          ? (router.push({ path: '/home/areamanage' }), (formArea.value = areaData))
+          : (router.push({ path: '/home/schoolmanage' }), (schoolForm.value = []), schoolForm.value.push(schoolAssets))
+        provinceOptions.value.optionInfo = []
+        provinceOptions.value.provinceValue = ''
+        cityOptions.value.cityInfo = []
+        cityOptions.value.cityValue = ''
+        distOptions.value.distInfo = []
+        distOptions.value.distValue = ''
+      }
     }
     //获取文件,读取exel
     function handleBeforeUpload (file) {
@@ -1903,7 +2032,10 @@ export default {
       codeShow,
       backstatus,
       phaselist,
-      timer
+      timer,
+      selectSchoolstate,
+      selectModels,
+      virtualSchoolform
     }
   },
 }
@@ -1911,6 +2043,7 @@ export default {
 <style scoped>
 .selectbox {
   width: 100%;
+  height: 85vh;
   margin: 0 auto;
 }
 
@@ -1990,10 +2123,10 @@ export default {
 /*创建学校样式*/
 .schoolbox {
   width: 100%;
-  padding: 1% 2%;
+  padding: 0% 2%;
   /* border: 1px solid #ccc; */
   line-height: 140px;
-  height: 100vh;
+  /* height: 100vh; */
   position: relative;
 }
 
@@ -2003,16 +2136,21 @@ export default {
 .school-name {
   width: 31%;
 }
-.school-libox {
+.school-libox,
+.selectbox {
   position: relative;
   border: 1px solid #ccc;
   padding: 1%;
   margin-bottom: 2%;
   background-color: #fff;
   border-radius: 5px;
-  margin-top: 2%;
+  margin-top: 2.5%;
+}
+.selectbox {
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
-
 .scholl-admin {
   width: 31%;
 }
@@ -2178,9 +2316,17 @@ export default {
   line-height: 40px;
   position: absolute;
   top: 0px;
+  right: 3%;
+  z-index: 999;
+}
+.backs-details {
+  text-align: right;
+  line-height: 40px;
+  position: absolute;
+  top: -40px;
   right: 2%;
+  z-index: 999;
 }
-
 .school-headertitle {
   padding-left: 25px;
   padding-bottom: 30px;
@@ -2332,6 +2478,56 @@ export default {
 .institutionbox {
   width: 100%;
 }
+.entity-text {
+  font-size: 20px;
+  color: #409eff;
+  font-weight: bold;
+}
+.virtual-text {
+  font-size: 20px;
+  color: #95d475;
+  font-weight: bold;
+}
+.select-block {
+  width: 300px;
+  padding: 10px;
+  border-radius: 5px;
+  box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px,
+    rgba(0, 0, 0, 0.22) 0px 10px 10px;
+  margin-right: 10%;
+  margin-left: 10%;
+  transition: all 0.5s ease 0s;
+  -webkit-transition: all 0.5s ease 0s;
+  background-color: #fff;
+}
+.select-block:hover {
+  cursor: pointer;
+  background-color: #f2f3f5;
+  transform: scale(1.1);
+  -webkit-transform: scale(1.1);
+}
+.select-text {
+  font-size: 18px;
+}
+.select-imgs {
+  width: 100px;
+  height: 100px;
+  vertical-align: -40px;
+  fill: currentColor;
+  overflow: hidden;
+  /* margin-right: 5px; */
+}
+.dividerbox {
+  width: 2px;
+  height: 80%;
+  background-color: #999;
+}
+.virtualsubmitbtn {
+  margin-top: 7%;
+}
+.virtual-unify {
+  margin-bottom: 25px !important;
+}
 </style>
 <style>
 .areabox .el-form {
@@ -2359,8 +2555,8 @@ export default {
   text-align: right;
   line-height: 40px;
   position: absolute;
-  top: 0px;
-  right: 7%;
+  top: -40px;
+  right: 8.5%;
 }
 
 .schoolbox .el-dialog__body {

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

@@ -820,6 +820,7 @@ function searchInit () {
   addschool.value.city = ''
   addschool.value.dist = ''
   addschool.value.isvirtual = false
+  checkboxArr.value = []
   tableData.value.forEach(item => item.checkboxs = false)
   // console.log(checkboxArr.value)
 }
@@ -837,15 +838,12 @@ function addschoolfn () {
     for (let i in optionsValue.value) {
       if (item.id === optionsValue.value[i][1]) { return }
     }
-    // if (item.areaId && item.id) {
-    //   optionsValue.value.push([item.areaId, item.id])
-    // }
     item.areaId && item.id ? optionsValue.value.push([item.areaId, item.id]) : ''
-    let serachtesult = dataSource.value.originalSchool.findIndex((items) => { return items.id === item.id })
-    serachtesult === -1 ? (dataArr.forEach((itemc) => { itemc.id === 'notarea' ? itemc.children.push(item) : '' }), optionsValue.value.push(['notarea', item.id])) : ''
+    !item.areaId ? (dataArr.forEach((itemArea) => { itemArea.id === 'notarea' ? itemArea.children.push(item) : '' }), optionsValue.value.push(['notarea', item.id])) : ''
   })
   console.log(optionsValue.value, '数据检查')
-  options.value = JSON.parse(JSON.stringify(dataArr))
+  console.log(dataArr, '归还数据')
+  options.value = dataArr
   adddialog.value = false
   // let ids = addschool.value.id
   // let pushArr = [addschool.value.areaId, addschool.value.id]
@@ -933,6 +931,7 @@ function exportExcel () {
       item === items.label ? (headerArr.push(items.label), keyArr.push(items.key)) : ''
     })
   })
+
   superaddition.forEach((item) => { headerArr.push(item.label), keyArr.push(item.key) })
 
   let totalHeader = {

+ 22 - 4
TEAMModelBI/ClientApp/src/view/schoolServe/school.vue

@@ -576,13 +576,28 @@ export default {
         headerClass: 'testclass',
       },
       {
-        key: "assisName",
-        dataKey: "assisName",
-        title: "关联管家",
+        key: "code",
+        dataKey: "code",
+        title: "类型",
         align: 'center',
         width: 130,
         headerClass: 'testclass',
+        cellRenderer: (data) => 
+        (
+        <>
+        <el-button  class="relbox" v-show={data.rowData.code ==='BIRel'} type="primary" size="small">实体</el-button>
+        <el-button   class="virtubox" v-show={data.rowData.code ==='VirtualBase'} type="success" size="small">虚拟</el-button >
+        </>
+        )
       },
+      // {
+      //   key: "assisName",
+      //   dataKey: "assisName",
+      //   title: "关联管家",
+      //   align: 'center',
+      //   width: 130,
+      //   headerClass: 'testclass',
+      // },
       {
         key: "serial",
         dataKey: "serial",
@@ -1225,7 +1240,7 @@ export default {
     }
     function removeSchool (value, index) {
       console.log(value, index)
-      let data = { schoolId: [value.id] }
+      let data = { schoolId: [value.id], code: [value.code] }
       ElMessageBox.confirm(`您确定要删除 ${value.name} 学校吗? 请慎重操作!`, '删除学校', {
         confirmButtonText: proxy.$t(`commonMsg.confirm`),
         cancelButtonText: proxy.$t(`commonMsg.closes`),
@@ -2104,6 +2119,9 @@ export default {
 .exportbox .el-dialog__footer {
   line-height: 60px;
 }
+.relbox,.virtubox{
+  margin-left:0px !important;
+}
 @media screen and (max-width: 2600px) {
   .school-formbox .school-form-badge {
     width: 15vw;

+ 1 - 0
TEAMModelBI/ClientApp/src/view/schoolmanage/schoolManege.vue

@@ -495,6 +495,7 @@ export default {
         }
       }
       let updateForm = {
+        code: nowPitchdata.value.code,
         name: nowPitchdata.value.name,
         schoolId: nowPitchdata.value.id,
         picture: nowPitchdata.value.picture,

+ 38 - 6
TEAMModelBI/Controllers/BINormal/AreaRelevantController.cs

@@ -72,13 +72,13 @@ namespace TEAMModelBI.Controllers.BINormal
                 if (!string.IsNullOrEmpty($"{_isDefalue}"))
                     isManyArea = true;
                 List<JoinAreaSchool> joinAreaSchools = new();
-                string sqltxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c WHERE c.areaId='{_areaId}'";
+                string sqltxt = $"SELECT c.code,c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c WHERE c.areaId='{_areaId}'";
 
                 if (isManyArea)
                 {
-                    sqltxt = $"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c join m in c.manyAreas WHERE (c.areaId ='{_areaId}' or m.areaId='{_areaId}')";
+                    sqltxt = $"SELECT c.code,c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c join m in c.manyAreas WHERE (c.areaId ='{_areaId}' or m.areaId='{_areaId}')";
                 }
-
+                //IES5實體學校
                 await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: sqltxt, requestOptions:new QueryRequestOptions() { PartitionKey = new PartitionKey("Base")})) 
                 {
                     using var json = await JsonDocument.ParseAsync(item.ContentStream);
@@ -88,6 +88,7 @@ namespace TEAMModelBI.Controllers.BINormal
                         {
                             JoinAreaSchool joinAreaSchool = new()
                             {
+                                code = obj.GetProperty("code").GetString(),
                                 id = obj.GetProperty("id").GetString(),
                                 name = obj.GetProperty("name").GetString(),
                                 schoolCode = obj.GetProperty("schoolCode").GetString(),
@@ -116,6 +117,32 @@ namespace TEAMModelBI.Controllers.BINormal
                         }                    
                     }
                 }
+                //虛擬學校
+                await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: sqltxt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("VirtualBase") }))
+                {
+                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    {
+                        foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                        {
+                            JoinAreaSchool joinAreaSchool = new()
+                            {
+                                code = obj.GetProperty("code").GetString(),
+                                id = obj.GetProperty("id").GetString(),
+                                name = obj.GetProperty("name").GetString(),
+                                schoolCode = obj.GetProperty("schoolCode").GetString(),
+                                province = obj.GetProperty("province").GetString(),
+                                city = obj.GetProperty("city").GetString(),
+                                dist = obj.GetProperty("dist").GetString(),
+                                picture = obj.GetProperty("picture").GetString(),
+                                period = obj.GetProperty("period").ToObject<List<Period>>().Select(x => x.name).ToList(),
+                                areaId = obj.GetProperty("areaId").GetString(),
+                                standard = obj.GetProperty("standard").GetString()
+                            };
+                            joinAreaSchools.Add(joinAreaSchool);
+                        }
+                    }
+                }
 
                 return Ok(new { state = 200, joinAreaSchools });
             }
@@ -139,6 +166,7 @@ namespace TEAMModelBI.Controllers.BINormal
             try
             {
                 if (!jsonElement.TryGetProperty("schoolId", out JsonElement schoolId)) return BadRequest();
+                string code = (jsonElement.TryGetProperty("code", out JsonElement codeJobj)) ? codeJobj.GetString() : "Base";
                 jsonElement.TryGetProperty("areaId", out JsonElement areaId);
                 jsonElement.TryGetProperty("standard", out JsonElement standard);
                 jsonElement.TryGetProperty("isDefault", out JsonElement isDefault);
@@ -165,7 +193,7 @@ namespace TEAMModelBI.Controllers.BINormal
                         return Ok(new { state = 401, msg = "区域已经规定了,不能切换能能力" });
                 }
 
-                School tempSchool = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{schoolId}", new PartitionKey("Base"));
+                School tempSchool = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{schoolId}", new PartitionKey($"{code}"));
                 if (bool.Parse($"{isDefault}") == true)
                 {
                     tempSchool.areaId = null;
@@ -206,11 +234,14 @@ namespace TEAMModelBI.Controllers.BINormal
                         }
                     }
                 }
-                School school = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(tempSchool, tempSchool.id, new PartitionKey("Base"));
+                School school = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(tempSchool, tempSchool.id, new PartitionKey($"{code}"));
 
                 //创建/修改学校信息中间件
                 //_ = _httpTrigger.RequestHttpTrigger(new { school = $"{school}" }, _option.Location, "set-sc-birelation");
-                await BIStats.SetSchoolBIRelation(cosmosClient, blobClient, tableClient, _dingDing, school);
+                if (school.code.Equals("Base")) //實體學校更新BI中間件
+                {
+                    await BIStats.SetSchoolBIRelation(cosmosClient, blobClient, tableClient, _dingDing, school);
+                }
 
                 //保存操作记录
                 await AzureStorageBlobExtensions.SaveBILog(blobClient, tableClient, "school-update", $"{_tmdName}【{_tmdId}】已操作学校(ID:{schoolId})移除已区域(ID:{areaId})", _dingDing, httpContext: HttpContext);
@@ -589,6 +620,7 @@ namespace TEAMModelBI.Controllers.BINormal
         /// </summary>
         public record JoinAreaSchool
         {
+            public string code { get; set; }
             public string id { get; set; }
             public string name { get; set; }
             public string schoolCode { get; set; }

+ 139 - 95
TEAMModelBI/Controllers/BISchool/BatchSchoolController.cs

@@ -1042,7 +1042,7 @@ namespace TEAMModelBI.Controllers.BISchool
                 jsonElement.TryGetProperty("city", out JsonElement city);
                 jsonElement.TryGetProperty("dist", out JsonElement dist);
                 jsonElement.TryGetProperty("address", out JsonElement address);
-
+                string code = (jsonElement.TryGetProperty("code", out JsonElement _code))? _code.GetString() : string.Empty;
                 jsonElement.TryGetProperty("assist", out JsonElement assist);
                 List<IdInfo> idInfos = assist.ToObject<List<IdInfo>>();
                 //jsonElement.TryGetProperty("site", out JsonElement site);//分开部署,就不需要,一站多用时,取消注释
@@ -1060,114 +1060,158 @@ namespace TEAMModelBI.Controllers.BISchool
                 //    blobClient = _azureStorage.GetBlobContainerClient(containerName: "0-public", BIConst.Global);
                 //}
 
-                School tempShool = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{_schoolId}", new PartitionKey("Base"));
-                if (tempShool != null)
+                //IES5 實體學校變更
+                if (!code.Equals("VirtualBase"))
                 {
-                    List<Period> periods = new();
-                    string campusId = Guid.NewGuid().ToString();
-                    //periodS.ForEach(x =>
-                    //{
-                    //    periods.Add(new Period { id = Guid.NewGuid().ToString(), name = x.ToString(), campusId = campusId });
-                    //});
-
-                    //tempShool.period = periods;
-                    tempShool.size = !string.IsNullOrEmpty($"{size}") ? int.Parse($"{size}") : tempShool.size;
-                    tempShool.scale = !string.IsNullOrEmpty($"{scale}") ? int.Parse($"{scale}") : tempShool.scale;
-                    tempShool.picture = $"{picture}";
-                    tempShool.type = int.Parse($"{_type}");
-                    tempShool.name = $"{schoolName}";
-                    tempShool.areaId = $"{areaId}";
-                    tempShool.standard = $"{standard}";
-                    tempShool.province = string.IsNullOrEmpty($"{province}") ? tempShool.province : $"{province}";
-                    tempShool.city = string.IsNullOrEmpty($"{city}") ? tempShool.city : $"{city}";
-                    tempShool.dist = string.IsNullOrEmpty($"{dist}") ? tempShool.dist : $"{dist}";
-                    tempShool.address = string.IsNullOrEmpty($"{address}") ? tempShool.address : $"{address}";
-
-                    //修改学校
-                    await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(tempShool, tempShool.id, new PartitionKey("Base"));
-
-                    //创建学校信息中间件
-                    //_ = _httpTrigger.RequestHttpTrigger(new { school = $"{tempShool}" }, _option.Location, "set-sc-birelation");
-                    //await BIStats.SetSchoolBIRelation(cosmosClient, blobClient, tableClient, _dingDing, tempShool);
-
-                    //修改学校教师关联的信息
-                    string sql = $"SELECT distinct value(c) FROM c join A1 in c.schools where A1.schoolId='{tempShool.id}'";
-                    List<Teacher> teachers = new();
-                    await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<Teacher>(sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("Base") }))
-                    {
-                        teachers.Add(item);
-                    }
-                    foreach (var item in teachers)
+                    School tempShool = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>($"{_schoolId}", new PartitionKey("Base"));
+                    if (tempShool != null)
                     {
-                        Teacher.TeacherSchool teacherSchool = item.schools.Find(x => x.schoolId.Equals(tempShool.id));
-                        if (teacherSchool != null)
+                        List<Period> periods = new();
+                        string campusId = Guid.NewGuid().ToString();
+                        //periodS.ForEach(x =>
+                        //{
+                        //    periods.Add(new Period { id = Guid.NewGuid().ToString(), name = x.ToString(), campusId = campusId });
+                        //});
+
+                        //tempShool.period = periods;
+                        tempShool.size = !string.IsNullOrEmpty($"{size}") ? int.Parse($"{size}") : tempShool.size;
+                        tempShool.scale = !string.IsNullOrEmpty($"{scale}") ? int.Parse($"{scale}") : tempShool.scale;
+                        tempShool.picture = $"{picture}";
+                        tempShool.type = int.Parse($"{_type}");
+                        tempShool.name = $"{schoolName}";
+                        tempShool.areaId = $"{areaId}";
+                        tempShool.standard = $"{standard}";
+                        tempShool.province = string.IsNullOrEmpty($"{province}") ? tempShool.province : $"{province}";
+                        tempShool.city = string.IsNullOrEmpty($"{city}") ? tempShool.city : $"{city}";
+                        tempShool.dist = string.IsNullOrEmpty($"{dist}") ? tempShool.dist : $"{dist}";
+                        tempShool.address = string.IsNullOrEmpty($"{address}") ? tempShool.address : $"{address}";
+                        //計算學校版本
+                        List<SchoolProductSumData> services = new List<SchoolProductSumData>();
+                        Azure.Response productSumResponse = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync($"{_schoolId}", new PartitionKey("ProductSum"));
+                        if (productSumResponse.Status == 200)
                         {
-                            teacherSchool.name = tempShool.name;
-                            teacherSchool.picture = tempShool.picture;
-                            teacherSchool.areaId = tempShool.areaId;
+                            var doc = JsonDocument.Parse(productSumResponse.Content);
+                            if (doc.RootElement.TryGetProperty("service", out JsonElement service))
+                            {
+                                services.AddRange(service.ToObject<List<SchoolProductSumData>>());
+                            }
                         }
-                        await cosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync(item, item.id, new PartitionKey($"Base"));
-                    }
+                        tempShool.edition = BISchoolService.calSchoolEdition(tempShool, services.Select(s => s.prodCode).ToList());
 
-                    //需要重大修改后保留
-                    BIRelation biRel = new();
-                    var respRel = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(tempShool.id, new PartitionKey("BIRel"));
-                    if (respRel.Status == 200)
-                    {
-                        using var fileJson = await JsonDocument.ParseAsync(respRel.ContentStream);
-                        biRel = fileJson.ToObject<BIRelation>();
-                    }
-                    else
-                    {
-                        biRel.id = tempShool.id;
-                    }
+                        //修改学校
+                        await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(tempShool, tempShool.id, new PartitionKey("Base"));
 
-                    string aName = null;
-                    if (!string.IsNullOrEmpty($"{tempShool.areaId}"))
-                    {
-                        aName = await CosmosQueryHelper.GetStr(cosmosClient, "Normal", $"select value(c.name) from c where c.id='{tempShool.areaId}'", "Base-Area");
-                    }
+                        //创建学校信息中间件
+                        //await BIStats.SetSchoolBIRelation(cosmosClient, blobClient, tableClient, _dingDing, tempShool);
 
-                    biRel.name = tempShool.name;
-                    biRel.picture = tempShool.picture;
-                    biRel.region = tempShool.region;
-                    biRel.province = tempShool.province;
-                    biRel.city = tempShool.city;
-                    biRel.dist = tempShool.dist;
-                    biRel.address = tempShool.address;
-                    biRel.areaId = tempShool.areaId;
-                    biRel.size = tempShool.size;
-                    biRel.scale = tempShool.scale;
-                    biRel.upDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-                    biRel.areaName = aName;
-                    if (idInfos.Count > 0)
-                    {
-                        foreach (var item in idInfos)
+                        //修改学校教师关联的信息
+                        string sql = $"SELECT distinct value(c) FROM c join A1 in c.schools where A1.schoolId='{tempShool.id}'";
+                        List<Teacher> teachers = new();
+                        await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<Teacher>(sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("Base") }))
                         {
-                            var tempAss = biRel.assists.Find(f => f.id.Equals(item.id));
-                            if (tempAss == null)
-                                biRel.assists.Add(item);
+                            teachers.Add(item);
+                        }
+                        foreach (var item in teachers)
+                        {
+                            Teacher.TeacherSchool teacherSchool = item.schools.Find(x => x.schoolId.Equals(tempShool.id));
+                            if (teacherSchool != null)
+                            {
+                                teacherSchool.name = tempShool.name;
+                                teacherSchool.picture = tempShool.picture;
+                                teacherSchool.areaId = tempShool.areaId;
+                            }
+                            await cosmosClient.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync(item, item.id, new PartitionKey($"Base"));
                         }
-                    }
-                    else
-                    {
-                        biRel.assists = idInfos;
-                    }
 
-                    if (respRel.Status == 200)
-                    {
-                        await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<BIRelation>(biRel, biRel.id, new PartitionKey("BIRel"));
+                        //学校信息中间件更新
+                        BIRelation biRel = new();
+                        var respRel = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemStreamAsync(tempShool.id, new PartitionKey("BIRel"));
+                        if (respRel.Status == 200)
+                        {
+                            using var fileJson = await JsonDocument.ParseAsync(respRel.ContentStream);
+                            biRel = fileJson.ToObject<BIRelation>();
+                        }
+                        else
+                        {
+                            biRel.id = tempShool.id;
+                        }
+
+                        string aName = null;
+                        if (!string.IsNullOrEmpty($"{tempShool.areaId}"))
+                        {
+                            aName = await CosmosQueryHelper.GetStr(cosmosClient, "Normal", $"select value(c.name) from c where c.id='{tempShool.areaId}'", "Base-Area");
+                        }
+
+                        biRel.name = tempShool.name;
+                        biRel.picture = tempShool.picture;
+                        biRel.region = tempShool.region;
+                        biRel.province = tempShool.province;
+                        biRel.city = tempShool.city;
+                        biRel.dist = tempShool.dist;
+                        biRel.address = tempShool.address;
+                        biRel.areaId = tempShool.areaId;
+                        biRel.size = tempShool.size;
+                        biRel.scale = tempShool.scale;
+                        biRel.upDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                        biRel.areaName = aName;
+                        biRel.edition = tempShool.edition;
+                        if (idInfos.Count > 0)
+                        {
+                            foreach (var item in idInfos)
+                            {
+                                var tempAss = biRel.assists.Find(f => f.id.Equals(item.id));
+                                if (tempAss == null)
+                                    biRel.assists.Add(item);
+                            }
+                        }
+                        else
+                        {
+                            biRel.assists = idInfos;
+                        }
+
+                        if (respRel.Status == 200)
+                        {
+                            await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<BIRelation>(biRel, biRel.id, new PartitionKey("BIRel"));
+                        }
+                        else
+                        {
+                            await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").CreateItemAsync<BIRelation>(biRel, new PartitionKey("BIRel"));
+                        }
                     }
-                    else
+                    
+                    //保存操作记录
+                    await AzureStorageBlobExtensions.SaveBILog(blobClient, tableClient, "school-update", $"{_tmdName}【{_tmdId}】修改学校功能,修改的学校:{_schoolId},{_type},{picture},{size},{idInfos.ToArray()}", _dingDing, httpContext: HttpContext);
+
+                    return Ok(new { state = 200 });
+                }
+                //IES5 虛擬學校變更
+                else
+                {
+                    VirtualBase tempVirtual = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<VirtualBase>($"{_schoolId}", new PartitionKey("VirtualBase"));
+                    if (tempVirtual != null)
                     {
-                        await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").CreateItemAsync<BIRelation>(biRel, new PartitionKey("BIRel"));
+                        tempVirtual.picture = $"{picture}";
+                        tempVirtual.type = int.Parse($"{_type}");
+                        tempVirtual.name = $"{schoolName}";
+                        tempVirtual.areaId = $"{areaId}";
+                        tempVirtual.standard = $"{standard}";
+                        tempVirtual.province = string.IsNullOrEmpty($"{province}") ? tempVirtual.province : $"{province}";
+                        tempVirtual.city = string.IsNullOrEmpty($"{city}") ? tempVirtual.city : $"{city}";
+                        tempVirtual.dist = string.IsNullOrEmpty($"{dist}") ? tempVirtual.dist : $"{dist}";
+                        tempVirtual.address = string.IsNullOrEmpty($"{address}") ? tempVirtual.address : $"{address}";
+                        string aName = null;
+                        if (!string.IsNullOrEmpty($"{tempVirtual.areaId}"))
+                        {
+                            aName = await CosmosQueryHelper.GetStr(cosmosClient, "Normal", $"select value(c.name) from c where c.id='{tempVirtual.areaId}'", "Base-Area");
+                        }
+                        tempVirtual.areaName = aName;
+                        //修改学校
+                        await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<VirtualBase>(tempVirtual, tempVirtual.id, new PartitionKey("VirtualBase"));
                     }
+                    // 保存操作记录
+                    await AzureStorageBlobExtensions.SaveBILog(blobClient, tableClient, "school-update", $"{_tmdName}【{_tmdId}】修改学校功能,修改的学校:{_schoolId},{_type},{picture},{size},{idInfos.ToArray()}", _dingDing, httpContext: HttpContext);
+                    return Ok(new { state = 200 });
                 }
-
-                //保存操作记录
-                await AzureStorageBlobExtensions.SaveBILog(blobClient, tableClient, "school-update", $"{_tmdName}【{_tmdId}】修改学校功能,修改的学校:{_schoolId},{_type},{picture},{size},{idInfos.ToArray()}", _dingDing, httpContext: HttpContext);
-
-                return Ok(new { state = 200 });
             }
             catch (Exception ex)
             {

+ 123 - 74
TEAMModelBI/Controllers/BISchool/SchoolController.cs

@@ -180,39 +180,43 @@ namespace TEAMModelBI.Controllers.BISchool
             try
             {
                 var cosmosClient = _azureCosmos.GetCosmosClient();
+                var partitionKeys = new List<string>{"Base","VirtualBase"};
                 //StringBuilder sqltxt = new($"SELECT c.id,c.name,c.schoolCode,c.province,c.city,c.dist,c.picture,c.period,c.areaId,c.standard,c.manyAreas FROM c WHERE c.pk='School' and (c.areaId = '' or c.areaId = null or IS_DEFINED(c.areaId) = false)");
                 string sqltxt = $"SELECT value(c) FROM c WHERE (c.areaId = '' or c.areaId = null or IS_DEFINED(c.areaId) = false)";
                 List<NotAreaSchool> notAreaSchools = new();
-                await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: sqltxt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
+                foreach (string pk in partitionKeys)
                 {
-                    using var json = await JsonDocument.ParseAsync(item.ContentStream);
-                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIterator(queryText: sqltxt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"{pk}") }))
                     {
-                        foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                        using var json = await JsonDocument.ParseAsync(item.ContentStream);
+                        if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
                         {
-                            NotAreaSchool notAreaSchool = new()
-                            {
-                                id = obj.GetProperty("id").GetString(),
-                                name = obj.GetProperty("name").GetString(),
-                                schoolCode = obj.GetProperty("schoolCode").GetString(),
-                                picture = obj.GetProperty("picture").GetString(),
-                                period = obj.GetProperty("period").ToObject<List<Period>>().Select(x => x.name).ToList(),
-                                province = obj.GetProperty("province").GetString(),
-                                city = obj.GetProperty("city").GetString()
-                            };
-                            try
+                            foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
                             {
-                                notAreaSchool.dist = obj.GetProperty("dist").GetString();
-                                notAreaSchool.areaId = obj.GetProperty("areaId").GetString();
-                                notAreaSchool.standard = obj.GetProperty("standard").GetString();
-                                notAreaSchool.areas = obj.GetProperty("manyAreas").ToObject<List<SchoolArea>>();
+                                NotAreaSchool notAreaSchool = new()
+                                {
+                                    code = obj.GetProperty("code").GetString(),
+                                    id = obj.GetProperty("id").GetString(),
+                                    name = obj.GetProperty("name").GetString(),
+                                    schoolCode = obj.GetProperty("schoolCode").GetString(),
+                                    picture = obj.GetProperty("picture").GetString(),
+                                    period = obj.GetProperty("period").ToObject<List<Period>>().Select(x => x.name).ToList(),
+                                    province = obj.GetProperty("province").GetString(),
+                                    city = obj.GetProperty("city").GetString()
+                                };
+                                try
+                                {
+                                    notAreaSchool.dist = obj.GetProperty("dist").GetString();
+                                    notAreaSchool.areaId = obj.GetProperty("areaId").GetString();
+                                    notAreaSchool.standard = obj.GetProperty("standard").GetString();
+                                    notAreaSchool.areas = obj.GetProperty("manyAreas").ToObject<List<SchoolArea>>();
+                                }
+                                catch { }
+                                notAreaSchools.Add(notAreaSchool);
                             }
-                            catch { }
-                            notAreaSchools.Add(notAreaSchool);
                         }
                     }
                 }
-
                 return Ok(new { state = 200, notAreaSchools });
             }
             catch (Exception ex)
@@ -239,6 +243,7 @@ namespace TEAMModelBI.Controllers.BISchool
                 if (!jsonElement.TryGetProperty("areaId", out JsonElement _areaId)) return BadRequest();
                 jsonElement.TryGetProperty("areaName", out JsonElement areaName);
                 jsonElement.TryGetProperty("isDefault", out JsonElement isDefault);
+                List<string> codes = (jsonElement.TryGetProperty("code", out JsonElement codeJobj)) ? codeJobj.ToObject<List<string>>() : new List<string>();
                 //jsonElement.TryGetProperty("site", out JsonElement site); //分开部署,就不需要,一站多用时,取消注释
                 var isManyArea = false;
                 if (!string.IsNullOrEmpty($"{isDefault}"))
@@ -246,6 +251,13 @@ namespace TEAMModelBI.Controllers.BISchool
 
                 var (_tmdId, _tmdName, pic, did, dname, dpic) = HttpJwtAnalysis.JwtXAuthBI(HttpContext.GetXAuth("AuthToken"), _option);
                 List<string> schoolCodes = _schoolCode.ToObject<List<string>>();
+                if(schoolCodes.Count > 0 && codes.Count.Equals(0)) //分區鍵值初始化
+                {
+                    for (int i = 0; i < schoolCodes.Count; i++)
+                    {
+                        codes.Add("Base");
+                    }
+                }
 
                 StringBuilder msg = new($"{_tmdName}【{_tmdId}】操作学校加入区域功能,加入的区域:{standard},学校ID:{string.Join("|", schoolCodes.ToArray())}");
                 var cosmosClient = _azureCosmos.GetCosmosClient();
@@ -272,7 +284,9 @@ namespace TEAMModelBI.Controllers.BISchool
                 {
                     foreach (var tempCode in schoolCodes)
                     {
-                        School school = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>(tempCode, new PartitionKey("Base"));
+                        int index = schoolCodes.IndexOf(tempCode);
+                        string code = codes[index];
+                        School school = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>(tempCode, new PartitionKey($"{code}"));
                         if (school != null)
                         {
                             if (isManyArea)
@@ -311,7 +325,10 @@ namespace TEAMModelBI.Controllers.BISchool
                                 await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(item, item.id, new PartitionKey("Base"));
                             }
                             await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(school, school.id, new PartitionKey(school.code));
-                            await BIStats.SetSchoolBIRelation(cosmosClient, blobClient, tableClient, _dingDing, school);
+                            if (school.code.Equals("Base"))
+                            {
+                                await BIStats.SetSchoolBIRelation(cosmosClient, blobClient, tableClient, _dingDing, school);
+                            }
                         }
                     }
                 }
@@ -352,7 +369,7 @@ namespace TEAMModelBI.Controllers.BISchool
                 {
                     schoolAssists = itemSchool;
                 }
-
+                //IES5實體學校
                 if (schoolAssists.id != null)
                 {
                     var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(schoolAssists.id, new PartitionKey("ProductSum"));
@@ -378,8 +395,16 @@ namespace TEAMModelBI.Controllers.BISchool
 
                     return Ok(new { state = 200, schoolAssists });
                 }
-                else return Ok(new { state = 404, msg = "未找到该学校!" });
-
+                //無實體學校 => 取得虛擬學校
+                else
+                {
+                    await foreach (var itemSchool in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<AssistSchool>(queryText: sqlTxt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("VirtualBase") }))
+                    {
+                        schoolAssists = itemSchool;
+                    }
+                    if (schoolAssists.id != null) return Ok(new { state = 200, schoolAssists });
+                    else return Ok(new { state = 404, msg = "未找到该学校!" });
+                }
             }
             catch (Exception ex)
             {
@@ -1034,6 +1059,14 @@ namespace TEAMModelBI.Controllers.BISchool
                 var cosmosClient = _azureCosmos.GetCosmosClient();
                 var tableClient = _azureStorage.GetCloudTableClient();
                 var blobClient = _azureStorage.GetBlobContainerClient(containerName: "0-public");
+                List<string> codes = (jsonElement.TryGetProperty("code", out JsonElement codeJobj)) ? codeJobj.ToObject<List<string>>() : new List<string>();
+                if (schools.Count > 0 && codes.Count.Equals(0)) //分區鍵值初始化
+                {
+                    for (int i = 0; i < schools.Count; i++)
+                    {
+                        codes.Add("Base");
+                    }
+                }
                 ////分开部署,就不需要,一站多用时,取消注释
                 //if ($"{site}".Equals(BIConst.Global))
                 //{
@@ -1046,66 +1079,81 @@ namespace TEAMModelBI.Controllers.BISchool
 
                 foreach (var tempId in schools)
                 {
-                    List<string> scTchIds = new();
-                    List<string> scStuIds = new();
-                    string scTecSql = $"select value(c.id) from c where ARRAY_LENGTH(c.roles) > 0 and c.status = 'join'";
-                    await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<string>(queryText: scTecSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{tempId}") }))
+                    int index = schools.IndexOf(tempId);
+                    string code = codes[index];
+                    //IES5實體學校刪除
+                    if (!code.Equals("VirtualBase"))
                     {
-                        scTchIds.Add(item);
-                    }
-
-                    string scStuSql = $"select value(c.id) from c";
-                    await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Student").GetItemQueryIterator<string>(queryText: scStuSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{tempId}") }))
-                    {
-                        scStuIds.Add(item);
-                    }
+                        List<string> scTchIds = new();
+                        List<string> scStuIds = new();
+                        string scTecSql = $"select value(c.id) from c where ARRAY_LENGTH(c.roles) > 0 and c.status = 'join'";
+                        await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<string>(queryText: scTecSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{tempId}") }))
+                        {
+                            scTchIds.Add(item);
+                        }
 
-                    var response = await cosmosClient.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{tempId}", new PartitionKey($"Base"));
-                    if (response.Status == 204)
-                        msg.AppendLine($"{tmdName}【{tmdId}】删除学校,删除状态:{response.Status},删除ID:{tempId}");
-                    else
-                        delSchoolRels.Add(new DelSchoolRel() { id = $"{tempId}", code = "Base", type = 1, status = response.Status });
+                        string scStuSql = $"select value(c.id) from c";
+                        await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Student").GetItemQueryIterator<string>(queryText: scStuSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{tempId}") }))
+                        {
+                            scStuIds.Add(item);
+                        }
 
-                    //删除学校信息中间
-                    var resBiRel = await cosmosClient.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{tempId}", new PartitionKey($"BIRel"));
-                    if (resBiRel.Status == 204)
-                        msg.AppendLine($"{tmdName}【{tmdId}】删除学校信息中间件,删除状态:{resBiRel.Status},删除ID:{tempId}");
-                    else
-                        delSchoolRels.Add(new DelSchoolRel() { id = $"{tempId}", code = "Base", type = 1, status = response.Status });
+                        var response = await cosmosClient.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{tempId}", new PartitionKey($"Base"));
+                        if (response.Status == 204)
+                            msg.AppendLine($"{tmdName}【{tmdId}】删除学校,删除状态:{response.Status},删除ID:{tempId}");
+                        else
+                            delSchoolRels.Add(new DelSchoolRel() { id = $"{tempId}", code = "Base", type = 1, status = response.Status });
 
-                    foreach (var item in scTchIds)
-                    {
-                        //学校教师信息
-                        var tchRespnse = await cosmosClient.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{item}", new PartitionKey($"Teacher-{tempId}"));
-                        if (tchRespnse.Status == 204)
-                            msg.AppendLine($"删除教师,删除状态:{tchRespnse.Status},删除ID:{item}");
+                        //删除学校信息中间
+                        var resBiRel = await cosmosClient.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{tempId}", new PartitionKey($"BIRel"));
+                        if (resBiRel.Status == 204)
+                            msg.AppendLine($"{tmdName}【{tmdId}】删除学校信息中间件,删除状态:{resBiRel.Status},删除ID:{tempId}");
                         else
-                            delSchoolRels.Add(new DelSchoolRel() { id = $"{item}", code = $"Teacher-{tempId}", type = 2, status = response.Status });
+                            delSchoolRels.Add(new DelSchoolRel() { id = $"{tempId}", code = "Base", type = 1, status = response.Status });
 
-                        //教师基础信息
-                        var tchBaseResponse = await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync($"{item}", new PartitionKey("Base"));
-                        if (tchBaseResponse.Status == 200)
+                        foreach (var item in scTchIds)
                         {
-                            using var json = await JsonDocument.ParseAsync(tchBaseResponse.ContentStream);
-                            Teacher teacher = json.ToObject<Teacher>();
-                            var tempSc = teacher.schools.Find(f => f.schoolId.Equals($"{tempId}"));
-                            if (tempSc != null)
+                            //学校教师信息
+                            var tchRespnse = await cosmosClient.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{item}", new PartitionKey($"Teacher-{tempId}"));
+                            if (tchRespnse.Status == 204)
+                                msg.AppendLine($"删除教师,删除状态:{tchRespnse.Status},删除ID:{item}");
+                            else
+                                delSchoolRels.Add(new DelSchoolRel() { id = $"{item}", code = $"Teacher-{tempId}", type = 2, status = response.Status });
+
+                            //教师基础信息
+                            var tchBaseResponse = await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReadItemStreamAsync($"{item}", new PartitionKey("Base"));
+                            if (tchBaseResponse.Status == 200)
                             {
-                                teacher.schools.Remove(tempSc);
-                                await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, teacher.id, new PartitionKey("Base"));
+                                using var json = await JsonDocument.ParseAsync(tchBaseResponse.ContentStream);
+                                Teacher teacher = json.ToObject<Teacher>();
+                                var tempSc = teacher.schools.Find(f => f.schoolId.Equals($"{tempId}"));
+                                if (tempSc != null)
+                                {
+                                    teacher.schools.Remove(tempSc);
+                                    await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, teacher.id, new PartitionKey("Base"));
+                                }
                             }
+                            else
+                                delSchoolRels.Add(new DelSchoolRel { id = $"{item}", code = "Base", type = 2, status = response.Status });
+                        }
+                        //删除学校学生
+                        foreach (var item in scStuIds)
+                        {
+                            var stuRespnse = await cosmosClient.GetContainer("TEAMModelOS", "Student").DeleteItemStreamAsync($"{item}", new PartitionKey($"Base-{tempId}"));
+                            if (stuRespnse.Status == 204)
+                                msg.AppendLine($"删除学生,删除状态:{stuRespnse.Status},删除ID:{item}");
+                            else
+                                delSchoolRels.Add(new DelSchoolRel() { id = $"{item}", code = $"Base-{tempId}", type = 3, status = response.Status });
                         }
-                        else
-                            delSchoolRels.Add(new DelSchoolRel { id = $"{item}", code = "Base", type = 2, status = response.Status });
                     }
-                    //删除学校学生
-                    foreach (var item in scStuIds)
+                    //虛擬學校刪除
+                    else
                     {
-                        var stuRespnse = await cosmosClient.GetContainer("TEAMModelOS", "Student").DeleteItemStreamAsync($"{item}", new PartitionKey($"Base-{tempId}"));
-                        if (stuRespnse.Status == 204)
-                            msg.AppendLine($"删除学生,删除状态:{stuRespnse.Status},删除ID:{item}");
+                        var response = await cosmosClient.GetContainer("TEAMModelOS", "School").DeleteItemStreamAsync($"{tempId}", new PartitionKey($"VirtualBase"));
+                        if (response.Status == 204)
+                            msg.AppendLine($"{tmdName}【{tmdId}】删除学校,删除状态:{response.Status},删除ID:{tempId}");
                         else
-                            delSchoolRels.Add(new DelSchoolRel() { id = $"{item}", code = $"Base-{tempId}", type = 3, status = response.Status });
+                            delSchoolRels.Add(new DelSchoolRel() { id = $"{tempId}", code = "VirtualBase", type = 1, status = response.Status });
                     }
                 }
 
@@ -2389,6 +2437,7 @@ namespace TEAMModelBI.Controllers.BISchool
         /// </summary>
         public record NotAreaSchool
         {
+            public string code { get; set; }
             public string id { get; set; }
             public string name { get; set; }
             public string schoolCode { get; set; }

+ 36 - 10
TEAMModelBI/Controllers/Census/SchoolController.cs

@@ -1386,6 +1386,7 @@ namespace TEAMModelBI.Controllers.Census
             jsonElement.TryGetProperty("tmdId", out JsonElement tmdId);
             jsonElement.TryGetProperty("areaId", out JsonElement areaId);
 
+            //IES5實體學校
             StringBuilder sql = new($"select value(c) from c");
 
             if (!string.IsNullOrEmpty($"{role}") && !string.IsNullOrEmpty($"{tmdId}") && string.IsNullOrEmpty($"{areaId}"))
@@ -1414,16 +1415,41 @@ namespace TEAMModelBI.Controllers.Census
                 scInfos.Add(item);
             }
 
-            //虛擬學校 [需與學校修正做配套,先封印]
-            //StringBuilder sqlV = new($"select value(c) from c");
-            //if (!string.IsNullOrEmpty($"{areaId}") && !string.IsNullOrEmpty($"{role}"))
-            //{
-            //    sqlV.Append($" where c.areaId ='{areaId}'");
-            //}
-            //await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<BIRelation>(queryText: sqlV.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("VirtualBase") }))
-            //{
-            //    scInfos.Add(item);
-            //}
+            //虛擬學校
+            ////取得虛擬學校學區名稱
+            List<string> vrAreaIdList = new List<string>();
+            StringBuilder sqlVrArea = new($"SELECT DISTINCT VALUE c.areaId FROM c WHERE IS_DEFINED(c.areaId) AND NOT IS_NULL(c.areaId) AND c.areaId != ''");
+            await foreach (string item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<string>(queryText: sqlVrArea.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("VirtualBase") }))
+            {
+                vrAreaIdList.Add(item);
+            }
+            Dictionary<string, string> areaDic = new Dictionary<string, string>();
+            string vrAreaListStr = JsonSerializer.Serialize(vrAreaIdList);
+            StringBuilder sqlArea = new($"SELECT c.id, c.name FROM c WHERE ARRAY_CONTAINS({vrAreaListStr}, c.id, true)");
+            await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "Normal").GetItemQueryStreamIterator(queryText: sqlArea.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base-Area") }))
+            {
+                var json = await JsonDocument.ParseAsync(item.ContentStream);
+                foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
+                {
+                    string arId = obj.GetProperty("id").GetString();
+                    string arName = obj.GetProperty("name").GetString();
+                    areaDic.Add(arId, arName);
+                }
+            }
+            //取得虛擬學校資料
+            StringBuilder sqlV = new($"select value(c) from c");
+            if (!string.IsNullOrEmpty($"{areaId}") && !string.IsNullOrEmpty($"{role}"))
+            {
+                sqlV.Append($" where c.areaId ='{areaId}'");
+            }
+            await foreach (var item in cosmosClient.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<BIRelation>(queryText: sqlV.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("VirtualBase") }))
+            {
+                if(!string.IsNullOrWhiteSpace(item.areaId) && areaDic.ContainsKey(item.areaId))
+                {
+                    item.areaName = areaDic[item.areaId];
+                }
+                scInfos.Add(item);
+            }
 
             return Ok(new { state = RespondCode.Ok, allCnt = scInfos.Count, scInfos });
         }

+ 3 - 3
TEAMModelBI/TEAMModelBI.csproj

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

+ 4 - 4
TEAMModelOS.FunctionV4/CosmosDB/TriggerArt.cs

@@ -353,7 +353,7 @@ namespace TEAMModelOS.FunctionV4.CosmosDB
                                 var artResponse = await client.GetContainer(Constant.TEAMModelOS, "Normal").ReadItemStreamAsync($"{scInfo.areaId}", partitionKey: new Azure.Cosmos.PartitionKey("ArtSetting"));
                                 if (response.Status == 200)
                                 {
-                                    using var json = await JsonDocument.ParseAsync(response.ContentStream);
+                                    using var json = await JsonDocument.ParseAsync(artResponse.ContentStream);
                                     setting = json.ToObject<ArtSetting>();
                                 }
                             }
@@ -402,11 +402,11 @@ namespace TEAMModelOS.FunctionV4.CosmosDB
                                             }).Sum(n => n.real) * c.percent * 0.01
                                         }).Sum(n => n.real + n.score) * x.percent * 0.01
                                     });
-                                    double realScore = Math.Round((double)quotaPercent.Sum(c => c.score), 2);                                   
-                                    sc.score = Math.Round(realScore);
+                                    double realScore = Math.Round((double)quotaPercent.Sum(c => c.score),2);                                   
+                                    sc.score = realScore;
                                     //}
                                 }
-                                rs.totalScore = rs.subjectScores.Where(m => m.score >= 0).Sum(z => z.score);
+                                rs.totalScore = Math.Round(rs.subjectScores.Where(m => m.score >= 0).Sum(z => z.score),2);
                                 tasks.Add(client.GetContainer(Constant.TEAMModelOS, Constant.Student).ReplaceItemAsync(rs, rs.id, new PartitionKey(rs.code)));
                                 //}
                             }

+ 1 - 4
TEAMModelOS.SDK/DI/AzureCosmos/AzureCosmosFactory.cs

@@ -10,20 +10,18 @@ namespace TEAMModelOS.SDK.DI
     {
         private readonly IServiceProvider _services;
         private readonly IOptionsMonitor<AzureCosmosFactoryOptions> _optionsMonitor;
-        private readonly ILogger _logger;
         //private Option _option;
         private ConcurrentDictionary<string, CosmosClient> CosmosClients { get; } = new ConcurrentDictionary<string, CosmosClient>();
         
         //   private CosmosDatabase database { get; set; }
         
-        public AzureCosmosFactory(IServiceProvider services, IOptionsMonitor<AzureCosmosFactoryOptions> optionsMonitor, ILogger<AzureCosmosFactory> logger)
+        public AzureCosmosFactory(IServiceProvider services, IOptionsMonitor<AzureCosmosFactoryOptions> optionsMonitor )
         {
             if (services == null) throw new ArgumentNullException(nameof(services));
             if (optionsMonitor == null) throw new ArgumentNullException(nameof(optionsMonitor));
 
             _services = services;
             _optionsMonitor = optionsMonitor;
-            _logger = logger;  
         }
 
         /// <summary>
@@ -43,7 +41,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (Exception e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }

+ 1 - 9
TEAMModelOS.SDK/DI/AzureStorage/AzureStorageFactory.cs

@@ -23,15 +23,13 @@ namespace TEAMModelOS.SDK.DI
     {
         private readonly IServiceProvider _services;
         private readonly IOptionsMonitor<AzureStorageFactoryOptions> _optionsMonitor;
-        private readonly ILogger _logger;
-        public AzureStorageFactory(IServiceProvider services, IOptionsMonitor<AzureStorageFactoryOptions> optionsMonitor, ILogger<AzureStorageFactory> logger)
+        public AzureStorageFactory(IServiceProvider services, IOptionsMonitor<AzureStorageFactoryOptions> optionsMonitor)
         {
             if (services == null) throw new ArgumentNullException(nameof(services));
             if (optionsMonitor == null) throw new ArgumentNullException(nameof(optionsMonitor));
 
             _services = services;
             _optionsMonitor = optionsMonitor;
-            _logger = logger;
         }
 
         public BlobServiceClient GetBlobServiceClient(string name = "Default")
@@ -43,7 +41,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
 
@@ -58,7 +55,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -73,7 +69,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -321,7 +316,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -342,7 +336,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -363,7 +356,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }

+ 146 - 0
TEAMModelOS.SDK/Extension/HttpContextExtensions.cs

@@ -1,7 +1,12 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
+using System.Linq;
 using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
 using Microsoft.Extensions.Primitives;
 
 namespace TEAMModelOS.SDK.Extension
@@ -15,7 +20,148 @@ namespace TEAMModelOS.SDK.Extension
         {
             return httpContext.Request.Headers["Authorization"].ToString();
         }
+        /// <summary>
+        /// 获取 Action 特性
+        /// </summary>
+        /// <typeparam name="TAttribute"></typeparam>
+        /// <param name="httpContext"></param>
+        /// <returns></returns>
+        public static TAttribute GetMetadata<TAttribute>(this HttpContext httpContext)
+            where TAttribute : class
+        {
+            return httpContext.GetEndpoint()?.Metadata?.GetMetadata<TAttribute>();
+        }
+        /// <summary>
+        /// 获取 控制器/Action 描述器
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <returns></returns>
+        public static ControllerActionDescriptor GetControllerActionDescriptor(this HttpContext httpContext)
+        {
+            return httpContext.GetEndpoint()?.Metadata?.FirstOrDefault(u => u is ControllerActionDescriptor) as ControllerActionDescriptor;
+        }
+        /// <summary>
+        /// 获取本机 IPv4地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetLocalIpAddressToIPv4(this HttpContext context)
+        {
+            return context.Connection.LocalIpAddress?.MapToIPv4()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取本机 IPv6地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetLocalIpAddressToIPv6(this HttpContext context)
+        {
+            return context.Connection.LocalIpAddress?.MapToIPv6()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取远程 IPv4地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetRemoteIpAddressToIPv4(this HttpContext context)
+        {
+            return context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取远程 IPv6地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetRemoteIpAddressToIPv6(this HttpContext context)
+        {
+            return context.Connection.RemoteIpAddress?.MapToIPv6()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取完整请求地址
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        public static string GetRequestUrlAddress(this HttpRequest request)
+        {
+            return new StringBuilder()
+                    .Append(request.Scheme)
+                    .Append("://")
+                    .Append(request.Host)
+                    .Append(request.PathBase)
+                    .Append(request.Path)
+                    .Append(request.QueryString)
+                    .ToString();
+        }
+
+        /// <summary>
+        /// 获取来源地址
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="refererHeaderKey"></param>
+        /// <returns></returns>
+        public static string GetRefererUrlAddress(this HttpRequest request, string refererHeaderKey = "Referer")
+        {
+            return request.Headers[refererHeaderKey].ToString();
+        }
 
+        /// <summary>
+        /// 读取 Body 内容
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
+        /// <returns></returns>
+        public static async Task<string> ReadBodyContentAsync(this HttpContext httpContext)
+        {
+            if (httpContext == null) return default;
+            return await httpContext.Request.ReadBodyContentAsync();
+        }
+
+        /// <summary>
+        /// 读取 Body 内容
+        /// </summary>
+        /// <param name="request"></param>
+        /// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
+        /// <returns></returns>
+        public static async Task<string> ReadBodyContentAsync(this HttpRequest request)
+        {
+            request.Body.Seek(0, SeekOrigin.Begin);
+
+            using var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true);
+            var body = await reader.ReadToEndAsync();
+
+            // 回到顶部,解决此类问题 https://gitee.com/dotnetchina/Furion/issues/I6NX9E
+            request.Body.Seek(0, SeekOrigin.Begin);
+            return body;
+        }
+
+      
+        /// <summary>
+        /// 判断是否是 WebSocket 请求
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static bool IsWebSocketRequest(this HttpContext context)
+        {
+            return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
+        }
+        /// <summary>
+        /// 设置响应头 Tokens
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <param name="accessToken"></param>
+        /// <param name="refreshToken"></param>
+        public static void SetTokensOfResponseHeaders(this HttpContext httpContext, string accessToken, string refreshToken = null)
+        {
+            httpContext.Response.Headers["access-token"] = accessToken;
+            if (!string.IsNullOrWhiteSpace(refreshToken))
+            {
+                httpContext.Response.Headers["x-access-token"] = refreshToken;
+            }
+        }
         /// <summary>
         /// 取得JWT驗證金鑰,Authorization Bearer
         /// </summary>

+ 8 - 0
TEAMModelOS.SDK/Models/Cosmos/School/School.cs

@@ -163,4 +163,12 @@ namespace TEAMModelOS.SDK.Models
         /// </summary>
         public int record { get; set; } = 1;
     }
+
+    /// <summary>
+    /// 虛擬學校
+    /// </summary>
+    public class VirtualBase : School
+    {
+        public string areaName { get; set; }
+    } 
 }

+ 2 - 2
TEAMModelOS.SDK/Models/Service/BI/BIProdAnalysis.cs

@@ -607,7 +607,7 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                             {
                                 foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
                                 {
-                                    School crtVSchool = new School();
+                                    VirtualBase crtVSchool = new VirtualBase();
                                     crtVSchool.code = "VirtualBase";
                                     crtVSchool.id = obj.GetProperty("shortCode").GetString();
                                     crtVSchool.pk = "School";
@@ -619,7 +619,7 @@ namespace TEAMModelOS.SDK.Models.Service.BI
                                     crtVSchool.dist = (obj.TryGetProperty("distName", out JsonElement distNameJ)) ? Convert.ToString(distNameJ) : null;
                                     crtVSchool.address = (obj.TryGetProperty("address", out JsonElement addressJ)) ? Convert.ToString(addressJ) : null;
                                     crtVSchool.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-                                    await _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "School").CreateItemAsync<School>(crtVSchool);
+                                    await _azureCosmosClient.GetContainer(Constant.TEAMModelOS, "School").CreateItemAsync<VirtualBase>(crtVSchool);
                                 }
                             }
                         }

+ 44 - 38
TEAMModelOS.SDK/Models/Service/BI/BISchoolService.cs

@@ -19,47 +19,12 @@ namespace TEAMModelOS.SDK.Models.Service.BI
         /// <param name="server"></param>
         /// <param name="id"></param>
         /// <returns></returns>
-        public static async Task UpSchoolEdition(CosmosClient cosmosClient,  DingDing _dingDing, List<string> server, string id)
+        public static async Task UpSchoolEdition(CosmosClient cosmosClient,  DingDing _dingDing, List<string> services, string id)
         {
             try
             {
                 School school = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<School>(id, new PartitionKey("Base"));
-                int edition = 0;
-                if (school.size <= 100 && school.scale == 0)
-                {
-                    edition = 1; 
-                }
-                else if (school.size >= 300 && school.scale >= 500 && school.scale<100 && server.Contains("YMPCVCIM"))
-                { 
-                    edition = 2;
-                }
-                else if (school.size >= 300 && school.scale >= 1000 && server.Contains("YMPCVCIM") && server.Count > 2)
-                { 
-                    edition = 3; 
-                }
-
-                if (edition == 0)
-                {
-                    edition = 1;
-                }
-                if (school.edition != null)
-                {
-                    school.edition.current = edition;
-                    if (school.edition.record < edition)
-                    {
-                        school.edition.record = edition;
-                    }
-                }
-                else
-                {
-                    Edition tempEdition = new()
-                    {
-                        current = edition,
-                        record = edition
-                    };
-
-                    school.edition = tempEdition;
-                }
+                school.edition = calSchoolEdition(school, services);
                 await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<School>(school, id, new PartitionKey("Base"));
                 BIRelation biRel = await cosmosClient.GetContainer(Constant.TEAMModelOS, "School").ReadItemAsync<BIRelation>(id, new PartitionKey("BIRel"));
                 biRel.edition = school.edition;
@@ -67,10 +32,51 @@ namespace TEAMModelOS.SDK.Models.Service.BI
             }
             catch (Exception ex)
             {
-                _ = _dingDing.SendBotMsg($"BI,{Environment.GetEnvironmentVariable("Option:Location")},UpSchoolEdition() 服务列表:{server},学校id:{id}\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.成都开发測試群組);
+                _ = _dingDing.SendBotMsg($"BI,{Environment.GetEnvironmentVariable("Option:Location")},UpSchoolEdition() 服务列表:{services},学校id:{id}\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.成都开发測試群組);
             }
         }
+        /// <summary>
+        /// 計算學校版本公式
+        /// </summary>
+        public static Edition calSchoolEdition(School school, List<string> services)
+        {
+            int edition = 0;
+            if (school.size <= 100 && school.scale == 0)
+            {
+                edition = 1;
+            }
+            else if (school.size >= 300 && school.scale >= 500 && school.scale < 100 && services.Contains("YMPCVCIM"))
+            {
+                edition = 2;
+            }
+            else if (school.size >= 300 && school.scale >= 1000 && services.Contains("YMPCVCIM") && services.Count > 2)
+            {
+                edition = 3;
+            }
 
+            if (edition == 0)
+            {
+                edition = 1;
+            }
+            if (school.edition != null)
+            {
+                school.edition.current = edition;
+                if (school.edition.record < edition)
+                {
+                    school.edition.record = edition;
+                }
+            }
+            else
+            {
+                Edition tempEdition = new()
+                {
+                    current = edition,
+                    record = edition
+                };
 
+                school.edition = tempEdition;
+            }
+            return school.edition;
+        }
     }
 }

+ 62 - 59
TEAMModelOS/ClientApp/public/lang/en-US.js

@@ -62,10 +62,10 @@ const LANG_EN_US = {
         score: 'Evaluation Results',
         time: '評論時間',
         header1: 'Name',
-        header2: '總學時',
-        header3: '線上研修學時',
-        header4: '未修滿能力點',
-        header5: '已修滿的能力點數量',
+        header2: 'Total Study Length',
+        header3: "Online Study's  Study Length",
+        header4: 'Unfinished Competency',
+        header5: 'No. of Competency Finished',
         header6: '認證材料學時',
         header7: '校本研修學時',
         header8: '課堂實錄學時',
@@ -6750,38 +6750,38 @@ const LANG_EN_US = {
     },
     //研修
     td: {
-        td1: '研修統計',
-        td2: '學時統計',
+        td1: 'Study Statistics',
+        td2: 'Study Length Statistics',
         td3: 'Total Study Length:',
         td4: 'minutes',
         td5: 'Study Length',
         td6: 'Not Started:',
-        td7: '進行中:',
-        td8: '已完成:',
-        td9: 'Pass Rate',
-        td10: '學校概覽',
-        td11: '學校列表',
-        td12: '導出學校數據',
-        td13: '導出教師數據',
-        td14: '搜索學校',
+        td7: 'In progress:',
+        td8: 'Completed:',
+        td9: 'Qualified Rate',
+        td10: 'School Overview',
+        td11: 'School List',
+        td12: 'Export School Data',
+        td13: 'Export Teacher Data',
+        td14: 'Search',
         td15: 'No. of participants',
-        td16: '詳情>>>',
-        td17: '未搜索到學校',
-        td18: '暫無學校數據',
-        td19: '學校數量',
+        td16: 'Details>>>',
+        td17: 'No school found',
+        td18: 'No school data yet',
+        td19: 'School No.',
         td20: 'Total Study Length',
-        td21: '合格人數',
+        td21: 'No. of Qualified People',
         td22: 'Online Study',
         td23: 'School Study',
         td24: 'Certified Material',
         td25: 'Lesson Recording',
         td26: '未修滿',
         td27: '能力點',
-        td28: '學校',
-        td29: '姓名',
-        td30: '線上研修學時',
-        td31: '所選微能力點',
-        td32: '已修滿的能力點數量',
+        td28: 'School',
+        td29: 'Name',
+        td30: "Online Study's  Study Length",
+        td31: 'Selected Competency',
+        td32: 'No. of Competency Finished',
         td33: '認證材料學時',
         td34: '校本研修學時',
         td35: '課堂實錄學時',
@@ -6795,15 +6795,15 @@ const LANG_EN_US = {
         td43: '已提交',
         td44: '未提交',
         td45: '研修情況統計表',
-        td46: '未開始人數',
-        td47: '進行中人數',
-        td48: '已完成人數',
-        td49: '參訓總人數',
-        td50: '學校數據統計',
-        td51: '資源列表',
-        td52: '添加資源',
-        td53: '完成率:',
-        td54: '返回區級',
+        td46: 'No. of people not started',
+        td47: 'No. of people in progress',
+        td48: 'No. of people completed',
+        td49: 'Total number of participants',
+        td50: 'School Data Statistics',
+        td51: 'Resource List',
+        td52: 'Add Resources',
+        td53: 'Completed Rate:',
+        td54: 'Click to go back',
         td55: '所有學校',
         td56: '共建',
         td57: '個試點校,參訓人數',
@@ -6814,15 +6814,15 @@ const LANG_EN_US = {
         td62: 'Description',
         td63: '請選擇作業附件上傳類型',
         td64: '簽到成功',
-        td65: '數據概覽',
+        td65: 'Data Overview',
         td66: '的',
         td67: '查詢簽到數據失敗',
         td68: '獲取作業提交數據失敗',
-        td69: 'Pass Rate Statistics',
+        td69: 'Qualified Rate Statistics',
         td70: 'Competency Type Elective Ratio',
         td71: 'Competency Elective No.',
-        td72: '研修小組統計',
-        td73: '暫無小組數據',
+        td72: 'Study Team Statistics',
+        td73: 'No team data yet',
         td74: 'Not grouped',
         td75: 'View Member',
         td76: 'completed online study',
@@ -6867,12 +6867,12 @@ const LANG_EN_US = {
         td115: '簽到失敗',
         td116: '溫馨提示:區級活動不計入校本研修學時',
         td117: '未發布',
-        td118: '進行中',
+        td118: 'In progress',
         td119: '已結束',
         td120: '活動視頻:',
         td121: '播放',
-        td122: '刪除',
-        td123: '上傳',
+        td122: 'Delete',
+        td123: 'Upload',
         td124: '(視頻只能上傳一個)',
         td125: '活動資料:',
         td126: '繼續上傳',
@@ -6884,8 +6884,8 @@ const LANG_EN_US = {
         td132: '繼續挑選(已選',
         td133: '位)',
         td134: '發布活動',
-        td135: '上傳中',
-        td136: '上傳完成',
+        td135: 'Uploading',
+        td136: 'Upload completed',
         td137: 'Add Teacher',
         td138: 'Team Team',
         td139: 'Filter',
@@ -6894,8 +6894,8 @@ const LANG_EN_US = {
         td142: '請勾選參加活動的老師',
         td143: '刪除文件',
         td144: '是否確認刪除',
-        td145: '刪除成功',
-        td146: '刪除失敗',
+        td145: 'Delete successfully',
+        td146: 'Failed to delete',
         td147: '文件上傳中,請等待文件上傳完成',
         td148: '活動視頻只支持mp4文件格式',
         td149: '學校空間已滿',
@@ -6950,13 +6950,13 @@ const LANG_EN_US = {
         td198: 'Failed to remove',
         td199: 'Remove Teacher',
         td200: 'Are you sure you want to remove ',
-        td201: '研修名單',
+        td201: 'Study List',
         td202: '添加成功',
         td203: '保存研修名單失敗',
         td204: '查詢研修名單失敗',
-        td205: '數據更新中,當前數據更新於',
-        td206: ',如需查看最新數據請稍後。',
-        td207: '當前已是最新數據,更新於'
+        td205: 'The data is being updated and the current data were updated in ',
+        td206: '. Please check the latest data later.',
+        td207: 'The current data is the latest, updated on '
     },
     // 单位管理
     unit: {
@@ -7262,7 +7262,7 @@ const LANG_EN_US = {
             size: 'Space Usage',
             list: 'School List',
             details: 'Details',
-            backarea: 'back area',
+            backarea: 'Click to go back',
         },
         header: {
             areaTilte: 'School',
@@ -7270,8 +7270,8 @@ const LANG_EN_US = {
             areaStudent: 'Student',
             areaData: 'Total Output Data',
             areaSize: 'Total Space',
-            weekAdd: 'Weekly Increment',
-            monthAdd: 'Monthly Increment',
+            weekAdd: 'Weekly',
+            monthAdd: 'Monthly',
         },
         basics: {
             weekClass: 'Lesson Record (Week)',
@@ -7289,12 +7289,12 @@ const LANG_EN_US = {
             basicsV: 'Basic Version',
             standardV: 'Standard Version',
             marjorV: 'Advanced Version',
-            activityP: 'Activity percentage',
-            versionsP: 'Versions percentage',
-            schoolP: 'School data percentage',
-            sizeP: 'Size percentage',
-            scheduleP: 'Progress stage',
-            subjectP: 'Subject percentage'
+            activityP: 'Activity Percentage',
+            versionsP: 'Version Percentage',
+            schoolP: 'School Percentage',
+            sizeP: 'Space Percentage',
+            scheduleP: 'Progress Stage',
+            subjectP: 'Subject Percentage'
         },
         class: {
             total: 'Year Total Data',
@@ -7304,7 +7304,7 @@ const LANG_EN_US = {
         },
         research: {
             accomplish: 'Completed',
-            underneath: 'In progress',
+            underway: 'In progress',
             unfinished: 'Unfinished',
             classroom: 'Lesson Recording',
             online: 'Online Study',
@@ -7327,9 +7327,11 @@ const LANG_EN_US = {
             badge: 'School Logo',
             name: 'Name',
             code: 'School Code',
-        }
+        },
+        notimages: 'No pictures yet',
     },
     schoolStatistics: {
+        title: 'District Overview',
         header: {
             teachNum: 'Teacher',
             studentNum: 'Student',
@@ -7350,6 +7352,7 @@ const LANG_EN_US = {
             contrastYesterday: 'Compare with yesterday',
             contrastLastmonth: 'Compare with last month',
             contrastLastyear: 'Compare with last year',
+            monthOftheyear: 'Total data for each month of this year'
         },
         gradeTitle: 'Grade Distribution',
         subjectTitle: 'Subject Distribution',

+ 7 - 4
TEAMModelOS/ClientApp/public/lang/zh-CN.js

@@ -2740,7 +2740,7 @@ const LANG_ZH_CN = {
             trueAns: '正确',
             falseAns: '错误',
             //Scoring.vue
-            print: '打印报告',
+            print: '生成PDF',
             subjectLabel: '学科:',
             gradeLabel: '年级:',
             classLabel: '班级:',
@@ -7281,8 +7281,8 @@ const LANG_ZH_CN = {
             versionsP: '版本占比',
             schoolP: '学校数据占比',
             sizeP: '空间占比',
-            scheduleP:'进度阶段',
-            subjectP:'科目占比'
+            scheduleP: '进度阶段',
+            subjectP: '科目占比'
         },
         class: {
             total: '今年总数据',
@@ -7315,10 +7315,12 @@ const LANG_ZH_CN = {
             badge: '校徽',
             name: '名称',
             code: '简码',
-        }
+        },
+        notimages: '暂无图片',
     },
     //学校数据统计
     schoolStatistics: {
+        title: '学区总览',
         header: {
             teachNum: '教师数',
             studentNum: '学生数',
@@ -7339,6 +7341,7 @@ const LANG_ZH_CN = {
             contrastYesterday: '昨日对比',
             contrastLastmonth: '上月对比',
             contrastLastyear: '去年对比',
+            monthOftheyear:'全年各月总数据'
         },
         gradeTitle: '年级占比',
         subjectTitle: '科目占比',

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

@@ -2742,7 +2742,7 @@ const LANG_ZH_TW = {
             trueAns: '正確',
             falseAns: '錯誤',
             //Scoring.vue
-            print:'友善列印',
+            print: '友善列印',
             subjectLabel: '學科:',
             gradeLabel: '年級:',
             classLabel: '班級:',
@@ -7249,7 +7249,7 @@ const LANG_ZH_TW = {
             size: '空間使用情況',
             list: '學校列表',
             details: '詳 情',
-            backarea:'返回學區',
+            backarea: '返回學區',
         },
         header: {
             areaTilte: '區內學校',
@@ -7314,9 +7314,11 @@ const LANG_ZH_TW = {
             badge: '校徽',
             name: '名稱',
             code: '簡碼',
-        }
+        },
+        notimages: '暫無圖片',
     },
     schoolStatistics: {
+        title: '學區總覽',
         header: {
             teachNum: '教師數',
             studentNum: '學生數',
@@ -7337,8 +7339,9 @@ const LANG_ZH_TW = {
             contrastYesterday: '昨日對比',
             contrastLastmonth: '上月對比',
             contrastLastyear: '去年對比',
+            monthOftheyear: '全年各月總數據'
         },
-        gradeTitle: '年級比',
-        subjectTitle: '科目比',
+        gradeTitle: '年級比',
+        subjectTitle: '科目比',
     },
 }

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

@@ -57,4 +57,7 @@ export default {
     findAreaArtList: function (data) {
         return post('/school/area/find-area-art', data)
     },
+    findKnoPercents: function (data) {
+        return post('/school/area/get-all-knowledge', data)
+    },
 }

+ 7 - 0
TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.less

@@ -200,6 +200,13 @@
     position: absolute;
     right: 20px;
     top: 8px;
+    display: flex;
+    align-items: center;
+
+    p {
+      margin-right: 10px;
+      color: #01b4ef;
+    }
 
     .ivu-icon {
       font-size: 22px;

+ 313 - 133
TEAMModelOS/ClientApp/src/components/evaluation/ExerciseList.vue

@@ -5,144 +5,170 @@
     <Loading :top="100" v-show="dataLoading" hideMask></Loading>
     <div v-if="exerciseList.length === 0" class="no-data-text">
       <img src="@/assets/icon/no_data_evaluation.png" width="120" />
-      <span style="margin-top: 15px; color: #808080">{{$t('evaluation.noData')}}</span>
-    </div>
-    <div class="cp-content-wrap" ref="mathJaxContainer" v-else>
-      <p style="color: #1b87e6;text-align: end;text-decoration: underline;cursor: pointer;" v-if="!isAnalysis"><span @click="onSelectAll">{{ $t('evaluation.choosePageItems') }}</span></p>
-      <div class="cp-exercise-item" v-for="(item, index) of exerciseList" :key="index">
-        <!-- 题干部分 -->
-        <div class="item-question" @click="onQuestionToggle(index, item.id, $event)">
-          <div>
-            <div class="item-question-order">
-              {{ pageSize * (pageNum - 1) + index + 1 }} :
-            </div>
-            <div class="item-question-text" v-html="item.question"></div>
-          </div>
-          <span class="item-btn-toggle">
-            <Icon :type="collapseList.indexOf(index) > -1 ? 'ios-arrow-dropup' : 'ios-arrow-dropdown' " />
-          </span>
-        </div>
-        <!-- 选项部分 -->
-        <div v-for="(option, optionIndex) in item.option" :key="optionIndex" class="item-options" @click="onQuestionToggle(index, item.id, $event)">
-          <div class="item-option-content">
-            <div class="item-option-order">
-              <!-- {{ String.fromCharCode(64 + parseInt(optionIndex + 1)) }} : -->
-              {{ option.code }} :
-            </div>
-            <div class="item-option-text" v-html="option.value"></div>
-          </div>
-        </div>
-        <div class="exercise-item-children" v-if="item.children.length">
-          <BaseChild :parentIndex="index" :children="item.children" :isShowAnalysis="isShowAnalysis" :analysisJson="getChildAnalysisJson(item)" :optionRate="getChildOptionRate(item)" inBank></BaseChild>
-        </div>
-        <transition name="slide" v-if="item.type !== 'compose'">
-          <div v-if="collapseList.includes(exerciseList.indexOf(item))" class="toggle-area">
-            <div>
-              <!-- 答案展示部分 -->
-              <div class="item-explain">
-                <span class="explain-title">【{{$t('evaluation.answer')}}】</span>
-                <div class="item-explain-details">
-                  <!-- 问答题答案 -->
-                  <div v-if="item.type === 'subjective' || item.type === 'complete' || item.type === 'connector' || item.type === 'correct'">
-                    <span v-for="(answer, index) in item.answer" :key="index" v-html="item.answer.length ? answer : $t('utils.noData')"></span>
-                  </div>
-                  <!-- 问答题答案 -->
-                  <div v-else-if="item.type === 'judge'">
-                    <span>{{ item.answer.length ? (item.answer[0] === 'A' ? $t('evaluation.isTrue') : $t('evaluation.isFalse')) : $t('utils.noData') }}</span>
-                  </div>
-                  <!-- 其余题型答案 -->
-                  <div v-else>
-                    <span :class="[ item.type === 'complete' ? 'item-answer-item' : '' ]" v-for="(answer, index) in item.answer" :key="index">{{ answer }}</span>
-                  </div>
-                </div>
-              </div>
-              <!-- 解析部分 -->
-              <div class="item-explain">
-                <span class="explain-title">【{{$t('evaluation.explain')}}】</span>
-                <div class="item-explain-details">
-                  <span v-html="item.explain || $t('utils.noData')"></span>
-                </div>
-              </div>
-              <!-- 知识点部分 -->
-              <div class="item-explain">
-                <span class="explain-title">【{{$t('evaluation.knowledgePoints')}}】</span>
-                <div class="item-explain-details">
-                  <span v-if="!item.knowledge || !item.knowledge.length">{{ $t('utils.noData') }}</span>
-                  <div v-else>
-                    <span v-for="(point, pointIndex) in item.knowledge" class="item-point-tag" :key="pointIndex">
-                      {{ point }}
-                    </span>
-                  </div>
-                </div>
-              </div>
-              <!-- 认知层次部分 -->
-              <div class="item-explain">
-                <span class="explain-title" :style="{ color:isShowAnalysis ? '#099209' : '#10abe7' }">【{{$t('evaluation.field')}}】</span>
-                <div class="item-explain-details">
-                  <span>{{ exersicesField[item.field - 1] }}</span>
+                  <span style="margin-top: 15px; color: #808080">{{ $t('evaluation.noData') }}</span>
                 </div>
-              </div>
-              <!-- 补救资源部分 -->
-              <div class="item-explain">
-                <span class="explain-title">【{{ $t('evaluation.newExercise.repair') }}】</span>
-                <div class="item-explain-details">
-                  <div v-if="item.repair && item.repair.length" style="display: flex;flex-wrap: wrap;">
-                    <div class="repair-item" v-for="(link,index) in item.repair">
-                      <img :src="$tools.getFileThum(link.type,link.name)" width="20" />
-                      <span class="repair-item-link" @click.stop="onRepairLinkClick(link)">{{ link.name }}</span>
+                <div class="cp-content-wrap" ref="mathJaxContainer" v-else>
+                  <p style="color: #1b87e6;text-align: end;text-decoration: underline;cursor: pointer;" v-if="!isAnalysis"><span
+                      @click="onSelectAll">{{ $t('evaluation.choosePageItems') }}</span></p>
+                  <div class="cp-exercise-item" v-for="(item, index) of exerciseList" :key="index">
+                    <div class="print-info" v-if="paperInfo && (printIndex === index || (isPrintPaper && index === 0))" style="font-size:16px;margin-bottom: 20px;">
+                      <span style="margin-right: 20px;font-size: 18px;">{{ `${$t('evaluation.index.paper')}:${paperInfo.name}` }}</span>
+                      <span style="margin-right: 20px;font-size: 18px;">{{ `${$t('totalAnalysis.echarts_text11')}:${$tools.formatTime(paperInfo.startTime,
+                        'yyyy-MM-dd')}` }}</span>
+                      <span style="margin-right: 20px;font-size: 18px;">{{ `${$t('totalAnalysis.echarts_text12')}:${paperInfo.stuCount}` }}</span>
+                      <span style="margin-right: 20px;font-size: 18px;">{{ `${$t('totalAnalysis.echarts_text13')}:${paperInfo.stuCount - paperInfo.lostStu.length}`
+                      }}</span>
+                      <span style="margin-right: 20px;font-size: 18px;">{{ `${$t('totalAnalysis.echarts_text15')}:${paperInfo.average}` }}</span>
                     </div>
-                  </div>
-                  <span v-else>{{ $t('utils.noData') }}</span>
-                </div>
-              </div>
-              <div class="item-explain" v-if="isShowAnalysis">
-                <span class="explain-title">【{{ $t('totalAnalysis.showAnalysis') }}】</span>
-                <div class="item-explain-details">
-                  <AnalysisItemTable :analysisJson="analysisJson[index]"></AnalysisItemTable>
-                  </br>
-                  <OptionsTable v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'" :options="item.option.map(i => i.code)" :optionRate="optionRate[index]" :answer="item.answer"></OptionsTable>
-                  </br>
-                  <Row>
-                    <Col span="24">
-                    <BaseTestSingleScatter :ids="'singleScatter' + index" :scatterData="scatterData" :currentIndex="index > 8 ? '' + (index + 1) : '0' + (index + 1)"></BaseTestSingleScatter>
-                    </Col>
-                  </Row>
-                  <Row>
-                    <Col span="12" v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'">
-                    <BaseRateLine :ids="'R1R6' + index" :echartsData="getOptionLineData(item,index)"></BaseRateLine>
-                    </Col>
-                    <Col span="12">
-                    <BaseLine :ids="'rateLine' + index" :echartsData="analysisJson[index]"></BaseLine>
-                    </Col>
-                  </Row>
-                </div>
-              </div>
-            </div>
-            <!-- 如果是综合题 则加载子题渲染组件 -->
-            <!-- <div v-else>
+                    <!-- 题干部分 -->
+                    <div class="item-question" @click="onQuestionToggle(index, item.id, $event)">
+                      <div>
+                        <div class="item-question-order">
+                          {{ pageSize * (pageNum - 1) + index + 1 }} :
+                        </div>
+                        <div class="item-question-text" v-html="item.question"></div>
+                      </div>
+                      <span class="item-btn-toggle print-hidden">
+                        <p v-if="collapseList.indexOf(index) > -1" @click.stop="onPrintItem($event, index)">下载本题PDF</p>
+                        <Icon :type="collapseList.indexOf(index) > -1 ? 'ios-arrow-dropup' : 'ios-arrow-dropdown'" />
+                      </span>
+                    </div>
+                    <!-- 选项部分 -->
+                    <div v-for="(option, optionIndex) in item.option" :key="optionIndex" class="item-options"
+                      @click="onQuestionToggle(index, item.id, $event)">
+                      <div class="item-option-content">
+                        <div class="item-option-order">
+                          <!-- {{ String.fromCharCode(64 + parseInt(optionIndex + 1)) }} : -->
+                          {{ option.code }} :
+                        </div>
+                        <div class="item-option-text" v-html="option.value"></div>
+                      </div>
+                    </div>
+                    <div class="exercise-item-children" v-if="item.children.length">
+                      <BaseChild :parentIndex="index" :children="item.children" :isShowAnalysis="isShowAnalysis"
+                        :analysisJson="getChildAnalysisJson(item)" :optionRate="getChildOptionRate(item)" inBank></BaseChild>
+                    </div>
+                    <transition name="slide" v-if="item.type !== 'compose'">
+                      <div v-if="collapseList.includes(exerciseList.indexOf(item))" class="toggle-area">
+                        <div>
+                          <!-- 答案展示部分 -->
+                          <div class="item-explain">
+                            <span class="explain-title">【{{ $t('evaluation.answer') }}】</span>
+                            <div class="item-explain-details">
+                              <!-- 问答题答案 -->
+                              <div
+                                v-if="item.type === 'subjective' || item.type === 'complete' || item.type === 'connector' || item.type === 'correct'">
+                                <span v-for="(answer, index) in item.answer" :key="index"
+                                  v-html="item.answer.length ? answer : $t('utils.noData')"></span>
+                              </div>
+                              <!-- 问答题答案 -->
+                              <div v-else-if="item.type === 'judge'">
+                                <span>{{ item.answer.length ? (item.answer[0] === 'A' ? $t('evaluation.isTrue') :
+                                  $t('evaluation.isFalse')) : $t('utils.noData') }}</span>
+                              </div>
+                              <!-- 其余题型答案 -->
+                              <div v-else>
+                                <span :class="[item.type === 'complete' ? 'item-answer-item' : '']"
+                                  v-for="(answer, index) in item.answer" :key="index">{{ answer }}</span>
+                              </div>
+                            </div>
+                          </div>
+                          <!-- 解析部分 -->
+                          <div class="item-explain">
+                            <span class="explain-title">【{{ $t('evaluation.explain') }}】</span>
+                            <div class="item-explain-details">
+                              <span v-html="item.explain || $t('utils.noData')"></span>
+                            </div>
+                          </div>
+                          <!-- 知识点部分 -->
+                          <div class="item-explain">
+                            <span class="explain-title">【{{ $t('evaluation.knowledgePoints') }}】</span>
+                            <div class="item-explain-details">
+                              <span v-if="!item.knowledge || !item.knowledge.length">{{ $t('utils.noData') }}</span>
+                              <div v-else>
+                                <span v-for="(point, pointIndex) in item.knowledge" class="item-point-tag" :key="pointIndex">
+                                  {{ point }}
+                                </span>
+                              </div>
+                            </div>
+                          </div>
+                          <!-- 认知层次部分 -->
+                          <div class="item-explain">
+                            <span class="explain-title" :style="{ color: isShowAnalysis ? '#099209' : '#10abe7' }">【{{
+                              $t('evaluation.field') }}】</span>
+                            <div class="item-explain-details">
+                              <span>{{ exersicesField[item.field - 1] }}</span>
+                            </div>
+                          </div>
+                          <!-- 补救资源部分 -->
+                          <div class="item-explain">
+                            <span class="explain-title">【{{ $t('evaluation.newExercise.repair') }}】</span>
+                            <div class="item-explain-details">
+                              <div v-if="item.repair && item.repair.length" style="display: flex;flex-wrap: wrap;">
+                                <div class="repair-item" v-for="(link, index) in item.repair">
+                                  <img :src="$tools.getFileThum(link.type, link.name)" width="20" />
+                                  <span class="repair-item-link" @click.stop="onRepairLinkClick(link)">{{ link.name }}</span>
+                                </div>
+                              </div>
+                              <span v-else>{{ $t('utils.noData') }}</span>
+                            </div>
+                          </div>
+                          <div class="item-explain" v-if="isShowAnalysis">
+                            <span class="explain-title">【{{ $t('totalAnalysis.showAnalysis') }}】</span>
+                            <div class="item-explain-details">
+                              <AnalysisItemTable :analysisJson="analysisJson[index]"></AnalysisItemTable>
+                              <br>
+                              <OptionsTable v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'"
+                                :options="item.option.map(i => i.code)" :optionRate="optionRate[index]" :answer="item.answer">
+                              </OptionsTable>
+                              <br>
+                              <Row>
+                                <Col span="24">
+                                <BaseTestSingleScatter :ids="'singleScatter' + index" :scatterData="scatterData"
+                                  :currentIndex="index > 8 ? '' + (index + 1) : '0' + (index + 1)"></BaseTestSingleScatter>
+                                </Col>
+                              </Row>
+                              <Row>
+                                <Col span="12" v-if="item.type === 'single' || item.type === 'multiple' || item.type === 'judge'">
+                                <BaseRateLine :ids="'R1R6' + index" :echartsData="getOptionLineData(item, index)"></BaseRateLine>
+                                </Col>
+                                <Col span="12">
+                                <BaseLine :ids="'rateLine' + index" :echartsData="analysisJson[index]"></BaseLine>
+                                </Col>
+                              </Row>
+                            </div>
+                          </div>
+                        </div>
+                        <!-- 如果是综合题 则加载子题渲染组件 -->
+                        <!-- <div v-else>
 							<BaseChild :children="item.children"></BaseChild>
 						</div> -->
-          </div>
-        </transition>
+                      </div>
+                    </transition>
 
-        <!-- 底部题目操作栏 -->
-        <div class="item-tools">
-          <span class="item-tools-info">{{$t('evaluation.filter.type')}}:{{ exersicesType[item.type] }}</span>
-          <span class="item-tools-info">{{$t('evaluation.filter.diff')}}:{{ exersicesDiff[item.level - 1] }}</span>
-          <!-- <span class="item-tools-info">{{$t('evaluation.filter.level')}}:{{ exersicesField[item.field - 1] }}</span> -->
-          <!-- <span class="item-tools-info">{{$t('evaluation.filter.useCount')}}:{{ item.usageCount || 0 }} {{ $t('unit.text4') }}</span> -->
-          <!-- <span class="item-tools-info">{{$t('evaluation.updateTime')}}:{{ $tools.formatTime(item.createTime)  || 0 }} </span> -->
-          <Button type="info" v-if="!isAnalysis" :style="{backgroundColor:selectList.map(i => i.id).indexOf(item.id) > -1 ? '#bbbbbb' : '#2db7f5'}" @click.stop="onSelectItem(item,index)">{{ selectList.map(i => i.id).indexOf(item.id) > -1 ? $t('evaluation.remove') : $t('evaluation.choose')}}</Button>
-        </div>
-      </div>
-    </div>
+                    <!-- 底部题目操作栏 -->
+                    <div class="item-tools">
+                      <span class="item-tools-info">{{ $t('evaluation.filter.type') }}:{{ exersicesType[item.type] }}</span>
+                      <span class="item-tools-info">{{ $t('evaluation.filter.diff') }}:{{ exersicesDiff[item.level - 1] }}</span>
+                      <!-- <span class="item-tools-info">{{$t('evaluation.filter.level')}}:{{ exersicesField[item.field - 1] }}</span> -->
+                      <!-- <span class="item-tools-info">{{$t('evaluation.filter.useCount')}}:{{ item.usageCount || 0 }} {{ $t('unit.text4') }}</span> -->
+                      <!-- <span class="item-tools-info">{{$t('evaluation.updateTime')}}:{{ $tools.formatTime(item.createTime)  || 0 }} </span> -->
+                      <Button type="info" v-if="!isAnalysis"
+                        :style="{ backgroundColor: selectList.map(i => i.id).indexOf(item.id) > -1 ? '#bbbbbb' : '#2db7f5' }"
+                        @click.stop="onSelectItem(item, index)">{{ selectList.map(i => i.id).indexOf(item.id) > -1 ?
+                          $t('evaluation.remove') : $t('evaluation.choose') }}</Button>
+                    </div>
+                  </div>
+                </div>
 
-    <!-- 底部分页区域 -->
-    <Page :total="totalNum" v-if="!isAnalysis" show-sizer show-total :page-size="pageSize" :current="pageNum" @on-page-size-change="pageSizeChange" @on-change="pageChange" :page-size-opts="[5, 10, 15, 20]" />
-  </div>
+                <!-- 底部分页区域 -->
+                <Page :total="totalNum" v-if="!isAnalysis" show-sizer show-total :page-size="pageSize" :current="pageNum"
+                  @on-page-size-change="pageSizeChange" @on-change="pageChange" :page-size-opts="[5, 10, 15, 20]" />
+          </div>
 </template>
 <script>
-import blobTool from "@/utils/blobTool.js";
+import JsPDF from 'jspdf'
+import domtoimage from '@/utils/dom_to_image';
 import AnalysisItemTable from '@/components/evaluation/AnalysisItemTable'
 import OptionsTable from '@/components/evaluation/OptionsTable'
 import BaseLine from '@/components/student-analysis/total/BaseLine.vue'
@@ -204,6 +230,8 @@ export default {
   },
   data() {
     return {
+      isPrintPaper: false,
+      printIndex: -1,
       examPropScope: null,
       dataLoading: false,
       exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
@@ -220,15 +248,168 @@ export default {
       selectList: [],
       originData: [],
       exerciseList: [],
-      scatterData: []
+      scatterData: [],
+      paperInfo: null
     };
   },
   created() {
     this.pageSize = this.isAnalysis ? 999 : 5
     this.pageChange(1)
     this.getScatterData()
+    this.paperInfo = JSON.parse(localStorage.curExam)
   },
   methods: {
+    onPrintItem(e, index) {
+      this.printIndex = index
+      this.isPrintPaper = false
+      // 获取需要打印预览的DOM节点
+      let printDom = e.srcElement.parentNode.parentNode.parentNode
+      printDom.style.borderColor = 'transparent'
+      Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 0 })
+      domtoimage.toJpeg(printDom, {
+        bgcolor: '#fff'
+      }).then(pageData => {
+        let that = this
+        console.log(pageData)
+        let img = new Image();
+        img.src = pageData;
+        img.onload = async function () {
+          let contentWidth = img.width
+          let contentHeight = img.height
+          // 创建 canvas 并在其上绘制图片
+          const canvas = document.createElement('canvas');
+          canvas.width = img.naturalWidth;
+          canvas.height = img.naturalHeight;
+          let pageHeight = contentWidth / 592.28 * 841.89
+          let leftHeight = contentHeight
+          let position = 25
+          const a4Height = 297
+          const a4Width = 210
+          let imgWidth = a4Width
+          let imgHeight = a4Width / contentWidth * contentHeight
+          const ctx = canvas.getContext('2d');
+          ctx.drawImage(img, 0, 0);
+          // 创建一个新的 PDF 文档
+          const PDF = new JsPDF({
+            orientation: 'p',
+            unit: 'mm',
+            format: 'a4',
+            putOnlyUsedFonts: true
+          })
+          let curPage = 1
+          let headerCoverImg = await that.$tools.getImgsBase64ByUrls(['https://files.catbox.moe/f1gwu0.png'])
+          let headerBase64 = headerCoverImg[0]
+          PDF.addImage(headerBase64, "JPEG", 0, 0, imgWidth, 20);
+          // 如果是一页的情况
+          if (leftHeight <= pageHeight) {
+            PDF.addImage(pageData, 'JPEG', 0, 25, imgWidth,
+              imgHeight)
+          } else {
+            // 如果超出则多页
+            while (leftHeight > 0) {
+              PDF.addImage(pageData, 'JPEG', 0, position,
+                imgWidth, imgHeight)
+              leftHeight -= pageHeight
+              position -= a4Height
+              if (leftHeight > 0) {
+                PDF.addPage()
+              }
+              curPage++
+            }
+          }
+          // 下载 PDF
+          PDF.save(that.paperInfo.name + '-第' + (index + 1) + '题数据.pdf');
+          that.isPrintPaper = false
+          that.printIndex = -1
+          Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 1 })
+        }
+      })
+    },
+    onPrintPaper() {
+      this.printIndex = 0
+      this.isPrintPaper = true
+      this.$Spin.show({
+        render: (h) => {
+          return h('div', [
+            h('Icon', {
+              'class': 'demo-spin-icon-load',
+              props: {
+                type: 'ios-loading',
+                size: 18
+              }
+            }),
+            h('div', '试题数量过多时,请耐心等待一会')
+          ])
+        }
+      });
+      this.collapseList = [...this.exerciseList.keys()];
+      this.$nextTick(() => {
+        // 获取需要打印预览的DOM节点
+        let paperDom = document.getElementsByClassName('cp-content-wrap')[0]
+        Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 0 })
+        domtoimage.toJpeg(paperDom, {
+          bgcolor: '#fff'
+        }).then(pageData => {
+          let that = this
+          console.log(pageData)
+          let img = new Image();
+          img.src = pageData;
+          img.onload = async function () {
+            let contentWidth = img.width
+            let contentHeight = img.height
+            // 创建 canvas 并在其上绘制图片
+            const canvas = document.createElement('canvas');
+            canvas.width = img.naturalWidth;
+            canvas.height = img.naturalHeight;
+            let pageHeight = contentWidth / 592.28 * 841.89
+            let leftHeight = contentHeight
+            let position = 25
+            const a4Height = 297
+            const a4Width = 210
+            let imgWidth = a4Width
+            let imgHeight = a4Width / contentWidth * contentHeight
+            const ctx = canvas.getContext('2d');
+            ctx.drawImage(img, 0, 0);
+            // 创建一个新的 PDF 文档
+            const PDF = new JsPDF({
+              orientation: 'p',
+              unit: 'mm',
+              format: 'a4',
+              putOnlyUsedFonts: true
+            })
+            let curPage = 1
+            let headerCoverImg = await that.$tools.getImgsBase64ByUrls(['https://files.catbox.moe/f1gwu0.png'])
+            let headerBase64 = headerCoverImg[0]
+            PDF.addImage(headerBase64, "JPEG", 0, 0, imgWidth, 20);
+            // 如果是一页的情况
+            if (leftHeight <= pageHeight) {
+              PDF.addImage(pageData, 'JPEG', 0, 25, imgWidth,
+                imgHeight)
+            } else {
+              // 如果超出则多页
+              while (leftHeight > 0) {
+                PDF.addImage(pageData, 'JPEG', 0, position,
+                  imgWidth, imgHeight)
+                leftHeight -= pageHeight
+                position -= a4Height
+                if (leftHeight > 0) {
+                  PDF.addPage()
+                }
+                curPage++
+              }
+            }
+            // 下载 PDF
+            let paperName = JSON.parse(localStorage.curExam).name
+            PDF.save(paperName + '.pdf');
+            that.$Spin.hide()
+            that.isPrintPaper = false
+            that.printIndex = -1
+            Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 1 })
+          }
+        })
+      })
+
+    },
     getScatterData() {
       let analysisJson = JSON.parse(JSON.stringify(this.getAnalysisJson))
       if (!analysisJson) return
@@ -493,8 +674,7 @@ export default {
   }
 };
 </script>
-<style src="./ExerciseList.less" lang="less" scoped>
-</style>
+<style src="./ExerciseList.less" lang="less" scoped></style>
 
 <style>
 .cp-exercise-item .item-question-text img {

二进制
TEAMModelOS/ClientApp/src/components/evaluation/cloudas_title.png


+ 8 - 2
TEAMModelOS/ClientApp/src/components/student-analysis/total/BaseSingleStuScatter.vue

@@ -46,6 +46,12 @@ export default {
       let _this = this
       // 指定图表的配置项和数据
       var option = {
+        title: {
+          text: this.$t('studentWeb.exam.chart.recognizePerformance'),
+          textStyle: {
+            fontSize: "14",
+          }
+        },
         tooltip: {
           trigger: 'item',
           showDelay: 0,
@@ -337,8 +343,8 @@ export default {
 
 <style scoped>
 #singleScatter {
-  width: 1000px;
-  height: 500px;
+  width: 420px;
+  height: 420px;
   margin: 0 auto;
   display: block;
   margin-top: 20px;

+ 3 - 0
TEAMModelOS/ClientApp/src/components/student-web/EventBasicInfo.vue

@@ -131,6 +131,9 @@ export default {
 </script>
 
 <style>
+.event-title {
+    display: block;
+}
 .info-part .base-info-text {
     margin-right: 5px;
 }

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

@@ -46,7 +46,7 @@
             </i-col>
             <!-- 落点区域说明 -->
             <i-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" :xxl="12">
-                <Card style="margin-bottom: 20px;">
+                <!-- <Card style="margin-bottom: 20px;">
                     <div class="scatter-statistics">
                         <div class="scatter-table-line">
                             <span>{{$t('totalAnalysis.sca_text1')}}</span>
@@ -90,10 +90,43 @@
                             <span>{{ chartsData.tableData.filter(item => item.scatter === "C'").length }}</span>
                         </div>
                     </div>
+                </Card> -->
+                <!-- 友善列印调整整合落点区域和题号提示,不单独显示 -->
+                <Card style="margin-bottom: 20px;">
+                    <div style="padding-top: 50px; padding-left: 30px; height: 450px;">
+                        <p style="margin-bottom: 20px;">
+                            {{ $t('totalAnalysis.sca_text1') }}
+                            <br/>
+                            <span style="font-size: 20px; margin-top: 10px; display: inline-block; color: #ff5508;">
+                                {{ chartsData.tableData[chartsData.tableData.length - 1].scatter }}
+                            </span>
+                        </p>
+                        <p style="margin-bottom: 20px;">
+                            {{ $t('totalAnalysis.sca_text2') }}
+                            <br/>
+                            <span style="font-size: 20px; margin-top: 10px; display: inline-block; color: #ff5508;">
+                                {{ getScatterTip(chartsData.tableData[chartsData.tableData.length - 1].scatter) }}
+                            </span>
+                        </p>
+                        <p style="margin-bottom: 20px;">
+                            {{ $t('totalAnalysis.sca_table_text3') }}
+                            <br/>
+                            <span style="font-size: 20px; margin-top: 10px; display: inline-block; color: #ff5508;">
+                                {{ ownData ? ownData.hardList : '-' }}
+                            </span>
+                        </p>
+                        <p style="margin-bottom: 20px;">
+                            {{ $t('totalAnalysis.sca_table_text4') }}
+                            <br/>
+                            <span style="font-size: 20px; margin-top: 10px; display: inline-block; color: #ff5508;">
+                                {{ ownData ? ownData.carefulList : '-' }}
+                            </span>
+                        </p>
+                    </div>
                 </Card>
             </i-col>
         </Row>
-        <Row :gutter="20">
+        <!-- <Row :gutter="20">
             <i-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" :xxl="24">
                 <Card style="margin-bottom: 20px;">
                     <div>
@@ -108,7 +141,7 @@
                     </div>
                 </Card>
             </i-col>
-        </Row>
+        </Row> -->
     </div>
 </template>
 
@@ -156,6 +189,12 @@ export default {
             return data
         },
     },
+    methods: {
+        getScatterTip(scatter) {
+            let arr = ["A", "A'", "B", "B'", "C", "C'"]
+            return this.$t('totalAnalysis.sca_text' + (arr.indexOf(scatter) + 5))
+        },
+    }
 }
 </script>
 

+ 2 - 2
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/LessonTestReport.less

@@ -4,7 +4,7 @@
     .que-item{
         margin-left:10px;
         // max-height:300px;
-        overflow-y:scroll;
+        // overflow-y:scroll;
     }
 
     
@@ -89,7 +89,7 @@
 }
 
 .qcontent {
-    margin-top: 30px;
+    padding-top: 30px;
     list-style-type: none;
     
     .exam-type{

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

@@ -101,7 +101,7 @@
                         {{$t("studentWeb.exam.report.noScore")}}: {{rightAns.noAns}}
                     </Checkbox> -->
                 </CheckboxGroup>
-                <p style="float: right">
+                <p style="float: right" class="print-hidden">
                     <span style="margin-right: 10px">{{ $t("studentWeb.exam.report.wrongPractice") }}:</span>
                     <Dropdown @on-click="itemChange" style="margin-right: 30px;">
                         <a href="javascript:void(0)">
@@ -761,7 +761,9 @@
                     })
                 }
                 if(this.progress === 'finish') {
-                    let subjectId = this.getItemTitle.owner === 'school' ? this.paperInfo.subjectId : this.examInfo.subject.id
+                    // let subjectId = this.getItemTitle.owner === 'school' ? this.paperInfo.subjectId : this.examInfo.subject.id
+                    // 艺术评测的科目id和paperInfo科目id不一致,取examInfo的科目id
+                    let subjectId = this.examInfo.subject.id
                     let indexQ = this.quData ? this.quData.subjects.findIndex(item => item.id === subjectId) : -1
                     if(indexQ != -1 && this.quData.data[indexQ]) {
                         this.quDataSub = this.quData.data[indexQ]

+ 208 - 1
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperView.vue

@@ -1,8 +1,11 @@
 <template>
-    <div class="event-content" id="paperView">
+    <div class="event-content print-me" id="paperView">
         <Loading v-show="isLoad" bgColor="rgba(0, 0, 0, 0.3)"></Loading>
         <vuescroll>
             <EventBasicInfo :info="nowActive" :paper="paperData" />
+            <p v-if="nowActive.type === 'Exam' || nowActive.type === 'Atr'" style="float: right" class="print-hidden">
+                <Button class="stu-print" @click="onPrint">{{ $t('learnActivity.score.print') }}</Button>
+            </p>
             <div class="paper-content">
                 <!--多學科試卷-->
                 <div v-if="paperData.length && nowActive.type === 'Exam'">
@@ -145,6 +148,9 @@
     import StudentScore from "../LessonTestReportCharts/StudentScore";
     import ArtView from './ArtView.vue';
     // import PaperTest from "./PaperTest";
+    import JsPDF from 'jspdf'
+    import domtoimage from '@/utils/dom_to_image';
+    import html2Canvas from 'html2canvas';
     export default {
         name: "PaperView",
         components: {
@@ -206,6 +212,9 @@
                 sasInfo: undefined,
                 previewStatus: false,
                 previewFile: null,
+                canvas: [],
+                canvas2: [],
+                allDom: [],
             };
         },
         methods: {
@@ -677,6 +686,204 @@
                     r(sasInfo)
                 })
             },
+            getBase64Image() {
+                return new Promise((r, j) => {
+                    const imgUpload = new Image()
+                    imgUpload.src = require('./cloudas_title.png')
+                    imgUpload.onload = () => {
+                        if(imgUpload) {
+                            let canvas = document.createElement("canvas");
+                            canvas.width = imgUpload.width;
+                            canvas.height = imgUpload.height;
+                            let ctx = canvas.getContext("2d");
+                            ctx.drawImage(imgUpload, 0, 0, imgUpload.width, imgUpload.height);
+                            let dataURL = canvas.toDataURL("image/png")  // 可选其他值 image/jpeg
+                            r(dataURL)
+                        } else {
+                            j(imgUpload)
+                        }
+                    }
+                })
+            },
+            async onPrint() {
+                let base64 = await this.getBase64Image()
+                // <o:p>word转为html会产生的标签
+                // 过滤掉 HTML 文档中的 <o:p> 标签 从word文档粘贴的会导致生成失败
+                const opElements = Array.from(document.getElementsByTagName('o:p'))
+                opElements.forEach(function (opElement) {
+                    opElement.parentNode.removeChild(opElement);
+                });
+                this.$nextTick(() => {
+                    // 获取需要打印预览的DOM节点
+                    let modalDom = document.getElementsByClassName('print-me')[0].getElementsByClassName('__view')[0]
+                    Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 0 })
+                    Array.from(document.getElementsByClassName('print-me')[0].getElementsByClassName('ivu-icon')).forEach(dom => { dom.style.opacity = 0 })
+                    domtoimage.toJpeg(modalDom, {
+                        bgcolor: '#fff'
+                    }).then(pageData => {
+                        let that = this
+                        console.log(pageData)
+                        let img = new Image();
+                        img.src = pageData;
+                        img.onload = function () {
+                            let contentWidth = img.width
+                            let contentHeight = img.height
+                            // 创建 canvas 并在其上绘制图片
+                            const canvas = document.createElement('canvas');
+                            canvas.width = img.naturalWidth;
+                            canvas.height = img.naturalHeight;
+                            let pageHeight = contentWidth / 592.28 * 841.89
+                            let leftHeight = contentHeight
+                            let position = 0
+                            const a4Height = 297
+                            const a4Width = 210
+                            let imgWidth = a4Width
+                            let imgHeight = a4Width / contentWidth * contentHeight
+                            const ctx = canvas.getContext('2d');
+                            ctx.drawImage(img, 0, 0);
+                            // 创建一个新的 PDF 文档
+                            const PDF = new JsPDF({
+                                orientation: 'p',
+                                unit: 'mm',
+                                format: 'a4',
+                                putOnlyUsedFonts: true
+                            })
+                            let curPage = 1
+                            PDF.addImage(base64, "JPEG", 5, -5, imgWidth - 10, 20);
+                            // 如果是一页的情况
+                            if (leftHeight <= pageHeight) {
+                                PDF.addImage(pageData, 'JPEG', 5, 15, imgWidth - 10, imgHeight)
+                            } else {
+                                // 如果超出则多页
+                                while (leftHeight > 0) {
+                                    PDF.addImage(pageData, 'JPEG', 5, position + 15, imgWidth - 10, imgHeight)
+                                    leftHeight -= pageHeight
+                                    position -= a4Height
+                                    if (leftHeight > 0) {
+                                        PDF.addPage()
+                                    }
+                                    curPage++
+                                }
+                            }
+                            // 下载 PDF
+                            PDF.save(that.nowActive.name + '-' + that.userInfo.sub + '-个人报告.pdf');
+                        }
+                    }).catch(e => {
+                        console.log(e);
+                        this.$Message.error("下载失败")
+                    }).finally(() => {
+                        Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 1 })
+                        Array.from(document.getElementsByClassName('print-me')[0].getElementsByClassName('ivu-icon')).forEach(dom => { dom.style.opacity = 1 })
+                    })
+                })
+                
+            },
+            // 不使用这个,太耗内存
+            async newCanvas() {
+                this.canvas = await this.getCanvas()
+                this.paging()
+            },
+            getCanvas() {
+                return new Promise((resolve, reject) => {
+                    let ele = document.getElementsByClassName('print-me')
+                    const scale = window.devicePixelRatio > 1 ? window.devicePixelRatio : 2
+                    this.toCanvas3(ele)
+                    let promiseArr = []
+                    this.allDom.forEach(children => {
+                        promiseArr.push(new Promise((r, j) => {
+                            html2Canvas(children, {
+                                scale: scale,
+                                dpi: 500,
+                                background: '#fff',
+                            }).then(res => {
+                                // A4宽高:210 * 297
+                                res.imgWidth = 210
+                                res.imgHeight = 210 / res.width * res.height
+                                r(res)
+                            })
+                        }))
+                    })
+                    Promise.all(promiseArr).then(result => {
+                        resolve(result)
+                    }).catch(e => {
+                        console.log(e)
+                        reject(e)
+                    })
+                })
+            },
+            toCanvas3(children) {
+                for (let i = 0; i < children.length; i++) {
+                    // 子元素中需要继续计算dom元素
+                    if(children[i].children.length && children[i].className.includes('keep-on')) {
+                        this.toCanvas3(children[i].children)
+                    } else {
+                        this.allDom.push(children[i])
+                    }
+                }
+            },
+            paging() {
+                const imgArr = [[]];
+                let pageH = 0;// 页面的高度
+                let allH = 0;// 当前组所有dom的高度和
+                let j = 0;
+                for (let k = 0; k < this.canvas.length; k++) { // 涉及到k--的操作,使用for循环方便
+                    pageH += this.canvas[k].imgHeight;
+                    if (pageH > 297 && this.canvas[k].imgHeight < 297) { // 当某个页面装不下下一个dom时,则分页
+                        imgArr[j][0].allH = allH - this.canvas[k].imgHeight;
+                        allH = pageH = 0;
+                        k--;
+                        j++;
+                        imgArr.push([]);
+                    } else {
+                        if (this.canvas[k].imgHeight > 297) { // 特殊情况:某个dom高度大于了页面高度,特殊处理
+                            this.canvas[k].topH = 297 - (pageH - this.canvas[k].imgHeight);// 该dom顶部距离页面上方的距离
+                            pageH = (2 * this.canvas[k].imgHeight - pageH) % 297;
+                            this.canvas[k].pageH = pageH;// 该dom底部距离页面上方的距离
+                        }
+                        imgArr[j].push(this.canvas[k]);
+                        allH += this.canvas[k].imgHeight;
+                    }
+                    if (k === this.canvas.length - 1) imgArr[j][0].allH = allH;
+                }
+                this.canvas2 = imgArr;
+                this.toPdf()
+            },
+            toPdf() {
+                const PDF = new JsPDF({
+                    orientation: 'p',
+                    unit: 'mm',
+                    format: 'a4',
+                    putOnlyUsedFonts: true
+                });
+                this.canvas2.forEach((page, index) => {
+                    let position = 0
+                    let leftHeight = page[0].allH
+                    let pageHeight = page[0].width / 210 * 297
+                    
+                    if (index !== 0 && leftHeight <= pageHeight) PDF.addPage();
+                    page.forEach((item, pIndex) => {
+                        if(item.imgHeight < 297) {
+                            PDF.addImage(item.toDataURL('image/jpeg', 1.0), 'JPEG', 0, position, item.imgWidth, item.imgHeight);
+                            // 小块不足页面高度,直接加进去,同时计算当前页面已经有多高的内容
+                            position += item.imgHeight;
+                            leftHeight -= item.imgHeight
+                        } else {
+                            while(leftHeight > 0) {
+                                position = 0
+                                PDF.addImage(item.toDataURL('image/jpeg', 1.0), 'JPEG', 0, position, item.imgWidth, item.imgHeight);
+                                // 当前图片的总长,已经生成一页,需要减去这一页的高度
+                                leftHeight -= item.imgHeight
+                                // 往下偏移297,也就是展示下一张
+                                position = item.imgHeight
+                                if(leftHeight > 0) PDF.addPage()
+                            }
+                        }
+                    })
+                });
+                PDF.save('-个人报告.pdf');
+            },
+
+            
             /* =====未调用===== */
             hidehint() {
                 this.ishideHint = !this.ishideHint;

二进制
TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/cloudas_title.png


+ 13 - 10
TEAMModelOS/ClientApp/src/view/areaMgmt/AreaIndex.vue

@@ -104,7 +104,7 @@
           <template #badge="{ row }">
             <!--<img :src="row.picture" width="50px" height="50px" />-->
             <img :src="row.picture" class="schoolImg" v-if="row.picture" />
-            <div class="schoolnot" v-else>暂无图片</div>
+            <div class="schoolnot" v-else>{{$t('areaStatistics.notimages')}}</div>
           </template>
           <template #action="{ row, index }">
             <Button type="primary" size="small" style="margin-right: 5px" @click="show(row)">{{$t('areaStatistics.tag.details')}}</Button>
@@ -540,7 +540,7 @@ export default {
             bottom: 50,
             textStyle: {
               color: "#000",
-              fontSize: 20
+              fontSize: 18
             }
           },
           calculable: true,
@@ -549,7 +549,7 @@ export default {
             axisLine: {
               lineStyle: {
                 color: "rgba(204,187,225,0.5)",
-              }
+              },
             },
             splitLine: {
               show: false
@@ -576,13 +576,12 @@ export default {
             height: 10,
             xAxisIndex: [0],
             bottom: 10,
-            "start": 5,
-            "end": 100,
+            "start": 0,
+            "end": 65,
             handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
             handleSize: '100%',
             handleStyle: {
               color: "#ccc",
-
             },
             textStyle: {
               color: "rgba(204,187,225,0.9)",
@@ -598,7 +597,7 @@ export default {
             end: 35
           }],
           series: [{
-            name: this.$t('areaStatistics.basics.vitality'),
+            name: this.$t('areaStatistics.class.vitality'),
             type: "line",
             symbolSize: 10,
             symbol: 'circle',
@@ -635,7 +634,7 @@ export default {
           ],
           tooltip: {
             trigger: 'item',
-            formatter: "{a} <br/>{b}人数 : {c} ({d}%)"
+            formatter: "{a} <br/>{b} : {c} ({d}%)"
           },
           legend: {
             orient: 'vertical',
@@ -805,9 +804,13 @@ export default {
           this.areaData.versions.series[0].data[2].value = majorOneself.length
           //课例活跃度
           let namedata = []
+          let languages = localStorage.getItem('local')
+          console.log(languages, '检查当前语系')
+          let startText = languages === 'en-us' ? 'Week' : languages === 'zh-tw' || languages === 'zh-cn' ? '第' : '第'
+          let endText = languages === 'en-us' ? '' : languages === 'zh-tw' ? '週' : languages === 'zh-cn' ? '周' : '周'
           for (let i in res.weekLess) {
             let num = Number(i) + Number(1)
-            namedata.push('第' + num + '周')
+            namedata.push(startText + num + endText)
           }
           this.areaData.dynamic.xAxis[0].data = namedata
           this.areaData.dynamic.series[0].data = res.weekLess
@@ -1688,7 +1691,7 @@ export default {
 }
 .schoolnot {
   width: 15%;
-  line-height: 50px;
+  line-height: 60px;
   margin: 0 auto;
   background: #95a5a6;
   color: #ecf0f1;

+ 32 - 32
TEAMModelOS/ClientApp/src/view/areaMgmt/AreaLayout.vue

@@ -169,7 +169,7 @@
 <script>
 import { mapGetters } from 'vuex'
 export default {
-  data() {
+  data () {
     return {
       isLock: false,
       openNames: [],
@@ -190,18 +190,18 @@ export default {
   },
 
   methods: {
-    getAreaId(areaId) {
+    getAreaId (areaId) {
       console.log(areaId)
       this.areaId = areaId
     },
     //获取快速登录的code
-    getLoginCode() {
+    getLoginCode () {
       this.isLoading = true
       let idToken = localStorage.getItem('id_token')
       return this.$api.login.getCode(idToken)
     },
     //教师个人跳转苏格拉底
-    toPrivSokrate() {
+    toPrivSokrate () {
       this.getLoginCode().then(
         res => {
           this.loginCode = res.code
@@ -212,7 +212,7 @@ export default {
         this.isLoading = false
       })
     },
-    toActivity() {
+    toActivity () {
       this.getLoginCode().then(
         res => {
           this.loginCode = res.code
@@ -223,7 +223,7 @@ export default {
         this.isLoading = false
       })
     },
-    menuClick(menu) {
+    menuClick (menu) {
       if (menu.router == '#') {
         if (menu.to === 'privSokrate') {
           this.toPrivSokrate()
@@ -232,20 +232,20 @@ export default {
         }
       }
     },
-    mouseOver() {
+    mouseOver () {
       if (!this.isLock) this.isCollapsed = false
 
     },
-    mouseLeave() {
+    mouseLeave () {
       if (!this.isLock) this.isCollapsed = true
     },
-    initMenu() {
+    initMenu () {
 
 
 
 
     },
-    changeMenuStatus() {
+    changeMenuStatus () {
       this.$refs.side1.toggleCollapse()
       this.$EventBus.$emit('onCollapseChange', this.isCollapsed)
     }
@@ -255,20 +255,20 @@ export default {
       curSiteConfig: 'config/getCurSiteConfig',
     }),
     // 教师专业成长
-    teacherMenu() {
+    teacherMenu () {
       return [
         //学区总览
-         {
-             icon: 'iconfont icon-data-count',
-             name: '学区总览',
-             router: '/area/areaIndex',
-             tag: '',
-             role: '',
-             permission: '',
-             menuName: 'areaIndex',
-             child: [],
-             isShow: !this.$jsFn.checkJinNiu() && !this.$jsFn.checkTrain() && this.$store.state.config.srvAdrType != 'product'
-         },
+        {
+          icon: 'iconfont icon-data-count',
+          name: this.$t('schoolStatistics.title'),
+          router: '/area/areaIndex',
+          tag: '',
+          role: '',
+          permission: '',
+          menuName: 'areaIndex',
+          child: [],
+          isShow: !this.$jsFn.checkJinNiu() && !this.$jsFn.checkTrain() && this.$store.state.config.srvAdrType != 'product'
+        },
         // 研修平台
         {
           icon: 'iconfont icon-basic-setting',
@@ -400,7 +400,7 @@ export default {
         },
       ]
     },
-    studentMenu() {
+    studentMenu () {
       return [
         {
           icon: 'iconfont icon-yishu',
@@ -455,7 +455,7 @@ export default {
         }
       ]
     },
-    studyMenu() {
+    studyMenu () {
       return [
         {
           icon: 'iconfont icon-xueqing',
@@ -470,28 +470,28 @@ export default {
         }
       ]
     },
-    teacherMenuTitle() {
+    teacherMenuTitle () {
       if (this.isShowStudentMenu) {
         return this.isCollapsed ? this.$t('area.base.teacher') : this.$t('area.base.teacherFull')
       } else {
         return this.isCollapsed ? this.$t('area.base.area') : this.$t('area.base.areaAll')
       }
     },
-    isShowStudentMenu() {
+    isShowStudentMenu () {
       // let res = (!this.$jsFn.checkJinNiu() && !this.$jsFn.checkTrain() && this.srvAdr == 'China' && ['02944f32-f534-3397-ea56-e6f1fc6c3714', '69e3d413-50a1-4f5e-844a-e0f7c9622ea3'].includes(this.areaId)) || this.$store.state.config.srvAdrType === 'test'
       let res = (!this.$jsFn.checkJinNiu() && !this.$jsFn.checkTrain() && this.srvAdr == 'China') || this.$store.state.config.srvAdrType === 'test'
       console.log('isShowStudentMenu:', res)
       return res
     },
-    isShowArtMenu() {
+    isShowArtMenu () {
       let schoolProfile = JSON.parse(decodeURIComponent(localStorage.school_profile || '{}', "utf-8"))
       let areaArt = schoolProfile.areaShows?.find(item => item.code == this.areaId && item.status === 1)
       return (!!areaArt && !this.$jsFn.checkJinNiu() && !this.$jsFn.checkTrain() && this.srvAdr == 'China') || this.$store.state.config.srvAdrType === 'test'
     },
-    rotateIcon() {
+    rotateIcon () {
       return ["collapse-icon", this.isCollapsed ? "rotate-icon" : ""]
     },
-    schoolStatusInfo() {
+    schoolStatusInfo () {
       if (this.isCollapsed) {
         return ''
       } else {
@@ -509,7 +509,7 @@ export default {
       }
     }
   },
-  created() {
+  created () {
     this.initMenu()
     let cloudSetting = localStorage.getItem('cloudSetting')
     if (cloudSetting) {
@@ -525,7 +525,7 @@ export default {
   },
   watch: {
     $route: {
-      handler(val, oldval) {
+      handler (val, oldval) {
         if (this.teacherMenu.length == 0) {
           this.initMenu()
         }
@@ -549,7 +549,7 @@ export default {
       //立即执行
       immediate: true
     },
-    '$i18n.locale'(n, o) {
+    '$i18n.locale' (n, o) {
       this.initMenu()
     }
   }

+ 4 - 4
TEAMModelOS/ClientApp/src/view/areaMgmt/AreaSchool.vue

@@ -189,7 +189,7 @@ export default {
           //变量则写在options中
           series: [
             {
-              name: '课例',
+              name: this.$t('schoolStatistics.class.lessonTitle'),
               type: 'bar',
               barWidth: '20',
               data: [],
@@ -281,7 +281,7 @@ export default {
           //变量则写在options中
           series: [
             {
-              name: '活动',
+              name: this.$t('schoolStatistics.class.activityTitle'),
               type: 'bar',
               barWidth: '20',
               data: [],
@@ -373,7 +373,7 @@ export default {
           //变量则写在options中
           series: [
             {
-              name: '互动',
+              name: this.$t('schoolStatistics.class.interactionTitle'),
               type: 'bar',
               barWidth: '20',
               data: [],
@@ -661,7 +661,7 @@ export default {
         },
         series: [
           {
-            name: '全年各月总数据',
+            name: this.$t('schoolStatistics.class.monthOftheyear'),
             type: 'line',
             symbolSize: 8,
             markPoint: {

+ 330 - 146
TEAMModelOS/ClientApp/src/view/art/AreaArt.vue

@@ -3,131 +3,144 @@
     <vuescroll ref="art-dasboard">
       <Loading v-show="isLoading"></Loading>
       <back-to-top @on-to-top="backToTop"></back-to-top>
-      <div class="export-box"  @click="exportArtTable">
-        <span class="icon iconfont icon-download" style="margin-right:5px;margin-top: 5px;" :title="`下载艺术评测数据表`"></span>
-        <span>下载数据总表</span>
-      </div>
-      <div class="tab-box" style="padding:0px 20px 5px 20px;">
-        <span class="pane" v-for="item in periodList" style="line-height:30px;padding:2px;" @click="tabClick(item.value)" :class="{ active: periodId === item.value }">
-          {{item.label}}
-        </span>
-        <!-- 艺术活动选择 -->
-        <Select v-model="curAreaArtIndex" @on-change="onAcChange" style="margin:15px 0">
-          <Option v-for="(item,index) in areaArtList" :value="index" :key="index">
-            {{ item.name }}
-          </Option>
-        </Select>
-      </div>
-      <TipsInfo v-if="emptyData" msg="暂无数据"></TipsInfo>
-      <template v-else>
-        <!-- 头部统计 -->
-        <div class="top-block-wrap">
-          <div class="content-con-item border-style" v-for="(item,index) in topData" :key="index">
-            <div class="left-area" :style="{background: item.color, width: '36%'}">
-              <Icon :type="item.icon" class="icon" style="font-size: 40px; color: rgb(255, 255, 255);"></Icon>
-            </div>
-            <div class="right-area" style="width: 64%;">
-              <div>
-                <div class="count-to-wrapper">
-                  <p class="content-outer">
-                    <CountTo :decimals="item.type === 'rate' ? 1 : 0" class="count-to-count-text count-style" :endVal="item.number" :duration="600"></CountTo>
-                    <span style="font-size: 28px;" v-if="item.type === 'rate'">%</span>
-                  </p>
-                </div>
-                <p>{{item.text}}</p>
-              </div>
-            </div>
-          </div>
-        </div>
-        <!-- 艺术素质评测概览 -->
-        <div class="online-train-wrap">
-          <h4 class="block-title">艺术素质评测概览</h4>
-          <div class="chart-data-wrap">
-            <Overall :overall="overallData"></Overall>
-          </div>
-        </div>
-        <!-- 知识点得分统计 -->
-        <div class="online-train-wrap">
-          <h4 class="block-title">知识点得分率(音乐)</h4>
-          <div class="chart-line-wrap">
-            <div class="kng-level-wrap chart-data-wrap">
-              <KngLevel :kngData="musicKn"></KngLevel>
-            </div>
-            <div class="kng-point-wrap border-style chart-data-wrap">
-              <KngPoint :kngData="musicKn"></KngPoint>
-            </div>
-          </div>
-        </div>
-        <div class="online-train-wrap">
-          <h4 class="block-title">知识点得分率(美术)</h4>
-          <div class="chart-line-wrap">
-            <div class="kng-level-wrap chart-data-wrap">
-              <KngLevel :kngData="drawKn"></KngLevel>
-            </div>
-            <div class="kng-point-wrap border-style chart-data-wrap">
-              <KngPoint :kngData="drawKn"></KngPoint>
-            </div>
-          </div>
-        </div>
-        <!-- 评测学校情况对比 -->
-        <div class="online-train-wrap">
-          <h4 class="block-title">学校评测情况(音乐)</h4>
-          <div class="chart-data-wrap">
-            <SchoolComp :schools="musicExam"></SchoolComp>
-          </div>
-        </div>
-        <div class="online-train-wrap">
-          <h4 class="block-title">学校评测情况(美术)</h4>
-          <div class="chart-data-wrap">
-            <SchoolComp :schools="drawExam"></SchoolComp>
-          </div>
-        </div>
-        <!-- 艺术特长获奖情况 -->
-        <div class="online-train-wrap">
-          <h4 class="block-title">艺术特长获奖情况</h4>
-          <div class="chart-line-wrap">
-            <div class="award-chart-wrap chart-data-wrap">
-              <!-- <Award :awardData="musicAward" titleText="音乐赛事艺术获奖情况"></Award> -->
-              <EmptyData textContent="暂无音乐获奖情况数据"></EmptyData>
-            </div>
-            <div class="award-chart-wrap chart-data-wrap">
-              <!-- <Award :awardData="drawAward" titleText="美术赛事艺术获奖情况"></Award> -->
-              <EmptyData textContent="暂无美术获奖情况数据"></EmptyData>
-            </div>
-          </div>
-        </div>
-        <!-- 学校列表 -->
-        <div class="online-train-wrap">
-          <div style="height:30px">
-            <h4 class="block-title">学校列表</h4>
-            <Input v-special-char search placeholder="搜索学校" class="school-search" v-model="keyword" />
-          </div>
-          <div class="school-data-wrap">
-            <div class="school-data-item border-style" v-for="(item,index) in schoolListShow" :key="index">
-              <img class="school-img" :src="item.picture || defImg">
-              <div style="margin-left:10px;height:fit-content;">
-                <p class="school-name" :title="item.name">{{item.name}}<span class="to-school-detail" @click="toSchoolDetail(item)">
-                    详情 >
-                  </span></p>
-                <div style="display:flex;margin-top:10px">
-                  <p class="school-value">
-                    <span class="value">{{ parseInt(item.musicPass * 100) || 0}} <span style="font-size:18px">%</span> </span>
-                    <span>音乐及格率</span>
-                  </p>
-                  <p class="school-value">
-                    <span class="value">{{ parseInt(item.drawPass * 100) || 0}} <span style="font-size:18px">%</span> </span>
-                    <span>美术及格率</span>
-                  </p>
-                </div>
+                      <div class="export-box" @click="exportArtTable">
+                        <span class="icon iconfont icon-download" style="margin-right:5px;margin-top: 5px;" :title="`下载艺术评测数据表`"></span>
+                        <span>下载数据总表</span>
+                      </div>
+                      <div class="tab-box" style="padding:0px 20px 5px 20px;">
+                        <span class="pane" v-for="item in periodList" style="line-height:30px;padding:2px;" @click="tabClick(item.value)"
+                          :class="{ active: periodId === item.value }">
+                          {{ item.label }}
+                        </span>
+                        <!-- 艺术活动选择 -->
+                        <Select v-model="curAreaArtIndex" @on-change="onAcChange" style="margin:15px 0">
+                          <Option v-for="(item, index) in areaArtList" :value="index" :key="index">
+                            {{ item.name }}
+                          </Option>
+                        </Select>
+                      </div>
+                      <TipsInfo v-if="emptyData" msg="暂无数据"></TipsInfo>
+                      <template v-else>
+                        <!-- 头部统计 -->
+                        <div class="top-block-wrap">
+                          <div class="content-con-item border-style" v-for="(item, index) in topData" :key="index">
+                            <div class="left-area" :style="{ background: item.color, width: '36%' }">
+                              <Icon :type="item.icon" class="icon" style="font-size: 40px; color: rgb(255, 255, 255);"></Icon>
+                            </div>
+                            <div class="right-area" style="width: 64%;">
+                              <div>
+                                <div class="count-to-wrapper">
+                                  <p class="content-outer">
+                                    <CountTo :decimals="item.type === 'rate' ? 1 : 0" class="count-to-count-text count-style"
+                                      :endVal="item.number" :duration="600"></CountTo>
+                                    <span style="font-size: 28px;" v-if="item.type === 'rate'">%</span>
+                                  </p>
+                                </div>
+                                <p>{{ item.text }}</p>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                        <!-- 艺术素质评测概览 -->
+                        <div class="online-train-wrap">
+                          <h4 class="block-title">艺术素质评测概览</h4>
+                          <div class="chart-data-wrap">
+                            <Overall :overall="overallData"></Overall>
+                          </div>
+                        </div>
+                        <!-- 知识点得分统计 -->
+                        <div class="online-train-wrap">
+                          <h4 class="block-title">知识点得分率(音乐)</h4>
+                          <p class="export-btn" @click="exportKnoData('subject_music')">
+                            <span class="icon iconfont icon-download" :title="`下载知识点得分率表格`"></span>
+                            导出数据
+                          </p>
+                          <div class="chart-line-wrap">
+                            <div class="kng-level-wrap chart-data-wrap">
+                              <KngLevel :kngData="musicKn"></KngLevel>
+                            </div>
+                            <div class="kng-point-wrap border-style chart-data-wrap">
+                              <KngPoint :kngData="musicKn"></KngPoint>
+                            </div>
+                          </div>
+                        </div>
+                        <div class="online-train-wrap">
+                          <h4 class="block-title">知识点得分率(美术)</h4>
+                          <p class="export-btn" @click="exportKnoData('subject_painting')">
+                            <span class="icon iconfont icon-download" :title="`下载知识点得分率表格`"></span>
+                            导出数据
+                          </p>
+                          <div class="chart-line-wrap">
+                            <div class="kng-level-wrap chart-data-wrap">
+                              <KngLevel :kngData="drawKn"></KngLevel>
+                            </div>
+                            <div class="kng-point-wrap border-style chart-data-wrap">
+                              <KngPoint :kngData="drawKn"></KngPoint>
+                            </div>
+                          </div>
+                        </div>
+                        <!-- 评测学校情况对比 -->
+                        <div class="online-train-wrap">
+                          <h4 class="block-title">学校评测情况(音乐)</h4>
+                          <div class="chart-data-wrap">
+                            <SchoolComp :schools="musicExam"></SchoolComp>
+                          </div>
+                        </div>
+                        <div class="online-train-wrap">
+                          <h4 class="block-title">学校评测情况(美术)</h4>
+                          <div class="chart-data-wrap">
+                            <SchoolComp :schools="drawExam"></SchoolComp>
+                          </div>
+                        </div>
+                        <!-- 艺术特长获奖情况 -->
+                        <div class="online-train-wrap">
+                          <h4 class="block-title">艺术特长获奖情况</h4>
+                          <div class="chart-line-wrap">
+                            <div class="award-chart-wrap chart-data-wrap">
+                              <!-- <Award :awardData="musicAward" titleText="音乐赛事艺术获奖情况"></Award> -->
+                              <EmptyData textContent="暂无音乐获奖情况数据"></EmptyData>
+                            </div>
+                            <div class="award-chart-wrap chart-data-wrap">
+                              <!-- <Award :awardData="drawAward" titleText="美术赛事艺术获奖情况"></Award> -->
+                              <EmptyData textContent="暂无美术获奖情况数据"></EmptyData>
+                            </div>
+                          </div>
+                        </div>
+                        <!-- 学校列表 -->
+                        <div class="online-train-wrap">
+                          <div style="height:30px">
+                            <h4 class="block-title">学校列表</h4>
+                            <Input v-special-char search placeholder="搜索学校" class="school-search" v-model="keyword" />
+                          </div>
+                          <div class="school-data-wrap">
+                            <div class="school-data-item border-style" v-for="(item, index) in schoolListShow" :key="index">
+                              <img class="school-img" :src="item.picture || defImg">
+                              <div style="margin-left:10px;height:fit-content;">
+                                <p class="school-name" :title="item.name">{{ item.name }}<span class="to-school-detail"
+                                    @click="toSchoolDetail(item)">
+                                    详情 >
+                                  </span></p>
+                                <div style="display:flex;margin-top:10px">
+                                  <p class="school-value">
+                                    <span class="value">{{ parseInt(item.musicPass * 100) || 0 }} <span style="font-size:18px">%</span>
+                                    </span>
+                                    <span>音乐及格率</span>
+                                  </p>
+                                  <p class="school-value">
+                                    <span class="value">{{ parseInt(item.drawPass * 100) || 0 }} <span style="font-size:18px">%</span>
+                                    </span>
+                                    <span>美术及格率</span>
+                                  </p>
+                                </div>
 
-              </div>
-            </div>
-            <EmptyData textContent="暂无学校数据" v-show="!schoolListShow.length"></EmptyData>
-          </div>
-        </div>
-      </template>
-    </vuescroll>
-  </div>
+                              </div>
+                            </div>
+                            <EmptyData textContent="暂无学校数据" v-show="!schoolListShow.length"></EmptyData>
+                          </div>
+                        </div>
+                      </template>
+                    </vuescroll>
+                  </div>
 </template>
 <script>
 import excel from '@/utils/excel.js'
@@ -146,6 +159,7 @@ export default {
   },
   data() {
     return {
+      areaKnoJson: null,
       emptyData: false,
       isLoading: false,
       keyword: '',
@@ -163,6 +177,8 @@ export default {
   computed: {
     curPeriodData() {
       let data = {}
+      data.classJoinCount = this.allData.classCount || 0;
+      data.stuJoinCount = this.allData && this.allData.perStuCounts ? this.allData.perStuCounts[0].realCount : 0
       data.areaSchool = this.allData.areaSchool || {}
       data.periodAll = this.allData.periodAll || {}
       data.overall = this.allData.periodAll?.subject || []
@@ -174,11 +190,13 @@ export default {
     // 头部统计数据
     topData() {
       let { scCount, classCount, stuCount, subjectCount } = this.curPeriodData.areaSchool
+      let { classJoinCount, stuJoinCount } = this.curPeriodData
+      let totalStandard = parseInt(+(this.curPeriodData.knData.map(i => i.stand).reduce((a, b) => a + b, 0)) / 2)
       let joinCount = this.curPeriodData.periodAll ? this.curPeriodData.periodAll.schoolScore?.length : 0
       let topData = [
         {
           icon: 'md-cube',
-          color: '#2d8cf0',
+          color: '#6acd7f',
           number: scCount || 0,
           text: '区域学校数',
           type: 'num'
@@ -194,16 +212,17 @@ export default {
           icon: 'md-cube',
           color: '#2db7f5',
           number: classCount || 0,
-          text: '班级数',
+          text: '班级数',
           type: 'num'
         },
         {
-          icon: 'md-bookmark',
-          color: '#ff9900',
-          number: subjectCount || 0,
-          text: '学科数',
+          icon: 'md-cube',
+          color: '#2db7f5',
+          number: classJoinCount || 0,
+          text: '参与班级数',
           type: 'num'
         },
+
         {
           icon: 'ios-people',
           color: '#5cadff',
@@ -211,13 +230,27 @@ export default {
           text: '学生人数',
           type: 'num'
         },
-        // {
-        //     icon: 'md-checkmark-circle',
-        //     color: '#19be6b',
-        //     number: 15666,
-        //     text: '总人数',
-        //     type: 'num'
-        // },
+        {
+          icon: 'ios-people',
+          color: '#5cadff',
+          number: stuJoinCount,
+          text: '参与学生数',
+          type: 'num'
+        },
+        {
+          icon: 'md-bookmark',
+          color: '#ff9900',
+          number: subjectCount || 0,
+          text: '学科数',
+          type: 'num'
+        },
+        {
+          icon: 'md-checkmark-circle',
+          color: '#19be6b',
+          number: totalStandard,
+          text: '标准差',
+          type: 'num'
+        }
       ]
       return topData
     },
@@ -299,11 +332,118 @@ export default {
     this.getAreaArtList()
   },
   methods: {
-    exportArtTable(){
+    exportKnoData(subjectId) {
+      console.error(subjectId, this.areaKnoJson)
       let sheets = []
       // 区级概况
-      let areaHeaders = ['学校总数量', '参与学校数量', '班级数量','学科数量','学生人数','音乐最高分','音乐最低分','音乐平均分','音乐优秀率','音乐合格率', '美术最高分', '美术最低分', '美术平均分', '美术优秀率', '美术合格率']
-      let areaKeys = ['scCount', 'scJoinCount', 'classCount','subjectCount','stuCount','m_max','m_min','m_average','m_excellent','m_pass','p_max','p_min','p_average','p_excellent','p_pass']
+      let area_knoHeaders = ['知识点名称', '知识块名称', '知识块配分', '知识块维度', '知识点得分率']
+      let area_knoKeys = ['pointName', 'blockName', 'blockScore', 'dim', 'pointScoreRate']
+      let subjectBlocks = this.areaKnoJson.blocks.find(i => i.subjectId === subjectId).dim
+      let area_knoData = this.areaKnoJson.areaSubjectPersent.find(i => i.subjectId === subjectId).psersent.map(item => {
+        let blockInfo = subjectBlocks.find(i => i.name === item.block[0])
+        return {
+          pointName: item.know,
+          blockName: item.block.length ? item.block[0] : '-',
+          blockScore: blockInfo ? blockInfo.score : 0,
+          dim: blockInfo ? blockInfo.dim[0] : '-',
+          pointScoreRate: parseInt(item.score * 100) + '%'
+        }
+      })
+      const areaSheet = {
+        title: area_knoHeaders,
+        key: area_knoKeys,
+        data: area_knoData,
+        filename: '区级概况',
+        autoWidth: true
+      }
+      sheets.push(areaSheet)
+      // 校级概况
+      let sch_knoHeaders = ['学校名称', '知识点名称', '知识块名称', '知识块配分', '知识块维度', '知识点得分率']
+      let sch_knoKeys = ['schoolName', 'pointName', 'blockName', 'blockScore', 'dim', 'pointScoreRate']
+      let sch_knoData = []
+      this.areaKnoJson.schoolSubjectPersent.forEach(school => {
+        school.subject.find(i => i.subjectId === subjectId).psersent.forEach(item => {
+          let blockInfo = subjectBlocks.find(i => i.name === item.block[0])
+          sch_knoData.push({
+            schoolName: school.schoolName,
+            pointName: item.know,
+            blockName: item.block.length ? item.block[0] : '-',
+            blockScore: blockInfo ? blockInfo.score : 0,
+            dim: blockInfo ? blockInfo.dim[0] : '-',
+            pointScoreRate: parseInt(item.score * 100) + '%'
+          })
+        })
+      })
+      const schSheet = {
+        title: sch_knoHeaders,
+        key: sch_knoKeys,
+        data: sch_knoData,
+        filename: '校级概况',
+        autoWidth: true
+      }
+      sheets.push(schSheet)
+      // 年级概况
+      let grade_knoHeaders = ['年级名称', '知识点名称', '知识块名称', '知识块配分', '知识块维度', '知识点得分率']
+      let grade_knoKeys = ['gradeName', 'pointName', 'blockName', 'blockScore', 'dim', 'pointScoreRate']
+      let grade_knoData = []
+      this.areaKnoJson.gradeSubjectPersent.find(i => i.subjectId === subjectId).psersent.forEach(grade => {
+        grade.knowledge.forEach(item => {
+          let blockInfo = subjectBlocks.find(i => i.name === item.block[0])
+          grade_knoData.push({
+            gradeName: grade.gradeName,
+            pointName: item.know,
+            blockName: item.block.length ? item.block[0] : '-',
+            blockScore: blockInfo ? blockInfo.score : 0,
+            dim: blockInfo ? blockInfo.dim[0] : '-',
+            pointScoreRate: parseInt(item.score * 100) + '%'
+          })
+        })
+      })
+      const gradeSheet = {
+        title: grade_knoHeaders,
+        key: grade_knoKeys,
+        data: grade_knoData,
+        filename: '年级概况',
+        autoWidth: true
+      }
+      sheets.push(gradeSheet)
+      // 班级概况
+      let class_knoHeaders = ['学校名称', '年级名称', '班级名称', '知识点名称', '知识块名称', '知识块配分', '知识块维度', '知识点得分率']
+      let class_knoKeys = ['schoolName', 'gradeName', 'className', 'pointName', 'blockName', 'blockScore', 'dim', 'pointScoreRate']
+      let class_knoData = []
+      this.areaKnoJson.classPersent.filter(i => i.subjectId === subjectId).forEach(classItem => {
+        classItem.know.forEach(item => {
+          let blockInfo = subjectBlocks.find(i => i.name === item.block[0])
+          class_knoData.push({
+            schoolName: classItem.schoolName,
+            gradeName: item.gradeName,
+            className: item.className,
+            pointName: item.knowledgeName,
+            blockName: item.block.length ? item.block[0] : '-',
+            blockScore: blockInfo ? blockInfo.score : 0,
+            dim: blockInfo ? blockInfo.dim[0] : '-',
+            pointScoreRate: parseInt(item.score * 100) + '%'
+          })
+        })
+      })
+      const classSheet = {
+        title: class_knoHeaders,
+        key: class_knoKeys,
+        data: class_knoData,
+        filename: '班级概况',
+        autoWidth: true
+      }
+      sheets.push(classSheet)
+      // 合并导出
+      let curPeriodName = this.periodList.find(i => i.value === this.periodId).label
+      let subjectName = subjectId === 'subject_music' ? '音乐' : '美术'
+      excel.export_array_to_sheet(sheets, `${sessionStorage.getItem('areaName')} - 知识点得分率报告(${curPeriodName}-${subjectName})`)
+    },
+    exportArtTable() {
+      let sheets = []
+      // 区级概况
+      let areaHeaders = ['学校总数量', '参与学校数量', '班级数量', '参与班级数量', '学科数量', '学生人数', '参与学生数量', '音乐最高分', '音乐最低分', '音乐平均分', '音乐优秀率', '音乐合格率', '音乐标准差', '美术最高分', '美术最低分', '美术平均分', '美术优秀率', '美术合格率', '美术标准差']
+      let areaKeys = ['scCount', 'scJoinCount', 'classCount', 'classJoinCount', 'subjectCount', 'stuCount', 'stuJoinCount', 'm_max', 'm_min', 'm_average', 'm_excellent', 'm_pass', 'm_stan', 'p_max', 'p_min', 'p_average', 'p_excellent', 'p_pass', 'p_stan']
       let mScore = this.allData.periodAll.subject.find(i => i.name === 'subject_music')
       let pScore = this.allData.periodAll.subject.find(i => i.name === 'subject_painting')
       let areaDatas = [
@@ -311,18 +451,22 @@ export default {
           scCount: this.allData.areaSchool.scCount,
           scJoinCount: this.allData.periodAll.schoolScore.length,
           classCount: this.allData.areaSchool.classCount,
+          classJoinCount: this.curPeriodData.classJoinCount,
           subjectCount: this.allData.areaSchool.subjectCount,
           stuCount: this.allData.areaSchool.stuCount,
+          stuJoinCount: this.curPeriodData.stuJoinCount,
           m_max: mScore.max,
           m_min: mScore.min,
           m_average: mScore.average,
           m_excellent: parseInt(mScore.excellent * 100) + '%',
-          m_pass:  parseInt(mScore.pass * 100) + '%',
+          m_pass: parseInt(mScore.pass * 100) + '%',
+          m_stan: this.curPeriodData.knData.find(i => i.key === 'subject_music').stand,
           p_max: pScore.max,
           p_min: pScore.min,
           p_average: pScore.average,
           p_excellent: parseInt(pScore.excellent * 100) + '%',
-          p_pass: parseInt(pScore.pass * 100) + '%'
+          p_pass: parseInt(pScore.pass * 100) + '%',
+          p_stan: this.curPeriodData.knData.find(i => i.key === 'subject_painting').stand
         }
       ]
       const areaSheet = {
@@ -335,7 +479,7 @@ export default {
       sheets.push(areaSheet)
       // 各学校数据
       let schoolHeaders = ['学校名称', '音乐最高分', '音乐平均分', '音乐优秀率', '音乐合格率', '美术最高分', '美术平均分', '美术优秀率', '美术合格率']
-      let schoolKeys = ['name', 'm_max','m_average','m_excellent','m_pass', 'p_max',  'p_average', 'p_excellent', 'p_pass']
+      let schoolKeys = ['name', 'm_max', 'm_average', 'm_excellent', 'm_pass', 'p_max', 'p_average', 'p_excellent', 'p_pass']
       let schoolDatas = []
       this.allData.periodAll.schoolScore.forEach(school => {
         let sch_music_score = school.scores.find(i => i.subjectId === 'subject_music')
@@ -415,7 +559,7 @@ export default {
       console.log(this.periodList);
       console.log(this.periodId);
       let curPeriodName = this.periodList.find(i => i.value === this.periodId).label
-      excel.export_array_to_sheet(sheets, `${sessionStorage.getItem('areaName')} - 艺术评测报告(${ curPeriodName })`)
+      excel.export_array_to_sheet(sheets, `${sessionStorage.getItem('areaName')} - 艺术评测报告(${curPeriodName})`)
     },
     getAreaArtList() {
       this.$api.areaArt.findAreaArtList({
@@ -428,6 +572,13 @@ export default {
           this.onAcChange()
         }
       })
+      // 获取区级当前学段汇总知识点得分率数据
+      this.$api.areaArt.findKnoPercents({
+        id: sessionStorage.getItem('areaId'),
+        periodType: this.periodId,
+      }).then(res => {
+        this.areaKnoJson = res
+      })
     },
     onAcChange() {
       this.getAreaArtAnalysis()
@@ -574,12 +725,14 @@ export default {
   border-radius: 20px;
   // margin-right: 20px;
 }
+
 .school-img {
   width: 90px;
   height: 90px;
   margin-right: 20px;
   border-radius: 10px;
 }
+
 .school-data-item {
   position: relative;
   display: flex;
@@ -592,10 +745,12 @@ export default {
   min-width: 360px;
   border-radius: 5px;
   transition: all 0.2s ease 0s;
+
   &:hover {
     box-shadow: 0 26px 40px -24px #aaa;
     transform: translateY(-4px);
   }
+
   .school-name {
     // width: 158px;
     // text-overflow: ellipsis;
@@ -606,6 +761,7 @@ export default {
     // font-weight: 600;
     font-size: 16px;
   }
+
   .school-value {
     margin-top: 5px;
     font-size: 12px;
@@ -624,6 +780,7 @@ export default {
       font-size: 24px;
     }
   }
+
   .to-school-detail {
     margin-left: 8px;
     color: #2d8cf0;
@@ -631,6 +788,7 @@ export default {
     cursor: pointer;
   }
 }
+
 .school-data-wrap {
   margin-bottom: 20px;
   display: flex;
@@ -639,25 +797,31 @@ export default {
   margin-top: 20px;
   justify-content: start;
 }
+
 .award-chart-wrap {
   width: 49%;
 }
+
 .kng-point-wrap {
   width: ~"calc(100% - 420px)";
 }
+
 .chart-line-wrap {
   display: flex;
   justify-content: space-between;
 }
+
 .kng-level-wrap {
   width: fit-content;
   margin-right: 30px;
 }
+
 .chart-data-wrap {
   background: #ffffff;
   margin-top: 10px;
   padding: 40px 20px;
 }
+
 .block-title {
   border-left: 4px solid #1cc0f3;
   padding-left: 10px;
@@ -667,59 +831,79 @@ export default {
   color: #414749;
   margin-bottom: 20px;
 }
+
 .online-train-wrap {
+  position: relative;
   border-radius: 5px;
   width: 100%;
   // background: white;
   padding: 0px 20px;
   // box-shadow: 0px 4px 4px 1px #f0f0f0;
   margin-top: 35px;
+
+  .export-btn {
+    position: absolute;
+    right: 20px;
+    top: 10px;
+    cursor: pointer;
+  }
 }
+
+
 .border-style {
   box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
   border-radius: 4px;
 }
+
 .content-con-item {
-  width: 18%;
+  width: 24%;
   height: 110px;
   position: relative;
   border-radius: 2px;
+  margin-bottom: 20px;
   overflow: hidden;
+
   .left-area {
     float: left;
     height: 100%;
     display: table;
     text-align: center;
+
     .icon {
       display: table-cell;
       vertical-align: middle;
     }
   }
+
   .right-area {
     background: white;
     float: left;
     height: 100%;
     display: table;
     text-align: center;
+
     .count-style {
       font-size: 50px;
     }
   }
 }
+
 .top-block-wrap {
   width: 100%;
   display: flex;
   justify-content: space-between;
+  flex-wrap: wrap;
   margin-top: 10px;
   padding: 0px 20px 0px 20px;
 }
+
 .area-data-container {
   position: relative;
   width: 100%;
   height: 100%;
   background: #ededed;
 
-  .export-box{
+  .export-box {
     position: absolute;
     right: 20px;
     top: 20px;

+ 8 - 8
TEAMModelOS/ClientApp/src/view/learnactivity/StuReport.vue

@@ -67,20 +67,20 @@
     </Row>
     <slot></slot>
 
-    <div class="QAsheet">
+    <div class="QAsheet" style="padding: 0 20px;">
       <div class='title-rect-group '>
         <h2 class="title-rect-name" @click="checkedAnsFilter">{{$t("studentWeb.exam.report.answerBack")}}</h2>
       </div>
       <div class="filterBtn">
         <CheckboxGroup v-model="checkedAns">
           <Checkbox label="right">
-            {{$t("studentWeb.exam.report.right")}}: {{rightAns.right}}
+            {{$t("studentWeb.exam.report.right")}} {{rightAns.right}}
           </Checkbox>
           <Checkbox label="wrong">
-            {{$t("studentWeb.exam.report.wrong")}}: {{rightAns.wrong}}
+            {{$t("studentWeb.exam.report.wrong")}} {{rightAns.wrong}}
           </Checkbox>
           <Checkbox label="noAns">
-            {{$t("studentWeb.exam.report.noScore")}}: {{rightAns.noAns}}
+            {{$t("studentWeb.exam.report.noScore")}} {{rightAns.noAns}}
           </Checkbox>
         </CheckboxGroup>
         <!-- <span @click="closeDetail">
@@ -109,7 +109,7 @@
                 </div>
               </i-col>
               <i-col :xs="2" :sm="2" :md="1" :lg="2">
-                <div class="qtype">{{ !question.parent ? getTestType(question.type) : getTestType('compose') }}</div>
+                <div class="qtype" :style="{ background: stuDataNew.data[index] == question.score ? '#00ad6c' : '#ff5508' }">{{ !question.parent ? getTestType(question.type) : getTestType('compose') }}</div>
               </i-col>
               <i-col :xs="18" :sm="18" :md="21" :lg="19">
                 <div class="qdesc">
@@ -652,10 +652,10 @@ export default {
 }
 
 .qtype {
-  color: #6d7278;
-  border: 2px #979797 solid;
+  color: #fff;
+  border: 2px #fff solid;
   border-radius: 4px;
-  padding: 2px;
+  padding: 2px 5px;
   text-align: center;
   max-width: fit-content;
 }

+ 80 - 25
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/ScatterAnalysis/ScatterAnalysis.vue

@@ -3,7 +3,7 @@
     <Row class-name="base-table-row">
       <Col span="12">
       <div class="component-title">
-        <span>{{$t('totalAnalysis.sca_title1')}}</span>
+        <span>{{ $t('totalAnalysis.sca_title1') }}</span>
         <!--                <Select v-model="currentClass" @on-change="onClassSelect">
                     <Option v-for="(item,index) in classList" :value="index" :key="item">{{ item }}</Option>
                 </Select> -->
@@ -13,44 +13,44 @@
       <Col span="12">
       <div class="scatter-statistics">
         <div class="scatter-table-line">
-          <span>{{$t('totalAnalysis.sca_text1')}}</span>
-          <span style="text-align: left;width: 250px;">{{$t('totalAnalysis.sca_text2')}}</span>
-          <span>{{$t('totalAnalysis.sca_text3')}}</span>
+          <span>{{ $t('totalAnalysis.sca_text1') }}</span>
+          <span style="text-align: left;width: 250px;">{{ $t('totalAnalysis.sca_text2') }}</span>
+          <span>{{ $t('totalAnalysis.sca_text3') }}</span>
         </div>
         <Divider />
         <div class="scatter-table-line">
           <span>A</span>
-          <span style="text-align: left;width: 250px;">{{$t('totalAnalysis.sca_text5')}}</span>
+          <span style="text-align: left;width: 250px;">{{ $t('totalAnalysis.sca_text5') }}</span>
           <span>{{ tableData.filter(item => item.scatter === 'A').length }}</span>
         </div>
         <Divider />
         <div class="scatter-table-line">
           <span>A'</span>
-          <span style="text-align: left;width: 250px;">{{$t('totalAnalysis.sca_text6')}}</span>
+          <span style="text-align: left;width: 250px;">{{ $t('totalAnalysis.sca_text6') }}</span>
           <span>{{ tableData.filter(item => item.scatter === "A'").length }}</span>
         </div>
         <Divider />
         <div class="scatter-table-line">
           <span>B</span>
-          <span style="text-align: left;width: 250px;">{{$t('totalAnalysis.sca_text7')}}</span>
+          <span style="text-align: left;width: 250px;">{{ $t('totalAnalysis.sca_text7') }}</span>
           <span>{{ tableData.filter(item => item.scatter === 'B').length }}</span>
         </div>
         <Divider />
         <div class="scatter-table-line">
           <span>B'</span>
-          <span style="text-align: left;width: 250px;">{{$t('totalAnalysis.sca_text8')}}</span>
+          <span style="text-align: left;width: 250px;">{{ $t('totalAnalysis.sca_text8') }}</span>
           <span>{{ tableData.filter(item => item.scatter === "B'").length }}</span>
         </div>
         <Divider />
         <div class="scatter-table-line">
           <span>C</span>
-          <span style="text-align: left;width: 250px;">{{$t('totalAnalysis.sca_text9')}}</span>
+          <span style="text-align: left;width: 250px;">{{ $t('totalAnalysis.sca_text9') }}</span>
           <span>{{ tableData.filter(item => item.scatter === 'C').length }}</span>
         </div>
         <Divider />
         <div class="scatter-table-line">
           <span>C'</span>
-          <span style="text-align: left;width: 250px;">{{$t('totalAnalysis.sca_text10')}}</span>
+          <span style="text-align: left;width: 250px;">{{ $t('totalAnalysis.sca_text10') }}</span>
           <span>{{ tableData.filter(item => item.scatter === "C'").length }}</span>
         </div>
       </div>
@@ -60,41 +60,85 @@
     <!-- 学生稳定度统计表 -->
     <Row class-name="base-table-row">
       <div>
-        <BaseTable @onStuClick="onStuClick" :columns="tableColumns" :tableDatas="tableData" :tableName="$t('totalAnalysis.sca_title2')" tableRef="scatterTable" ref="scatterTable"></BaseTable>
+        <BaseTable @onStuClick="onStuClick" :columns="tableColumns" :tableDatas="tableData"
+          :tableName="$t('totalAnalysis.sca_title2')" tableRef="scatterTable" ref="scatterTable"></BaseTable>
       </div>
     </Row>
 
     <!-- 学生报告 -->
-    <Modal v-model="stuReportStatus" class-name="custom-modal-top print-me" :title="$t('learnActivity.score.stuRpt')" :width="1000" footer-hide>
-      <div slot="header" style="padding:15px 0px 0px 0px;" id="printDom">
+    <Modal v-model="stuReportStatus" class-name="custom-modal-top print-me" :title="$t('learnActivity.score.stuRpt')"
+      :width="1000" footer-hide>
+      <div slot="header" style="position:relative;padding:60px 0px 20px 0px;" id="printDom">
+        <img src="./cloudas_title.png" alt="" style="position:absolute;top:-45px;left:0;width:100%">
         <span class="stu-info">
           <span class="stu-label">
-            {{$t('learnActivity.score.subjectLabel')}}
+            {{ $t('evaluation.index.paper') }}:
           </span>
-          <span class="stu-value">{{subjectName}}</span>
+          <span class="stu-value">{{ examInfo.name }}</span>
         </span>
         <span class="stu-info">
           <span class="stu-label">
-            {{$t('learnActivity.score.classLabel')}}
+            {{ $t('learnActivity.score.subjectLabel') }}
           </span>
-          <span class="stu-value">{{className}}</span>
+          <span class="stu-value">{{ subjectName }}</span>
         </span>
         <span class="stu-info">
           <span class="stu-label">
-            {{$t('learnActivity.score.stuLabel')}}
+            {{ $t('totalAnalysis.echarts_text11') }}:
           </span>
-          <span class="stu-value">{{viewStuData.name}}</span>
+          <span class="stu-value">{{ $tools.formatTime(examInfo.startTime, 'yyyy-MM-dd') }}</span>
         </span>
-        <span style="float:right;margin: -15px 40px">
-          <Button type="primary" @click="onPrint">{{$t('learnActivity.score.print')}}</Button>
+        <span class="stu-info">
+          <span class="stu-label">
+            {{ $t('totalAnalysis.text7') }}:
+          </span>
+          <span class="stu-value">{{ examInfo.stuCount }}</span>
+        </span>
+        <br>
+        <br>
+        <span class="stu-info">
+          <span class="stu-label">
+            {{ $t('learnActivity.score.classLabel') }}
+          </span>
+          <span class="stu-value">{{ className }}</span>
+        </span>
+        <span class="stu-info">
+          <span class="stu-label">
+            {{ $t('learnActivity.score.stuLabel') }}
+          </span>
+          <span class="stu-value">{{ viewStuData.name }}</span>
+        </span>
+        <span class="stu-info">
+          <span class="stu-label">
+            {{ $t('totalAnalysis.base_id') }}:
+          </span>
+          <span class="stu-value">{{ viewStuData.id }}</span>
+        </span>
+        <span style="float:right;margin: -5px 40px" class="print-hidden">
+          <Button type="primary" @click="onPrint">{{ $t('learnActivity.score.print') }}</Button>
         </span>
       </div>
       <StuReport :stuData="viewStuData" :classId="chooseClass" :examInfo="examInfo" :subject="chooseSubject">
-        <BaseSingleStuScatter :scatterData="tableData" :stuData="viewStuData"></BaseSingleStuScatter>
-        <div style="margin-left:30px;color:red;font-size:16px;margin-top:10px">
-          <p>{{ $t('totalAnalysis.sca_table_text4') }}:{{ viewStuData.carefulList }}</p>
-          <p>{{ $t('totalAnalysis.sca_table_text3') }}:{{ viewStuData.hardList }}</p>
+        <div style="display: flex;justify-content: space-between;margin-top: 3%;">
+          <Card style="width: 49%;">
+            <BaseSingleStuScatter :scatterData="tableData" :stuData="viewStuData"></BaseSingleStuScatter>
+          </Card>
+          <Card style="width: 49%;padding-top: 60px;padding-left: 30px;">
+            <p style="margin-bottom:20px;">{{ $t('totalAnalysis.sca_table_text6') }}<br><span
+                style="font-size: 20px;margin-top: 10px;display: inline-block;color: #ff5508;">{{ viewStuData.scatter
+                }}</span></p>
+            <p style="margin-bottom:20px;">{{ $t('totalAnalysis.sca_text2') }}<br><span
+                style="font-size: 20px;margin-top: 10px;display: inline-block;color: #ff5508;">{{
+                  getScatterTip(viewStuData.scatter) }}</span></p>
+            <p style="margin-bottom:20px;">{{ $t('totalAnalysis.sca_table_text4') }}<br><span
+                style="font-size: 20px;margin-top: 10px;display: inline-block;color: #ff5508;">{{ viewStuData.carefulList
+                }}</span></p>
+            <p style="margin-bottom:20px;">{{ $t('totalAnalysis.sca_table_text3') }}<br><span
+                style="font-size: 20px;margin-top: 10px;display: inline-block;color: #ff5508;">{{ viewStuData.hardList
+                }}</span></p>
+          </Card>
         </div>
+
       </StuReport>
     </Modal>
   </div>
@@ -241,9 +285,16 @@ export default {
   },
 
   methods: {
+    getScatterTip(scatter) {
+      let arr = ["A", "A'", "B", "B'", "C", "C'"]
+      return this.$t('totalAnalysis.sca_text' + (arr.indexOf(scatter) + 5))
+    },
     onPrint() {
       // 获取需要打印预览的DOM节点
       let modalDom = document.getElementsByClassName('print-me')[0].getElementsByClassName('ivu-modal-content')[0]
+      Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 0 })
+      Array.from(document.getElementsByClassName('ivu-icon-ios-close')).forEach(dom => { dom.style.opacity = 0 })
+      Array.from(document.getElementsByClassName('toggle-info')).forEach(dom => { dom.style.opacity = 0 })
       domtoimage.toJpeg(modalDom, {
         bgcolor: '#fff'
       }).then(pageData => {
@@ -275,6 +326,7 @@ export default {
             putOnlyUsedFonts: true
           })
           let curPage = 1
+          PDF.addImage("./cloudas_title.png", "JPEG", 15, 40, 180, 180);
           // 如果是一页的情况
           if (leftHeight <= pageHeight) {
             PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth,
@@ -294,6 +346,9 @@ export default {
           }
           // 下载 PDF
           PDF.save(that.viewStuData.name + '-个人报告.pdf');
+          Array.from(document.getElementsByClassName('print-hidden')).forEach(dom => { dom.style.opacity = 1 })
+          Array.from(document.getElementsByClassName('ivu-icon-ios-close')).forEach(dom => { dom.style.opacity = 1 })
+          Array.from(document.getElementsByClassName('toggle-info')).forEach(dom => { dom.style.opacity = 1 })
         }
       })
     },

二进制
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/ScatterAnalysis/cloudas_title.png


+ 74 - 32
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/TestAnalysis/QuestionList.vue

@@ -3,73 +3,115 @@
     <!-- 左侧题目列表清单 -->
     <div class="ql-left-box">
       <Loading :top="300" v-show="dataLoading" type="3"></Loading>
-      <ExerciseList :propsList="questionList" :flatIds="flatList.length ? flatList.map(i => i.id) : []" :examScope="examScope" :analysisJson="paperAnalysisJson" :optionRate="paperOptionRateArr" isAnalysis @pageScroll="doScroll" ref="exList" isShowAnalysis></ExerciseList>
+      <ExerciseList :propsList="questionList" :flatIds="flatList.length ? flatList.map(i => i.id) : []"
+        :examScope="examScope" :analysisJson="paperAnalysisJson" :optionRate="paperOptionRateArr" isAnalysis
+        @pageScroll="doScroll" ref="exList" isShowAnalysis></ExerciseList>
     </div>
 
     <!-- 右侧题目列表题型概览 -->
-    <div class='ql-right-box' ref="rightBox" :style="{width: isOpen ? '17%' : '18%'}">
-      <Button type="info" @click="handleBackTo" style="width: 100%;height: 40px;margin-bottom: 15px;border-radius: 2px;">{{ $t('totalAnalysis.backUp')}}</Button>
-      <Button type="info" @click="isShowAnalysis = true" style="width: 100%;height: 40px;margin-bottom: 15px;border-radius: 2px;background-color: #29a482;border: none;">{{ $t('evaluation.paperList.paperAnalysis')}}</Button>
+    <div class='ql-right-box' ref="rightBox" :style="{ width: isOpen ? '17%' : '18%' }">
+      <Button type="info" @click="handleBackTo"
+        style="width: 100%;height: 40px;margin-bottom: 15px;border-radius: 2px;">{{ $t('totalAnalysis.backUp') }}</Button>
+      <Button type="info" @click="isShowAnalysis = true"
+        style="width: 100%;height: 40px;margin-bottom: 15px;border-radius: 2px;background-color: #29a482;border: none;">{{
+          $t('evaluation.paperList.paperAnalysis') }}</Button>
+      <Button type="info" @click="onPrintPaper"
+        style="width: 100%;height: 40px;margin-bottom: 15px;border-radius: 2px;background-color: #419901;border: none;">{{
+          `下载本卷PDF` }}</Button>
       <div style="background-color: #fff;">
         <div class="ql-right-score">
-          <p style="margin-top: 20px;">{{ $t('totalAnalysis.paperSubject')}}:{{ $store.state.totalAnalysis.currentSubject }}</p>
-          <p style="margin-top: 10px;">{{ $t('totalAnalysis.paperItemsCount')}}:{{ questionList.length }}</p>
-          <p style="margin-top: 10px;">{{$t('totalAnalysis.ql_text1')}}
-            :<span>{{sumArr(questionList.map(item => item.score))}} {{$t('totalAnalysis.ql_text8')}}</span>
+          <p style="margin-top: 20px;">{{ $t('totalAnalysis.paperSubject') }}:{{ $store.state.totalAnalysis.currentSubject
+          }}</p>
+          <p style="margin-top: 10px;">{{ $t('totalAnalysis.paperItemsCount') }}:{{ questionList.length }}</p>
+          <p style="margin-top: 10px;">{{ $t('totalAnalysis.ql_text1') }}
+            :<span>{{ sumArr(questionList.map(item => item.score)) }} {{ $t('totalAnalysis.ql_text8') }}</span>
           </p>
         </div>
 
         <div class="ql-right-list">
           <div>
             <div class="ql-right-part" v-if="SingleList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text2')}}({{sumArr(SingleList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text2') }}({{
+                sumArr(SingleList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
-                <span class="ql-right-item" v-for="(item,index) in SingleList" :key="index" @click="handleItemClick(item,$event)" :ref="'indexRef' + flatList.indexOf(item)" :data-order="flatList.indexOf(item)">{{getIndexOrder(item)}}</span>
+                <span class="ql-right-item" v-for="(item, index) in SingleList" :key="index"
+                  @click="handleItemClick(item, $event)" :ref="'indexRef' + flatList.indexOf(item)"
+                  :data-order="flatList.indexOf(item)">{{ getIndexOrder(item) }}</span>
               </div>
             </div>
             <div class="ql-right-part" v-if="MultipleList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text3')}}({{sumArr(MultipleList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text3') }}({{
+                sumArr(MultipleList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
-                <span class="ql-right-item" v-for="(item,index) in MultipleList" :key="index" @click="handleItemClick(item,$event)" :ref="'indexRef' + flatList.indexOf(item)" :data-order="flatList.indexOf(item)">{{getIndexOrder(item)}}</span>
+                <span class="ql-right-item" v-for="(item, index) in MultipleList" :key="index"
+                  @click="handleItemClick(item, $event)" :ref="'indexRef' + flatList.indexOf(item)"
+                  :data-order="flatList.indexOf(item)">{{ getIndexOrder(item) }}</span>
               </div>
             </div>
             <div class="ql-right-part" v-if="JudgeList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text4')}}({{sumArr(JudgeList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text4') }}({{
+                sumArr(JudgeList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
-                <span class="ql-right-item" v-for="(item,index) in JudgeList" :key="index" @click="handleItemClick(item,$event)" :ref="'indexRef' + flatList.indexOf(item)" :data-order="flatList.indexOf(item)">{{getIndexOrder(item)}}</span>
+                <span class="ql-right-item" v-for="(item, index) in JudgeList" :key="index"
+                  @click="handleItemClick(item, $event)" :ref="'indexRef' + flatList.indexOf(item)"
+                  :data-order="flatList.indexOf(item)">{{ getIndexOrder(item) }}</span>
               </div>
             </div>
             <div class="ql-right-part" v-if="CompleteList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text5')}}({{sumArr(CompleteList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text5') }}({{
+                sumArr(CompleteList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
-                <span class="ql-right-item" v-for="(item,index) in CompleteList" :key="index" @click="handleItemClick(item,$event)" :ref="'indexRef' + flatList.indexOf(item)" :data-order="flatList.indexOf(item)">{{getIndexOrder(item)}}</span>
+                <span class="ql-right-item" v-for="(item, index) in CompleteList" :key="index"
+                  @click="handleItemClick(item, $event)" :ref="'indexRef' + flatList.indexOf(item)"
+                  :data-order="flatList.indexOf(item)">{{ getIndexOrder(item) }}</span>
               </div>
             </div>
             <div class="ql-right-part" v-if="SubjectiveList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text6')}}({{sumArr(SubjectiveList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text6') }}({{
+                sumArr(SubjectiveList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
-                <span class="ql-right-item" v-for="(item,index) in SubjectiveList" :key="index" @click="handleItemClick(item,$event)" :ref="'indexRef' + flatList.indexOf(item)" :data-order="flatList.indexOf(item)">{{getIndexOrder(item)}}</span>
+                <span class="ql-right-item" v-for="(item, index) in SubjectiveList" :key="index"
+                  @click="handleItemClick(item, $event)" :ref="'indexRef' + flatList.indexOf(item)"
+                  :data-order="flatList.indexOf(item)">{{ getIndexOrder(item) }}</span>
               </div>
             </div>
             <div class="ql-right-part" v-if="ConnectorList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text14')}}({{sumArr(ConnectorList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text14') }}({{
+                sumArr(ConnectorList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
-                <span class="ql-right-item" v-for="(item,index) in ConnectorList" :key="index" @click="handleItemClick(item,$event)" :ref="'indexRef' + flatList.indexOf(item)" :data-order="flatList.indexOf(item)">{{getIndexOrder(item)}}</span>
+                <span class="ql-right-item" v-for="(item, index) in ConnectorList" :key="index"
+                  @click="handleItemClick(item, $event)" :ref="'indexRef' + flatList.indexOf(item)"
+                  :data-order="flatList.indexOf(item)">{{ getIndexOrder(item) }}</span>
               </div>
             </div>
             <div class="ql-right-part" v-if="CorrectList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text15')}}({{sumArr(CorrectList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text15') }}({{
+                sumArr(CorrectList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
-                <span class="ql-right-item" v-for="(item,index) in CorrectList" :key="index" @click="handleItemClick(item,$event)" :ref="'indexRef' + flatList.indexOf(item)" :data-order="flatList.indexOf(item)">{{getIndexOrder(item)}}</span>
+                <span class="ql-right-item" v-for="(item, index) in CorrectList" :key="index"
+                  @click="handleItemClick(item, $event)" :ref="'indexRef' + flatList.indexOf(item)"
+                  :data-order="flatList.indexOf(item)">{{ getIndexOrder(item) }}</span>
               </div>
             </div>
             <div class="ql-right-part" v-if="ComposeList.length">
-              <span class="ql-right-part-title"><span class="ql-line"></span>{{$t('totalAnalysis.ql_text7')}}({{sumArr(ComposeList.map(item => item.score))}}{{$t('totalAnalysis.ql_text8')}})</span>
+              <span class="ql-right-part-title"><span class="ql-line"></span>{{ $t('totalAnalysis.ql_text7') }}({{
+                sumArr(ComposeList.map(item =>
+                  item.score)) }}{{ $t('totalAnalysis.ql_text8') }})</span>
               <div class="ql-right-items">
                 <!-- 如果是综合题 则需要把小题题序放出来 -->
-                <span v-for="(item,index) in ComposeList" :key="index" style="background-color: none;" class="ql-right-items">
-                  <span class="ql-right-item" v-for="(child,childIndex) in item.children" :key="childIndex" @click="handleItemClick(item,$event)" :ref="'indexRef' + (flatList.indexOf(child))" :data-order="flatList.indexOf(child)">{{ fillIndexOrder(flatList.indexOf(child)) }}
-                    ({{getIndexOrder(item)}} - {{ childIndex + 1 }})</span>
+                <span v-for="(item, index) in ComposeList" :key="index" style="background-color: none;"
+                  class="ql-right-items">
+                  <span class="ql-right-item" v-for="(child, childIndex) in item.children" :key="childIndex"
+                    @click="handleItemClick(item, $event)" :ref="'indexRef' + (flatList.indexOf(child))"
+                    :data-order="flatList.indexOf(child)">{{ fillIndexOrder(flatList.indexOf(child)) }}
+                    ({{ getIndexOrder(item) }} - {{ childIndex + 1 }})</span>
                 </span>
               </div>
             </div>
@@ -138,9 +180,10 @@ export default {
   },
 
   methods: {
-
+    onPrintPaper(){
+      this.$refs.exList.onPrintPaper()
+    },
     async initFullPaper(examPaper, exam) {
-      console.log(examPaper.name)
       examPaper.examId = exam.code.replace('Exam-', '')
       let fullPaperJson = await this.$evTools.getFullPaper(examPaper, exam.scope)
       this.questionList = fullPaperJson.item
@@ -186,7 +229,6 @@ export default {
     doScroll(scrollDistance) {
       this.$nextTick(() => {
         let parentVm = this.$parent.$parent.$parent
-        console.error(`output->this.$parent`, parentVm.$refs['vs'].scrollTo, scrollDistance)
         parentVm.$refs['vs'].scrollTo({
           y: scrollDistance,
         },
@@ -343,15 +385,15 @@ export default {
   border: 0;
 }
 
-.ql-item .ivu-collapse > .ivu-collapse-item > .ivu-collapse-header {
+.ql-item .ivu-collapse>.ivu-collapse-item>.ivu-collapse-header {
   color: #06a9bb;
 }
 
-.ql-item .ivu-collapse > .ivu-collapse-item > .ivu-collapse-header > .ivu-icon {
+.ql-item .ivu-collapse>.ivu-collapse-item>.ivu-collapse-header>.ivu-icon {
   vertical-align: unset;
 }
 
-.ql-item .ivu-collapse > .ivu-collapse-item {
+.ql-item .ivu-collapse>.ivu-collapse-item {
   border-top: 0;
 }
 

+ 2 - 2
TEAMModelOS/ClientApp/src/view/student-analysis/total-analysis/TestAnalysis/TestAnalysis.vue

@@ -109,7 +109,7 @@ export default {
         key: 'type',
         minWidth: 100,
         renderType: (h, params) => {
-          return h('span', this.exersicesType[params.row.type] || this.$t('td.td164'))
+          return h('span', this.exersicesType[params.row.type] || this.$t('courseManage.noData1'))
         },
       },
       {
@@ -117,7 +117,7 @@ export default {
         key: 'knowledge',
         minWidth: 150,
         renderType: function (h, params) {
-          return h('span', params.row.knowledge || this.$t('td.td164'))
+          return h('span', params.row.knowledge || this.$t('courseManage.noData1'))
         },
       },
       {

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

@@ -414,11 +414,12 @@ export default {
         this.selectClass = Number(localStorage.getItem("courseIndex"))
     },
     mounted() {
+        console.log(localStorage.getItem("scale"))
         if(this.$store.state.userInfo.scope === "student") {
-            let scale = localStorage.getItem("scale")
+            let scale = Number(localStorage.getItem("scale"))
             let isScale = false
             if(scale > 0) {
-                let countAuthorized = localStorage.getItem("countAuthorized")
+                let countAuthorized = Number(localStorage.getItem("countAuthorized"))
                 isScale = scale < countAuthorized ? true : false
             } else {
                 isScale = true

+ 8 - 7
TEAMModelOS/Controllers/Analysis/ArtAnalysisController.cs

@@ -130,14 +130,14 @@ namespace TEAMModelOS.Controllers.Analysis
                 }
                 var subjectScore = new
                 {
-                    name = As.Where(a => a.subjectId.Equals(subjectId.GetString())).Select(x => x.score)
+                    name = As.Where(a => a.subjectId.Equals(subjectId.GetString())).Select(x => x.score).Where(c => c > 0)
                 };
 
                 double max = subjectScore.name.Max(s => Math.Abs(s));
                 double min = subjectScore.name.Min(s => Math.Abs(s));
                 double total = subjectScore.name.Sum();
                 double average = Math.Round(total / stus.Count, 2);
-                double excellent = Math.Round(subjectScore.name.Where(s => s >= 90).Count() * 1.0 / stus.Count, 2);
+                double excellent = Math.Round(subjectScore.name.Where(s => s >= 80).Count() * 1.0 / stus.Count, 2);
                 double pass = Math.Round(subjectScore.name.Where(s => s >= 60).Count() * 1.0 / stus.Count, 2);
                 double powSum = 0;
                 foreach (var sc in subjectScore.name)
@@ -215,13 +215,14 @@ namespace TEAMModelOS.Controllers.Analysis
                         scores.Add(sc);
                         classTotal += sc;
                     }
+                    scores = scores.Where(x => x > 0).ToList();
                     double maxc = scores.Max(s => Math.Abs(s));
                     double minc = scores.Min(s => Math.Abs(s));
-                    double excellentc = scores.Where(s => s >= 90).Count();
+                    double excellentc = scores.Where(s => s >= 80).Count();
                     double ex = Math.Round(excellentc / scores.Count,2);
                     double passc = scores.Where(s => s >= 60).Count();
                     double pa = Math.Round(passc / scores.Count, 2);
-                    clsInfo.Add((cls.id, classTotal / cls.members.Count, maxc, minc, ex, pa));
+                    clsInfo.Add((cls.id, classTotal / scores.Count, maxc, minc, ex, pa));
                 }
                 //班级信息
                 var cInfo = clsInfo.Select(x => new
@@ -236,7 +237,7 @@ namespace TEAMModelOS.Controllers.Analysis
                     examResults[0].classes.Where(c => c.id.Equals(x.cId)).FirstOrDefault().gradeId
                 });
                 //年级信息
-                var grades = students.GroupBy(c => c.gradeId).Select(x => new { gradeId = x.Key, list = x.ToList().Select(v => v.score) });                
+                var grades = students.GroupBy(c => c.gradeId).Select(x => new { gradeId = x.Key, list = x.ToList().Select(v => v.score).Where(c => c > 0) });                
                 var gscore = grades.Select(x => new
                 {
                     id = x.gradeId,
@@ -244,7 +245,7 @@ namespace TEAMModelOS.Controllers.Analysis
                     score = Math.Round((double)(x.list.Sum() / x.list.Count()), 2),
                     max = x.list.Max(s => Math.Abs((double)s)),
                     min = x.list.Min(s => Math.Abs((double)s)),
-                    excellent = Math.Round( x.list.Where(s => s >= 90).Count() * 1.0 / x.list.Count(),2),
+                    excellent = Math.Round( x.list.Where(s => s >= 80).Count() * 1.0 / x.list.Count(),2),
                     pass = Math.Round(x.list.Where(s => s >= 60).Count() * 1.0 / x.list.Count(), 2)
                 });
                 //获奖次数
@@ -278,7 +279,7 @@ namespace TEAMModelOS.Controllers.Analysis
                     x.Key,
                     x.Value
                 });*/
-                return Ok(new { count = tchList.Count, scount = stus.Count, max, min, average, excellent, pass, pow, students, cInfo, blk, kno, optCount, gscore });
+                return Ok(new { count = tchList.Count, scount = stus.Count - info.lostStu.Count, max, min, average, excellent, pass, pow, students, cInfo, blk, kno, optCount, gscore });
             }
             catch (Exception e)
             {

文件差异内容过多而无法显示
+ 477 - 49
TEAMModelOS/Controllers/Common/AreaController.cs


+ 2 - 2
TEAMModelOS/Controllers/Common/ExamController.cs

@@ -1136,6 +1136,7 @@ namespace TEAMModelOS.Controllers
 
                     }
                     result.sum[newIndex] = result.studentScores[newIndex].Sum();
+                    classResult = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(result, result.id, new PartitionKey($"{result.code}"));
                     if (isAns)
                     {
                         if ($"{scope}".Equals(Constant.ScopeStudent))
@@ -1182,8 +1183,7 @@ namespace TEAMModelOS.Controllers
                             activity.taskStatus = 1;
                             await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<StuActivity>(activity, id.ToString(), new PartitionKey($"Activity-{userId}"));
                         }
-                    }
-                    classResult = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(result, result.id, new PartitionKey($"{result.code}"));
+                    }                    
                 }
                 await Task.WhenAll(tasks);
 

+ 50 - 0
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -108,6 +108,56 @@ namespace TEAMModelOS.Controllers
 
         }
 
+
+        /// <summary>
+        /// 修改教师信息
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [ProducesDefaultResponseType]
+        [HttpPost("delete-unlinked-record")]
+        [AuthToken(Roles = "admin,teacher,area")]
+        [Authorize(Roles = "IES")]
+        public async Task<IActionResult> DeleteUnlinkedRecord(JsonElement request) {
+            var (userid, name, _, school) = HttpContext.GetAuthTokenInfo();
+            List<string> recordsUrls =  await _azureStorage.GetBlobContainerClient(userid).List("records");
+            HashSet<string> recordIds= new HashSet<string>();
+            Dictionary<string, List<string>> recordUrls = new Dictionary<string, List<string>>();
+            recordsUrls.ForEach(z => {
+                var path = z.Split("/");
+                if (path.Length > 2)
+                {
+                    string id = path[1];
+                    long idnum = 0;
+                    if (long.TryParse(id, out idnum) && idnum>0) {
+                        recordIds.Add(id);
+                        if (recordUrls.ContainsKey(id))
+                        {
+                            recordUrls[id].Add(z);
+                        }
+                        else {
+                            recordUrls[id]=new List<string>() {z};
+                        }
+                    }
+                }
+            });
+            if (recordIds.Any()) {
+                IEnumerable<string> unlink = null; 
+                string sql = $"select value c from c where c.tmdid='{userid}' and  c.id in ({string.Join(",",recordIds.Select(x=>$"'{x}'"))})";
+                var result=  await  _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).GetList<LessonRecord>(sql, $"LessonRecord");
+                if (result != null && result.list.Any())
+                {
+                    unlink = recordIds.Except(result.list.Select(z => z.id));
+                }
+                else {
+                    unlink=recordIds;
+                }
+                var unlinkData=  recordUrls.Where(x => unlink.Contains(x.Key));
+                return Ok(new { unlinkData });
+            }
+            return Ok(new { unlinkData = new List<string>() });
+        }
+
         /// <summary>
         /// 修改教师信息
         /// </summary>

文件差异内容过多而无法显示
+ 16 - 12
TEAMModelOS/Controllers/XTest/FixDataController.cs


+ 32 - 0
TEAMModelOS/Filter/AspNetCoreBuilderServiceCollectionExtensions.cs

@@ -0,0 +1,32 @@
+using Google.Protobuf.WellKnownTypes;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+
+namespace TEAMModelOS.Filter
+{
+    public static class AspNetCoreBuilderServiceCollectionExtensions
+    {  /// <summary>
+       /// 注册 Mvc 过滤器
+       /// </summary>
+       /// <typeparam name="TFilter"></typeparam>
+       /// <param name="services"></param>
+       /// <param name="configure"></param>
+       /// <returns></returns>
+        public static IServiceCollection AddMvcFilter<TFilter>(this IServiceCollection services, Action<MvcOptions> configure = default)
+            where TFilter : IFilterMetadata
+        {
+            services.Configure<MvcOptions>(options =>
+            {
+                options.Filters.Add<TFilter>();
+
+                // 其他额外配置
+                configure?.Invoke(options);
+            });
+
+            return services;
+        }
+    }
+
+}

+ 79 - 0
TEAMModelOS/Filter/BlobLoggerProvider.cs

@@ -0,0 +1,79 @@
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.IO;
+using System.Text;
+using System;
+using TEAMModelOS.SDK.DI;
+using Azure;
+using Azure.Storage.Blobs.Specialized;
+using System.Threading.Tasks;
+
+namespace TEAMModelOS.Filter
+{
+    public class BlobLoggerProvider: ILoggerProvider
+    {
+        private readonly AzureStorageFactory _storageFactory;
+
+        public BlobLoggerProvider(AzureStorageFactory storageFactory)
+        {
+            _storageFactory=storageFactory;
+            var container = _storageFactory.GetBlobContainerClient("0-service-log");
+            container.CreateIfNotExists(PublicAccessType.None); //嘗試創建School容器,如存在則不做任何事,保障容器一定存在
+        }
+
+        public ILogger CreateLogger(string categoryName)
+        {
+            return new BlobLogger(categoryName, _storageFactory.GetBlobContainerClient("0-service-log"));
+        }
+
+        public void Dispose()
+        {
+            // Dispose any resources used by the provider.
+        }
+    }
+
+    public class BlobLogger : ILogger
+    {
+        private readonly string _categoryName;
+        private readonly BlobContainerClient _containerClient;
+
+        public BlobLogger(string categoryName, BlobContainerClient containerClient)
+        {
+            _categoryName = categoryName;
+            _containerClient = containerClient;
+        }
+
+        public IDisposable BeginScope<TState>(TState state)
+        {
+            return null;
+        }
+
+        public bool IsEnabled(LogLevel logLevel)
+        {
+            return true;
+        }
+
+        public async void Log<TState> ( LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+        {
+            if (formatter == null)
+            {
+                throw new ArgumentNullException(nameof(formatter));
+            }
+            var message = formatter(state, exception);
+            var appendBlob = _containerClient.GetAppendBlobClient($"{_categoryName}/{DateTimeOffset.UtcNow:yyyy-MM-dd}.log");
+            // var blobClient = _containerClient.GetBlobClient($"{_categoryName}/{DateTimeOffset.UtcNow:yyyy-MM-dd}.log");
+            if (!appendBlob.Exists())
+            {
+               await appendBlob.CreateAsync();
+                using var stream = new MemoryStream(Encoding.UTF8.GetBytes(message));
+               await appendBlob.AppendBlockAsync(stream);
+            }
+            else {
+                using var stream = new MemoryStream(Encoding.UTF8.GetBytes($"\n,{message}"));
+             await   appendBlob.AppendBlockAsync(stream);
+            }
+        }
+    }
+}

+ 82 - 0
TEAMModelOS/Filter/RequestAuditFilter.cs

@@ -0,0 +1,82 @@
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System.Security.Claims;
+using System;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Extension;
+using Microsoft.Extensions.Logging;
+using TEAMModelOS.SDK;
+using DocumentFormat.OpenXml.Office2010.Excel;
+using DocumentFormat.OpenXml.Wordprocessing;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using Azure.Core;
+using DocumentFormat.OpenXml.Office2016.Excel;
+
+namespace TEAMModelOS.Filter
+{
+    public class RequestAuditFilter : IAsyncActionFilter
+    {
+        private readonly ILogger _logger;
+      
+        public RequestAuditFilter(ILoggerFactory loggerFactory)
+        {
+            _logger = loggerFactory.CreateLogger<RequestAuditFilter>();
+        }
+        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+        {
+            //============== 这里是执行方法之前获取数据 ====================
+
+            // 获取控制器、路由信息
+            var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
+
+            // 获取请求的方法
+            var method = actionDescriptor.MethodInfo;
+
+            // 获取 HttpContext 和 HttpRequest 对象
+            var httpContext = context.HttpContext;
+            var httpRequest = httpContext.Request;
+
+            // 获取客户端 Ipv4 地址
+            var remoteIPv4 = httpContext.GetRemoteIpAddressToIPv4();
+
+            // 获取请求的 Url 地址
+           // var requestUrl = httpRequest.GetRequestUrlAddress();
+
+            
+            // 获取来源 Url 地址
+           //var refererUrl = httpRequest.GetRefererUrlAddress();
+
+            // 获取请求参数(写入日志,需序列化成字符串后存储)
+            var parameters = context.ActionArguments;
+
+            // 获取操作人(必须授权访问才有值)"userId" 为你存储的 claims type,jwt 授权对应的是 payload 中存储的键名
+            var userId = httpContext.User?.FindFirstValue("userId");
+            var authtoken = context.HttpContext.GetXAuth("AuthToken");
+            string id = string.Empty, name = string.Empty, picture = string.Empty, school = string.Empty;
+            if (!string.IsNullOrWhiteSpace(authtoken)) {
+                var jwt = new JwtSecurityTokenHandler().ReadJwtToken(authtoken);
+                id = jwt.Payload.Sub;
+                school = jwt.Payload.Azp;
+                name = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("name"))?.Value;
+            }
+            // 请求时间
+            var requestedTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+            //============== 这里是执行方法之后获取数据 ====================
+            var actionContext = await next();
+
+            // 获取返回的结果
+            var returnResult = actionContext.Result;
+
+            // 判断是否请求成功,没有异常就是请求成功
+            var isRequestSucceed = actionContext.Exception == null;
+
+            // 获取调用堆栈信息,提供更加简单明了的调用和异常堆栈
+           // var stackTrace = EnhancedStackTrace.Current();
+           // string region = await _searcher.SearchIpAsync(remoteIPv4);
+           //同一个账号,同一IP,同一接口,UA标识(UA标识随意切换则表示可能会存在DDOS),时间段
+            _logger.LogInformation(new{ ua=httpContext.GetUserAgent(), ip=remoteIPv4,time=requestedTime,path =$"{httpRequest.PathBase}{httpRequest.Path}",host= $"{httpRequest.Host}", param=parameters,id ,name ,school,succeed =isRequestSucceed }.ToJsonString());
+        }
+    }
+}

+ 9 - 0
TEAMModelOS/Program.cs

@@ -1,6 +1,7 @@
 
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
 using System;
 using TEAMModelOS.SDK;
 
@@ -16,6 +17,14 @@ namespace TEAMModelOS
 
         public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
+            //.ConfigureLogging(logging => {
+            //    logging.ClearProviders();
+            //    logging.AddConsole();
+            //    logging.AddDebug();
+            //    logging.AddEventLog();
+            //    logging.AddAzureWebAppDiagnostics();
+            //    logging.AddProvider(new BlobLoggerProvider());
+            //})
                .ConfigureWebHostDefaults(webBuilder =>
                {
 #if DEBUG 

+ 5 - 0
TEAMModelOS/Startup.cs

@@ -25,6 +25,7 @@ using Microsoft.AspNetCore.StaticFiles;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Primitives;
 using Microsoft.IdentityModel.Tokens;
 using TEAMModelOS.Controllers;
@@ -171,6 +172,9 @@ namespace TEAMModelOS
             services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));
             services.AddXkwAPIHttpService(Configuration);
             //services.AddHostedService<>
+            services.AddSingleton<ILoggerProvider, BlobLoggerProvider>();
+            services.AddMvcFilter<RequestAuditFilter>();
+            
         }
 
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -210,6 +214,7 @@ namespace TEAMModelOS
             //调用放在之后、UseRouting 和 UseCors,但在 UseEndpoints之前
             app.UseAuthentication();
             app.UseAuthorization();
+            
             app.UseEndpoints(endpoints =>
             {
                 endpoints.MapControllers();

+ 4 - 4
TEAMModelOS/TEAMModelOS.csproj

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

+ 29 - 29
TEAMModelOS/appsettings.Development.json

@@ -18,47 +18,47 @@
     "IdTokenSalt": "8263692E2213497BB55E74792B7900B4",
     "HttpTrigger": "https://teammodelosfunction-test.chinacloudsites.cn/api/",
     //"HttpTrigger": "http://localhost:7071/api/"
-    "Version": "5.2305.17.1"
+    "Version": "5.2305.31.1"
   },
   "Azure": {
     // 测试站数据库
-    //"Storage": {
-    //  "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodeltest;AccountKey=O2W2vadCqexDxWO+px+QK7y1sHwsYj8f/WwKLdOdG5RwHgW/Dupz9dDUb4c1gi6ojzQaRpFUeAAmOu4N9E+37A==;EndpointSuffix=core.chinacloudapi.cn"
-    //},
-    //"Cosmos": {
-    //  "ConnectionString": "AccountEndpoint=https://cdhabookdep-free.documents.azure.cn:443/;AccountKey=JTUVk92Gjsx17L0xqxn0X4wX2thDPMKiw4daeTyV1HzPb6JmBeHdtFY1MF1jdctW1ofgzqkDMFOtcqS46by31A==;"
-    //},
-    //"Redis": {
-    //  "ConnectionString": "52.130.252.100:6379,password=habook,ssl=false,abortConnect=False,writeBuffer=10240"
-    //},
-    //"ServiceBus": {
-    //  "ConnectionString": "Endpoint=sb://teammodelos.servicebus.chinacloudapi.cn/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=Sy4h4EQ8zP+7w/lOLi1X3tGord/7ShFHimHs1vC50Dc=",
-    //  "ActiveTask": "dep-active-task",
-    //  "ItemCondQueue": "dep-itemcond",
-    //  "GenPdfQueue": "dep-genpdf"
-    //},
-    //"SignalR": {
-    //  "ConnectionString": "Endpoint=https://channel.service.signalr.net;AccessKey=KrblW06tuA4a/GyqRPDU0ynFFmAWxbAvyJihHclSXbQ=;Version=1.0;"
-    //},
-    // 正式站数据库
     "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;"
+    },
+    // 正式站数据库
+    //"Storage": {
+    //  "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=teammodelos;AccountKey=Dl04mfZ9hE9cdPVO1UtqTUQYN/kz/dD/p1nGvSq4tUu/4WhiKcNRVdY9tbe8620nPXo/RaXxs+1F9sVrWRo0bg==;EndpointSuffix=core.chinacloudapi.cn"
+    //},
+    //"Cosmos": {
+    //  "ConnectionString": "AccountEndpoint=https://teammodelos.documents.azure.cn:443/;AccountKey=clF73GwPECfP1lKZTCvs8gLMMyCZig1HODFbhDUsarsAURO7TcOjVz6ZFfPqr1HzYrfjCXpMuVD5TlEG5bFGGg==;"
+    //},
+    //"Redis": {
+    //  "ConnectionString": "CoreRedisCN.redis.cache.chinacloudapi.cn:6380,password=LyJWP1ORJdv+poXWofAF97lhCEQPg1wXWqvtzXGXQuE=,ssl=True,abortConnect=False"
+    //},
+    //"ServiceBus": {
+    //  "ConnectionString": "Endpoint=sb://coreiotservicebuscnpro.servicebus.chinacloudapi.cn/;SharedAccessKeyName=TEAMModelOS;SharedAccessKey=llRPBMDJG9w1Nnifj+pGhV0g4H2REcq0PjvX2qqpcOg=",
+    //  "ActiveTask": "active-task",
+    //  "ItemCondQueue": "itemcond",
+    //  "GenPdfQueue": "genpdf"
+    //},
+    //"SignalR": {
+    //  "ConnectionString": "Endpoint=https://channel.signalr.azure.cn;AccessKey=AtcB7JYFNUbUXb1rGxa3PVksQ2X5YSv3JOHZR9J88tw=;Version=1.0;"
+    //}
   },
   "HaBookAuth": {
     "CoreId": {

+ 1 - 1
TEAMModelOS/appsettings.json

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