zhouj1203@hotmail.com 11 月之前
父节点
当前提交
5a2d7011ac
共有 55 个文件被更改,包括 2927 次插入1594 次删除
  1. 26 3
      TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue
  2. 1 1
      TEAMModelBI/ClientApp/src/view/htcommunity/adminpanel.vue
  3. 87 2
      TEAMModelBI/ClientApp/src/view/issueCoupons/crteadCoupon.vue
  4. 3 3
      TEAMModelBI/TEAMModelBI.csproj
  5. 2 2
      TEAMModelContest/contest.client/package.json
  6. 4 3
      TEAMModelContest/contest.client/src/utils/common.js
  7. 1 1
      TEAMModelContest/contest.client/src/view/myactivity/MyReview.vue
  8. 551 551
      TEAMModelOS.FunctionV4/HttpTrigger/BIHttpTrigger.cs
  9. 2 35
      TEAMModelOS.FunctionV4/HttpTrigger/IESHttpTrigger.cs
  10. 3 0
      TEAMModelOS.FunctionV4/Program.cs
  11. 3 3
      TEAMModelOS.FunctionV4/TEAMModelOS.FunctionV4.csproj
  12. 56 5
      TEAMModelOS.FunctionV4/TimeTrigger/IESTimerTrigger.cs
  13. 12 2
      TEAMModelOS.FunctionV4/local.settings.json
  14. 25 0
      TEAMModelOS.SDK/DI/CoreAPI/CoreAPIHttpService.cs
  15. 1 0
      TEAMModelOS.SDK/DI/HttpTrigger/HttpTrigger.cs
  16. 15 19
      TEAMModelOS.SDK/DI/Mail/MailFactory.cs
  17. 2 0
      TEAMModelOS.SDK/Models/Cosmos/School/ExamInfo.cs
  18. 1 1
      TEAMModelOS.SDK/Models/Service/BI/BIStats.cs
  19. 10 10
      TEAMModelOS.SDK/Models/Service/Common/TeacherService.cs
  20. 95 68
      TEAMModelOS.SDK/Models/Service/SystemService.cs
  21. 3 3
      TEAMModelOS.SDK/TEAMModelOS.SDK.csproj
  22. 1 1
      TEAMModelOS/ClientApp/public/index.html
  23. 6 4
      TEAMModelOS/ClientApp/public/lang/en-US.js
  24. 2 0
      TEAMModelOS/ClientApp/public/lang/zh-CN.js
  25. 2 0
      TEAMModelOS/ClientApp/public/lang/zh-TW.js
  26. 4 1
      TEAMModelOS/ClientApp/src/api/http.js
  27. 6 2
      TEAMModelOS/ClientApp/src/api/learnActivity.js
  28. 1 1
      TEAMModelOS/ClientApp/src/components/student-web/EventView/EventContentTypeTemplate/PaperViewBox/PaperTest.vue
  29. 1 1
      TEAMModelOS/ClientApp/src/css/site.css
  30. 6 2
      TEAMModelOS/ClientApp/src/utils/evTools.js
  31. 26 6
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseChild.vue
  32. 312 0
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseEditAnsScore.vue
  33. 43 16
      TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue
  34. 10 1
      TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue
  35. 203 13
      TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.vue
  36. 1024 656
      TEAMModelOS/ClientApp/src/view/iot/areaiot.vue
  37. 1 1
      TEAMModelOS/ClientApp/src/view/iot/schooliot.vue
  38. 4 4
      TEAMModelOS/ClientApp/src/view/learnactivity/ExamMgt.vue
  39. 6 2
      TEAMModelOS/ClientApp/src/view/learnactivity/tabs/ExamPaper.vue
  40. 2 1
      TEAMModelOS/ClientApp/src/view/mycourse/score/DialogBoxAddTable.vue
  41. 5 1
      TEAMModelOS/ClientApp/src/view/mycourse/score/ScoreBody.vue
  42. 25 0
      TEAMModelOS/Controllers/Both/PaperController.cs
  43. 92 85
      TEAMModelOS/Controllers/Both/ScoreCalcController.cs
  44. 5 5
      TEAMModelOS/Controllers/Client/HiScanController.cs
  45. 5 5
      TEAMModelOS/Controllers/Client/HiTAControlller.cs
  46. 11 11
      TEAMModelOS/Controllers/Client/HiTeachController.cs
  47. 10 10
      TEAMModelOS/Controllers/Student/StudentController.cs
  48. 5 5
      TEAMModelOS/Controllers/Student/TmdUserController.cs
  49. 7 23
      TEAMModelOS/Controllers/System/WeChatPayController.cs
  50. 6 4
      TEAMModelOS/Controllers/Third/IRS/ThirdIRSController.cs
  51. 184 12
      TEAMModelOS/Controllers/XTest/TestController.cs
  52. 1 1
      TEAMModelOS/Startup.cs
  53. 4 4
      TEAMModelOS/TEAMModelOS.csproj
  54. 3 3
      TEAMModelOS/appsettings.Development.json
  55. 1 1
      TEAMModelOS/appsettings.json

+ 26 - 3
TEAMModelBI/ClientApp/src/view/areaServe/areamanage.vue

@@ -185,7 +185,7 @@
             <div class="haveSchool">
               <div class="schoolLeft" v-loading="loadingSchoolList" element-loading-text="数据加载中..." v-if="PowerShow">
                 <div class="haveSchool-title">
-                  <div class="select-text">{{ $t(`areaManages.operational.areaAddSchool.schooltitle`) }}</div>
+                  <div class="select-text">{{ $t(`areaManages.operational.areaAddSchool.schooltitle`) }}</div>                                  
                   <div class="select-click">
                     <el-dropdown trigger="click" @command="handleCommand">
                       <span class="el-dropdown-link">
@@ -218,6 +218,10 @@
                   </div>
                   <div class="synchronization-title" v-show="currentlySelect.cutArea"><span>已同步省平台</span></div>
                 </div>
+                <div  class="search-school-btn">
+                <el-button type="primary"  @click="selectAll" size="small">全選</el-button>
+                  <el-button type="primary"  @click="deselectAll" size="small">全不選</el-button>
+                </div>
                 <ul>
                   <li class="details-list-school" v-for="(item, index) in notjoinSchool" :key="item.id" :class="{ active: position === index }">
                     <div class="details-list-checkboxs" @change="multipleChange">
@@ -1449,7 +1453,10 @@ export default {
     //学校列表 未加入学区
     function notjoinSchoolarea () {
       let values = schoolSeach.value
-      let newdata = notjoinPrimitive.value.filter((item) => { return item.name.includes(values) })
+      let newdata = notjoinPrimitive.value.filter((item) => {        
+        // 撈出名稱跟地址包含關鍵字的學校
+         return item.name.includes(values) || item.city?.includes(values) 
+        })        
       notjoinSchool.value = JSON.parse(JSON.stringify(newdata))
     }
     //学校显示(虚拟、实体)
@@ -1518,6 +1525,20 @@ export default {
       return jsonData.map((v) => filterVal.map((j) => v[j]))
     }
 
+    function selectAll() {                
+      notjoinSchool.value.forEach(item => {
+        item.checkstate = true;
+      });      
+      multiplecheck.value = true;
+    }
+    function deselectAll() {      
+      notjoinSchool.value.forEach(item => {
+        item.checkstate = false;
+      });    
+      multiplecheck.value = false;
+    }
+
+
     watch(abilityModel, (newdata) => {
       console.log(newdata)
       newdata
@@ -1653,7 +1674,9 @@ export default {
       exportAreadata,
       exportData,
       exportExcel,
-      formatJson
+      formatJson,
+      selectAll,
+      deselectAll
     }
   },
 }

+ 1 - 1
TEAMModelBI/ClientApp/src/view/htcommunity/adminpanel.vue

@@ -63,7 +63,7 @@
                     </el-table-column>
                 </el-table>
                 <div style="margin-top: 0px; width: 100%; text-align: left; " v-show="isShowCsSchool">
-                    <el-select v-model="selectedSchInfo" value-key="shortCode" :placeholder="$t('purchase.selectSchool')" style="width: 50%">
+                    <el-select v-model="selectedSchInfo" filterable value-key="shortCode" :placeholder="$t('purchase.selectSchool')" style="width: 50%">
                         <el-option v-for="item in csSchoolList" :key="item.shortCode" :label="item.name + ' (' + item.shortCode + ')'" :value="item" />
                     </el-select>
                     <el-button style="margin-left: 5px;" @click="addSchoolSeat()">{{$t('purchase.confirmOk')}}</el-button>

+ 87 - 2
TEAMModelBI/ClientApp/src/view/issueCoupons/crteadCoupon.vue

@@ -17,10 +17,14 @@
                     <el-radio label="hiteachBookSPLicense"  border>HiTeach專書贈送授權</el-radio>
                     <el-radio label="fbGroupOpening"  border>HiTeach社團開幕活動</el-radio>
                     <el-radio label="giftWebIRS501M"  border>贈送WEBIRS50一個月</el-radio>
+                    <el-radio label="giftWebIRS503M"  border>贈送WEBIRS50三個月</el-radio>
+                    <el-radio label="giftSmart3M"  border>贈送智慧評分三個月</el-radio>
                     <el-radio label="giftSmart1Y"  border>贈送智慧評分一年</el-radio>
+                    <el-radio label="gifCollaboration3M"  border>贈送協作三個月</el-radio>
                     <el-radio label="gifCollaboration1Y"  border>贈送協作一年</el-radio>
                     <el-radio label="gifAIGPT1Y"  border>贈送AIGPT一年</el-radio>
                     <el-radio label="gifVoiceToText1Y"  border>贈送語音轉錄文字一年</el-radio>
+                    <el-radio label="gifAITXTAnalysis3M"  border>贈送AI文句分析三個月</el-radio>
                 </el-radio-group>
             </el-form-item>
             <el-form-item v-if="detailSettingFlag" label="票券類型" prop="couponType" >
@@ -207,6 +211,41 @@ const shortcuts = [
 const eventType= ref('')
 
 const setEventType = (type)=>{
+    
+    // 重置規則和資訊
+    crtCouponForm.info = [
+        {
+            "l": "zh-tw",
+            "n": "",
+            "u": ""
+        },
+        {
+            "l": "zh-cn",
+            "n": "",
+            "u": ""
+        },
+        {
+            "l": "en-us",
+            "n": "",
+            "u": ""
+        }
+    ]
+
+    crtCouponForm.rule = [
+        {
+            "q": [
+                {
+                    operator: '',
+                    type: '',
+                    how: '',
+                    val:''
+                }
+            ],
+            "b": "",
+            "p": "0"
+        }
+    ]
+
     switch(type){
         case 'hiteach50-3':
             crtCouponForm.couponType =  'Event'
@@ -337,6 +376,28 @@ const setEventType = (type)=>{
             crtCouponForm.info[2].l = 'en-us'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/'
         break
+        case 'giftWebIRS503M':
+            crtCouponForm.couponType =  'Event'
+            crtCouponForm.eventName = 'giftWebIRS503M'
+            crtCouponForm.rule[0].b = '901003'
+            crtCouponForm.info[0].l = 'zh-tw' 
+            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
+            crtCouponForm.info[1].l = 'zh-cn'
+            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
+            crtCouponForm.info[2].l = 'en-us'
+            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
+        break
+        case 'giftSmart3M':
+            crtCouponForm.couponType =  'Event'
+            crtCouponForm.eventName = 'giftSmart3M'
+            crtCouponForm.rule[0].b = '901008'
+            crtCouponForm.info[0].l = 'zh-tw' 
+            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
+            crtCouponForm.info[1].l = 'zh-cn'
+            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
+            crtCouponForm.info[2].l = 'en-us'
+            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
+        break
         case 'giftSmart1Y':
             crtCouponForm.couponType =  'Event'
             crtCouponForm.eventName = 'giftSmart1Y'
@@ -359,6 +420,28 @@ const setEventType = (type)=>{
             crtCouponForm.info[2].l = 'en-us'
             crtCouponForm.info[2].u = 'https://www.habook.com/en/'
         break
+        case 'gifCollaboration3M':
+            crtCouponForm.couponType =  'Event'
+            crtCouponForm.eventName = 'gifCollaboration3M'
+            crtCouponForm.rule[0].b = '901011'
+            crtCouponForm.info[0].l = 'zh-tw' 
+            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
+            crtCouponForm.info[1].l = 'zh-cn'
+            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
+            crtCouponForm.info[2].l = 'en-us'
+            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
+        break
+        case 'gifAITXTAnalysis3M':
+            crtCouponForm.couponType =  'Event'
+            crtCouponForm.eventName = 'gifAITXTAnalysis3M'
+            crtCouponForm.rule[0].b = '901012'
+            crtCouponForm.info[0].l = 'zh-tw' 
+            crtCouponForm.info[0].u = 'https://www.habook.com/zh-tw'
+            crtCouponForm.info[1].l = 'zh-cn'
+            crtCouponForm.info[1].u = 'https://www.habook.com.cn/'
+            crtCouponForm.info[2].l = 'en-us'
+            crtCouponForm.info[2].u = 'https://www.habook.com/en/'
+        break
         case 'gifAIGPT1Y':
             crtCouponForm.couponType =  'Event'
             crtCouponForm.eventName = 'gifAIGPT1Y'
@@ -423,7 +506,7 @@ const howList = [
 const ruleBList = [
     { label: "AI文字分析模組(一年)", val: "901001"},
     { label: "AI蘇格拉底小數據(一年)", val: "901002"},
-    { label: "Web IRS 50人 (展延3個月)", val: "901003"},
+    { label: "Web IRS 50人 (給予或展延3個月)", val: "901003"},
     { label: "Web IRS 50人 (延長1個月)", val: "901004"},
     { label: "智慧評分 (一年)", val: "901005"},
     { label: "HiTeachCC-6任務數(展延)", val: "903001"},
@@ -434,7 +517,9 @@ const ruleBList = [
     { label: "AI GPT(一年)", val: "901007"},
     { label: "語音轉錄文字(一年)", val: "901010"},
     { label: "智慧評分 (三個月)", val: "901008"},
-    { label: "Web IRS 50人(3個月+75天- 50券STEP3專用)", val: "901009"}    
+    { label: "Web IRS 50人(3個月+75天- 50券STEP3專用)", val: "901009"},
+    { label: "小組協作(三個月)", val: "901011"},
+    { label: "AI文句分析(三個月)", val: "901012"},
 ]
 
 const crtCouponForm = reactive({

+ 3 - 3
TEAMModelBI/TEAMModelBI.csproj

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

+ 2 - 2
TEAMModelContest/contest.client/package.json

@@ -3,8 +3,8 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
-    "serve": "vue-cli-service serve",
-    "build": "vue-cli-service build",
+    "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
+    "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
     "lint": "vue-cli-service lint"
   },
   "dependencies": {

+ 4 - 3
TEAMModelContest/contest.client/src/utils/common.js

@@ -1,4 +1,5 @@
 import SparkMD5 from "spark-md5"
+import { ElMessage } from 'element-plus'
 
 export default {
     /* 时间格式化 */
@@ -94,9 +95,9 @@ export default {
 						else if (videoTrack && !audioTrack) {
 							let videoFormat = videoTrack.Format.toLowerCase()
 							if (videoFormat === 'avc') {
-								Modal.info({
-									title: "温馨提示",
-									content: "视频文件未检测到音频轨,建议您调整后重新上传"
+								ElMessage({
+									type: 'warning',
+									message: '视频文件未检测到音频轨,建议您调整后重新上传'
 								})
 								r(true)
 							} else {

+ 1 - 1
TEAMModelContest/contest.client/src/view/myactivity/MyReview.vue

@@ -109,7 +109,7 @@
                                                     </div>
                                                 </div>
                                                 <div class="rule-tree">
-                                                    <el-table :data="ruleInfo.trees" row-key="id" default-expand-all height="600">
+                                                    <el-table :data="ruleInfo.trees" row-key="id" default-expand-all height="500">
                                                         <el-table-column prop="label" :label="$t('home.ruleInfo.rule')" align="center" width="170" />
                                                         <el-table-column prop="desc" :label="$t('home.ruleInfo.describe')" align="center" />
                                                         <el-table-column prop="oldScore" :label="$t('home.ruleInfo.score')" align="center" width="100" />

文件差异内容过多而无法显示
+ 551 - 551
TEAMModelOS.FunctionV4/HttpTrigger/BIHttpTrigger.cs


+ 2 - 35
TEAMModelOS.FunctionV4/HttpTrigger/IESHttpTrigger.cs

@@ -74,41 +74,7 @@ namespace TEAMModelOS.FunctionV4
             _option = option?.Value;
             _configuration = configuration;
         }
-        // <summary>
-        /// </summary>
-        /// <param name="req"></param>
-        /// <param name="log"></param>
-        /// <returns></returns>
-        [Function("http-log")]
-        public async Task<HttpResponseData> HttpLog([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req) {
-            var response = req.CreateResponse(HttpStatusCode.OK);
-            try {
-                string data;
-                using (var reader = new StreamReader(req.Body))
-                {
-                    data = await reader.ReadToEndAsync();
-                }
-                var gmt8Time = DateTimeOffset.UtcNow.GetGMTTime(8);
-                var appendBlob = _azureStorage.GetBlobContainerClient("0-service-log").GetAppendBlobClient($"http-log/{gmt8Time:yyyy}/{gmt8Time:MM}/{gmt8Time:dd}/{gmt8Time:HH}.log");
-                if (!await appendBlob.ExistsAsync())
-                {
-                    await appendBlob.CreateAsync();
-                }
-                using (var stream = new MemoryStream(Encoding.UTF8.GetBytes($"{data},\n")))
-                {
-                    await appendBlob.AppendBlockAsync(stream);
-                }
-                await response.WriteAsJsonAsync(new { code = 1 });
-                return response;  
-
-            } catch(Exception ex ) {
-                await _dingDing.SendBotMsg($"{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
-                await response.WriteAsJsonAsync(new { code = 1 });
-                return response;
-            }
-           
-        }
-
+       
          /// <summary>
          /// </summary>
          /// <param name="req"></param>
@@ -713,6 +679,7 @@ namespace TEAMModelOS.FunctionV4
         public async Task<HttpResponseData> OnlineRecord([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req)
         {
             var response = req.CreateResponse(HttpStatusCode.OK);
+            return response;
             string data = await new StreamReader(req.Body).ReadToEndAsync();
             var json = JsonDocument.Parse(data).RootElement;
             try

+ 3 - 0
TEAMModelOS.FunctionV4/Program.cs

@@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting;
 
 using System;
 using System.Collections.Generic;
+using System.Configuration;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
@@ -22,6 +23,7 @@ using TEAMModelOS.FunctionV4.ServiceBus;
 using TEAMModelOS.Models;
 using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.DI.Mail;
 using TEAMModelOS.SDK.DI.Multiple;
 
 namespace TEAMModelOS.FunctionV4
@@ -84,6 +86,7 @@ namespace TEAMModelOS.FunctionV4
                services.AddHostedService<BlobRootServiceBusSub>();
                services.AddIPSearcher("");
                services.TryAddSingleton(new Region2LongitudeLatitudeTranslator(""));
+               services.AddNetMail( );
            })
            .Build();
             await host.RunAsync();

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

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

+ 56 - 5
TEAMModelOS.FunctionV4/TimeTrigger/IESTimerTrigger.cs

@@ -8,7 +8,9 @@ using HTEXLib.COMM.Helpers;
 using Microsoft.Azure.Cosmos.Table;
 using Microsoft.Azure.Functions.Worker;
 using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using Microsoft.OData.Edm;
 using StackExchange.Redis;
 using System;
@@ -23,8 +25,10 @@ using System.Text;
 using System.Text.Json;
 using System.Threading.Tasks;
 using System.Web;
+using TEAMModelOS.Models;
 using TEAMModelOS.SDK;
 using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.DI.Mail;
 using TEAMModelOS.SDK.Extension;
 using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Models.Cosmos.School;
@@ -50,9 +54,13 @@ namespace TEAMModelOS.FunctionV4.TimeTrigger
         private readonly IConverter _converter;
         private readonly SnowflakeId _snowflakeId;
         private readonly IHttpClientFactory _httpClient;
+        private readonly CoreAPIHttpService _coreAPIHttpService;
         private IPSearcher _ipSearcher;
         private readonly Region2LongitudeLatitudeTranslator _longitudeLatitudeTranslator;
-        public IESTimerTrigger(Region2LongitudeLatitudeTranslator longitudeLatitudeTranslator, IPSearcher ipSearcher, IHttpClientFactory httpClient, SnowflakeId snowflakeId, IConverter converter, AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis)
+        private readonly MailFactory _mailFactory;
+        private readonly IConfiguration _configuration;
+
+        public IESTimerTrigger(IConfiguration configuration,MailFactory mailFactory,  CoreAPIHttpService coreAPIHttpService, Region2LongitudeLatitudeTranslator longitudeLatitudeTranslator, IPSearcher ipSearcher, IHttpClientFactory httpClient, SnowflakeId snowflakeId, IConverter converter, AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis)
         {
             _azureCosmos = azureCosmos;
             _dingDing = dingDing;
@@ -62,7 +70,10 @@ namespace TEAMModelOS.FunctionV4.TimeTrigger
             _snowflakeId=snowflakeId;
             _httpClient = httpClient;
             _ipSearcher = ipSearcher;
+            _coreAPIHttpService = coreAPIHttpService;
             _longitudeLatitudeTranslator = longitudeLatitudeTranslator;
+            _mailFactory = mailFactory;
+            _configuration= configuration;
         }
         /// <summary>
         /// 防火墙日志记录文件
@@ -71,13 +82,53 @@ namespace TEAMModelOS.FunctionV4.TimeTrigger
         /// <param name="log"></param>
         /// <returns></returns>
         [Function("DailyReport")]
-        //https://docs.azure.cn/zh-cn/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-csharp
         //0 1 * * * * 一天中每小时的第 1 分钟
         //0 */10 * * * *  每五分钟一次
-        public async Task DailyReport([TimerTrigger("0 1 * * * *")] TimerInfo myTimer, ILogger log)
+        public async Task DailyReport([TimerTrigger("0 40 * * * *")] TimerInfo myTimer, ILogger log)
         {
-            
+            try
+            {
+                await _dingDing.SendBotMsg($"{_coreAPIHttpService.options.location},定时任务{DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss")}", GroupNames.成都开发測試群組);
+                string local = Environment.GetEnvironmentVariable("Option:Location");
+               // if (local.Contains("Test")||local.Contains("Dep"))
+                {
 
+                    int am =9;
+                    int pm = 9;
+                    //int am = DateTimeOffset.Now.Hour;
+                    //int pm = DateTimeOffset.Now.Hour;
+                    var url = Environment.GetEnvironmentVariable("HaBookAuth:CoreAPI");
+                    var clientID = Environment.GetEnvironmentVariable("HaBookAuth:CoreService:clientID");
+                    var clientSecret = Environment.GetEnvironmentVariable("HaBookAuth:CoreService:clientSecret");
+                    string location = "";
+                    if (local.Contains("China"))
+                    {
+                        location = "China";
+                    }
+                    else if (local.Contains("Global"))
+                    {
+                        location = "Global";
+                    }
+                    var client = _httpClient.CreateClient();
+                    var token = CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, location).Result;
+                    if (client.DefaultRequestHeaders.Contains("Authorization"))
+                    {
+                        client.DefaultRequestHeaders.Remove("Authorization");
+                        client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
+                    }
+                    else
+                    {
+                        client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
+                    }
+                    var data = await TEAMModelOS.SDK.Models.Service.SystemService.AccumulateDaily(_configuration,_azureRedis, _azureCosmos, _coreAPIHttpService, _dingDing, client, _snowflakeId, url, _mailFactory, am, pm);
+                    //await _dingDing.SendBotMsg($"返回数据{data.ToJsonString()}", GroupNames.成都开发測試群組);
+                }
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{DailyReport}\n{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+            }
+             
         }
         /// <summary>
         /// 防火墙日志记录文件
@@ -85,7 +136,7 @@ namespace TEAMModelOS.FunctionV4.TimeTrigger
         /// <param name="req"></param>
         /// <param name="log"></param>
         /// <returns></returns>
-        [Function("FireWallFileLog")]
+       // [Function("FireWallFileLog")]
         //https://docs.azure.cn/zh-cn/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-csharp
         //0 1 * * * * 一天中每小时的第 1 分钟
         //0 */10 * * * *  每五分钟一次

+ 12 - 2
TEAMModelOS.FunctionV4/local.settings.json

@@ -23,7 +23,12 @@
     "Azure:CoreService:delnotification": "https://api2.teammodel.cn/service/delnotification",
     "Azure:CoreService:deviceinfo": "https://api2.teammodel.cn/oauth2/getdeviceinfos",
     "Azure:CoreService:getnotification": "https://api2.teammodel.cn/service/getnotification",
-    "Azure:CoreService:sendnotification": "https://api2.teammodel.cn/service/sendnotification"
+    "Azure:CoreService:sendnotification": "https://api2.teammodel.cn/service/sendnotification",
+    "MailOption:fromMail": "customer_service@cdhabook.com",
+    "MailOption:smtp": "smtp.263.net",
+    "MailOption:port": "25",
+    "MailOption:username": "customer_service@cdhabook.com",
+    "MailOption:password": "CDhabook2023~!@"
   }
   //"Values": {
   //  "AzureWebJobsSecretStorageType": "Blob",
@@ -49,6 +54,11 @@
   //  "Azure:CoreService:delnotification": "https://api2.teammodel.cn/service/delnotification",
   //  "Azure:CoreService:deviceinfo": "https://api2.teammodel.cn/oauth2/getdeviceinfos",
   //  "Azure:CoreService:getnotification": "https://api2.teammodel.cn/service/getnotification",
-  //  "Azure:CoreService:sendnotification": "https://api2.teammodel.cn/service/sendnotification"
+  //  "Azure:CoreService:sendnotification": "https://api2.teammodel.cn/service/sendnotification",
+  //  "MailOption:fromMail": "customer_service@cdhabook.com",
+  //  "MailOption:smtp": "smtp.263.net",
+  //  "MailOption:port": "25",
+  //  "MailOption:username": "customer_service@cdhabook.com",
+  //  "MailOption:password": "CDhabook2023~!@"
   //}
 }

+ 25 - 0
TEAMModelOS.SDK/DI/CoreAPI/CoreAPIHttpService.cs

@@ -863,6 +863,31 @@ lang 语系"zh-cn" ,zh-tw", "en-us"
                 return null;
             }
         }
+        public async Task  SendMail(Dictionary<string, object> data, string location, IConfiguration _configuration) 
+        {
+            var url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
+            //url = "https://api2-rc.teammodel.cn";
+            url = $"{url}/service/sendmail";
+            var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+            var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+            if (location.Contains("China"))
+            {
+                location = "China";
+            }
+            else if (location.Contains("Global"))
+            {
+                location = "Global";
+            }
+            var client = _httpClient;
+            var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, location);
+            if (client.DefaultRequestHeaders.Contains("Authorization"))
+            {
+                client.DefaultRequestHeaders.Remove("Authorization");
+            }
+            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
+            HttpResponseMessage responseMessage = await client.PostAsJsonAsync(url, data);
+        }
+
         /// <summary>
         ///  获取单个用户信息
         /// </summary>

+ 1 - 0
TEAMModelOS.SDK/DI/HttpTrigger/HttpTrigger.cs

@@ -63,6 +63,7 @@ namespace TEAMModelOS.SDK.DI
             }
             string link = domain.Contains("localhost") ? $"http://{domain}/api/{url}" : $"https://{domain}/api/{url}";
             HttpContent httpContent = new StringContent(data.ToJsonString());
+            _httpClient.Timeout=  TimeSpan.FromSeconds(30);
             HttpResponseMessage responseMessage = await _httpClient.PostAsync(link, httpContent);
             if (responseMessage.StatusCode == HttpStatusCode.OK)
             {

+ 15 - 19
TEAMModelOS.SDK/DI/Mail/MailFactory.cs

@@ -1,4 +1,6 @@
-using Microsoft.Extensions.DependencyInjection;
+using DocumentFormat.OpenXml.Wordprocessing;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection.Extensions;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
@@ -18,13 +20,16 @@ namespace TEAMModelOS.SDK.DI.Mail
     public class MailFactory
     {
         private readonly ILogger _logger;
-        private readonly IOptionsMonitor<MailOptions> _optionsMonitor;
+        private readonly MailOptions _optionsMonitor;
         private ConcurrentDictionary<string, SmtpClient> smtpClients { get; } = new ConcurrentDictionary<string, SmtpClient>();
-        public MailFactory(IOptionsMonitor<MailOptions> optionsMonitor, ILogger<MailFactory> logger)
+        public MailFactory( ILogger<MailFactory> logger, IConfiguration _configuration)
         {
-
-            if (optionsMonitor == null) throw new ArgumentNullException(nameof(optionsMonitor));
-            _optionsMonitor = optionsMonitor;
+            _optionsMonitor= new MailOptions();
+            _optionsMonitor.fromMail=_configuration.GetValue<string>("MailOption:fromMail");
+            _optionsMonitor.port=_configuration.GetValue<Int32>("MailOption:port");
+            _optionsMonitor.smtp=_configuration.GetValue<string>("MailOption:smtp");
+            _optionsMonitor.username=_configuration.GetValue<string>("MailOption:username");
+            _optionsMonitor.password=_configuration.GetValue<string>("MailOption:password");
             _logger = logger;
         }
 
@@ -32,14 +37,14 @@ namespace TEAMModelOS.SDK.DI.Mail
         {
             try
             {
-                var cklient = smtpClients.GetOrAdd(name, x => new SmtpClient(_optionsMonitor.Get(name).smtp)
+                var cklient = smtpClients.GetOrAdd(name, x => new SmtpClient(_optionsMonitor.smtp)
                 {
-                    Credentials= new NetworkCredential(_optionsMonitor.Get(name).username, _optionsMonitor.Get(name).password),
+                    Credentials= new NetworkCredential(_optionsMonitor.username, _optionsMonitor.password),
                     UseDefaultCredentials=false,
                     EnableSsl=true
                 });
                 //955 是错误的。
-                cklient.Port= _optionsMonitor.Get(name).port;
+                cklient.Port= _optionsMonitor.port;
                 return cklient;
             }
             catch (OptionsValidationException e)
@@ -52,19 +57,10 @@ namespace TEAMModelOS.SDK.DI.Mail
     }
     public static class NetMailFactoryExtensions
     {
-        public static IServiceCollection AddNetMail(this IServiceCollection services, MailOptions mailOptions, string name = "Default")
+        public static IServiceCollection AddNetMail(this IServiceCollection services )
         {
             if (services == null) throw new ArgumentNullException(nameof(services));
-            if (mailOptions == null) throw new ArgumentNullException(nameof(mailOptions));
             services.TryAddSingleton<MailFactory>();
-            services.Configure<MailOptions>(name, o => {
-                o.Name = name;
-                o.fromMail=mailOptions.fromMail;
-                o.port=mailOptions.port;
-                o.smtp=mailOptions.smtp;
-                o.username=mailOptions.username;
-                o.password=mailOptions.password;
-            });
             return services;
         }
         public static string SendEmail(this SmtpClient smtpClient, AzureCosmosFactory azureCosmos,DingDing dingDing  , TMDOrder order, string subject, string content, string toMail,string tmdid,string tmdname ,string sender = "醍摩豆客服助手")

+ 2 - 0
TEAMModelOS.SDK/Models/Cosmos/School/ExamInfo.cs

@@ -129,6 +129,8 @@ namespace TEAMModelOS.SDK.Models
         public List<FMember> staffIds { get; set; } = new List<FMember>();
         //是否啟動cloudas計算 true:啟動
         public bool cloudas { get; set;}
+        //統測ID [測試中]
+        //public string jointExamId { get; set; }
         public string moofenCode { get; set; }
     }
     public class Custom {

+ 1 - 1
TEAMModelOS.SDK/Models/Service/BI/BIStats.cs

@@ -34,7 +34,7 @@ namespace TEAMModelOS.SDK.Models.Service.BI
         {
             try
             {
-                if (string.IsNullOrEmpty(scId))
+                if (string.IsNullOrEmpty(scId) || scId.Equals("SYSTEM_NO_SCHOOL",StringComparison.OrdinalIgnoreCase))
                     return;
 
                 DateTimeOffset cuurDateOff = DateTimeOffset.UtcNow;

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

@@ -184,11 +184,11 @@ namespace TEAMModelOS.Services
             //換取AuthToken,提供給前端
 
             //用户在线记录
-            try
-            {
-                _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-            }
-            catch { }
+            //try
+            //{
+            //    _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+            //}
+            //catch { }
 
             //換取AuthToken,提供給前端
             var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTeacher, standard:  "", roles: roles.ToArray(), expire: 1);
@@ -496,11 +496,11 @@ namespace TEAMModelOS.Services
             var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTeacher, standard: areaa != null ? areaa.standard : "", roles: roles.ToArray(), expire: 1);
 
             //用户在线记录
-            try
-            {
-                _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-            }
-            catch { }
+            //try
+            //{
+            //    _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+            //}
+            //catch { }
 
 
             //取得Teacher Blob 容器位置及SAS 

+ 95 - 68
TEAMModelOS.SDK/Models/Service/SystemService.cs

@@ -7,6 +7,7 @@ using HTEXLib;
 using HTEXLib.COMM.Helpers;
 using HTEXLib.PPTX.Models;
 using Microsoft.Azure.Amqp.Framing;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Newtonsoft.Json.Linq;
 using NUnit.Framework.Internal.Execution;
@@ -49,16 +50,14 @@ IES晚间报告:<br>{tmdname}您好,以下是您的今日个人IES教学汇
 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如有布置作业任务,将于次日8点通过早报方式发送。
 ";
         static string cn_examList = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{examName}】已经有{submitCount}位学生提交,仍有{unsubmitCount}位学生未提交。<br>";
-        static string cn_lessonList = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{lessonName}】课例应出席人数{memberCount},实际出席人数{attendCount},出席率{attendRate}。<br>";
-        static string cn_groupListJoin = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{grouplistName}】已有总人数{memberCount},有{joinCount}加入。<br>";
-        static string cn_groupListLeave = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{grouplistName}】已有总人数{memberCount},有{joinCount}离开。<br>";
+        static string cn_lessonList = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{lessonName}】课例应出席人数{memberCount},实际出席人数{attendCount},出席率{attendRate}%。<br>";
+        static string cn_groupListJoin = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{grouplistName}】已有总人数{memberCount},有{joinCount}加入。<br>";
+        static string cn_groupListLeave = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{grouplistName}】已有总人数{memberCount},有{joinCount}离开。<br>";
 
         static string cn_zb = @"
-IES早间报告:
-    {tmdname}您好,以下是您发布过的作业任务汇总报告。
-     以下是报告的具体详细信息。
-    {cn_homeworkTitle}
-        {cn_homeworkList}
+IES早间报告:<br>{tmdname}您好,以下是您发布过的作业任务汇总报告。
+<br>&nbsp;&nbsp;&nbsp;以下是报告的具体详细信息。
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{cn_homeworkTitle}<br>{cn_homeworkList}
 ";
         static string cn_homeworkList = "【{homeworkName}】已经有{submitCount}位学生提交,仍有{unsubmitCount}位学生未提交。<br>";
         static string cn_homeworkTitle = "作业任务提交详情:";
@@ -66,73 +65,71 @@ IES早间报告:
         static string cn_lessonTitle = "课堂教学出席详情:";
         static string cn_groupTitle = "个人课程名单变化详情:";
         #endregion
-
         #region
         static string tw_wb = @"
-IES晚間報告:
-    {tmdname}您好,以下是您的今日個人IES教學總結報告,截至{sendTime},您已發布{examCount}次測驗任務,{homeworkCount}次作業任務,並使用HiTeach上傳了{lessonCount}節課堂教學活動。
-    以下是詳細資訊。
-    {tw_examTitle}
-        {tw_examList}
-    {tw_lessonTitle}
-        {tw_lessonList}
-    {tw_groupTitle}
-        {tw_groupList}
-    如有佈署作業任務,將於隔天早上8點透過晨間報告方式發送。
+IES晚間報告:<br>{tmdname}您好,以下是您的今日個人IES教學總結報告,截至{sendTime},您已發布{examCount}次測驗任務,{homeworkCount}次作業任務,並使用HiTeach上傳了{lessonCount}節課堂教學活動。
+<br>&nbsp;&nbsp;&nbsp;以下是詳細資訊。
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{tw_examTitle}<br>{tw_examList}
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{tw_lessonTitle}<br>{tw_lessonList}
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{tw_groupTitle}<br>{tw_groupList}
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如有佈署作業任務,將於隔天早上8點透過晨間報告方式發送。
 ";
-        static string tw_examList = "【{examName}】已經有{submitCount}位學生繳交,仍有{unsubmitCount}位學生未繳交。\r\n";
-        static string tw_lessonList = "【{lessonName}】課堂應出席人數{memberCount},實際出席人數{attendCount},出席率{attendRate}。\r\n";
-        static string tw_groupListJoin = "【{grouplistName}】已有總人數{memberCount},有{joinCount}加入。\r\n";
-        static string tw_groupListLeave = "【{grouplistName}】已有總人數{memberCount},有{joinCount}離開。\r\n";
-
+        static string tw_examList = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{examName}】已經有{submitCount}位學生繳交,仍有{unsubmitCount}位學生未繳交。<br>";
+        static string tw_lessonList = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{lessonName}】課堂應出席人數{memberCount},實際出席人數{attendCount},出席率{attendRate}%。<br>";
+        static string tw_groupListJoin = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{grouplistName}】已有總人數{memberCount}位,有{joinCount}位加入。<br>";
+        static string tw_groupListLeave = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;【{grouplistName}】已有總人數{memberCount}位,有{joinCount}位離開。<br>";
         static string tw_zb = @"
-IES晨間報告:
-    {tmdname}您好,以下是您曾經發佈過的作業任務總結報告。
-    以下是具體詳細資訊。
-    {tw_homeworkTitle}
-        {tw_homeworkList}
+IES晨間報告:<br>{tmdname}您好,以下是您曾經發佈過的作業任務總結報告。
+<br>&nbsp;&nbsp;&nbsp;以下是具體詳細資訊。
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{tw_homeworkTitle}<br>{tw_homeworkList}
 ";
-        static string tw_homeworkList = "【{homeworkName}】已經有{submitCount}位學生繳交,仍有{unsubmitCount}位學生未繳交。\r\n";
-        static string tw_homeworkTitle = "\r\n作業任務繳交詳情:";
-        static string tw_examTitle = "\r\n測驗任務完成詳情:";
-        static string tw_lessonTitle = "\r\n課堂教學出席詳情:";
-        static string tw_groupTitle = "\r\n個人課程名單變動詳情:";
+        static string tw_homeworkList = "【{homeworkName}】已經有{submitCount}位學生繳交,仍有{unsubmitCount}位學生未繳交。<br>";
+        static string tw_homeworkTitle = "作業任務繳交詳情:";
+        static string tw_examTitle = "測驗任務完成詳情:";
+        static string tw_lessonTitle = "課堂教學出席詳情:";
+        static string tw_groupTitle = "個人課程名單變動詳情:";
         #endregion
-
-
         #region
         static string en_wb = @"
-IES Evening Report:
-    Hello {tmdname}, here is your personal IES teaching summary report for today. As of {sendTime}, you have published {examCount} test tasks, {homeworkCount} homework tasks, and uploaded {lessonCount} HiTeach lesson activity records.
-    The following are the specific details.
-    {en_examTitle}
-        {en_examList}
-    {en_lessonTitle}
-        {en_lessonList}
-    {en_groupTitle}
-        {en_groupList}
-    If there are homework tasks assigned, they will be sent through the morning report at 8 am the next day.
+IES Evening Report:<br>Hello {tmdname}, here is your personal IES teaching summary report for today. As of {sendTime}, you have published {examCount} test tasks, {homeworkCount} homework tasks, and uploaded {lessonCount} HiTeach lesson activity records.
+<br>&nbsp;&nbsp;&nbsp;The following are the specific details.
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{en_examTitle}<br>{en_examList}
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{en_lessonTitle}<br>{en_lessonList}
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{en_groupTitle}<br>{en_groupList}
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;If there are homework tasks assigned, they will be sent through the morning report at 8 am the next day.
 ";
-        static string en_examList = "[ {examName} ] has {submitCount} students submitted, and there are still {unsubmitCount} students who have not submitted.\r\n";
-        static string en_lessonList = "[ {lessonName} ] lesson should have {memberCount} attendees, actual attendees {attendCount}, attendance rate {attendRate}.\r\n";
-        static string en_groupListJoin = "[ {grouplistName} ] has a total of {memberCount}, {joinCount} joined.\r\n";
-        static string en_groupListLeave = "[ {grouplistName} ] has a total of {memberCount}, {joinCount} left.\r\n";
+        static string en_examList = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[ {examName} ] has {submitCount} students submitted, and there are still {unsubmitCount} students who have not submitted.<br>";
+        static string en_lessonList = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[ {lessonName} ] lesson should have {memberCount} attendees, actual attendees {attendCount}, attendance rate {attendRate}%.<br>";
+        static string en_groupListJoin = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[ {grouplistName} ] has a total of {memberCount}, {joinCount} joined.<br>";
+        static string en_groupListLeave = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[ {grouplistName} ] has a total of {memberCount}, {joinCount} left.<br>";
         static string en_zb = @"
 IES Morning Report:
-    Hello {tmdname}, here is the summary report of the homework tasks you have posted.
-    {en_homeworkTitle}
-    {en_homeworkList}
+<br>&nbsp;&nbsp;&nbsp;Hello {tmdname}, here is the summary report of the homework tasks you have posted.
+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{en_homeworkTitle}<br>{en_homeworkList}
 ";
-        static string en_homeworkList = "[ {homeworkName} ] has {submitCount} students submitted, and there are still {unsubmitCount} students who have not submitted.\r\n";
-        static string en_homeworkTitle = "\r\nHomework Submission Details:";
-        static string en_examTitle = "\r\nTest task submission details:";
-        static string en_lessonTitle = "\r\nLesson attendance details:";
-        static string en_groupTitle = "\r\nPersonal course list change details:";
+        static string en_homeworkList = "[ {homeworkName} ] has {submitCount} students submitted, and there are still {unsubmitCount} students who have not submitted.<br>";
+        static string en_homeworkTitle = "Homework Submission Details:";
+        static string en_examTitle = "Test task submission details:";
+        static string en_lessonTitle = "Lesson attendance details:";
+        static string en_groupTitle = "Personal course list change details:";
         #endregion
 
-
-        public static async Task<List<CodeValue> > AccumulateDaily(AzureRedisFactory _azureRedis,AzureCosmosFactory _azureCosmos,
-            CoreAPIHttpService coreAPIHttpService, DingDing dingDing,HttpClient _httpClient, SnowflakeId _snowflakeId,string notifyUrl, MailFactory _mailFactory)
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="_azureRedis"></param>
+        /// <param name="_azureCosmos"></param>
+        /// <param name="coreAPIHttpService"></param>
+        /// <param name="dingDing"></param>
+        /// <param name="_httpClient"></param>
+        /// <param name="_snowflakeId"></param>
+        /// <param name="notifyUrl"></param>
+        /// <param name="_mailFactory"></param>
+        /// <param name="am"></param>
+        /// <param name="pm"></param>
+        /// <returns></returns>
+        public static async Task<List<CodeValue> > AccumulateDaily(IConfiguration _configuration,AzureRedisFactory _azureRedis,AzureCosmosFactory _azureCosmos,
+            CoreAPIHttpService coreAPIHttpService, DingDing dingDing,HttpClient _httpClient, SnowflakeId _snowflakeId,string notifyUrl, MailFactory _mailFactory, int am=0 ,int pm=0)
         {
             DateTimeOffset now = DateTimeOffset.Now;
             string day = now.ToString("yyyyMMdd");
@@ -271,6 +268,10 @@ IES Morning Report:
             List<CodeValue> notifys = new List<CodeValue>();
             foreach (var teacher in teachers)
             {
+                if (!(teacher.id.Equals("1595321354")|| teacher.id.Equals("1535418750") || teacher.id.Equals("1528783259")))
+                {
+                    continue;
+                }
                 StringBuilder notify = new StringBuilder();
 #if DEBUG
                 int sendTime_pm = now.Hour;
@@ -279,6 +280,13 @@ IES Morning Report:
                 int sendTime_pm = 20;
                 int sendTime_am =8;
 #endif
+                if (am>0) { 
+                    sendTime_am = am;
+                }
+                if (pm>0)
+                {
+                    sendTime_pm = pm;
+                }
                 string lang = teacher.lang;
                 var tzt = now.GetGMTTime((int)teacher.timezone);
                 if (string.IsNullOrWhiteSpace(teacher.lang)) 
@@ -600,8 +608,7 @@ IES Morning Report:
                             }
                             break;
                     }
-
-#if DEBUG
+                    template= template.Replace("\r\n", "");
                     string eventId = $"Evening_Report-{_snowflakeId.NextId()}";
                     NotifyData notifyData = new NotifyData
                     {
@@ -619,9 +626,19 @@ IES Morning Report:
                     var coreUser = coreUsers.Find(x => x.id.Equals(teacher.id)  && !string.IsNullOrWhiteSpace(x.mail));
                     if (coreUser!=null)
                     {
-                        var token = _mailFactory.GetSmtpClient().SendEmail(_azureCosmos, dingDing, eventId, title, template, coreUser.mail, teacher.id, teacher.name);
+                        // var token = _mailFactory.GetSmtpClient().SendEmail(_azureCosmos, dingDing, eventId, title, template, coreUser.mail, teacher.id, teacher.name, sender: "TEAMModel");
+                        //var tid = lang.Equals("zh-cn") ? "d-136eddbd974046f1a721c8f4e210b9bf" : lang.Equals("zh-tw") ? "d-136eddbd974046f1a721c8f4e210b9bf" : "d-95ac2d657d1b4d9dbb7b79defc17f714";
+                        var tid = string.Empty;
+                        if (coreAPIHttpService.options.location.Contains("China"))
+                        {
+                            tid=lang.Equals("zh-cn") ? "IES5GeneralTemplateSC" : lang.Equals("zh-tw") ? "IES5GeneralTemplateTC" : "IES5GeneralTemplateEN";
+                        }
+                        else
+                        {
+                            tid=lang.Equals("zh-cn") ? "d-270b4ec690f541a9a4045d7a4032bc3b" : lang.Equals("zh-tw") ? "d-136eddbd974046f1a721c8f4e210b9bf" : "d-95ac2d657d1b4d9dbb7b79defc17f714";
+                        }
+                        await coreAPIHttpService.SendMail(new Dictionary<string, object> { { "to", coreUser.mail }, { "tid", tid }, { "vars", new { title = title, notificationcontent = template } } }, coreAPIHttpService.options.location, _configuration);
                     }
-#endif
                     notify.Append(template);
                 }
                 if (tzt.Hour==sendTime_am  && !string.IsNullOrWhiteSpace(homeworkSB.ToString()))
@@ -650,7 +667,7 @@ IES Morning Report:
                             template=template.Replace("{en_homeworkTitle}", en_homeworkTitle);
                             break;
                     }
-#if DEBUG
+                    template= template.Replace("\r\n", "");
                     string  eventId = $"Morning_Report-{_snowflakeId.NextId()}";
                     NotifyData notifyData = new NotifyData
                     {
@@ -668,9 +685,19 @@ IES Morning Report:
                     var coreUser = coreUsers.Find(x => x.id.Equals(teacher.id)  && !string.IsNullOrWhiteSpace(x.mail));
                     if (coreUser!=null)
                     {
-                        var token = _mailFactory.GetSmtpClient().SendEmail(_azureCosmos, dingDing, eventId, title, template, coreUser.mail, teacher.id, teacher.name);
+                       // var token = _mailFactory.GetSmtpClient().SendEmail(_azureCosmos, dingDing, eventId, title, template, coreUser.mail, teacher.id, teacher.name,sender:"TEAMModel");
+
+                        var tid = string.Empty ;
+                        if (coreAPIHttpService.options.location.Contains("China"))
+                        {
+                            tid=lang.Equals("zh-cn") ? "IES5GeneralTemplateSC" : lang.Equals("zh-tw") ? "IES5GeneralTemplateTC" : "IES5GeneralTemplateEN";
+                        }
+                        else {
+                            tid=lang.Equals("zh-cn")? "d-270b4ec690f541a9a4045d7a4032bc3b" : lang.Equals("zh-tw") ? "d-136eddbd974046f1a721c8f4e210b9bf" : "d-95ac2d657d1b4d9dbb7b79defc17f714";
+                        }
+
+                        await coreAPIHttpService.SendMail(new Dictionary<string, object> { { "to", coreUser.mail }, { "tid", tid }, { "vars", new { title = title, notificationcontent = template } } }, coreAPIHttpService.options.location, _configuration);
                     }
-#endif
                     notify.Append(template);
                 }
                 notifys.Add(new CodeValue { code=teacher.id, value= notify.ToString() });

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

@@ -2,9 +2,9 @@
 
 	<PropertyGroup>
 		<TargetFramework>net6.0</TargetFramework>
-		<Version>5.2406.5</Version>
-		<AssemblyVersion>5.2406.5.1</AssemblyVersion>
-		<FileVersion>5.2406.5.1</FileVersion>
+		<Version>5.2406.19</Version>
+		<AssemblyVersion>5.2406.19.1</AssemblyVersion>
+		<FileVersion>5.2406.19.1</FileVersion>
 		<PackageReleaseNotes>发版</PackageReleaseNotes>
 	</PropertyGroup>
 

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

@@ -58,7 +58,7 @@
 			document.write('<script id="vueRouter"  src="' + blobHost + '/0-public/js/vue-router.min.js"><\/script>');
 			document.write('<script id="echarts"  src="' + blobHost + '/0-public/js/echarts.490.js"><\/script>');
 			document.write('<script id="jspdf"  src="' + blobHost + '/0-public/js/jspdf.umd.min.js"><\/script>');
-			// document.write('<script id="mathjax"  src="' + blobHost + '/0-public/js/tex-mml-svg.js"><\/script>');
+			document.write('<script id="mathjax"  src="' + blobHost + '/0-public/js/tex-mml-svg.js"><\/script>');
 			document.write('<script id="mediaInfo"  src="' + blobHost + '/0-public/js/checkMedia.js"><\/script>');
 			document.write('<script id="e_theme_macarons"  src="<%= BASE_URL %>theme/e_theme_macarons.js"><\/script>');
 			document.write('<script id="mathjax" type="text/javascript" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"><\/script>');

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

@@ -1627,7 +1627,7 @@ const LANG_EN_US = {
             ans: 'Answer',
             tip6: 'Enter the answer',
             tip7: 'Switching will clear the current question data, do you confirm to switch?',
-            tip8: 'Maximum 50 questions of this type!',
+            tip8: 'Maximum of 50 questions of this question type!',
             tip9: 'Enter the speaking prompt',
             tip10: 'The number of questions cannot be 0!',
             tip11: 'The number of answers for single answer questions does not match the number of questions, please check and retry!',
@@ -1635,6 +1635,8 @@ const LANG_EN_US = {
             tip13: 'Switching  will clear the current answers setting, do you confirm to switch? ',
             onlyUpper: 'Only capital letters can be entered for objective questions.'
         },
+        secretTip1: 'Not public yet',
+        secretTip2: 'Checking the box means that only the school administrators and I can see this exam file',
         cpTip1: 'Pick from the question bank',
         cpTip2: 'Pick from the exam paper',
         paperPickTip1: 'Currently picked',
@@ -3359,7 +3361,7 @@ const LANG_EN_US = {
         advanced: 'Advanced Settings',
         over: 'Greater than or equal to',
         less: 'Less than or equal to',
-        point: 'point',
+        point: 'points',
         unit: '',
         setting: 'Lesson Record Setting',
         all: 'All',
@@ -5256,7 +5258,7 @@ const LANG_EN_US = {
                 rightSub: "Correct",
                 shwoPage: "Answer Card",
                 reAns: "Re-answer",
-                entireExam: 'Answer the whole paper',
+                entireExam: 'Answer the whole test paper',
                 singleQues: 'Answer a single question',
             },
             report: {
@@ -5439,7 +5441,7 @@ const LANG_EN_US = {
             chartText1: "Incorrectly Answered Question Difficulty Distribution",
             chartText2: "Learning Difficulty Distribution",
             button1: "Start Practice",
-            button2: "Search all incorrectly answered questions",
+            button2: "View incorrectly answered questions",
             button3: "Start Reviewing",
             button4: "Start",
             sundry1: "Quantity",

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

@@ -1634,6 +1634,8 @@ const LANG_ZH_CN = {
             tip12: '多选题答案数与题目数不一致,请检查后重试!',
             onlyUpper:'客观题仅能输入大写字母'
         },
+        secretTip1:'暂不公开',
+        secretTip2:'勾选后保存的试卷仅有管理员及本人能看到',
         cpTip1: '从题库挑选',
         cpTip2: '从试卷挑选',
         paperPickTip1: '当前已挑选',

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

@@ -1637,6 +1637,8 @@ const LANG_ZH_TW = {
             tip13: '切換模式將會清空目前已設定的答案,是否確認切換?',
             onlyUpper: '客觀題答案僅能輸入大寫英文字母'
         },
+        secretTip1: '暂不公开',
+        secretTip2: '勾选后保存的试卷仅有管理员及本人能看到',
         cpTip1: '從題庫挑選',
         cpTip2: '從試卷挑選',
         paperPickTip1: '當前已挑選',

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

@@ -292,13 +292,16 @@ export function fetch(url, params) {
  * @returns {Promise}
  */
 
-export function post(url, params) {
+export function post(url, params, timeout) {
     //params['id_token'] = localStorage.getItem('id_token')
     let data = {}
     data.method = url
     data.params = params
     data.lang = localStorage.getItem('local')
     return new Promise((resolve, reject) => {
+        if(timeout){
+            axios.defaults.timeout = timeout // 设置超时时长
+        }
         axios.post(url, params)
             .then(response => {
                 if (response) {

+ 6 - 2
TEAMModelOS/ClientApp/src/api/learnActivity.js

@@ -54,6 +54,10 @@ export default {
     FindExamInfo: function (data) {
         return post('/common/exam/find-summary', data)
     },
+    // 评测试卷修改答案、配分
+    updateAns: function(data) {
+        return post('/common/exam/update-ans', data)
+    },
     /*
      *自动组题
      */
@@ -369,8 +373,8 @@ export default {
 	    return post('/score/update-scorecalc', data)
 	},  
     /* 新增成績表 */
-	addScorecalc: function (data) {
-	    return post('/score/add-scorecalc', data)
+	addScorecalc: function (data, timeout) {
+	    return post('/score/add-scorecalc', data, timeout)
 	},
     /* 刪除成績表 */
 	deleteScorecalc: function (data) {

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

@@ -593,7 +593,7 @@
                         <span style="font-weight: 800;">{{ $t("studentWeb.exam.testpop.myAnswerSheet") }}</span>
                         <div style="font-size: 14px; margin-right: 10px;">
                             <i-switch v-model="entireExam" size="small" />
-                            <span>{{ entireExam ? $t("studentWeb.exam.testpop.entireExam") : $t("studentWeb.exam.testpop.previsingleQuesous") }}</span>
+                            <span>{{ entireExam ? $t("studentWeb.exam.testpop.entireExam") : $t("studentWeb.exam.testpop.singleQues") }}</span>
                         </div>
                     </div>
                     <!--切換頁-->

文件差异内容过多而无法显示
+ 1 - 1
TEAMModelOS/ClientApp/src/css/site.css


+ 6 - 2
TEAMModelOS/ClientApp/src/utils/evTools.js

@@ -131,7 +131,9 @@ export default {
 				score: paper.score,
 				sheet: paper.sheet || null,
 				typeSummaryInfo: paper.typeSummaryInfo || null,
-				orderTemp: paper.orderTemp || 0
+				orderTemp: paper.orderTemp || 0,
+				secret: paper.secret || 0,
+				creatorId: paper.creatorId || store.state.userInfo.TEAMModelId
 			}
 			r(paperItem)
 		})
@@ -158,7 +160,9 @@ export default {
 				subjectId: paper.subjectId,
 				subjectName: paper.subjectName,
 				score: paper.score,
-				multipleRule: paper.multipleRule
+				multipleRule: paper.multipleRule,
+				secret: paper.secret || 0,
+				creatorId: paper.creatorId || store.state.userInfo.TEAMModelId
 			}
 			r(paperItem)
 		})

+ 26 - 6
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseChild.vue

@@ -6,6 +6,9 @@
         <div class="child-tools-t flex-row-center" v-if="isShowScore" @click.stop="handleChildEdit(item,index)">
           {{$t('evaluation.editItem')}}{{$t('evaluation.child')}}
         </div>
+        <div class="child-tools-t flex-row-center" v-if="isChangePaper" @click.stop="handleChildEdit(item,index)">
+          调整{{$t('evaluation.child')}}
+        </div>
         <div class="child-tools-t flex-row-center" v-if="canFix" @click.stop="handleFixChild(item,index)">
           {{ $t('evaluation.fixTip1') }}
         </div>
@@ -23,13 +26,13 @@
       </div>
       <div class="item-btn-toggle">
         <template v-if="!inBank">
-          <template v-if="isShowScore">
+          <template v-if="isShowScore || isChangePaper">
             <InputNumber :min="0" v-model="item.score" v-if="df" :step="0.5" @on-change="val => scoreChange(val, item.pid, item.id)" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;" @click.stop>
             </InputNumber>
             <InputNumber :min="0" v-model="item.score" v-else :step="0.5" style="display: inline-block ;width: 60px;margin-right: 10px;height: 30px;" @click.stop>
             </InputNumber>
           </template>
-          <span v-if="!isShowScore" style="margin-right: 10px;color: #2db7f5;font-weight: bold;">{{ item.score }}</span>
+          <span v-else style="margin-right: 10px;color: #2db7f5;font-weight: bold;">{{ item.score }}</span>
           <span style="margin-right: 20px;">{{$t('evaluation.paperList.score')}}</span>
         </template>
         <Icon :type="collapseList.includes(children.indexOf(item)) ? 'ios-arrow-dropup' : 'ios-arrow-dropdown'" />
@@ -128,6 +131,14 @@
         <Button type="success" @click="onSaveChild">{{$t('evaluation.confirm')}}</Button>
       </div>
     </Modal>
+    <!-- 修改试题答案、配分弹窗 -->
+    <Modal v-model="editAnsScoreModal" width="1080" class="edit-exercise-modal" :styles="{top: '20px'}" :mask-closable="false">
+      <div class="modal-header" slot="header">{{$t('evaluation.exerciseList.editChild')}}</div>
+      <BaseEditAnsScore v-if="editAnsScoreModal" :exerciseItem="curItem" @onEditSuccess="onEditChildFinish" refId="paperEdit" ref="paperEdit"></BaseEditAnsScore>
+      <div slot="footer">
+        <Button type="success" @click="onSaveChild">{{$t('evaluation.confirm')}}</Button>
+      </div>
+    </Modal>
 
     <!-- 补充解析和补救资源弹窗 -->
     <Modal v-model="addExplainModal" :styles="{top: '20px'}" width="1000px" :title="$t('evaluation.fixTip1')">
@@ -178,6 +189,10 @@ export default {
       type: Boolean,
       default: false
     },
+    isChangePaper: { //评测页面需修改答案、配分等
+      type: Boolean,
+      default: false
+    },
     inBank: {
       type: Boolean,
       default: false
@@ -210,7 +225,8 @@ export default {
       collapseList: [],
       surPlusScore: 0,
       exersicesField: this.$GLOBAL.EXERCISE_LEVELS(),
-      scatterData: []
+      scatterData: [],
+      editAnsScoreModal: false,
     }
   },
   created() {
@@ -251,12 +267,16 @@ export default {
       this.scatterData = newArr
     },
     onSaveChild() {
-      this.$refs.createChildRef.getContent(this.curItem.type)
+      if(this.isChangePaper) {
+        this.$refs.paperEdit.saveContent(this.curItem.type)
+      } else {
+        this.$refs.createChildRef.getContent(this.curItem.type)
+      }
     },
     handleChildEdit(item, index) {
       this.curItem = item
       this.curIndex = index
-      this.editChildModal = true
+      this.isChangePaper ? this.editAnsScoreModal = true : this.editChildModal = true
     },
     handleFixChild(item, index) {
       this.curItem = item
@@ -284,7 +304,7 @@ export default {
     },
     onEditChildFinish(item) {
       console.log(item)
-      this.editChildModal = false
+      this.isChangePaper ? this.editAnsScoreModal = false : this.editChildModal = false
       this.$emit('onEditChildFinish', item)
     },
     getOptionLineData(item, index) {

+ 312 - 0
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseEditAnsScore.vue

@@ -0,0 +1,312 @@
+<template>
+    <div class="ev-container" ref="editContainer">
+        <div>
+            <div>
+                <div class="exersices-content">
+                    <IconText :text="$t('evaluation.connector') + $t('evaluation.newExercise.stem')" :color="'#2d8cf0'" :icon="'ios-create'" style="margin-bottom:15px;"></IconText>
+                    <p v-html="editInfo.question"></p>
+                </div>
+                <div class="exersices-option" ref="optionRefs">
+                    <template v-if="exersicesType === 'single' || exersicesType === 'multiple'">
+                        <IconText :text="$t(`evaluation.newExercise.${exersicesType}Option`)" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+                        <div v-for="(item, index) in editInfo.option" :key="index" :ref="'optionBox' + index" :class="'editor-wrap-' + item" style="margin-top:10px;display:flex">
+                            <span class="fl-center option-order" :ref="'optionOrder' + index" :data-index="index">{{ renderIndex(index) }}</span>
+                            <!-- <span class="fl-center option-order">{{String.fromCharCode(64 + parseInt(index+1))}}</span> -->
+                            <div class="option-editor" v-html="item.value" style="display: flex; align-items: center; padding-left: 10px;"></div>
+                            <span :class="['fl-center', 'option-setting', trueArr.indexOf(index) > -1 ? 'option-true':'']" @click="settingAnswer(index)">{{ trueArr.indexOf(index) > -1 ? $t('evaluation.newExercise.trueAnswer') :$t('evaluation.newExercise.setAnswer') }}</span>
+                        </div>
+			            <p class="option-add">
+                            <span style="color:rgb(60,196,82);margin-left:15px;font-weight:bold">{{ $t('evaluation.newExercise.trueAnswer') }}:{{ paperAnswers.join("") }}</span>
+                        </p>
+                    </template>
+                    <template v-else-if="exersicesType === 'judge'">
+                        <IconText :text="$t('evaluation.newExercise.option')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+                        <RadioGroup v-model="paperAnswers[0]" type="button" size="large">
+                            <Radio label="A">{{ $t('evaluation.isTrue') }}</Radio>
+                            <Radio label="B">{{ $t('evaluation.isFalse') }}</Radio>
+                        </RadioGroup>
+                    </template>
+                    <template v-else>
+                        <IconText :text="$t('evaluation.newExercise.answerTitle')" :color="'#FF871C'" :icon="'md-reorder'"></IconText>
+                        <div style="margin-top:15px;">
+                            <div ref="answerEditor" style="text-align:left"></div>
+                        </div>
+                    </template>
+                </div>
+            </div>
+            <div class="exersices-attr my-radio-style">
+                <IconText :text="$t('evaluation.newExercise.knowledge')" :color="'#00b8ff'" :icon="'md-infinite'"></IconText>
+                <Button type="info" style="margin-top: 20px" @click="onSelectPoints" v-if="exercisePoints.length === 0">{{$t('evaluation.newExercise.choosePoint')}}</Button>
+                <div v-else style="margin-top: 10px">
+                <span v-for="(item, index) in exercisePoints" :key="index" class="exercise-item-point">
+                    {{ item }}
+                    <span class="exercise-item-point-close">
+                        <Icon type="md-close" @click="onDeletePoint(index)" />
+                    </span>
+                </span>
+                <span class="exercise-item-point-modify" @click="selectPointsModal = true">{{ this.$t('evaluation.newExercise.modify') }}</span>
+                </div>
+            </div>
+        </div>
+        <Modal v-model="selectPointsModal" :title="$t('evaluation.newExercise.choosePoint')" footer-hide ref="editPointRef" width="600px" style="z-index: 99999">
+            <BasePoints v-if="selectPointsModal" :period="schoolInfo.period[exercisePeriod].id" :subject="schoolInfo.period[exercisePeriod].subjects[exerciseSubject].id" @onCheckChange="onCheckChange" @onCancel="selectPointsModal = false" :points="exercisePoints" ref="pointRef" :scope="curScope"></BasePoints>
+        </Modal>
+    </div>
+</template>
+
+<script>
+import E from 'wangeditor'
+import IconText from '@/components/evaluation/IconText.vue'
+export default {
+    components: {
+        IconText
+    },
+    props: {
+        exerciseItem: {
+            type: Object,
+            default: () => null
+        },
+        refId: {
+            type: String,
+            default: () => ''
+        },
+    },
+    data () {
+        return {
+            editInfo: null,
+            selectPointsModal: false,
+            schoolInfo: {
+                period: [],
+            },
+            exercisePeriod: 0,
+            exercisePoints: [],
+            exerciseScope: 0,
+            exerciseSubject: 0,
+            subjectList: [],
+            exersicesType: null,
+            oldChildList: [],
+			optionTrueIndex: 0,
+			trueArr: [0],
+			paperAnswers: ["A"],
+			answerEditor: null,
+        }
+    },
+    mounted () {
+        
+    },
+    watch: {
+        exerciseItem: {
+            handler(newValue, oldValue) {
+                if (Object.keys(newValue).length) {
+                    console.log("BaseEditExerciese接受的", newValue);
+                    this.renderExercise(JSON.parse(JSON.stringify(newValue)));
+                }
+            },
+            immediate: true,
+            deep: true
+        },
+    },
+    computed: {
+        curScope() {
+            return this.exerciseScope === 1 ? 'school' : 'private'
+        },
+        hasSchool() {
+            return this.$store.state.userInfo.hasSchool
+        },
+    },
+    methods: {
+        // 渲染编辑习题内容回显
+        async renderExercise(editItem) {
+            console.log('edit-item', editItem);
+            this.editInfo = editItem;
+            // this.curId = editItem.id
+
+            let schoolProfile = await this.$store.dispatch('user/getSchoolProfile')
+            let schoolInfo = schoolProfile.school_base;
+
+            if (editItem.scope === "school" && schoolInfo) {
+                this.schoolInfo = schoolInfo;
+                this.exercisePeriod = schoolInfo.period.map((item) => item.id).indexOf(editItem.periodId);
+                this.subjectList = schoolInfo.period[this.exercisePeriod].subjects;
+                // this.gradeList = schoolInfo.period[this.exercisePeriod].grades;
+                // this.exerciseGrade = editItem.gradeIds.map(i => +i);
+                this.exerciseSubject = this.subjectList.map((item) => item.id).indexOf(editItem.subjectId);
+            }
+            // this.fieldList = [this.$t('evaluation.level1'), this.$t('evaluation.level2'), this.$t('evaluation.level3'), this.$t('evaluation.level4'), this.$t('evaluation.level5'), this.$t('evaluation.level6')]
+            // this.isEdit = true;
+            // this.exersicesDiff = editItem.level.toString() || "0";
+            this.exerciseScope = editItem.scope === "private" ? 0 : 1;
+            this.exersicesType = editItem.type;
+            // this.exerciseField = editItem.field - 1;
+            this.exercisePoints = editItem.knowledge || editItem.points || [];
+
+            if (editItem.type === 'compose') {
+                this.oldChildList = JSON.parse(JSON.stringify(editItem.children)) || []
+            }
+            // this.childList = editItem.children || [];
+            // this.stemContent = editItem.question;
+            // this.relateFileList = editItem.repair || [];
+            // this.optionsContent = editItem.option;
+            this.trueArr = editItem.answer.map((item) => item.charCodeAt() - 65)
+            this.paperAnswers = editItem.answer
+            // this.analysisContent = editItem.explain;
+            // this.analysisEditor.txt.html(editItem.explain);
+            console.log('当前编辑的试题', this.editInfo)
+            if(!['single', 'multiple', 'judge'].includes(this.exersicesType)) {
+                let answerEditor = new E(this.$refs.answerEditor)
+                answerEditor.config.onchange = (html) => {
+                    this.paperAnswers = [html]
+                }
+                answerEditor.config.uploadImgShowBase64 = true;
+                this.$editorTools.initMyEditor(answerEditor,this)
+                answerEditor.create()
+
+                this.answerEditor = answerEditor
+                this.answerEditor.txt.html(this.paperAnswers[0])
+            }
+        },
+        /* 根据下标渲染对应的字母顺序 */
+        renderIndex(index) {
+            return String.fromCharCode(64 + parseInt(index + 1))
+        },
+        /* 设置正确答案 */
+        settingAnswer(index) {
+            this.$nextTick(() => {
+                if(this.exersicesType === 'single') {
+                    if (this.trueArr.indexOf(index) === -1) {
+                        this.trueArr = [index]
+                    }
+                } else {
+                    if (this.trueArr.indexOf(index) > -1) {
+                        if (this.trueArr.length === 1) {
+                            this.$Message.warning(this.$t("evaluation.addTip3"));
+                        } else {
+                            this.trueArr.splice(this.trueArr.indexOf(index), 1);
+                        }
+                    } else {
+                        this.trueArr.push(index);
+                        this.trueArr = this.trueArr.sort(); // 选项排序
+                    }
+                }
+                this.getAnswerOrder(this.trueArr);
+            });
+        },
+        /* 获取最新答案选项 */
+        getAnswerOrder(arr) {
+            let arr2 = [];
+            arr.forEach((i) => {
+                arr2.push(this.getOrderCode(i));
+            });
+            this.paperAnswers = arr2.sort();
+        },
+        /* 根据index获取对应选项字母的值 */
+        getOrderCode(index) {
+            let wraps = Array.from(this.$refs.optionRefs.getElementsByClassName("option-order"));
+            for (let i = 0; i < wraps.length; i++) {
+                let item = wraps[i];
+                if (+index === +item.dataset.index) {
+                    return item.innerText;
+                    break;
+                }
+            }
+        },
+        onSelectPoints() {
+            if (this.hasSchool) {
+                this.selectPointsModal = true
+            } else {
+                this.$Message.warning(this.$t('evaluation.newExercise.noSchoolTip'))
+            }
+        },
+        /**
+         * 删除选好的关联知识点
+         * @param index
+         */
+        onDeletePoint(index) {
+            this.exercisePoints.splice(index, 1);
+        },
+        onCheckChange(val, list) {
+            console.log(val);
+            this.exercisePoints = val;
+            this.selectPointsModal = false
+        },
+        /* 滚回顶部 */
+        backToTop() {
+            this.$refs.editContainer.scrollIntoView();
+            // this.exersicesType = ''
+        },
+        saveContent(type) {
+            let exerciseItem = JSON.parse(JSON.stringify(this.editInfo))
+            this.isLoading = true;
+            
+            exerciseItem.answer = this.paperAnswers
+            // exerciseItem.explain = this.analysisContent;
+            // exerciseItem.repair = this.formatRepairResource(this.$refs.repairRef.datas);
+            // exerciseItem.field = this.exerciseField + 1;
+            exerciseItem.knowledge = this.exercisePoints;
+            // exerciseItem.periodId = this.isSchool ? this.schoolInfo.period[this.exercisePeriod].id : null;
+            // exerciseItem.gradeIds = this.isSchool ? this.exerciseGrade.length ? this.exerciseGrade.map(i => i + '') : this.gradeList.map((i, index) => index + '') : null;
+            // exerciseItem.subjectId = this.isSchool ? this.schoolInfo.period[this.exercisePeriod].subjects[this.exerciseSubject].id : null;
+            // exerciseItem.code = this.editInfo.scope === "school" ? this.$store.state.userInfo.schoolCode : this.$store.state.userInfo.TEAMModelId;
+            // exerciseItem.scope = this.exerciseScope === 0 ? "private" : "school";
+            // 收集编辑器宽度 方便压缩富文本图片中宽度为百分比的图片
+            /* let editorWidth = {}
+            if (exerciseItem.type === 'single') {
+                editorWidth.questionWidth = this.$refs.single.stemEditor.$textContainerElem.elems[0].clientWidth
+                editorWidth.optionWidth = this.$refs.single.optionEditors[0].$textContainerElem.elems[0].clientWidth
+            } else if (exerciseItem.type === 'multiple') {
+                editorWidth.questionWidth = this.$refs.multiple.stemEditor.$textContainerElem.elems[0].clientWidth
+                editorWidth.optionWidth = this.$refs.multiple.optionEditors[0].$textContainerElem.elems[0].clientWidth
+            } else {
+                editorWidth.questionWidth = this.analysisEditor.$textContainerElem.elems[0].clientWidth
+            } */
+            console.log('333333333333333', exerciseItem);
+            this.$emit("onEditSuccess", exerciseItem)
+        },
+        // 排除对象空属性
+        checkContent(Obj) {
+            return new Promise(async (r, j) => {
+                let flag = true;
+                let whiteList = this.getWhiteListByType(Obj.type);
+                console.log("富文本获取的原始试题数据", Obj);
+                for (let key in Obj) {
+                    if (whiteList.includes(key) && typeof Obj[key] === "string") {
+                        if (!this.getSimpleText(Obj[key])) {
+                            flag = await this.emptyConfirm(key)
+                            r(flag);
+                            return
+                        }
+                    } else {
+                        if (whiteList.includes(key) && !Obj[key]) {
+                            flag = await this.emptyConfirm(key)
+                            r(flag);
+                            return
+                        }
+                    }
+                }
+                r(flag);
+            })
+        },
+        // 根据不同题型 给出需要必填选项
+        getWhiteListByType(type) {
+            switch (type) {
+                case "single":
+                    return ["question", "option", "answer"];
+                    break;
+                case "multiple":
+                    return ["question", "option", "answer"];
+                    break;
+                case "complete":
+                    return ["question", "answer"];
+                    break;
+                default:
+                    return ["question"];
+                    break;
+            }
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+@import "../index/CreateExercises.less";
+</style>

+ 43 - 16
TEAMModelOS/ClientApp/src/view/evaluation/components/BaseExerciseList.vue

@@ -21,6 +21,7 @@
 						<!-- <div class="item-tools-t flex-row-center" v-show="isShowTools" @click.stop="handleSetScore(item,exerciseList.indexOf(item),typeItem.list,index)">
 							<Icon type="ios-list-box-outline" />配分</div> -->
 						<div class="item-tools-t flex-row-center" v-show="isShowTools && !isExamPaper" @click.stop="handleToolEdit(typeItem.list, item, index)"><Icon type="md-create" />{{ $t("evaluation.editItem") }}</div>
+						<div class="item-tools-t flex-row-center" v-show="isChangePaper && item.type !== 'compose'" @click.stop="handleToolEdit(typeItem.list, item, index)"><Icon type="md-create" />{{ '调整' }}</div>
 						<div class="item-tools-t flex-row-center" v-show="isAutoMode" @click.stop="handleToolChange(typeItem.list, item, index)"><Icon type="md-repeat" />{{ $t("evaluation.exchange") }}</div>
 						<div class="item-tools-t flex-row-center" v-show="isShowTools && !isExamPaper && noAnswerList.indexOf(item) > -1" @click.stop="onSetAnswer(item, exerciseList.indexOf(item), typeItem.list, index)"><Icon type="ios-brush-outline" />{{ $t("evaluation.exerciseList.setAnswer") }}</div>
 						<div class="item-tools-t flex-row-center" v-show="isShowTools && !isExamPaper" @click.stop="handleDelete(typeItem.list, item, index)"><Icon type="ios-archive-outline" />{{ $t("evaluation.deleteItem") }}</div>
@@ -55,11 +56,11 @@
 						</div>
 					</div>
 					<div class="exercise-item-children" v-if="item.children.length">
-						<BaseChild :children="item.children" :totalScore="item.score" :isShowScore="!isExamPaper" :canFix="canFix" @onEditChildFinish="onEditChildFinish"> </BaseChild>
+						<BaseChild :children="item.children" :totalScore="item.score" :isChangePaper="isChangePaper" :isShowScore="!isExamPaper" :canFix="canFix" @onEditChildFinish="onEditChildFinish"> </BaseChild>
 					</div>
 					<div class="item-btn-toggle" v-show="isShowTools && !isPreview">
 						<!-- 可调整分数 -->
-						<template @click.stop v-if="!isExamPaper">
+						<template @click.stop v-if="!isExamPaper || isChangePaper">
 							<InputNumber v-if="item.type !== 'compose'" :max="item.score + surPlusScore" :min="scoreStep" :step="scoreStep" v-model="item.score" style="display: inline-block; width: 60px; margin-right: 10px; height: 30px" @click.stop> </InputNumber>
 							<span style="margin-right: 10px" v-if="item.type === 'compose'">{{ getComposeScore(item) }}</span>
 							<span style="margin-right: 10px">{{ $t("evaluation.paperList.score") }}</span>
@@ -239,6 +240,13 @@
 				<Button type="success" :loading="editLoading" @click="doSaveEdit">{{ $t("evaluation.confirm") }}</Button>
 			</div>
 		</Modal>
+		<!-- 修改试题答案、配分弹窗 -->
+		<Modal v-model="editAnsScoreModal" :mask-closable="false" :styles="{ top: '20px' }" class-name="edit-exercise-modal" width="1200px" :title="$t('evaluation.exerciseList.editExercise')" @on-visible-change="editModalChange">
+			<BaseEditAnsScore v-if="editAnsScoreModal" :exerciseItem="currentExercise" @onEditSuccess="onEditSuccess" refId="paperEdit" ref="paperEdit"></BaseEditAnsScore>
+			<div slot="footer">
+				<Button type="success" :loading="editLoading" @click="doSaveEdit">{{ $t("evaluation.confirm") }}</Button>
+			</div>
+		</Modal>
 		<!-- 编辑题型说明弹窗 -->
 		<Modal v-model="editSumaryModal" class-name="ex-score-modal" width="600px" :title="exersicesType[curEditType] + $t('evaluation.summaryText')" @on-visible-change="editModalChange" @on-ok="onConfirmChangeSummary">
 			<Input v-model="curSummary" />
@@ -255,7 +263,11 @@
 </template>
 
 <script>
+	import BaseEditAnsScore from './BaseEditAnsScore.vue'
 	export default {
+		components: {
+			BaseEditAnsScore,
+		},
 		props: {
 			paper: {
 				type: Object,
@@ -273,6 +285,10 @@
 				type: Boolean,
 				default: false
 			},
+			isChangePaper: { //评测页面需修改答案、配分等
+				type: Boolean,
+				default: false
+			},
 			canFix: {
 				type: Boolean,
 				default: false
@@ -347,6 +363,7 @@
 				typeScoreModel: false,
 				setPaperInfoModal: false,
 				editExerciseModal: false,
+				editAnsScoreModal: false,
 				exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
 				exersicesDiff: this.$GLOBAL.EXERCISE_DIFFS(),
 				exersicesField: this.$GLOBAL.EXERCISE_LEVELS(),
@@ -399,7 +416,11 @@
 			/* 手动保存 */
 			doSaveEdit() {
 				this.editLoading = true;
-				this.$refs.paperEdit.getContent(this.currentExercise.type);
+				if(this.isChangePaper) {
+					this.$refs.paperEdit.saveContent(this.currentExercise.type)
+				} else {
+					this.$refs.paperEdit.getContent(this.currentExercise.type);
+				}
 			},
 			/* 保存补充解析的题目 */
 			doSaveFixItem() {
@@ -563,21 +584,27 @@
 			handleToolEdit(arr, item, index) {
 				console.error(this.errorList);
 				console.error(item);
-				this.currentExerciseIndex = this.exerciseList.indexOf(item); // 清单列表下的index
-				this.curOrderIndex = index;
-				if (item.scope === "school") {
-					this.getSchoolBaseInfo().then((res) => {
-						if (!res) return;
-						item.periodId = res.period[this.paperInfo.paperPeriod].id;
-						item.subjectId = res.period[this.paperInfo.paperPeriod].subjects[this.paperInfo.paperSubject].id;
+				if(this.isChangePaper) {
+					this.curTypeItems = arr;
+					this.currentExercise = item;
+					this.editAnsScoreModal = true;
+				} else {
+					this.currentExerciseIndex = this.exerciseList.indexOf(item); // 清单列表下的index
+					this.curOrderIndex = index;
+					if (item.scope === "school") {
+						this.getSchoolBaseInfo().then((res) => {
+							if (!res) return;
+							item.periodId = res.period[this.paperInfo.paperPeriod].id;
+							item.subjectId = res.period[this.paperInfo.paperPeriod].subjects[this.paperInfo.paperSubject].id;
+							this.curTypeItems = arr;
+							this.currentExercise = item;
+							this.editExerciseModal = true;
+						});
+					} else {
 						this.curTypeItems = arr;
 						this.currentExercise = item;
 						this.editExerciseModal = true;
-					});
-				} else {
-					this.curTypeItems = arr;
-					this.currentExercise = item;
-					this.editExerciseModal = true;
+					}
 				}
 				console.log("试卷当前编辑的题目", item);
 			},
@@ -765,7 +792,7 @@
 				if (this.$refs.paperEdit) {
 					this.$refs.paperEdit.isLoading = false;
 				}
-				this.editExerciseModal = false;
+				this.isChangePaper ? this.editAnsScoreModal = false : this.editExerciseModal = false;
 				this.$Message.success(this.$t("evaluation.editSuc"));
 				if (localStorage.getItem("curCreateType") === "import") {
 					// 导题编辑过需要把blob置空 防止复制试题过来造成数据复原

+ 10 - 1
TEAMModelOS/ClientApp/src/view/evaluation/index/CreatePaper.vue

@@ -8,6 +8,11 @@
         <Checkbox v-model="isSaveToBank" v-if="evaluationInfo.createType === 'import' && !isXkwMode"
           style="margin-left: 20px;" @on-change="onModeChange('item')"> {{ $t('evaluation.syncItems') }}
         </Checkbox>
+        <Checkbox v-model="isSecret" style="margin-left: 20px;" v-if="isSchool"> 
+          <Tooltip :content="$t('evaluation.secretTip2')" class="common-toolTip" placement="bottom-end" max-width="200">
+						 <span>{{ $t('evaluation.secretTip1') }}</span>
+					</Tooltip>
+        </Checkbox>
         <Checkbox v-model="isGeneratePaper" v-if="evaluationInfo.createType === 'import'" style="margin-left: 20px;"
           @on-change="onModeChange('paper')"> {{ $t('evaluation.composePaper') }}
         </Checkbox>
@@ -144,6 +149,7 @@ export default {
       isGeneratePaper: false, //是否组成试卷
       isSaveToBank: false, //是否保存到题库
       isSavePoints: false, //是否同步导入的知识点到校本知识点库
+      isSecret:false, // 是否保密试卷
       split1: 0.2,
       isLoading: false,
       isEditPaper: false,
@@ -1199,7 +1205,9 @@ export default {
         tags: [...new Set(this.evaluationInfo.tags)],
         multipleRule: multipleRule || 1,
         typeSummaryInfo: this.typeSummaryInfo,
-        orderTemp: this.orderTempIndex
+        orderTemp: this.orderTempIndex,
+        creatorId: this.$store.state.userInfo.TEAMModelId,
+        secret: this.isSecret ? 1 : 0
       }
       // 如果是编辑试卷并且试卷已经产生过答题卡 则让用户决定是否重新生成答题卡
       let isRemoveSheet = false
@@ -1601,6 +1609,7 @@ export default {
       this.doRender(routerData)
       this.editPaper = routerData
       this.isEditPaper = true
+      this.isSecret = routerData.secret ? true : false
       this.evaluationInfo.createType == 'manual'
       this.activeTab = 'preview'
       this.$EventBus.$emit('onPaperItemChange', routerData.item)

+ 203 - 13
TEAMModelOS/ClientApp/src/view/evaluation/index/TestPaper.vue

@@ -33,13 +33,16 @@
               <Button class="base-info-btn" type="info" @click="goAnswerSheet" v-show="paperInfo.item.length && paperInfo.id">{{ paper.sheetNo ?  $t('evaluation.paperList.reCreateSheet') : $t('evaluation.paperList.createSheet') }}</Button>
             </div>
             <div v-if="isExamPaper && !isPreviewItems && paperInfo.item.length">
-               <Button class="base-info-btn" type="info" @click="showPaperAttachments" v-if="isHiteachPaper">{{ $t('evaluation.quickPaper.attachments') }}</Button>
-              <Button class="base-info-btn" type="info" @click="onHandleToggle" v-show="!isShowAnalysis">{{ isAllOpen ? $t('evaluation.index.collapseAll') : $t('evaluation.index.openAll')}}</Button>
-              <Button class="base-info-btn" type="info" @click="onViewModelChange" v-show="!isShowAnalysis">{{ `${ viewModel === 'type' ? this.$t('evaluation.paperList.sortByOrder') : this.$t('evaluation.paperList.sortByType') }` }}</Button>
-              <Button class="base-info-btn" type="info" @click="isShowAnalysis = !isShowAnalysis" v-show="!isHideAnalysis">{{ isShowAnalysis ? this.$t('evaluation.paperList.paperDetails') : this.$t('evaluation.paperList.paperAnalysis')}}</Button>
-              <Button class="base-info-btn" type="info" @click="downloadSheet" :loading="downLoading" v-show="paperInfo.id && paper.sheetNo && !hideSheet && !isSharePreview">{{ $t('evaluation.paperList.goAnswerSheet') }}<span v-if="paperInfo.mode">-{{ paperInfo.mode }}</span></Button>
-              <Button class="base-info-btn" type="info" @click="goAnswerSheet" v-show="paperInfo.id && !hideSheet && !isSharePreview">{{ paper.sheetNo ?  $t('evaluation.paperList.reCreateSheet') : $t('evaluation.paperList.createSheet') }}</Button>
-              <Button class="base-info-btn" type="info" @click="exitPreview" v-show="paperInfo.id && isSharePreview">{{ $t('evaluation.index.backList') }}</Button>
+              <Button class="base-info-btn" type="info" @click="showPaperAttachments" v-show="!isChangePaper" v-if="isHiteachPaper">{{ $t('evaluation.quickPaper.attachments') }}</Button>
+              <Button class="base-info-btn" type="info" @click="showChangePaper" v-show="isExamInfoPaper && !isChangePaper">{{ '修改试卷' }}</Button>
+              <Button class="base-info-btn" type="info" @click="onHandleToggle" v-show="!isShowAnalysis && !isChangePaper">{{ isAllOpen ? $t('evaluation.index.collapseAll') : $t('evaluation.index.openAll')}}</Button>
+              <Button class="base-info-btn" type="info" @click="onViewModelChange" v-show="!isShowAnalysis && !isChangePaper">{{ `${ viewModel === 'type' ? this.$t('evaluation.paperList.sortByOrder') : this.$t('evaluation.paperList.sortByType') }` }}</Button>
+              <Button class="base-info-btn" type="info" @click="isShowAnalysis = !isShowAnalysis" v-show="!isHideAnalysis && !isChangePaper">{{ isShowAnalysis ? this.$t('evaluation.paperList.paperDetails') : this.$t('evaluation.paperList.paperAnalysis')}}</Button>
+              <Button class="base-info-btn" type="info" @click="downloadSheet" :loading="downLoading" v-show="paperInfo.id && paper.sheetNo && !hideSheet && !isSharePreview && !isChangePaper">{{ $t('evaluation.paperList.goAnswerSheet') }}<span v-if="paperInfo.mode">-{{ paperInfo.mode }}</span></Button>
+              <Button class="base-info-btn" type="info" @click="goAnswerSheet" v-show="paperInfo.id && !hideSheet && !isSharePreview && !isChangePaper">{{ paper.sheetNo ?  $t('evaluation.paperList.reCreateSheet') : $t('evaluation.paperList.createSheet') }}</Button>
+              <Button class="base-info-btn" type="info" @click="exitPreview" v-show="paperInfo.id && isSharePreview && !isChangePaper">{{ $t('evaluation.index.backList') }}</Button>
+              <Button class="base-info-btn" type="success" @click="showChangePaper(1)" v-show="isChangePaper">{{ '保存' }}</Button>
+              <Button class="base-info-btn" @click="showChangePaper" v-show="isChangePaper">{{ '取消' }}</Button>
             </div>
           </div>
           <!-- 试卷头部信息 -->
@@ -50,7 +53,7 @@
           <ExamPaperAnalysis :testPaper="paperInfo" v-if="isShowAnalysis" :hidePie="hidePie">
           </ExamPaperAnalysis>
           <!-- 题目类型及列表 -->
-          <BaseExerciseList :paper="paperInfo" @dataUpdate="onListUpdate" v-show="!isShowAnalysis" ref="exList" :isShowTools="!isPreview" :canFix="canFix" :isExamPaper="isExamPaper || isPreviewItems" @toggleChange="onToggleChange" @scoreUpdate="scoreUpdate"></BaseExerciseList>
+          <BaseExerciseList :paper="paperInfo" @dataUpdate="onListUpdate" v-show="!isShowAnalysis" ref="exList" :isChangePaper="isChangePaper" :isShowTools="!isPreview" :canFix="canFix" :isExamPaper="isExamPaper || isPreviewItems" @toggleChange="onToggleChange" @scoreUpdate="scoreUpdate"></BaseExerciseList>
         </div>
         <!-- <BaseHiteachPaper v-if="isHiteachPaper" :paper="paperInfo"></BaseHiteachPaper> -->
       </div>
@@ -98,6 +101,7 @@
   </div>
 </template>
 <script>
+import blobTool from '@/utils/blobTool.js'
 import ExamPaperAnalysis from '@/view/learnactivity/ExamPaperAnalysis.vue'
 import ManualCreate from '@/view/learnactivity/ManualCreate.vue'
 
@@ -127,6 +131,10 @@ export default {
       type: Boolean,
       default: false
     },
+    isExamInfoPaper: { //评测页面下的试卷,需调整答案、配分等
+      type: Boolean,
+      default: false
+    },
     hidePie: {
       type: Boolean,
       default: false
@@ -162,11 +170,24 @@ export default {
     periodCode: {
       type: String,
       default: ''
-    }
+    },
+    activityIndex: {
+        type: Number,
+        default: 0
+    },
+    subjectIndex: {
+        type: Number,
+        default: 0
+    },
+    restorePaper: {
+      type: Function,
+      require: true,
+      default: null
+    },
   },
   data() {
     return {
-      isHiteachPaper: true,
+      isHiteachPaper: false,
       editLoading: false,
       hasModify: false,
       curOrderTempIndex: 0,
@@ -195,6 +216,7 @@ export default {
         paperSubject: 0,
         paperPeriod: 0
       },
+      paperInfoOld: {},
       exersicesType: this.$GLOBAL.EXERCISE_TYPES(),
       exersicesDiff: this.$GLOBAL.EXERCISE_DIFFS(),
       exersicesField: this.$GLOBAL.EXERCISE_LEVELS(),
@@ -215,8 +237,8 @@ export default {
       analysisTableData: [],
       viewModel: 'type',
       paperDiff: 0,
-      curModalTab: ''
-
+      curModalTab: '',
+      isChangePaper: false,
     }
   },
   methods: {
@@ -453,8 +475,163 @@ export default {
 
     scoreUpdate(score) {
       this.allocatedScore = this.paperInfo.score - score
-    }
+    },
+    async showChangePaper(type) {
+        if(type === 1) {
+            if(this.allocatedScore != this.paperInfo.score) {
+                this.$Message.warning(this.$t('evaluation.paperList.noCompleteScoreTip'))
+                return
+            }
+            // 重新保存试卷 && 调接口保存答案、配分
+            let params = {
+                id: this.paperInfo.examId, //活动ID   
+                code: this.paperInfo.scope ? this.paperInfo.school : this.paperInfo.creatorId, //个人ID或者学校编码 
+                scode: this.paperInfo.code, //原本评测活动的完整code:'Exam-hbcn'
+                paperId: this.paperInfo.id, //试卷ID
+                subjectId: this.paperInfo.subjectId, //科目ID 
+                paperAns: [], //作答记录的完整数组  
+                point: [], //完整配分的数组
+                kno: [], //知识点
+                multipleRule: this.paperInfo.multipleRule, //评分规则
+            }
+            this.paperInfo.item.forEach(item => {
+              if(item.type === 'compose') {
+                item.children.forEach(child => {
+                  params.paperAns.push(child.answer)
+                  params.point.push(child.score)
+                  params.kno.push(child.knowledge)
+                })
+              } else {
+                params.paperAns.push(item.answer)
+                params.point.push(item.score)
+                params.kno.push(item.knowledge)
+              }
+            })
+            this.$api.learnActivity.updateAns(params).then(async res => {
+              if(res.code === 200) {
+                const itemJsonFile = await this.$evTools.createBlobPaper(this.paperInfo, this.paperInfo.slides);
+                // let blobPaper = await this.$evTools.createBlobPaper(paperItem, res.slides)
+                // 首先保存新题目的JSON文件到Blob 然后返回URL链接
+                let paperFile = new File([JSON.stringify(itemJsonFile)], "index.json");
+                // 获取初始化Blob需要的数据
+                let sasData = this.paperInfo.scope === 'school' ? await this.$tools.getSchoolSas() : await this.$tools.getPrivateSas()
+                //初始化Blob
+                let containerClient = new blobTool(sasData.url, sasData.name, sasData.sas, this.paperInfo.scope)
+                try {
+                    let promiseArr = []
+                    let blobFile = null
+                    let blobUrl = this.paperInfo.blob.slice(1)
+                    // 放入试题json文件
+                    for (let i = 0; i < this.paperInfo.item.length; i++) {
+                        promiseArr.push(new Promise(async (r, j) => {
+                            try {
+                                let item = this.paperInfo.item[i]
+                                const itemJsonFile = await this.$evTools.createBlobItem(item)
+                                let file = new File([JSON.stringify(itemJsonFile)], item.id + ".json");
+                                containerClient.upload(file, {
+                                    path: blobUrl,
+                                    checkSize: false
+                                }).then(res => {
+                                    r(200)
+                                })
+                            } catch (e) {
+                                console.log(e)
+                            }
+                        }))
+                        if(this.paperInfo.item[i].type === 'compose') {
+                          this.paperInfo.item[i].children.forEach(child => {
+                            promiseArr.push(new Promise(async (r, j) => {
+                              try {
+                                  let item = child
+                                  const itemJsonFile = await this.$evTools.createBlobItem(item)
+                                  let file = new File([JSON.stringify(itemJsonFile)], item.id + ".json");
+                                  containerClient.upload(file, {
+                                      path: blobUrl,
+                                      checkSize: false
+                                  }).then(res => {
+                                      r(200)
+                                  })
+                              } catch (e) {
+                                  console.log(e)
+                              }
+                          }))
+                          })
+                        }
+                    }
+                    // 放入index.json文件
+                    promiseArr.push(new Promise(async (r, j) => {
+                        try {
+                            blobFile = await containerClient.upload(paperFile, {
+                                path: blobUrl,
+                                checkSize: false
+                            })
+                            console.log('上传到试卷目录下', blobFile)
+                            r(blobFile)
+                        } catch (e) {
+                            j(e)
+                            this.$Message.error(e.spaceError)
+                            this.isLoading = false
+                        }
+                    }))
+                    // 不存在多媒体
 
+                    // 进行试卷文件上传Blob 先上传所有题目 再上传index.json文件
+                    Promise.all(promiseArr).then(async result => {
+                        try {
+                            if (blobFile.blob) {
+                                // 试卷保存更新后需要重新生成答题卡
+                                /* paperItem.blob = blobFile.blob.split('/index.json')[0]
+
+                                let params = {
+                                    paper: await this.$evTools.createCosmosPaper(paperItem),
+                                    option: this.isEditPaper ? 'update' : 'insert'
+                                }
+                                //  保存试卷到cosmos
+                                this.$api.learnActivity.SaveExamPaper(params).then(res => {
+                                    if (res.error == null) {
+                                        this.$Message.success(this.$t('evaluation.paperList.saveSuc'))
+                                        this.isLoading = false
+                                        this.$Spin.hide()
+                                        // this.savePeriodInfos()
+                                        // 清空已选试题
+                                        this.showQues = false
+                                        this.selectedArr = []
+                                        this.groupList = []
+                                        this.orderList = []
+                                        this.selShowList = []
+                                    } else {
+                                        this.$Message.error(this.$t('evaluation.paperList.saveFail'))
+                                        this.$Spin.hide()
+                                        this.isLoading = false
+                                    }
+                                },err => {
+                                    this.$Message.error(this.$t('evaluation.paperList.saveFail'))
+                                    this.isLoading = false
+                                    this.$Spin.hide()
+                                }) */
+                            } else {
+                                console.error(blobFile)
+                            }
+                        } catch (e) {
+                            console.log(e)
+                        }
+                    })
+                } catch (e) {
+                    console.log(e)
+                    this.$Message.error(this.$t('evaluation.paperList.saveItemsFailTip'))
+                    this.isLoading = false
+                }
+              }
+            })
+        } else {
+            this.isChangePaper = !this.isChangePaper
+            if(!this.isChangePaper) {
+              this.paperInfo = JSON.parse(JSON.stringify(this.paperInfoOld))
+              this.paper.item = this.paperInfo.item
+              this.$forceUpdate()
+            }
+        }
+    },
 
 
   },
@@ -463,6 +640,7 @@ export default {
     this.$Spin.hide()
     let paper = this.paper || this.$route.params.paper || JSON.parse(localStorage.getItem('c_edit_paper'))
     if (!paper || !paper.item) return
+    this.paperInfoOld = JSON.parse(JSON.stringify(paper))
     console.log('xxxx', paper)
     this.$EventBus.$emit('getNewPaper', paper)
     // localStorage.setItem('_paperInfo', JSON.stringify(paper))
@@ -510,6 +688,18 @@ export default {
       },
       immediate: true,
       deep: true
+    },
+    activityIndex: {
+      handler(n, o) {
+        if(this.isExamInfoPaper) this.isChangePaper = false
+        this.paperInfoOld = JSON.parse(JSON.stringify(this.paper))
+      }
+    },
+    subjectIndex: {
+      handler(n, o) {
+        if(this.isExamInfoPaper) this.isChangePaper = false
+        this.paperInfoOld = JSON.parse(JSON.stringify(this.paper))
+      }
     }
   }
 }

文件差异内容过多而无法显示
+ 1024 - 656
TEAMModelOS/ClientApp/src/view/iot/areaiot.vue


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

@@ -456,7 +456,7 @@ export default {
       initData.forEach((item) => {
         let years = item.year; let months = item.month; let days = item.day
         let isScope = this.isDateInRange(years, months, days, n)
-        if (isScope && item.toolType === 'HiTeach') {
+        if (isScope && item.toolType === 'HiTeach' || item.toolType === 'HiTeachCC') {
           let result = monthData.find(items => items.monthNum == months && items.year == years)
           if (result !== undefined) {
             result.schoolName = item.school.name

+ 4 - 4
TEAMModelOS/ClientApp/src/view/learnactivity/ExamMgt.vue

@@ -141,7 +141,7 @@
 				<!-- 作答详情 -->
 				<AnswerTable v-else-if="curBarIndex == 3" :examInfo="examDetaiInfo" :correctData="correctData" class="evaluation-base-info"></AnswerTable>
 				<!-- 评测试卷 -->
-				<ExamPaper v-else-if="curBarIndex == 1" :examInfo="examDetaiInfo" class="evaluation-base-info"></ExamPaper>
+				<ExamPaper v-else-if="curBarIndex == 1" :examInfo="examDetaiInfo" :activityIndex="curEvaIndex" class="evaluation-base-info"></ExamPaper>
 				<!-- 学情分析 -->
 				<CloudDAS v-else-if="curBarIndex == 4" :evInfo="examDetaiInfo"></CloudDAS>
 				<!-- 阅卷任务 -->
@@ -617,7 +617,7 @@
 				sessionStorage.setItem("examScope", this.evaListShow[index].scope);
 				//跨校个人评测
 				if (!this.evaListShow[this.curEvaIndex].curSchool) {
-					this.examDetaiInfo = this.evaListShow[this.curEvaIndex];
+					this.examDetaiInfo = JSON.parse(JSON.stringify(this.evaListShow[this.curEvaIndex]));
 					this.isLoading = false;
 					return;
 				}
@@ -627,7 +627,7 @@
 				if (this.evaListShow[this.curEvaIndex] && this.evaListShow[this.curEvaIndex].papers.length == 0) {
 					this.findExamInfo();
 				} else {
-					this.examDetaiInfo = this.evaListShow[this.curEvaIndex];
+					this.examDetaiInfo = JSON.parse(JSON.stringify(this.evaListShow[this.curEvaIndex]));
 					if (this.examDetaiInfo.progress === "finish") {
 						this.findSimpleAna();
 					}
@@ -696,7 +696,7 @@
 									this.setPaperQuInfo(resData.papers);
 								}
 								this.evaListShow.splice(this.curEvaIndex, 1, resData);
-								this.examDetaiInfo = resData;
+								this.examDetaiInfo = JSON.parse(JSON.stringify(resData));
 								// if (this.examDetaiInfo.progress === 'finish' && !this.dataErr) {
 								if (this.examDetaiInfo.progress === "finish") {
 									this.findSimpleAna();

+ 6 - 2
TEAMModelOS/ClientApp/src/view/learnactivity/tabs/ExamPaper.vue

@@ -10,7 +10,7 @@
                 </RadioGroup>
             </div>
             <div class="paper-wrap" v-if="!examInfo.examPaperErr">
-                <TestPaper v-if="examInfo && examInfo.papers && examInfo.papers[curSubIndex] && examInfo.papers[curSubIndex].item" :paper="examInfo.papers[curSubIndex]" style="color:#515a6e;" :isShowTools="false" isExamPaper :examId="examInfo.id" canFix></TestPaper>
+                <TestPaper v-if="examInfo && examInfo.papers && examInfo.papers[curSubIndex] && examInfo.papers[curSubIndex].item" :paper="examInfo.papers[curSubIndex]" style="color:#515a6e;" :isShowTools="false" isExamPaper isExamInfoPaper :examId="examInfo.id" :activityIndex="activityIndex" :subjectIndex="curSubIndex" canFix></TestPaper>
                 <EmptyData v-else style="padding-top:60px;" :textContent="$t('learnActivity.simple.paperErr')"></EmptyData>
             </div>
             <div v-else class="paper-err-wrap">
@@ -32,7 +32,11 @@ export default {
             default: () => {
                 return {}
             }
-        }
+        },
+        activityIndex: {
+            type: Number,
+            default: 0
+        },
     },
     data() {
         return {

+ 2 - 1
TEAMModelOS/ClientApp/src/view/mycourse/score/DialogBoxAddTable.vue

@@ -83,7 +83,8 @@ export default {
         "copyid": chooseID
       };
       //console.log("dataInfo=" + JSON.stringify(dataInfo));
-      this.$api.learnActivity.addScorecalc(dataInfo).then(
+      this.$emit('loadingStart');
+      this.$api.learnActivity.addScorecalc(dataInfo, 180000).then(
         res => {  
           this.$emit('closeModal', { value1: false, value2: res.id });    
         },

+ 5 - 1
TEAMModelOS/ClientApp/src/view/mycourse/score/ScoreBody.vue

@@ -646,7 +646,7 @@
       :classInfo="classInfo" :listType="listType">
     </DialogBoxScoreCont>
     <DialogBoxAddTable class="scoreDialogBox" :modalVisible="modalVisibleAddTable"
-      @closeModal="modalVisibleAddTableFun" :class_name="modalVisibleClassName" :Count_attend="Count_attend"
+    @loadingStart="loadingStart"  @closeModal="modalVisibleAddTableFun" :class_name="modalVisibleClassName" :Count_attend="Count_attend"
       :classInfo="classInfo" :listType="listType" :paramsInfo="paramsInfo" :lists="lists">
     </DialogBoxAddTable>
     <!-- 刪除視窗 -->
@@ -2211,7 +2211,11 @@ export default {
         this.Count_interact = data.value3;
       }
     },
+    loadingStart(){
+      this.isLoading = true;
+    },
     modalVisibleAddTableFun(data) {
+      this.isLoading = false;
       this.modalVisibleAddTable = data.value1;
       if (data.value2 !== null) {
         //this.Count_attend = data.value2;

+ 25 - 0
TEAMModelOS/Controllers/Both/PaperController.cs

@@ -153,6 +153,7 @@ namespace TEAMModelOS.Controllers
 #endif
         public async Task<IActionResult> Find(JsonElement request)
         {
+            var (id, _, _, _) = HttpContext.GetAuthTokenInfo();
             //ResponseBuilder builder = ResponseBuilder.custom();
             //if (!request.TryGetProperty("id_token", out JsonElement id_token)) return BadRequest();
             if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
@@ -174,8 +175,32 @@ namespace TEAMModelOS.Controllers
             
             if (scope.ToString().Equals("school"))
             {
+
+                var authData = HttpContext.GetAuthTokenKey("Roles");
+                object _roles = null, _permissions = null;
+                HttpContext?.Items.TryGetValue("Roles", out _roles);
+                HttpContext?.Items.TryGetValue("Permissions", out _permissions);
+                List<string> rolse = new List<string>();
+                if (_roles!= null)
+                {
+                    rolse= (List<string>)_roles;
+                }
+                List<string> permissions = new List<string>();
+                if (_permissions!= null)
+                {
+                    permissions= (List<string>)_permissions;
+                }
                 sql.Append("select  c.id,c.code,c.name,c.blob,c.periodId,c.gradeIds,c.subjectId,c.subjectName,c.score,c.useCount,c.scope,c.scoring,c.createTime,c.sheet ,c.mode ,c.sheetNo, c.tags,c.itemSort,c.qamode,c.isCodeOption,c.attachments,c.creatorId,c.secret from c");
                 AzureCosmosQuery cosmosDbQuery = SQLHelper.GetSQL(dict, sql);
+
+                //if (rolse.Contains("admin"))
+                //{
+                //   // cosmosDbQuery.QueryText= cosmosDbQuery.QueryText.Replace("where", $" where (( IS_DEFINED(c.secret) = false or  c.secret=0 )  or  c.secret=1 ) and  ");
+                //}
+                //else {
+                //    cosmosDbQuery.QueryText= cosmosDbQuery.QueryText.Replace("where", $" where (( IS_DEFINED(c.secret) = false or  c.secret=0 )  or (c.secret=1 and  c.creatorId='{id}') ) and  ");
+                //}
+                cosmosDbQuery.QueryText= cosmosDbQuery.QueryText.Replace("where", $" where (( IS_DEFINED(c.secret) = false or  c.secret=0 )  or (c.secret=1 and  c.creatorId='{id}') ) and  ");
                 await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Paper>(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Paper-{code}") }))
                 {
                     papers.Add(item);

+ 92 - 85
TEAMModelOS/Controllers/Both/ScoreCalcController.cs

@@ -75,7 +75,7 @@ namespace TEAMModelOS.Controllers
     {
         private readonly IHttpClientFactory _httpClient;
         public IWebHostEnvironment _environment { get; set; }
-        private readonly AzureStorageFactory _azureStorage; 
+        private readonly AzureStorageFactory _azureStorage;
         private readonly AzureRedisFactory _azureRedis;
         private readonly AzureCosmosFactory _azureCosmos;
         private readonly DingDing _dingDing;
@@ -551,7 +551,7 @@ namespace TEAMModelOS.Controllers
                 ishaveGrouplistId = !string.IsNullOrWhiteSpace(grouplistId + "");
                 bool ishaveTeammodelId = request.TryGetProperty("teammodelId", out JsonElement teammodelId);
                 bool ishaveSchoolId = request.TryGetProperty("schoolId", out JsonElement schoolId);
-                if ((schoolId + "") == "SYSTEM_NO_SCHOOL" || string.IsNullOrWhiteSpace(schoolId + "")) 
+                if ((schoolId + "") == "SYSTEM_NO_SCHOOL" || string.IsNullOrWhiteSpace(schoolId + ""))
                 {
                     ishaveSchoolId = false;
                 }
@@ -586,8 +586,8 @@ namespace TEAMModelOS.Controllers
                     if (!ishaveYear) { return BadRequest(); }
                     else { year = _year + ""; }
                 }
-                
-                
+
+
                 // 設定活動預設值
                 ScoreCalcLsRecord scoreCalcLsRecord = new ScoreCalcLsRecord();
                 ScoreCalcActivity scoreCalcActivity_eaxm = new ScoreCalcActivity();
@@ -2017,7 +2017,6 @@ namespace TEAMModelOS.Controllers
             }
             JsonElement rqt = System.Text.Json.JsonSerializer.SerializeToElement(new
             {
-                tmdid = teammodelId,
                 courseId = courseId,
                 groupIds = new List<string> { sgroupIds + "" }
             });
@@ -2029,8 +2028,8 @@ namespace TEAMModelOS.Controllers
             string tbname = "";
             string code = "";
             string school = null;
-            string sqlPrivate = "";
-            List<string> autoTch = new List<string>();
+            //string sqlPrivate = "";
+            //List<string> autoTch = new List<string>();
 
             // 如果是學校課程
             if (scope.GetString().Equals("school"))
@@ -2039,32 +2038,12 @@ namespace TEAMModelOS.Controllers
                 {
                     code = $"LessonRecord-{schoolId}";// 組合區域碼
                     tbname = "School";
-                    school = $"{schoolId}";
-                    List<string> ids = new List<string>();
+                    school = $"{schoolId}";                    
                     //只查询某个老师的课例
-                    if (!string.IsNullOrWhiteSpace($"{teammodelId}"))
+                    if (string.IsNullOrWhiteSpace($"{teammodelId}"))                                                            
                     {
-                        ids.Add($"{teammodelId}");// 組合老師id
-                    }
-                    else
-                    {
-                        return false;
-                        //string sqltch = "select distinct value(c.id) from c ";
-                        //await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
-                        //    .GetItemQueryIterator<string>(queryText: sqltch, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Teacher-{schoolId}") }))
-                        //{
-                        //    ids.Add(item);// 如果沒給老師id  撈出全部這間學校的老師ID
-                        //}
-                    }
-                    if (ids.Any())
-                    {
-                        string sqlTechbase = $"select distinct value(c.id) from c  where c.id in ({string.Join(",", ids.Select(x => $"'{x}'"))})  and (array_contains(c.lessonShow,'student') or array_contains(c.lessonShow,'all')) ";
-                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
-                           .GetItemQueryIterator<string>(queryText: sqlTechbase, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
-                        {
-                            autoTch.Add(item);
-                        }
-                    }
+                        return false;                        
+                    }                   
                 }
                 else
                 {
@@ -2075,22 +2054,7 @@ namespace TEAMModelOS.Controllers
             {
                 code = $"LessonRecord";
                 tbname = "Teacher";
-                if (!string.IsNullOrEmpty($"{teammodelId}"))
-                {
-                    sqlPrivate = $" and c.tmdid='{teammodelId}'";
-                    List<string> ids = new List<string>();
-                    ids.Add($"{teammodelId}");
-                    if (ids.Any())
-                    {
-                        string sqlTechbase = $"select distinct value(c.id) from c  where c.id in ({string.Join(",", ids.Select(x => $"'{x}'"))})  and (array_contains(c.lessonShow,'student') or array_contains(c.lessonShow,'all')) ";
-                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
-                           .GetItemQueryIterator<string>(queryText: sqlTechbase, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
-                        {
-                            autoTch.Add(item);
-                        }
-                    }
-                }
-                else
+                if (string.IsNullOrEmpty($"{teammodelId}"))                
                 {
                     //如果不传tmdid, 则必须传递,课程id或者名单列表
                     // 如果不传递tmdid
@@ -2110,7 +2074,7 @@ namespace TEAMModelOS.Controllers
             {
                 string sql_status_managePage = "(c.status<>404 or IS_DEFINED(c.status) = false ) and  ";
 
-                cosmosDbQuery.QueryText = cosmosDbQuery.QueryText.Replace("where", $" where  {sql_status_managePage}  array_length(c.groupIds)>0 {sqlPrivate}  and  ");
+                cosmosDbQuery.QueryText = cosmosDbQuery.QueryText.Replace("where", $" where  {sql_status_managePage}  array_length(c.groupIds)>0  and  ");
 
                 await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname)
                    .GetItemQueryStreamIterator(queryDefinition: cosmosDbQuery.CosmosQueryDefinition, continuationToken: continuationToken,
@@ -2207,34 +2171,64 @@ namespace TEAMModelOS.Controllers
             #endregion
 
             List<ScoreLessonBase> lessonBases = new List<ScoreLessonBase>();
-            string blobname = "";
-            if (ishaveClassId)
-            {// 如果有ClassId 要用schoolId去取JSON
-                blobname = $"{schoolId}";
-            }
-            else if (ishaveGrouplistId)
-            {// 如果沒有ClassId 要用teammodelId去取JSON
-                blobname = $"{teammodelId}";
-            }
+            string blobname = "";         
 
             foreach (var item in lessonRecords)
             {// 先檢查課堂紀錄是否存在再取資料
-                blobname = (item.scope.Equals("school")) ? $"{schoolId}" : $"{teammodelId}"; //[Jeff] 修正blobname:改用課堂紀錄的scope欄位判斷
-                if (_azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{item.id}/IES/base.json").Exists())
-                {
-                    ScoreCalcActivityItems scoreCalcActivityItems = new ScoreCalcActivityItems();
-                    scoreCalcActivityItems.id = item.id;
-                    scoreCalcActivityItems.use = true;
+                if (item.scope.Equals("school"))
+                {// 學校課程 用學校id取blob
+                    blobname = schoolId.GetString();
+                    if (_azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{item.id}/IES/base.json").Exists())
+                    {
+                        ScoreCalcActivityItems scoreCalcActivityItems = new ScoreCalcActivityItems();
+                        scoreCalcActivityItems.id = item.id;
+                        scoreCalcActivityItems.use = true;
 
-                    scoreCalcLsRecord.items.Add(scoreCalcActivityItems);
+                        scoreCalcLsRecord.items.Add(scoreCalcActivityItems);
 
-                    BlobDownloadResult baseblobDownload = await _azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{item.id}/IES/base.json").DownloadContentAsync();
-                    ScoreLessonBase lessonBase = baseblobDownload.Content.ToObjectFromJson<ScoreLessonBase>();
-                    if (lessonBase != null && lessonBase.summary != null)
-                    {
-                        lessonBases.Add(lessonBase);
+                        BlobDownloadResult baseblobDownload = await _azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{item.id}/IES/base.json").DownloadContentAsync();
+                        ScoreLessonBase lessonBase = baseblobDownload.Content.ToObjectFromJson<ScoreLessonBase>();
+                        if (lessonBase != null && lessonBase.summary != null)
+                        {
+                            lessonBases.Add(lessonBase);
+                        }
                     }
                 }
+                else 
+                { // 個人課程
+                    List<CourseTask_ta> ctList = new();
+                    // 檢查這個課程的助教跟教授裡面有沒有包含傳進來的teammodelId   有的話用教授取blob的資料
+                    string sqlCourseTask = $"SELECT distinct b.teacherId, b.assistants FROM c join b in c.schedules where c.pk = 'CourseTask' and (ARRAY_CONTAINS(b.assistants, '{teammodelId}') or  b.teacherId = '{teammodelId}') and b.groupId = '{grouplistId}' and c.code = 'CourseTask' and c.courseId = '{courseId}' AND ARRAY_LENGTH(b.assistants) > 0";
+
+                    //string sqlCourseTask = $"SELECT s.groupId, s.assistants FROM c JOIN s IN c.schedules WHERE c.pk='CourseTask'AND c.courseId = '{courseId}' AND s.groupId = '{grouplistId}' AND ARRAY_LENGTH(s.assistants) > 0";
+                    await foreach (var ctitem in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<CourseTask_ta>(queryText: sqlCourseTask))
+                    {
+                        ctList.Add(ctitem);
+                    }
+                    if (ctList.Count > 0)
+                    {
+                        blobname = ctList[0].teacherId;
+                        if (_azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{item.id}/IES/base.json").Exists())
+                        {
+                            ScoreCalcActivityItems scoreCalcActivityItems = new ScoreCalcActivityItems();
+                            scoreCalcActivityItems.id = item.id;
+                            scoreCalcActivityItems.use = true;
+
+                            scoreCalcLsRecord.items.Add(scoreCalcActivityItems);
+
+                            BlobDownloadResult baseblobDownload = await _azureStorage.GetBlobContainerClient(blobname).GetBlobClient($"/records/{item.id}/IES/base.json").DownloadContentAsync();
+                            ScoreLessonBase lessonBase = baseblobDownload.Content.ToObjectFromJson<ScoreLessonBase>();
+                            if (lessonBase != null && lessonBase.summary != null)
+                            {
+                                lessonBases.Add(lessonBase);
+                            }
+                        }
+                    }
+                    else { return false; }
+
+                    
+                }                
+                
             }
             List<ScoreLessonBase> lessonBasesCalcList = new List<ScoreLessonBase>();
             // 重新比對資料以防  班級人數跟分數的數量對不上             
@@ -2597,14 +2591,14 @@ namespace TEAMModelOS.Controllers
 
                 // 先排序傳進來的參數 aesc
                 List<sortItem> sortItemsrq = (from e in updateActItemSortRq.sortItems
-                                                orderby e.sort
-                                                select e).ToList();
+                                              orderby e.sort
+                                              select e).ToList();
 
                 // 先判斷是否為課堂紀錄 根據項目的類別需要調整
                 if (scoreCalcActivityBase.type == "lessonrecord")
                 {
                     ScoreCalcLsRecord scoreCalcLsRecordDB = await clientTeacher.ReadItemAsync<ScoreCalcLsRecord>(updateActItemSortRq.scoreCalcActId.ToString(), new PartitionKey($"ScoreCalcAct-{teammodelId}"));
-                    
+
                     //存放排序後的參數
                     List<List<double>> up_stuActAttendOrgVals = new();
                     List<List<double>> up_stuActAttendScores = new();
@@ -2615,11 +2609,11 @@ namespace TEAMModelOS.Controllers
                     List<double> up_itemRates = new();
 
                     //依照傳進來的id 順序修改 分數 比重 項目等順序
-                    for (int i = 0; i < sortItemsrq.Count; i++) 
+                    for (int i = 0; i < sortItemsrq.Count; i++)
                     {
                         for (int j = 0; j < scoreCalcLsRecordDB.items.Count; j++)
                         {
-                            if (sortItemsrq[i].id == scoreCalcLsRecordDB.items[j].id) 
+                            if (sortItemsrq[i].id == scoreCalcLsRecordDB.items[j].id)
                             {
                                 up_stuActAttendOrgVals.Add(scoreCalcLsRecordDB.stuActAttendOrgVals[j]);
                                 up_stuActAttendScores.Add(scoreCalcLsRecordDB.stuActAttendScores[j]);
@@ -2628,7 +2622,7 @@ namespace TEAMModelOS.Controllers
                                 up_stuActItactOrgVals.Add(scoreCalcLsRecordDB.stuActItactOrgVals[j]);
                                 up_stuActItactScores.Add(scoreCalcLsRecordDB.stuActItactScores[j]);
                                 up_itemRates.Add(scoreCalcLsRecordDB.itemRates[j]);
-                                scoreCalcLsRecordDB.items[j].sort = sortItemsrq[i].sort;                                
+                                scoreCalcLsRecordDB.items[j].sort = sortItemsrq[i].sort;
                             }
                         }
 
@@ -2642,17 +2636,17 @@ namespace TEAMModelOS.Controllers
                     scoreCalcLsRecordDB.itemRates = up_itemRates;
                     // 按照更新後的sort排序
                     scoreCalcLsRecordDB.items = (from e in scoreCalcLsRecordDB.items
-                                                  orderby e.sort
-                                                  select e).ToList();
-                    
+                                                 orderby e.sort
+                                                 select e).ToList();
+
                     scoreCalcLsRecordDB = await clientTeacher.ReplaceItemAsync(scoreCalcLsRecordDB, $"{scoreCalcLsRecordDB.id}", new PartitionKey(scoreCalcLsRecordDB.code));
                 }
                 else
                 {
                     ScoreCalcActivity scoreCalcActivityDB = await clientTeacher.ReadItemAsync<ScoreCalcActivity>(updateActItemSortRq.scoreCalcActId.ToString(), new PartitionKey($"ScoreCalcAct-{teammodelId}"));
-                    
+
                     //存放排序後的參數
-                    List<List<double>> up_stuActScores =new();
+                    List<List<double>> up_stuActScores = new();
                     List<List<double>> up_stuActScoresOrg = new();
                     List<double> up_itemRates = new();
 
@@ -2675,9 +2669,9 @@ namespace TEAMModelOS.Controllers
                     scoreCalcActivityDB.itemRates = up_itemRates;
                     // 按照更新後的sort排序
                     scoreCalcActivityDB.items = (from e in scoreCalcActivityDB.items
-                                                  orderby e.sort
-                                                  select e).ToList();
-                    
+                                                 orderby e.sort
+                                                 select e).ToList();
+
                     scoreCalcActivityDB = await clientTeacher.ReplaceItemAsync(scoreCalcActivityDB, $"{scoreCalcActivityDB.id}", new PartitionKey(scoreCalcActivityDB.code));
                 }
 
@@ -2695,7 +2689,7 @@ namespace TEAMModelOS.Controllers
                 return BadRequest();
             }
         }
-        
+
 
         /// <summary>
         /// (十七)查詢項目屬性資料
@@ -2783,8 +2777,21 @@ namespace TEAMModelOS.Controllers
             }
         }
 
+        /// <summary>
+        /// 查詢CourseTask用類別
+        /// </summary>
+        private class CourseTask_ta
+        {
+            /// <summary>
+            /// teacherId
+            /// </summary>
+            public string teacherId { get; set; }
+            /// <summary>
+            /// assistants
+            /// </summary>            
+            public List<string> assistants { get; set; } = new List<string>();
 
-
+        }
 
     }
 }

+ 5 - 5
TEAMModelOS/Controllers/Client/HiScanController.cs

@@ -122,11 +122,11 @@ namespace TEAMModelOS.Controllers.Core
                 }
 
                 //用户在线记录
-                try
-                {
-                    _ = _httpTrigger.RequestHttpTrigger(new { school = response.defaultSchool, scope = Constant.ScopeTeacher, id = $"{id}", ip = ip, expire = 1 }, _option.Location, "online-record");
-                }
-                catch { }
+                //try
+                //{
+                //    _ = _httpTrigger.RequestHttpTrigger(new { school = response.defaultSchool, scope = Constant.ScopeTeacher, id = $"{id}", ip = ip, expire = 1 }, _option.Location, "online-record");
+                //}
+                //catch { }
 
                 var (tblob_uri, tblob_sas) = _azureStorage.GetBlobContainerSAS($"{id}", BlobContainerSasPermissions.Write | BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List | BlobContainerSasPermissions.Delete);
                 return Ok(new { schools, teacher = new { name, picture, id, bloburl = tblob_uri, blobsas = tblob_sas } });

+ 5 - 5
TEAMModelOS/Controllers/Client/HiTAControlller.cs

@@ -630,11 +630,11 @@ namespace TEAMModelOS.Controllers.Client
                     //}
 
                     //用户在线记录
-                    try
-                    {
-                        _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-                    }
-                    catch { }
+                    //try
+                    //{
+                    //    _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+                    //}
+                   // catch { }
                     await SystemService.RecordAccumulateData(_azureRedis, _dingDing, new SDK.Models.Dtos.Accumulate { client="hita", count=1, id="ies", key="tmd_login", name="醍摩豆账号登录", scope="ies", target="ies" });
                     return Ok(new { schools, defaultschool, courses, size, resCount, itemCount, paperCount, activityCount });
                 }

+ 11 - 11
TEAMModelOS/Controllers/Client/HiTeachController.cs

@@ -2394,11 +2394,11 @@ namespace TEAMModelOS.Controllers.Client
                     }
                 }
                 //用户在线记录
-                try
-                {
-                    _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-                }
-                catch { }
+                //try
+                //{
+                //    _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+                //}
+                //catch { }
 
                 //取得Teacher Blob 容器位置及SAS 
                 var container = _azureStorage.GetBlobContainerClient(id);
@@ -2969,11 +2969,11 @@ namespace TEAMModelOS.Controllers.Client
                 }
 
                 //用户在线记录
-                try
-                {
-                    _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-                }
-                catch { }
+                //try
+                //{
+                //    _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeTeacher}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+                //}
+                //catch { }
 
                 //取得School Blob 容器位置及SAS
                 string school_code_blob = school_code.GetString().ToLower();
@@ -3028,7 +3028,7 @@ namespace TEAMModelOS.Controllers.Client
                 }
                 //SQL文
                 List<object> papers = new List<object>();
-                string queryWhere = " WHERE 1=1 ";
+                string queryWhere = $" WHERE (( IS_DEFINED(c.secret) = false OR c.secret=0 ) OR ( c.secret=1 AND c.creatorId='{id.ToString()}') )";
                 string queryOption = string.Empty;
                 //學段
                 if (request.TryGetProperty("periodId", out JsonElement periodId) && container.Equals("School"))

+ 10 - 10
TEAMModelOS/Controllers/Student/StudentController.cs

@@ -744,11 +744,11 @@ namespace TEAMModelOS.Controllers
             var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name, picture, _option.JwtSecretKey, scope: Constant.ScopeStudent, Website: "IES", timezone: timezone, areaId: areaId, schoolID: school_code, roles: new[] { "student" }, expire: 4,year: student.year);
 
             //用户在线记录
-            try
-            {
-                _ = _httpTrigger.RequestHttpTrigger(new { school = school_code, scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-            }
-            catch {}
+            //try
+            //{
+            //    _ = _httpTrigger.RequestHttpTrigger(new { school = school_code, scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+            //}
+            //catch {}
 
             await cosmosClient.GetContainer("TEAMModelOS", "Student").ReplaceItemAsync<Student>(student, id, new PartitionKey($"Base-{school_code}"));
           
@@ -1337,11 +1337,11 @@ namespace TEAMModelOS.Controllers
                         var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id.GetString(), name.GetString(), picture.GetString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, areaId: schoolInfo.areaId, scope: Constant.ScopeStudent, schoolID: school_code.GetString(), roles: new[] { "student" }, expire: 4);
                         
                         //用户在线记录
-                        try
-                        {
-                            _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-                        }
-                        catch { }
+                        //try
+                        //{
+                        //    _ = _httpTrigger.RequestHttpTrigger(new { school = school_code.GetString(), scope = $"{Constant.ScopeStudent}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+                        //}
+                        //catch { }
 
 
                         //保存学生登录信息

+ 5 - 5
TEAMModelOS/Controllers/Student/TmdUserController.cs

@@ -167,11 +167,11 @@ namespace TEAMModelOS.Controllers
                 var auth_token = JwtAuthExtension.CreateAuthToken(_option.HostName, id, name?.ToString(), picture?.ToString(), _option.JwtSecretKey, Website: "IES", timezone: timezone, scope: Constant.ScopeTmdUser, roles: new[] { "student" }, expire: 1);
 
                 //用户在线记录
-                try
-                {
-                    _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTmdUser}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
-                }
-                catch { }
+                //try
+                //{
+                //    _ = _httpTrigger.RequestHttpTrigger(new { school = defaultschool, scope = $"{Constant.ScopeTmdUser}", id = $"{id}", ip = $"{ip}", expire = 1 }, _option.Location, "online-record");
+                //}
+                //catch { }
 
                 if (!string.IsNullOrEmpty(defaultschool))
                 {

文件差异内容过多而无法显示
+ 7 - 23
TEAMModelOS/Controllers/System/WeChatPayController.cs


+ 6 - 4
TEAMModelOS/Controllers/Third/IRS/ThirdIRSController.cs

@@ -50,7 +50,9 @@ namespace TEAMModelOS.Controllers
         {
             {"2ffbd7f9-2445-4139-90a7-59e27c0d097e",("grouplist","ThirdIRS_139zhxy") },
             {"hbcn",("school","ThirdIRS_139zhxy") },
-            {"habook",("school","ThirdIRS_139zhxy") }
+            {"habook",("school","ThirdIRS_139zhxy") },
+            {"21eab496-9260-4881-a955-1eb5d3d04b88",("area","ThirdIRS_139zhxy") },
+            {"mzhmxx",("school","ThirdIRS_139zhxy") }
         };
         
         public AzureCosmosFactory _azureCosmos;
@@ -182,7 +184,7 @@ namespace TEAMModelOS.Controllers
         [HttpPost("third-irs/receive-139zhxy")]
         public async Task<IActionResult> Receive_139zhxy(JsonElement json)
         {
-            await _dingDing.SendBotMsg($"收到,接收到 139zhxy反馈器推送信息!{json.ToJsonString()}\n", GroupNames.成都开发測試群組);
+         //   await _dingDing.SendBotMsg($"收到,接收到 139zhxy反馈器推送信息!{json.ToJsonString()}\n", GroupNames.成都开发測試群組);
             string sign = string.Empty, timestamp = string.Empty, message="请求失败";
             int code = -1;
             long now =  DateTimeOffset.UtcNow.GetGMTTime(8).ToUnixTimeMilliseconds();
@@ -201,7 +203,7 @@ namespace TEAMModelOS.Controllers
                 //    if (local_sign.Equals(sign))
                     {
                         var result = json.ToObject<ZHXY139IRSResult>();
-                        await _dingDing.SendBotMsg($"接收到 139zhxy反馈器推送信息!{json.ToJsonString()}\n", GroupNames.成都开发測試群組);
+                       // await _dingDing.SendBotMsg($"接收到 139zhxy反馈器推送信息!{json.ToJsonString()}\n", GroupNames.成都开发測試群組);
                         var data = await _azureRedis.GetRedisClient(8).StringGetAsync($"LessonTask:{result.workId}");
                         if (!data.IsNullOrEmpty)
                         {
@@ -271,7 +273,7 @@ namespace TEAMModelOS.Controllers
                                 text = common
                             }});
                             message= "推送成功!";
-                                await _dingDing.SendBotMsg($"接收到 139zhxy反馈器推送信息并转发成功!{json.ToJsonString()}\n{ans.ToJsonString()}", GroupNames.成都开发測試群組);
+                             //   await _dingDing.SendBotMsg($"接收到 139zhxy反馈器推送信息并转发成功!{json.ToJsonString()}\n{ans.ToJsonString()}", GroupNames.成都开发測試群組);
                                 return Ok(new { code = 0, message, timestamp = now });
 
                             }

+ 184 - 12
TEAMModelOS/Controllers/XTest/TestController.cs

@@ -124,6 +124,12 @@ namespace TEAMModelOS.Controllers
         [RequestSizeLimit(102_400_000_00)] //最大10000m左右
         public async Task<IActionResult> RemoveStuCourse(JsonElement json)
         {
+            json.TryGetProperty("am", out JsonElement _am);
+            json.TryGetProperty("pm", out JsonElement _pm);
+            int am = 0;
+            int pm = 0;
+            _am.TryGetInt32(out am);
+            _pm.TryGetInt32(out pm);
             var url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
             var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
             var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
@@ -147,7 +153,7 @@ namespace TEAMModelOS.Controllers
             {
                 client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
             }
-            List<CodeValue> notifys= await SystemService.AccumulateDaily( _azureRedis,_azureCosmos, _coreAPIHttpService,_dingDing,client, _snowflakeId, url,_mailFactory);
+            List<CodeValue> notifys= await SystemService.AccumulateDaily(_configuration, _azureRedis,_azureCosmos, _coreAPIHttpService,_dingDing,client, _snowflakeId, url,_mailFactory,am ,pm );
             return Ok(new { notifys });
         }
 
@@ -3089,6 +3095,7 @@ namespace TEAMModelOS.Controllers
             jointExam.id = "daaa3965-db28-49ae-8074-c9d4865b62ad";
             jointExam.creatorId = "1595321354";
             jointExam.name = "統測測試(一)";
+            jointExam.scope = "private";
             //課程列表
             ////課程1: [1595321354]"courseId": "e1d817e5-71b3-bc50-52ac-fa828e5f38d6", "groupId": "4dd434a8-a94a-4373-9a05-1a1add533a71",
             JointExam.JointExamGroup jointExamGroup1 = new JointExam.JointExamGroup();
@@ -3109,21 +3116,51 @@ namespace TEAMModelOS.Controllers
             jointExamGroup2.courseLists.Add(jointExamGroupCourse2);
             jointExam.groupLists.Add(jointExamGroup2);
             //試卷
-            string paperJson = "{\"id\": \"a30bb6e7-f10a-7adf-0d88-a36c718f6d80\",\"code\": \"Paper-hbgl\",\"name\": \"英聽測試\",\"blob\": \"/exam/b8376136-e797-487b-862f-db04326c8757/paper/a30bb6e7-f10a-7adf-0d88-a36c718f6d80\",\"scope\": \"school\",\"multipleRule\": 1,\"point\": [100],\"answers\": [[\"A\"]],\"knowledge\": [[]],\"type\": [],\"field\": [2],\"sheet\": null,\"sheetNo\": null,\"time\": 0}";
-            PaperSimple paper = JsonConvert.DeserializeObject<PaperSimple>(paperJson);
+            ///STEP1 Blob拷貝 ※此步驟由前端做
+            ///拷貝目標:{TMID/SchoolId}/jointexam/{jointExam.id}/paper/{paperId}
+            string paperSourceBlobPath = "paper/語文理解";
+            string paperSourceBlobContainer = "tbslgb";
+            string paperSourceScope = "school";
+            var paperSourceBlobContainerClient = _azureStorage.GetBlobContainerClient(paperSourceBlobContainer);
+            string paperId = "1c6a1e85-4b34-b8af-00de-a379bc47a26c";
+            string paperTargetBlobPath = $"jointexam/{jointExam.id}/paper/{paperId}";
+            string paperTargetBlobContainer = jointExam.creatorId;
+            var paperTargetBlobContainerClient = _azureStorage.GetBlobContainerClient(paperTargetBlobContainer);
+            Pageable<BlobItem> sourceBlobs = paperSourceBlobContainerClient.GetBlobs(prefix: paperSourceBlobPath);
+            if (sourceBlobs.Count() > 0)
+            {
+                foreach (var blob in sourceBlobs)
+                {
+                    var sourceFileBlob = paperSourceBlobContainerClient.GetBlobClient(blob.Name);
+                    if (sourceFileBlob.Exists())
+                    {
+                        var sourceFileUri = paperSourceBlobContainerClient.GetBlobClient(blob.Name).Uri;
+                        string fileName = blob.Name.Replace(paperSourceBlobPath, "");
+                        string destBlobFilePath = $"{paperTargetBlobPath}{fileName}";
+                        await paperTargetBlobContainerClient.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
+                    }
+                    else
+                    {
+                        bool paperDataCopyErrFlg = true;
+                    }
+                }
+            }
+            ///STEP2 document製作
+            PaperSimple paper = new PaperSimple() { id= paperId , code = "Paper-tbslgb", name = "語文理解", blob = $"/{paperTargetBlobPath}", scope = "school", multipleRule = 1, point = new List<double>() { 50, 50}, answers = new List<List<string>>() { new List<string>() { "D" }, new List<string>() { "D" } }, knowledge = new List<List<string>>(), type = new List<string>() { "single","single"}, field=new List<int>() { 2, 2 } };
             jointExam.papers.Add(paper);
             //時間
             jointExam.createTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
             jointExam.startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
             jointExam.endTime = DateTimeOffset.UtcNow.AddDays(2).ToUnixTimeMilliseconds();
             jointExam.progress = "going";
-            
             await client.GetContainer(Constant.TEAMModelOS, "Common").UpsertItemAsync<JointExam>(jointExam, new PartitionKey("JointExam"));
             return Ok();
         }
         */
+
         //評量統一生成測試
         /*
+        [HttpPost("add-exam-byJoint")]
         public async Task<IActionResult> AddExamByJointExam(JsonElement json)
         {
             if (!json.TryGetProperty("jointExamId", out JsonElement _jointExamId)) return BadRequest();
@@ -3145,7 +3182,7 @@ namespace TEAMModelOS.Controllers
             List<ExamInfo> examList = new List<ExamInfo>();
             if(jointExam.groupLists.Count > 0)
             {
-                foreach(JointExam.JointExamGroup jointExamGroup in jointExam.groupLists)
+                foreach (JointExam.JointExamGroup jointExamGroup in jointExam.groupLists)
                 {
                     string actExamCreatorId = jointExamGroup.creatorId;
                     foreach(JointExam.JointExamGroup.JointExamGroupCourse jointExamGroupCourse in jointExamGroup.courseLists)
@@ -3154,37 +3191,171 @@ namespace TEAMModelOS.Controllers
                         string actExamCourseName = jointExamGroupCourse.courseName;
                         //評量資料生成
                         ExamInfo actExamInfo = new ExamInfo();
+                        actExamInfo.code = $"Exam-{jointExamGroup.creatorId}";
                         actExamInfo.owner = "teacher";
+                        actExamInfo.scope = jointExam.scope;
                         actExamInfo.creatorId = actExamCreatorId;
                         actExamInfo.id = Guid.NewGuid().ToString();
                         actExamInfo.source = "1"; //目前先設定為"智慧教室評量",實際上應讓老師選擇
                         actExamInfo.name = jointExam.name;
+                        actExamInfo.jointExamId = jointExam.id;
                         ///評量stuLists
-                        foreach(string actGroupId in jointExamGroupCourse.groupListIds)
+                        foreach (string actGroupId in jointExamGroupCourse.groupListIds)
                         {
                             actExamInfo.stuLists.Add(actGroupId);
                             List<string> targetRow = new List<string>() { actExamCourseId, actGroupId };
                             actExamInfo.targets.Add(System.Text.Json.JsonSerializer.SerializeToElement(targetRow));
                             actExamInfo.subjects.Add(new ExamSubject() { id = actExamCourseId, name = actExamCourseName });
                         }
-                        ///試卷
-                        actExamInfo.papers = jointExam.papers;
-
-
+                        ///試卷 ※先用統購的試卷記入,生成評量(generateExam)時會做blob的路徑修正
+                        actExamInfo.papers = JsonConvert.DeserializeObject<List<PaperSimple>>(JsonConvert.SerializeObject(jointExam.papers));
                         ///時間
                         actExamInfo.year = DateTimeOffset.UtcNow.Year;
-
+                        actExamInfo.startTime = jointExam.startTime;
+                        actExamInfo.endTime = jointExam.endTime;
 
                         examList.Add(actExamInfo);
                     }
                 }
+            }
 
+            //生成評量
+            if(examList.Count > 0)
+            {
+                foreach(ExamInfo exam in examList)
+                {
+                    await generateExam(jointExam, exam);
+                }
             }
+
             return Ok();
         }
         */
+        //生成評量(單)
+        /*
+        private async Task<string> generateExam(JointExam jointExam, ExamInfo exam)
+        {
+            var client = _azureCosmos.GetCosmosClient();
+            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            string Result = string.Empty;
+            exam.createTime = now;
+            if (exam.startTime <= 0) exam.startTime = now;
+            List<(string pId, List<string> gid)> ps = new();
+            var group = exam.groupLists;
+            if (group.Count > 0)
+            {
+                foreach (var keys in group)
+                {
+                    foreach (KeyValuePair<string, List<string>> pp in keys)
+                    {
+                        ps.Add((pp.Key, pp.Value));
+                    }
+                }
+            }
+            List<string> classes = ExamService.getClasses(exam.classes, exam.stuLists);
+            (List<RMember> tchList, List<RGroupList> classLists) = await GroupListService.GetMemberByListids(_coreAPIHttpService, client, _dingDing, classes, exam.school, ps);
+            exam.stuCount = tchList.Count;
+            string mode = string.Empty;
+            Response response = null;
+            if (string.IsNullOrEmpty(exam.id))
+            {
+                mode = "add";
+            }
+            else
+            {
+                response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(exam.id, new PartitionKey($"{exam.code}"));
+                if (response.Status == 200) mode = "upd";
+                else mode = "add";
+            }
+            //DB操作
+            if (mode.Equals("add")) //新建
+            {
+                if(string.IsNullOrWhiteSpace(exam.id)) exam.id = Guid.NewGuid().ToString();
+                exam.progress = (exam.startTime > now) ? "pending" : "going";
+                var messageBlob = new ServiceBusMessage();
+                exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}"); //統測只允許老師個人,Blob只指向creatorId
+                await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "insert", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
+                int n = 0;
+                List<string> sheetIds = new List<string>();
+                foreach (PaperSimple simple in exam.papers)
+                {
+                    simple.blob = $"/exam/{exam.id}/paper/{exam.subjects[n].id}";
+                    n++;
+                    simple.sheet = null;
+                }
+                exam = await client.GetContainer(Constant.TEAMModelOS, "Common").CreateItemAsync(exam, new PartitionKey($"{exam.code}"));
+                await BIStats.SetTypeAddStats(client, _dingDing, exam.school, "Exam", 1);//BI统计增/减量
+            }
+            else if(response != null) //更新
+            {
+                using var json = await JsonDocument.ParseAsync(response.ContentStream);
+                ExamInfo info = json.ToObject<ExamInfo>();
+                if (info.progress.Equals("going"))
+                {
+                    Result = "活动正在进行中,无法修改";
+                }
+                var messageBlob = new ServiceBusMessage();
+                exam.size = await _azureStorage.GetBlobContainerClient(exam.creatorId).GetBlobsSize($"exam/{exam.id}");
+                await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = $"exam", name = exam.creatorId }, _serviceBus, _configuration, _azureRedis);
+                exam.progress = info.progress;
+                int n = 0;
+                foreach (PaperSimple simple in exam.papers)
+                {
+                    simple.blob = "/exam/" + exam.id + "/paper/" + exam.subjects[n].id;
+                    n++;
+                }
+                exam = await client.GetContainer(Constant.TEAMModelOS, "Common").ReplaceItemAsync(exam, exam.id, new PartitionKey($"{exam.code}"));
+            }
+            //Blob操作 ※取得試卷源(blob)、複製到評測紀錄下
+            ///試卷源字典
+            List<Dictionary<string, string>> sourcePaperInfo = new List<Dictionary<string, string>>();
+            foreach (PaperSimple paperInfo in jointExam.papers)
+            {
+                string paperBlobPath = (!paperInfo.blob.EndsWith("/")) ? paperInfo.blob + "/" : paperInfo.blob;
+                paperBlobPath = (paperInfo.blob.StartsWith("/")) ? paperBlobPath.Remove(0, 1) : paperBlobPath;
+                sourcePaperInfo.Add(new Dictionary<string, string>() { { "id", paperInfo.id }, { "blob", paperBlobPath }, { "itemcount", paperInfo.point.Count.ToString() } });
+            }
+            bool paperDataCopyErrFlg = false; //試卷資料拷貝錯誤Flag  true:拷貝錯誤
+            //Blob拷貝程序
+            int paperIndex = 0;
+            foreach (Dictionary<string, string> sourcePaperInfoDic in sourcePaperInfo)
+            {
+                //拷貝源:Container => jointExam.creatorId  Path:papers.blob
+                //拷貝對象:Container => exam.creatorId, Path:exam/{exam.id}/paper/{exam.subjects[paperIndex].id}/
+                string targetScope = exam.scope; //評測對象 school:校本班級  private:私人課程
+                var sourceBlobContainer = _azureStorage.GetBlobContainerClient(jointExam.creatorId);
+                var blobPrivateContainer = _azureStorage.GetBlobContainerClient(exam.creatorId);
+                string sourceBlobPath = sourcePaperInfoDic["blob"];
+                string subjectId = exam.subjects[paperIndex].id;
+                string destBlobPath = $"exam/{exam.id}/paper/{subjectId}/"; //拷貝對象路徑 path:exam/{評測ID}/paper/{subjectID}/
+                Pageable<BlobItem> sourceBlobs = sourceBlobContainer.GetBlobs(prefix: sourceBlobPath);
+                if (sourceBlobs.Count() > 0)
+                {
+                    foreach (var blob in sourceBlobs)
+                    {
+                        var sourceFileBlob = sourceBlobContainer.GetBlobClient(blob.Name);
+                        if (sourceFileBlob.Exists())
+                        {
+                            var sourceFileUri = sourceBlobContainer.GetBlobClient(blob.Name).Uri;
+                            string fileName = blob.Name.Replace(sourceBlobPath, "");
+                            string destBlobFilePath = $"{destBlobPath}{fileName}";
+                            await blobPrivateContainer.GetBlobClient(destBlobFilePath).StartCopyFromUriAsync(sourceFileUri);
+                        }
+                        else
+                        {
+                            paperDataCopyErrFlg = true;
+                        }
+                    }
+                }
+                paperIndex++;
+            }
+
+            return Result;
+        }
+        */
+        //統測資料架構
         /*
-        public class JointExam : CosmosEntity //統測架構
+        public class JointExam : CosmosEntity 
         {
             public string creatorId { get; set; }
             public string name { get; set; }
@@ -3196,6 +3367,7 @@ namespace TEAMModelOS.Controllers
             public long updateTime { get; set; }
             public long startTime { get; set; }
             public long endTime { get; set; }
+            public string scope { get; set; }
 
             public class JointExamClass
             {

+ 1 - 1
TEAMModelOS/Startup.cs

@@ -201,7 +201,7 @@ namespace TEAMModelOS
             //services.AddHostedService<>
            // services.AddSingleton<ILoggerProvider, BlobLoggerProvider>();
             services.AddMvcFilter<RequestAuditFilter>();
-            services.AddNetMail(Configuration.GetSection("MailOption").Get<MailOptions>());
+            services.AddNetMail();
 #if !DEBUG
  //第一步: 配置gzip与br的压缩等级为最优
             //builder.Services.AddMyResponseCompression();

+ 4 - 4
TEAMModelOS/TEAMModelOS.csproj

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

文件差异内容过多而无法显示
+ 3 - 3
TEAMModelOS/appsettings.Development.json


+ 1 - 1
TEAMModelOS/appsettings.json

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